J
Jef Driesen
Hi,
For one of my projects, I created a simple logging framework. I have an
(opaque) context object which contains a buffer:
typedef struct dc_context_t {
char msg[4096];
/* More stuff here. */
} dc_context_t;
and a printf-like function to log a message. The logging function uses
the vsnprintf() function to write a string representation of the
arguments to the context buffer. The resulting string is then passed to
a user-defined callback function.
int
dc_context_log (dc_context_t *context, const char *format, ...)
{
va_list ap;
va_start (ap, format);
vsnprintf (context->msg, sizeof (context->msg), format, ap);
va_end (ap);
callback (context->msg);
}
This works perfect for all printf-style logging. For example:
dc_context_log (context, "%s (%d)", "An error message", 3);
However, I would like to log some binary data too. Let's say I want to
log a message with an inline hexdump, something like this:
"DEBUG: data=<DATA>, size=<SIZE>"
Since there is no printf format string available for hexdumps, I could
write a little dc_context_hexdump() helper function to convert the
binary data into a hex string, and then produce the log message in 3
steps:
dc_context_log (context, "%s: data=", "DEBUG");
dc_context_hexdump (context, hexdata, size);
dc_context_log (context, "", size);
This would work fine, except that I need the output in only one piece.
There are several reasons for that. I simplified the code a bit for this
post, but in reality the dc_context_log() function also receives extra
info, like the __FILE__ and __LINE__ macros. When printing the output, I
only want to see the file and line info appear only once, and not three
times. Also in a multithreaded environment, another thread may call the
logging function between each of the three calls, and thus interleaving
the output.
How would you workaround this?
A possible approach might be to convert the binary data to string
first, and then pass that to the dc_context_log call. Something like
this:
void
somefunction (dc_context_t *context, unsigned char *data, unsigned int
size)
{
/* Do something useful here. */
/* Log the binary data. */
hexstr = hexdump (data, size);
dc_context_log (context, "%s: data=%s, size=%u",
"DEBUG", hexstr, size);
}
But this hexdump function isn't thread-safe (because it would have to
return a pointer to a static buffer). And even if I would make a
re-entrant hexdump_r() version, it's not ideal because then I would have
to allocate a buffer inside the somefunction(). I don't want to do that
because the dc_context_log is actually called through a macro, which can
be configured at compile time to omit the actual call (e.g. in
production builds). But then the memory allocation would still be there,
and I don't want to pollute application code with #ifdef's. An temporary
buffer on the stack won't work either, because the size isn't fixed.
Suggestions are welcome!
Jef
For one of my projects, I created a simple logging framework. I have an
(opaque) context object which contains a buffer:
typedef struct dc_context_t {
char msg[4096];
/* More stuff here. */
} dc_context_t;
and a printf-like function to log a message. The logging function uses
the vsnprintf() function to write a string representation of the
arguments to the context buffer. The resulting string is then passed to
a user-defined callback function.
int
dc_context_log (dc_context_t *context, const char *format, ...)
{
va_list ap;
va_start (ap, format);
vsnprintf (context->msg, sizeof (context->msg), format, ap);
va_end (ap);
callback (context->msg);
}
This works perfect for all printf-style logging. For example:
dc_context_log (context, "%s (%d)", "An error message", 3);
However, I would like to log some binary data too. Let's say I want to
log a message with an inline hexdump, something like this:
"DEBUG: data=<DATA>, size=<SIZE>"
Since there is no printf format string available for hexdumps, I could
write a little dc_context_hexdump() helper function to convert the
binary data into a hex string, and then produce the log message in 3
steps:
dc_context_log (context, "%s: data=", "DEBUG");
dc_context_hexdump (context, hexdata, size);
dc_context_log (context, "", size);
This would work fine, except that I need the output in only one piece.
There are several reasons for that. I simplified the code a bit for this
post, but in reality the dc_context_log() function also receives extra
info, like the __FILE__ and __LINE__ macros. When printing the output, I
only want to see the file and line info appear only once, and not three
times. Also in a multithreaded environment, another thread may call the
logging function between each of the three calls, and thus interleaving
the output.
How would you workaround this?
A possible approach might be to convert the binary data to string
first, and then pass that to the dc_context_log call. Something like
this:
void
somefunction (dc_context_t *context, unsigned char *data, unsigned int
size)
{
/* Do something useful here. */
/* Log the binary data. */
hexstr = hexdump (data, size);
dc_context_log (context, "%s: data=%s, size=%u",
"DEBUG", hexstr, size);
}
But this hexdump function isn't thread-safe (because it would have to
return a pointer to a static buffer). And even if I would make a
re-entrant hexdump_r() version, it's not ideal because then I would have
to allocate a buffer inside the somefunction(). I don't want to do that
because the dc_context_log is actually called through a macro, which can
be configured at compile time to omit the actual call (e.g. in
production builds). But then the memory allocation would still be there,
and I don't want to pollute application code with #ifdef's. An temporary
buffer on the stack won't work either, because the size isn't fixed.
Suggestions are welcome!
Jef