patterncMinor
Logging library for C
Viewed 0 times
loggingforlibrary
Problem
Over the past couple of weeks I have been working on a general purpose logging library for C in my free time. At the moment I have stabilized the API and most of the features I wanted to implement have been implemented. I am looking for general suggestions (note I haven't done much API documentation yet) on code quality and correctness (especially the synchronization techniques, IO methods, and message building functions).
You can find the full repository here.
And below I have included the sample usage that appears in the repo:
I have written a synchronized version similar to the above wherein 4 separate threads all log to the same file, which you may find the the
Here is the
```
#ifndef SCRIBE_H
#define SCRIBE_H
#i
You can find the full repository here.
And below I have included the sample usage that appears in the repo:
#include
#include
#include "scribe.h"
int main(void)
{
if (SCRB_Success != scrb_init()) {
return EXIT_FAILURE;
}
// We open a new file called `example.log` in write mode without synchronization that only
// listens to info level log messages.
struct scrb_stream * const logstream = scrb_open_stream("example.log", "w", false, LVL_INFO);
// We define a log format to tell scribe how to format our messages.
// This format prints out the file, method, and line locations followed by the message itself.
struct scrb_format const * const fmt = scrb_create_format("[%F:%L:%M] %m", NULL);
// Now we're ready to go.
scrb_logln(logstream, fmt, LVL_INFO, "Hello from the example program!");
// We can do formatted output as well.
scrb_flogln(logstream, fmt, LVL_INFO, "Hello for the %dnd time!", 2);
// If we want to write directly to the file without formatting or location/time info
// (or paying attention to log levels) we can use the `scrb_putstrln` method to write directly to the file.
scrb_putstrln(logstream, "Now we're done.");
scrb_format_release(&fmt);
scrb_close_stream(&logstream);
return EXIT_SUCCESS;
}I have written a synchronized version similar to the above wherein 4 separate threads all log to the same file, which you may find the the
examples folder.Here is the
scribe.h header file (as per request):```
#ifndef SCRIBE_H
#define SCRIBE_H
#i
Solution
It'll take some time to describe everything, and I need to pretend to do some work at some point too. However...
In
-
-
Another way of writing:
is:
-
You have a collection of functions such as:
I see multiple issues, some serious and some less serious.
-
Some of the macro and inline function combinations worry me:
Apart from the issues previously raised, here we have a macro
Clearly, the goal here is to avoid moving the
If the meta information is determined from the stream, I wonder why the internal function can't determine the meta information from the stream. Without studying the code in more detail than I'm going to, it is not clear why the
-
You currently have the header doing:
Defining static variables in a header is dubious. The non-systematic names on the RHS are a red flag; they should be
I'm not sure what flexibility you think you're buying with this extra level of indirection repeated in each object file where the source file uses the
In
scribe.h:-
struct scrb_stream outstream = (struct scrb_stream) { ... }; you are assigning a compound literal instead of just initializing the structure. Lose the (struct scrb_stream) cast notation in these lines.-
Another way of writing:
if (severity & st->severity) {
va_list ap;
va_start (ap, msgfmt);
int const rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end (ap);
return rval;
} else {
return SCRB_Success;
}is:
int rval = SCRB_SUCCESS;
if (severity & st->severity) {
va_list ap;
va_start(ap, msgfmt);
rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end(ap);
}
return rval;-
You have a collection of functions such as:
static inline
void (scrb_close_stream)(struct scrb_stream ** streamptr)
{
scrb_close_stream__internal(streamptr);
}
#define scrb_close_stream(streamptr) (scrb_close_stream)((struct scrb_stream **)(streamptr))I see multiple issues, some serious and some less serious.
- Serious: Why do you want to throw away type safety with the macro? The cast in the macro expansion means that you can pass an integer to the function, or a
FILE, or astruct scrb_stream(rather than the address of astruct scrb_stream *) and the cast suppresses the error messages that the compiler would otherwise generate. Drop the cast; don't suppress the limited protections that the compiler can provide.
- Serious: You are embedding double underscores into the names. You care about C++ since you've included
#ifdef __cplusplus, but C++ reserves double-underscore anywhere in any name for use by the implementation. Don't do it!
- With the cast removed, the macro serves no purpose here.
- What is the perceived benefit of the static inline functions that call onto the
*_internalname? In this example, I see no benefit.
-
Some of the macro and inline function combinations worry me:
static inline
int (scrb_flog)(struct scrb_meta_info const mi,
struct scrb_stream * const st,
struct scrb_format const * const fmt,
uint16_t const severity,
char const * const msgfmt, ...)
{
if (severity & st->severity) {
va_list ap;
va_start (ap, msgfmt);
int const rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end (ap);
return rval;
} else {
return SCRB_Success;
}
}
#define scrb_flog(st, fmt, sv, msg, ...) (scrb_flog)(get_meta_info((st)->name), (st), (fmt), (sv), (msg), ##__VA_ARGS__)Apart from the issues previously raised, here we have a macro
scrb_flog() that calls a function scrb_flog() with an extra argument. This will lead to confusion somewhere along the line. It also really isn't necessary; you can avoid the overhead of the get_meta_info() function call in the 'not used' case by writing this as:static inline
int (scrb_flog)(struct scrb_stream * const st,
struct scrb_format const * const fmt,
uint16_t const severity,
char const * const msgfmt, ...)
{
int rval = SCRB_SUCCESS;
if (severity & st->severity) {
va_list ap;
va_start(ap, msgfmt);
rval = scrb_flog_internal(get_meta_info(st->name), st, fmt, severity, msgfmt, false, ap);
va_end(ap);
}
return rval;
}
#define scrb_flog(st, fmt, sv, msg, ...) (scrb_flog)((st), (fmt), (sv), (msg), ##__VA_ARGS__)Clearly, the goal here is to avoid moving the
if test into the called function. I've not seen measurements to justify that; my suspicion is that the extra code built into the calling functions may be worse than the function call.If the meta information is determined from the stream, I wonder why the internal function can't determine the meta information from the stream. Without studying the code in more detail than I'm going to, it is not clear why the
scrb_meta_info * argument is passed to scrb_flog_internal() rather than letting the function determine the meta info from the stream.-
You currently have the header doing:
static struct scrb_stream * const scrb_stdout = &stream_out_default;
static struct scrb_stream * const scrb_stdin = &stream_in_default;
static struct scrb_stream * const scrb_stderr = &stream_err_default;Defining static variables in a header is dubious. The non-systematic names on the RHS are a red flag; they should be
scrb_stream_out_default etc. More significantly, what is the scrb_init_default() function doing, and when might those pointers be changed, and if they're never changed, why don't you just haveextern struct scrb_stream *scrb_stdout;I'm not sure what flexibility you think you're buying with this extra level of indirection repeated in each object file where the source file uses the
Code Snippets
if (severity & st->severity) {
va_list ap;
va_start (ap, msgfmt);
int const rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end (ap);
return rval;
} else {
return SCRB_Success;
}int rval = SCRB_SUCCESS;
if (severity & st->severity) {
va_list ap;
va_start(ap, msgfmt);
rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end(ap);
}
return rval;static inline
void (scrb_close_stream)(struct scrb_stream ** streamptr)
{
scrb_close_stream__internal(streamptr);
}
#define scrb_close_stream(streamptr) (scrb_close_stream)((struct scrb_stream **)(streamptr))static inline
int (scrb_flog)(struct scrb_meta_info const mi,
struct scrb_stream * const st,
struct scrb_format const * const fmt,
uint16_t const severity,
char const * const msgfmt, ...)
{
if (severity & st->severity) {
va_list ap;
va_start (ap, msgfmt);
int const rval = scrb_flog__internal(mi, st, fmt, severity, msgfmt, false, ap);
va_end (ap);
return rval;
} else {
return SCRB_Success;
}
}
#define scrb_flog(st, fmt, sv, msg, ...) (scrb_flog)(get_meta_info((st)->name), (st), (fmt), (sv), (msg), ##__VA_ARGS__)static inline
int (scrb_flog)(struct scrb_stream * const st,
struct scrb_format const * const fmt,
uint16_t const severity,
char const * const msgfmt, ...)
{
int rval = SCRB_SUCCESS;
if (severity & st->severity) {
va_list ap;
va_start(ap, msgfmt);
rval = scrb_flog_internal(get_meta_info(st->name), st, fmt, severity, msgfmt, false, ap);
va_end(ap);
}
return rval;
}
#define scrb_flog(st, fmt, sv, msg, ...) (scrb_flog)((st), (fmt), (sv), (msg), ##__VA_ARGS__)Context
StackExchange Code Review Q#73885, answer score: 2
Revisions (0)
No revisions yet.