Error levels

From John's wiki
Jump to navigation Jump to search

I should have done this a long time ago. I'm going to start allocating standard error levels for my own use.

To be clear, these are process error levels, also known as exit status and error code. I endeavour to allocate particular error levels for my own use in the range [10, 100), comfortably in two-digit territory.

Note that I mostly program in PHP, so 'error', 'exception', and 'assertion' are particular to PHP, but they might apply in other contexts.

Note: in the following tables I indicate a corresponding HTTP response code with similar semantics.

Major error levels

The major error levels are:

You can always use one of the major error levels, but a more specific code is usually better if possible.

My error levels

These are some notes about error levels specifically allocated by me.

Error Level Category Constant HTTP Meaning Alternative
10 logic exit EXIT_CANNOT_CONTINUE 202 cannot continue; but nothing is abnormal or wrong consider EXIT_SPECIAL_SUCCESS
20 input error EXIT_BAD_DATA 404 there was a problem with inputs
21 input error EXIT_BAD_FORMAT 404 data was in an invalid format
22 input error EXIT_BAD_VALUE 404 data nominated an invalid value (but was in correct format)
30 user error EXIT_BAD_COMMAND 400 invalid command-line or options
31 user error EXIT_NO_FILE 400 a required file/directory path was not nominated
32 user error EXIT_WRONG_FILE 400 user nominated file (or directory) is missing use EXIT_FILE_MISSING for system files
33 user error EXIT_BAD_FILE 403 user nominated file (or directory) cannot be accessed due to invalid permissions use EXIT_NO_ACCESS for system files
34 user error EXIT_USER_CANCEL 403 user canceled
40 environment error EXIT_BAD_ENVIRONMENT 503 invalid run-time environment; cannot run
41 environment error EXIT_FILE_MISSING 503 a file (or directory) that is expected to always be available is not available use EXIT_WRONG_FILE for user nominated files
42 environment error EXIT_NO_ACCESS 503 a file (or directory) that should be accessible cannot be accessed due to invalid permissions use EXIT_BAD_FILE for user nominated files
43 environment error EXIT_BAD_CONFIG 503 config file missing or invalid
44 environment error EXIT_NO_LOCK 503 could not acquire lock
56 environment error EXIT_NO_SERVICE 503 cannot establish connection to a required service prefer EXIT_NO_DATABASE for database services
57 environment error EXIT_NO_DATABASE 503 cannot establish connection to a required database
58 environment error EXIT_EXHAUSTED 507 resources exhausted; out of memory, disk space, inodes, etc
59 environment error EXIT_OFFLINE 503 system offline; as configured by administrator
70 program error EXIT_BAD_PROGRAM 500 an unhandled and fatal situation encountered
71 program error EXIT_ERROR 500 an error caused process termination
72 program error EXIT_EXCEPTION 500 an unhandled exception caused process termination
73 program error EXIT_ASSERT 500 an assertion violation caused process termination
74 program error EXIT_TEST_FAILED 500 test failed; unit-test did not succeed
75 program error EXIT_INVALID 500 invalid error level; the programmer nominated an invalid error level and the host exited with 85 instead
80 program error EXIT_NOT_IMPLEMENTED 500 functionality not implemented
81 program error EXIT_NOT_SUPPORTED 500 situation not supported
82 program error EXIT_NOT_POSSIBLE 500 situation is supposed to be impossible
88 program error EXIT_DEBUG 500 programmer exited for debugging purposes
89 program error EXIT_ABORT 500 programmer aborted with error message
90 special purpose EXIT_SPECIAL_SUCCESS 202 special operation successful; used for safety (in case improperly invoked)
98 special purpose EXIT_OPTIONS_LISTED 200 program options listed; use when programs can be invoked to list their options in a machine readable format
99 special purpose EXIT_HELP 200 help or version number requested

Other error levels

These are some notes about how other software uses error levels.

