Reputation: 136
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);
}
Upvotes: 0
Views: 156
Reputation: 8344
For the sake of seeing an alternative, here is a variation on @Lundin's very good answer.
This inverts the use of macros to achieve an analogous result.
// errors.tbl - NB: no header guards!
#ifdef ERR_AS_STR
const char *errStrs[];
static const char *errToStr( int code ) { return errStrs[code]; }
static const char *errStrs[] = {
# define X(e,s) s
#else
enum {
# define X(e,s) e
#endif
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" ),
X( ERR_N, NULL )
# undef X
};
and
// main.c
#include <stdio.h>
#include "errors.tbl" // once as enums
#define ERR_AS_STR
#include "errors.tbl" // and now as array of strings (and getter)
#undef ERR_AS_STR
int main( void ) {
for( int i = ERR_NONE; i < ERR_N; i++ ) puts( errToStr( i ) );
return 0;
}
This is merely a minimal working prototype, to be adapted to suit requirements with regard to the visibility of the enum, the array and the "getter" function.
Upvotes: 2
Reputation: 140870
On some project, I decided I want to fix a similar problem. I decided that defining error code and error text in multiple places is not for me, I want to have all in one place. Instead, I decided that I will define error code and description in one place and preprocess the project files to extract the text and generate error codes to text table. Consider the following source code file:
#include <errorgen.h>
int some_library_function() {
return ERROR_GEN(ERROR_SOME_ERROR, "This is error description")
}
Before compilation, a CMake or python script went through all the files. For each file containing ERROR_GEN
, two files were generated - errorgen_list.h
and errorgen_str.h
. errorgen_list.h
contained all the enums collected from ERROR_GEN
functions. errorgen_str.h
contained a "This is error description",
a table to transform error code to a string. The script looks something along:
def get_all_errors_from_sources():
rereplace = re.compile(
r'ERROR_GEN\s*\(\s*(.*)\s*,\s*(".*")\s*\)\s*;',
flags=re.MULTILINE,
)
dir = os.path.dirname(__file__)
errors: List[Err] = []
for path in Path(dir).glob("**/*.c"):
for line in open(path).read():
res = rereplace.findall(line)
if res:
errors += [Err(*res)]
return errors
def prepare_sources(errors):
enumout = "/* @file */\n"
for k, v in errors:
enumout += "\t" + k + ", /// " + v + "\n"
msgout = "/* @file */\n"
for k, v in errors:
msgout += "\t" + v + ", // " + k + "\n"
return (enumout, msgout)
errors = get_all_errors_from_sources()
enumout, msgout = prepare_sources(errors)
with Path("errorgen_list.h", "w") as f:
f.write(enumout)
with Path("errorgen_str.h", "w") as f:
f.write(msgout )
Then with the following errorgen.h
:
enum errorgen_errors_e {
ERRORGEN_OK = 0,
ERRORGEN_START = -1000,
#include "errorgen_list.h"
ERRORGEN_STOP,
};
#define ERROR_GEN(a, b) a
const char *error_to_str(int);
And the following errorgen.c
:
static const char *const errorsgen_str[] = {
#include "errorgen_str.h"
};
const char *error_to_str(int err) {
// error handling...
return errorsgen_str[err - ERRORGEN_START - 1];
}
I could easily keep error code with error string together in the source code. This resulted in multitude of error codes and easier to track them and more readable error codes. The proper dependency has to modelled in the build system, so that the build system knows to generate errorgen_*
before compiling the files.
Upvotes: 0
Reputation: 213266
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)
};
Upvotes: 12