Logging so simple, you only need to include a single header file. seethe supports 6 different log levels (DEBUG
, INFO
, NOTICE
, WARNING
, ERROR
, and CRITICAL
), a SILENT
mode as well as printf
-style variadic printing.
Just #include "seethe.h"
in the file you're using and use any of the 6 macros. It's that simple. Look at the below sample code or example.c
for more boilerplate.
#include "seethe.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
// all the available macros
debug("debug log");
info("informational log");
notice("notice log");
warning("warning log");
error("error log");
critical("critical log");
// sample usage
info("starting program");
char *my_string;
if (!(my_string = malloc(BUFSIZ * sizeof(char)))) {
critical("failed to make size-%zu buffer. exiting\n", BUFSIZ);
abort();
}
debug("getting input from user");
printf("Enter some stuff: ");
while (fgets(my_string, BUFSIZ, stdin) != NULL) {
if (!strcmp(my_string, "\n")) {
info("user entered blank input. exiting");
return 0;
}
}
error("fgets failed to return a proper result");
return 1;
}
To change the warning level for a particular point in a particular file, re-#define
the pre-processor macro at the point you'd like:
...
#define LOG_LEVEL WARNING
warning("first warning");
#define LOG_LEVEL ERROR // now only ERROR and CRITICAL are visible
warning("second warning");
error("some error");
...
21:59:28 [WARNING] runner.c:37 - first warning
21:59:28 [ERROR] runner.c:40 - some error
(If you want to suppress the 'redefinition' error from gcc
, put #undef LOG_LEVEL
before your #define
).
To set the logging level for an entire file, define the LOG_LEVEL
before #include
'ing seeth.
#define LOG_LEVEL DEBUG
#include "seethe.h"
...
The SILENT
log level is provided for complete suppression of all warnings.
#define LOG_LEVEL SILENT
#include "seethe.h"
...
If you'd like to set a global LOG_LEVEL
for your entire project, compile the LOG_LEVEL
as a gcc
flag:
gcc *.c -D LOG_LEVEL=ERROR
I tried making seethe as configurable as possible, though there's still room for improvement.
It is possible to configure the different level colours using VT100 ANSI escape codes. The defaults are:
#define DEBUG_COLOUR ""
#define INFO_COLOUR "\x1B[36m"
#define NOTICE_COLOUR "\x1B[32;1m"
#define WARNING_COLOUR "\x1B[33m"
#define ERROR_COLOUR "\x1B[31m"
#define CRITICAL_COLOUR "\x1B[41;1m"
By default, every log message has a newline (\n
) appended to it in order to give puts
-like syntax (i.e. for those who hate typing \n
for every print statement). To remove the trailing newline and restore full printf
functionality, change "\n"
to ""
. The default is:
#define MSG_ENDING "\n"
Each log message has a timestamp attached to it, defined from the strftime
function. It can be changed (though if you are making a particularly long message, you should probably up the buffer from 80 char
s). The default is:
#define TIME_FORMAT "%T "
You can configure the border that shows up before the actual message (by default it's -
). The default is:
#define BORDER "-"
There are two ways to customize this: there are flags that can toggle existing components and I tried my best to make it easy(-ish) to make new ones.
-
Enabler flags — toggles for the logger message features. By default, everything is enabled (
=1
). To turn something off, change it to0
. Do not remove the macro. The built-in flags are:DISPLAY_COLOUR
— enable colour-coding the log messages based on levelDISPLAY_TIME
— output the time for each log event (date should be configurable)DISPLAY_LEVEL
— display the logging level of each messageDISPLAY_FUNC
— display the calling function for each log messageDISPLAY_FILE
— print the file from which the log message originates fromDISPLAY_LINE
— print the line from which the log message originates fromDISPLAY_BORDER
— print the border that comes before the messagesDISPLAY_MESSAGE
— print the log messageDISPLAY_ENDING
— print the message ending (by default it's\n
)DISPLAY_RESET
— reset the message colour-coding at the end of each log
It is not recommended to disable
DISPLAY_ENDING
orDISPLAY_RESET
.In the library, the flags are arranged like so:
#define DISPLAY_COLOUR 1 #define DISPLAY_TIME 1 #define DISPLAY_LEVEL 1 #define DISPLAY_FUNC 1 #define DISPLAY_FILE 1 #define DISPLAY_LINE 1 #define DISPLAY_BORDER 1 #define DISPLAY_MESSAGE 1 #define DISPLAY_ENDING 1 #define DISPLAY_RESET 1
-
Message components — the actual components that make up the log messages. Do note that although you can rearrange them freely, it is important to follow the same formatting structure as the existing components when creating new ones. This means if a component is removed, so will its spacing (hence why a lot of the existing components have a second
%s
in theprintf
—the extra space needs to be able to seamlessly appear and disappear for modularity and toggling).For a lot of components, it's a very simple check:
printf("%s%s", DISPLAY_FUNC ? func : "", DISPLAY_FUNC ? " " : "");
Do you need to display the calling function? If so, print a space along with it. Otherwise, don't print anything (not even the space).
But for some others, it is a lot more complex. Take a look at this monstrosity here:
printf( \ "%s%s%s%.d%s%s", \ DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? "(" : "", \ DISPLAY_FILE ? file : "", \ DISPLAY_FILE && DISPLAY_LINE ? ":" : "", \ DISPLAY_LINE ? line : 0, \ DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? ") " : "", \ !DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : "" \ );
This part of the
emit_log
macro is responsible for the part of the logger output that emits the calling function, source file, and line number (for examplemain (example.c:24)
).The left and right brackets (
(
/)
) are only printed when the actual function itself is printed and either source file and/or line are also printed (otherwise you'd get lines like04:44:32 [WARNING] (example.c:27) - warning log
where don't need the brackets, or04:44:32 [WARNING] main () - warning log
where neither file nor line are printed, resulting in empty brackets being printed next to the function).The second last print specifier is responsible for printing a space after the right bracket (
)
) in the case that the logger is configured to print the calling function. However, in the case that brackets aren't printed at all, we still need a space to trail the entire component, hence the!DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : ""
construct.As a result of this over-engineered mess, seethe can dynamically output:
-
main (example.c:24)
-
example.c:24
-
main (24)
-
main
-
24
Also, feel free to play around with the spacing on the
printf
print specifiers (I personally prefer%10s%s
for level display). -
- Allow for configuring print specifiers easily?
ISC License. Do whatever the fuck you want.