Error Level Convention Category Constant HTTP Meaning
0 success EXIT_SUCCESS 200 success!
1 generic EXIT_GENERIC_1 404 the generic "some problem occurred" value, prefer something more specific
2 generic EXIT_GENERIC_2 404 incorrect usage or invalid arguments, the 404 of error levels
126 UNIX UNIX EXIT_UNIX_BAD_PERMISSION 403 command was found but could not be executed due to permissions
127 UNIX UNIX EXIT_UNIX_BAD_COMMAND 404 command not found or could not be executed
128 UNIX UNIX EXIT_TERMINATED_BY_SIGNAL 500 process terminated by signal
130 UNIX UNIX EXIT_TERMINATED_BY_CTRLC 500 process terminated by Ctrl+C
255 generic EXIT_GENERIC_255 404 the other generic "some problem occurred" value, prefer something more specific

Error level range

The errors are in increasing order of should-not-happenness. Basically:

  • logic exit: cannot continue now, but nothing is wrong
  • input error: invalid data
  • user error: invalid options
  • environment error: an environment problem, cannot connect to service or incompatible versions etc
  • program error: shouldn't happen, a situation the code hasn't handled yet

Whether it's an input error, user error, or environment error is basically a decision for you to make. It's not super important which number you allocate, especially when multiple categories might apply. It's probably best if you try to blame the program or environment more and the user less.

Note that special purpose "success" codes are only used in situations where you want to protect the user from making a mistake. So if a version number is printed because there was a --version command-line argument; or help was printed because there was a --help command-line argument; or if a machine-readable list of options was requested and supplied; then we exit with a non-zero error level even though technically we haven't "failed", we just might have done something by accident that the use didn't expect or intend. The main reason for these options is that when you're running in bash with `set -e` enabled (and usually you should be for most non-interactive situations) then your script will stop if a process returns an error. So we return an error just in case we did something in a script that a user didn't intend. After all if they think they've successfully invoked a command, but all they've actually done is report the version number or displayed the program help text, they'd probably like to know!

Range Category Meaning
1-9 not much meaning, prefer don't use
10-19 logic exit cannot continue now, but nothing is abnormal or wrong
20-29 input error invalid data
30-39 user error invalid command-line or options
40-59 environment error improper run-time environment
70-89 program error should not happen, unsupported situation
90-99 special purpose success indicators, used for safety
100+ some have meanings in UNIX, otherwise don't use

BASH code

