How do you log information or error when writing a program in C? If it’s a console based application the early stages frequently see a lot of printf() calls throughout the program. The number of printf()s in the program keep increasing with time. By the time you are production ready you may see the need of refactoring the code (wrt. logging) because of the following reasons:

Developers in different modules have used different logging patterns

There are numerous logs which might not make sense to the user

It’s difficult to debug or trace the flow by looking at the logs because some modules have similar function names, the same string is used in the log in multiple places etc.

Refactoring log statements can be a dirty task in a large product. It’s wiser to plan logging in advance so that:

There’s a consistent pattern

Logs can be enabled or disabled based on the severity (informative, debug, error…)

It’s easier to trace back to the source code from the log

The intention of this article is to present a tiny and extensible logging framework that can be re-used in any C project. If nothing, the concept can be used to develop a robust standard logging framework.

Our logging framework has just 2 files – header and source. Comments are added in the code for self-explanation.

Header file: log.h

#pragma once #include #include /* Define the logging levels as macros */ #define ERROR 0 #define INFO 1 #define DEBUG 2 /* Macro to expand to logger function. Adds filename, function and line number for traceability */ #define log(level, format, ...) debug_log(__FILE__, __FUNCTION__, __LINE__, level, format, ##__VA_ARGS__) /* Logger function declaration, follows printf for 5th & 6th arguments */ void debug_log(const char *file, const char *func, int line, int level, const char *format, ...) __attribute__((__format__ (printf, 5, 6)));

Source file: log.c

#include "log.h" /* Log level to be set after reading config file or cmdline option */ extern int current_log_level; /* Log level strings to print the level */ char *logarr[] = {"ERROR", "INFO", "DEBUG"}; /* Logger function definition */ void debug_log(const char *file, const char *func, int line, int level, const char *format, ...) { /* Handle variable list arguments */ va_list ap; va_start(ap, format); /* Make sure we have the correct level */ if (level < 0 || level > DEBUG) return; /* All logs matching a level (and below) should be printed */ if (level <= current_log_level) { /* Print filename,function, line no., log level */ fprintf(stderr, "[%s, %s(), ln %d] %s: ", file, func, line, logarr[level]); /* Print the rest of the information */ vfprintf(stderr, format, ap); } /* Reset variable list arguments before exit */ va_end(ap); }

Usage in code:

log(ERROR, "kill() failed. errno: %d (%s)

", errno, strerror(errno));

Output:

[test.c, main(), ln 230] ERROR: kill failed. errno: 3 (No such process)

Wasn’t that easy? A few lines at the beginning of the project can save you a lot of time and rework! Find the same code in action in one of my projects.