Search code examples
cstringc11error-reporting

Is There a Better Way to Stringify Error Codes?


This is more of an aesthetic question than a purely functional one, but I want to know if there's any answer. See, I'm programming an error reporter for a project I've recently begun working on, and it's mostly code-based for ease of function use. Think of errno, with various values defined to note specific problems. It's a prevalent system among error handlers. However, I don't want to just give the user a code, because I know from the time before I started programming that a random array of numbers can often be overwhelming when trying to self-diagnose the problem.

I want to give them a string representation of the code. However, I cannot think of a pragmatic way to do this. A stringify (#) macro wouldn't work because the error code sent to the function is unknown at compile time. Is there something beyond a large switch that could rectify this issue?

For context, this is my little error logger; this is the last step of the error pipeline, if the program gets here, a fatal error has occurred.

// Notes: We don't do a lot of error-checking in this function, beyond
// the stuff built into PushNotification. The reason for that is
// fairly simple; at this point, we don't give a darn. If the program
// gets here, something's irreversibly wrong regardless. 
// Last edit: July 3rd, 2024
_Noreturn void InternalLogError(const char* caller, const char* file, u32 line,
                      u32 code)
{

    char error_message[128];
    // This is the place in which I want to add the code-to-string bit.
    snprintf(error_message, 128,
             "\nMemphim failed catastrophically.\n"
             "Code: %d.\nCaller: %s @ %s\nLine: %d\n",
             code, caller, file, line);

    if (CheckShellAvailability()) puts(error_message);
    else PushNotification("Memphim Error Reporter", error_message);

    exit(EXIT_FAILURE);
}

Solution

  • A string look-up table would be one obvious way to do it.

    typedef enum
    {
      ERR_NONE,
      ERR_SERVERS_ON_FIRE,
      ERR_LIVE_BEAVERS,
    
      ERR_N // total number of supported errors
    } err_t;
    
    static const char* err_str[] = 
    {
      [ERR_NONE]             = "No error",
      [ERR_SERVERS_ON_FIRE]  = "The servers are on fire",
      [ERR_LIVE_BEAVERS]     = "Live beavers in the server room",
    };
    
    static_assert(sizeof(err_str)/sizeof(*err_str) == ERR_N, 
                  "err_t and err_str are not consistent with each other");
    
    ...
    
    void InternalLogError(..., err_t code)
    {
      puts(err_str[code]);
    }
    

    Or in case avoiding storing the values at separate places must be avoided, the X-macro version:

    #define ERR_LIST(X)                                        \
    /*  code                 str                           */  \
      X(ERR_NONE,            "No error")                       \
      X(ERR_SERVERS_ON_FIRE, "The servers are on fire")        \
      X(ERR_LIVE_BEAVERS,    "Live beavers in the server room")
    
    typedef enum
    {
      #define ERR_T_ENUM(code, str) code,
      ERR_LIST(ERR_T_ENUM)
      
      ERR_N // total number of supported errors
    } err_t;
    
    static const char* err_str[] = 
    {
      #define ERR_STR_LUT(code, str) [code] = str,
      ERR_LIST(ERR_STR_LUT)
    };