#!/bin/bash
# 2024-02-09 jj5 - SEE: https://www.jj5.net/wiki/error_levels
LX_EXIT_SUCCESS=0                 # success!
LX_EXIT_CANNOT_CONTINUE=10        # logic exit: cannot continue; but nothing is abnormal or wrong: consider EXIT_SPECIAL_SUCCESS
LX_EXIT_BAD_DATA=20               # input error: there was a problem with inputs
LX_EXIT_BAD_FORMAT=21             # input error: data was in an invalid format
LX_EXIT_BAD_VALUE=22              # input error: data nominated an invalid value (but was in correct format)
LX_EXIT_BAD_COMMAND=30            # user error: invalid command-line or options
LX_EXIT_NO_FILE=31                # user error: a required file/directory path not nominated
LX_EXIT_WRONG_FILE=32             # user error: user nominated file (or directory) missing; use EXIT_FILE_MISSING for system files
LX_EXIT_BAD_FILE=33               # user error: user nominated file (or directory) cannot be accessed due to invalid permissions: use EXIT_NO_ACCESS for system files
LX_EXIT_USER_CANCEL=34            # user error: user canceled
LX_EXIT_BAD_ENVIRONMENT=40        # environment error: invalid run-time environment; cannot run
LX_EXIT_FILE_MISSING=41           # environment error: a file (or directory) that is expected to always be available is not available: use EXIT_WRONG_FILE for user nominated files
LX_EXIT_NO_ACCESS=42              # environment error: a file (or directory) that should be accessible cannot be accessed due to invalid permissions: use EXIT_BAD_FILE for user nominated files
LX_EXIT_BAD_CONFIG=43             # environment error: invalid configuration; config file missing or invalid
LX_EXIT_NO_LOCK=44                # environment error: could not acquire lock
LX_EXIT_NO_SERVICE=56             # environment error: cannot establish connection to a required service: prefer EXIT_NO_DATABASE for database services
LX_EXIT_NO_DATABASE=57            # environment error: cannot establish connection to a required database
LX_EXIT_EXHAUSTED=58              # environment error: resources exhausted; out of memory, disk space, inodes, etc
LX_EXIT_OFFLINE=59                # environment error: system offline; as configured by administrator
LX_EXIT_BAD_PROGRAM=70            # program error: an unhandled and fatal situation encountered
LX_EXIT_ERROR=71                  # program error: an error caused process termination
LX_EXIT_EXCEPTION=72              # program error: an unhandled exception caused process termination
LX_EXIT_ASSERT=73                 # program error: an assertion violation caused process termination
LX_EXIT_TEST_FAILED=74            # program error: test failed; unit-test did not succeed
LX_EXIT_INVALID=75                # program error: invalid error level; the programmer nominated an invalid error level and the host exited with 85 instead
LX_EXIT_NOT_IMPLEMENTED=80        # program error: functionality not implemented
LX_EXIT_NOT_SUPPORTED=81          # program error: situation not supported
LX_EXIT_NOT_POSSIBLE=82           # program error: situation is supposed to be impossible
LX_EXIT_DEBUG=88                  # program error: programmer exited for debugging purposes
LX_EXIT_ABORT=89                  # program error: programmer aborted with error message
LX_EXIT_SPECIAL_SUCCESS=90        # special purpose: special operation successful; used for safety (in case improperly invoked)
LX_EXIT_OPTIONS_LISTED=98         # special purpose: program options listed; use when programs can be invoked to list their options in a machine readable format
LX_EXIT_HELP=99                   # special purpose: help or version number requested 
LX_EXIT_UNIX_BAD_PERMISSION=126   # UNIX error: command was found but could not be executed due to permissions
LX_EXIT_UNIX_BAD_COMMAND=127      # UNIX error: command not found or could not be executed
LX_EXIT_TERMINATED_BY_SIGNAL=128  # UNIX error: process terminated by signal
LX_EXIT_TERMINATED_BY_CTRLC=130   # UNIX error: process terminated by Ctrl+C
LX_EXIT_GENERIC_1=1               # general error: error during processing
LX_EXIT_GENERIC_2=2               # general error: error during processing
LX_EXIT_GENERIC_255=255           # general error: error during processing

PHP code

<?php

// 2024-02-09 jj5 - SEE: https://www.jj5.net/wiki/error_levels

define( 'LX_EXIT_SUCCESS', 0 );
define( 'LX_EXIT_CANNOT_CONTINUE', 10 );
define( 'LX_EXIT_BAD_DATA', 20 );
define( 'LX_EXIT_BAD_FORMAT', 21 );
define( 'LX_EXIT_BAD_VALUE', 22 );
define( 'LX_EXIT_BAD_COMMAND', 30 );
define( 'LX_EXIT_NO_FILE', 31 );
define( 'LX_EXIT_WRONG_FILE', 32 );
define( 'LX_EXIT_BAD_FILE', 33 );
define( 'LX_EXIT_USER_CANCEL', 34 );
define( 'LX_EXIT_BAD_ENVIRONMENT', 40 );
define( 'LX_EXIT_FILE_MISSING', 41 );
define( 'LX_EXIT_NO_ACCESS', 42 );
define( 'LX_EXIT_BAD_CONFIG', 43 );
define( 'LX_EXIT_NO_LOCK', 44 );
define( 'LX_EXIT_NO_SERVICE', 56 );
define( 'LX_EXIT_NO_DATABASE', 57 );
define( 'LX_EXIT_EXHAUSTED', 58 );
define( 'LX_EXIT_OFFLINE', 59 );
define( 'LX_EXIT_BAD_PROGRAM', 70 );
define( 'LX_EXIT_ERROR', 71 );
define( 'LX_EXIT_EXCEPTION', 72 );
define( 'LX_EXIT_ASSERT', 73 );
define( 'LX_EXIT_TEST_FAILED', 74 );
define( 'LX_EXIT_INVALID', 75 );
define( 'LX_EXIT_NOT_IMPLEMENTED', 80 );
define( 'LX_EXIT_NOT_SUPPORTED', 81 );
define( 'LX_EXIT_NOT_POSSIBLE', 82 );
define( 'LX_EXIT_DEBUG', 88 );
define( 'LX_EXIT_ABORT', 89 );
define( 'LX_EXIT_SPECIAL_SUCCESS', 90 );
define( 'LX_EXIT_OPTIONS_LISTED', 98 );
define( 'LX_EXIT_HELP', 99 );
define( 'LX_EXIT_UNIX_BAD_PERMISSION', 126 );
define( 'LX_EXIT_UNIX_BAD_COMMAND', 127 );
define( 'LX_EXIT_TERMINATED_BY_SIGNAL', 128 );
define( 'LX_EXIT_TERMINATED_BY_CTRLC', 130 );
define( 'LX_EXIT_GENERIC_1', 1 );
define( 'LX_EXIT_GENERIC_2', 2 );
define( 'LX_EXIT_GENERIC_255', 255 );

function my_exit( int|string|Throwable $argument = EXIT_SUCCESS, bool $print_error = true, bool|null $print_hint = null ) {

  // if $argument is an int it is treated as an error code

  // if $argument is a string it is treated as an error message and EXIT_ABORT is used

  // if $argument is some type of Throwable an appropriate error level is determined

  // if $print_error is true an error message is logged

  // if $print_hint is true a hint for the programmer concerning other possibly related codes is printed

  // the DEBUG constant can influence the behaviour of this function, see the code for details

  if (
    $argument === EXIT_SUCCESS          ||
    $argument === EXIT_SPECIAL_SUCCESS  ||
    $argument === EXIT_OPTIONS_LISTED   ||
    $argument === EXIT_HELP
  ) {

    // shortcircuit the "success" cases which don't need an error message

    exit( $argument );

  }

  $is_debug = defined( 'DEBUG' ) && DEBUG;

  if ( $print_hint === null ) {

    $print_hint = $is_debug;

  }

  static $map = [
     EXIT_SUCCESS => [
       'code'         => 0,
       'name'         => "EXIT_SUCCESS",
       'category'     => "success",
       'description'  => "success!",
     ],
     EXIT_CANNOT_CONTINUE => [
       'code'         => 10,
       'name'         => "EXIT_CANNOT_CONTINUE",
       'category'     => "logic exit",
       'description'  => "cannot continue; but nothing is abnormal or wrong",
       'hint'         => "consider EXIT_SPECIAL_SUCCESS",
     ],
     EXIT_BAD_DATA => [
       'code'         => 20,
       'name'         => "EXIT_BAD_DATA",
       'category'     => "input error",
       'description'  => "there was a problem with inputs",
     ],
     EXIT_BAD_FORMAT => [
       'code'         => 21,
       'name'         => "EXIT_BAD_FORMAT",
       'category'     => "input error",
       'description'  => "data was in an invalid format",
     ],
     EXIT_BAD_VALUE => [
       'code'         => 22,
       'name'         => "EXIT_BAD_VALUE",
       'category'     => "input error",
       'description'  => "data nominated an invalid value (but was in correct format)",
     ],
     EXIT_BAD_COMMAND => [
       'code'         => 30,
       'name'         => "EXIT_BAD_COMMAND",
       'category'     => "user error",
       'description'  => "invalid command-line or options",
     ],
     EXIT_NO_FILE => [
       'code'         => 31,
       'name'         => "EXIT_NO_FILE",
       'category'     => "user error",
       'description'  => "a required file/directory path not nominated",
     ],
     EXIT_WRONG_FILE => [
       'code'         => 32,
       'name'         => "EXIT_WRONG_FILE",
       'category'     => "user error",
       'description'  => "user nominated file (or directory) missing; use EXIT_FILE_MISSING for system files",
     ],
     EXIT_BAD_FILE => [
       'code'         => 33,
       'name'         => "EXIT_BAD_FILE",
       'category'     => "user error",
       'description'  => "user nominated file (or directory) cannot be accessed due to invalid permissions",
       'hint'         => "use EXIT_NO_ACCESS for system files",
     ],
     EXIT_USER_CANCEL => [
       'code'         => 34,
       'name'         => "EXIT_USER_CANCEL",
       'category'     => "user error",
       'description'  => "user canceled",
     ],
     EXIT_BAD_ENVIRONMENT => [
       'code'         => 40,
       'name'         => "EXIT_BAD_ENVIRONMENT",
       'category'     => "environment error",
       'description'  => "invalid run-time environment; cannot run",
     ],
     EXIT_FILE_MISSING => [
       'code'         => 41,
       'name'         => "EXIT_FILE_MISSING",
       'category'     => "environment error",
       'description'  => "a file (or directory) that is expected to always be available is not available",
       'hint'         => "use EXIT_WRONG_FILE for user nominated files",
     ],
     EXIT_NO_ACCESS => [
       'code'         => 42,
       'name'         => "EXIT_NO_ACCESS",
       'category'     => "environment error",
       'description'  => "a file (or directory) that should be accessible cannot be accessed due to invalid permissions",
       'hint'         => "use EXIT_BAD_FILE for user nominated files",
     ],
     EXIT_BAD_CONFIG => [
       'code'         => 43,
       'name'         => "EXIT_BAD_CONFIG",
       'category'     => "environment error",
       'description'  => "invalid configuration; config file missing or invalid",
     ],
     EXIT_NO_LOCK => [
       'code'         => 44,
       'name'         => "EXIT_NO_LOCK",
       'category'     => "environment error",
       'description'  => "could not acquire lock",
     ],
     EXIT_NO_SERVICE => [
       'code'         => 56,
       'name'         => "EXIT_NO_SERVICE",
       'category'     => "environment error",
       'description'  => "cannot establish connection to a required service",
       'hint'         => "prefer EXIT_NO_DATABASE for database services",
     ],
     EXIT_NO_DATABASE => [
       'code'         => 57,
       'name'         => "EXIT_NO_DATABASE",
       'category'     => "environment error",
       'description'  => "cannot establish connection to a required database",
     ],
     EXIT_EXHAUSTED => [
       'code'         => 58,
       'name'         => "EXIT_EXHAUSTED",
       'category'     => "environment error",
       'description'  => "resources exhausted; out of memory, disk space, inodes, etc",
     ],
     EXIT_OFFLINE => [
       'code'         => 59,
       'name'         => "EXIT_OFFLINE",
       'category'     => "environment error",
       'description'  => "system offline; as configured by administrator",
     ],
     EXIT_BAD_PROGRAM => [
       'code'         => 70,
       'name'         => "EXIT_BAD_PROGRAM",
       'category'     => "program error",
       'description'  => "an unhandled and fatal situation encountered",
     ],
     EXIT_ERROR => [
       'code'         => 71,
       'name'         => "EXIT_ERROR",
       'category'     => "program error",
       'description'  => "an error caused process termination",
     ],
     EXIT_EXCEPTION => [
       'code'         => 72,
       'name'         => "EXIT_EXCEPTION",
       'category'     => "program error",
       'description'  => "an unhandled exception caused process termination",
     ],
     EXIT_ASSERT => [
       'code'         => 73,
       'name'         => "EXIT_ASSERT",
       'category'     => "program error",
       'description'  => "an assertion violation caused process termination",
     ],
     EXIT_TEST_FAILED => [
       'code'         => 74,
       'name'         => "EXIT_TEST_FAILED",
       'category'     => "program error",
       'description'  => "test failed; unit-test did not succeed",
     ],
     EXIT_INVALID => [
       'code'         => 75,
       'name'         => "EXIT_INVALID",
       'category'     => "program error",
       'description'  => "invalid error level; the programmer nominated an invalid error level and the host exited with 85 instead",
     ],
     EXIT_NOT_IMPLEMENTED => [
       'code'         => 80,
       'name'         => "EXIT_NOT_IMPLEMENTED",
       'category'     => "program error",
       'description'  => "functionality not implemented",
     ],
     EXIT_NOT_SUPPORTED => [
       'code'         => 81,
       'name'         => "EXIT_NOT_SUPPORTED",
       'category'     => "program error",
       'description'  => "situation not supported",
     ],
     EXIT_NOT_POSSIBLE => [
       'code'         => 82,
       'name'         => "EXIT_NOT_POSSIBLE",
       'category'     => "program error",
       'description'  => "situation is supposed to be impossible",
     ],
     EXIT_DEBUG => [
       'code'         => 88,
       'name'         => "EXIT_DEBUG",
       'category'     => "program error",
       'description'  => "programmer exited for debugging purposes",
     ],
     EXIT_ABORT => [
       'code'         => 89,
       'name'         => "EXIT_ABORT",
       'category'     => "program error",
       'description'  => "programmer aborted with error message",
     ],
     EXIT_SPECIAL_SUCCESS => [
       'code'         => 90,
       'name'         => "EXIT_SPECIAL_SUCCESS",
       'category'     => "special purpose",
       'description'  => "special operation successful; used for safety (in case improperly invoked)",
     ],
     EXIT_OPTIONS_LISTED => [
       'code'         => 98,
       'name'         => "EXIT_OPTIONS_LISTED",
       'category'     => "special purpose",
       'description'  => "program options listed; use when programs can be invoked to list their options in a machine readable format",
     ],
     EXIT_HELP => [
       'code'         => 99,
       'name'         => "EXIT_HELP",
       'category'     => "special purpose",
       'description'  => "help or version number requested ",
     ],
     EXIT_UNIX_BAD_PERMISSION => [
       'code'         => 126,
       'name'         => "EXIT_UNIX_BAD_PERMISSION",
       'category'     => "UNIX error",
       'description'  => "command was found but could not be executed due to permissions",
     ],
     EXIT_UNIX_BAD_COMMAND => [
       'code'         => 127,
       'name'         => "EXIT_UNIX_BAD_COMMAND",
       'category'     => "UNIX error",
       'description'  => "command not found or could not be executed",
     ],
     EXIT_TERMINATED_BY_SIGNAL => [
       'code'         => 128,
       'name'         => "EXIT_TERMINATED_BY_SIGNAL",
       'category'     => "UNIX error",
       'description'  => "process terminated by signal",
     ],
     EXIT_TERMINATED_BY_CTRLC => [
       'code'         => 130,
       'name'         => "EXIT_TERMINATED_BY_CTRLC",
       'category'     => "UNIX error",
       'description'  => "process terminated by Ctrl+C",
     ],
     EXIT_GENERIC_1 => [
       'code'         => 1,
       'name'         => "EXIT_GENERIC_1",
       'category'     => "general error",
       'description'  => "error during processing",
     ],
     EXIT_GENERIC_2 => [
       'code'         => 2,
       'name'         => "EXIT_GENERIC_2",
       'category'     => "general error",
       'description'  => "error during processing",
     ],
     EXIT_GENERIC_255 => [
       'code'         => 255,
       'name'         => "EXIT_GENERIC_255",
       'category'     => "general error",
       'description'  => "error during processing",
     ],
  ];

  if ( is_int( $argument ) ) {

    $spec = $map[ $argument ] ?? $map[ EXIT_INVALID ];

  }
  elseif ( is_string( $argument ) ) {

    error_log( $argument );

    $spec = $map[ EXIT_ABORT ];

  }
  elseif ( $argument instanceof ErrorException ) {

    $spec = $map[ EXIT_ERROR ];

  }
  elseif ( $argument instanceof AssertionError ) {

    $spec = $map[ EXIT_ASSERT ];

  }
  elseif ( $argument instanceof Throwable ) {

    $spec = $map[ EXIT_EXCEPTION ];

  }
  else {

    $spec = $map[ EXIT_INVALID ];

  }

  $code = $spec[ 'code' ];

  if ( $print_error ) {

    $name = $spec[ 'name' ];
    $category = $spec[ 'category' ];
    $description = $spec[ 'description' ];
    $hint = $spec[ 'hint' ] ?? null;

    if ( $hint && $print_hint ) {

      error_log( "hint: $hint" );

    }

    if ( $is_debug ) {

      error_log( "$category: $description ($name)" );

    }
    else {

      error_log( "$category: $description" );

    }
  }

  exit( $code );

}