LogMod is a simple logging module for C applications. It provides functionality to initialize a logging context, retrieve loggers, and log messages with different severity levels.
- Initialize logging context with application ID and logger table.
- Retrieve or create loggers by context ID.
- Log messages with different severity levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL).
- Support for custom log labels for application-specific logging needs.
- Custom callback support for advanced logging scenarios.
- Thread-safe logging with custom lock functions.
- Optionally log messages to a file.
- ANSI color support for terminal output.
- Compatible with both C89 and C99 standards.
- Single header file implementation for easy integration.
LogMod is single-header-only library, so it includes additional macros for more complex uses cases. #define LOGMOD_STATIC
hides all LogMod API symbols by making them static. Also, if you want to include logmod.h
from multiple C files, to avoid duplication of symbols you may define LOGMOD_HEADER
macro.
/* In every .c file that uses LogMod include only declarations: */
#define LOGMOD_HEADER
#include "logmod.h"
/* Additionally, create one logmod.c file for LogMod implementation: */
#include "logmod.h"
To initialize the logging context, use the logmod_init
function:
#include "logmod.h"
struct logmod logmod;
struct logmod_context table[5];
logmod_err code = logmod_init(&logmod, "APPLICATION_ID", table, 5);
if (code != LOGMOD_OK) {
// Handle error
}
To retrieve or create a logger by context ID, use the logmod_get_logger
function:
struct logmod_logger *logger = logmod_get_logger(&logmod, "CONTEXT_ID");
if (logger == NULL) {
// Handle error
}
LogMod provides two main macros for logging messages, one for C89 compatibility and another for C99:
// Log messages with different severity levels
logmod_log(TRACE, logger, "This is a trace message");
logmod_log(DEBUG, logger, "This is a debug message with a value: %d", 42);
logmod_log(INFO, logger, "This is an info message with multiple values: %s, %d", "test", 123);
// Log messages with different severity levels using tuple syntax for C89 compatibility
logmod_nlog(TRACE, logger, ("This is a trace message"), 0);
logmod_nlog(DEBUG, logger, ("This is a debug message with a value: %d", 42), 1);
logmod_nlog(INFO, logger, ("This is an info message with multiple values: %s, %d", "test", 123), 2);
The last parameter in the C89 version (logmod_nlog
) indicates the number of arguments in the format string (excluding the format string itself).
LogMod allows you to define custom log labels for application-specific logging needs. Custom log labels must start with level LOGMOD_LEVEL_CUSTOM
.
Here's how to define and use custom log labels:
// Define custom log labels's levels
enum {
LOGMOD_LEVEL_HTTP = LOGMOD_LEVEL_CUSTOM,
LOGMOD_LEVEL_TESTMODE
};
// Define custom log label properties (name, color, output stream)
static const struct logmod_label custom_labels[] = {
{ "HTTP", LOGMOD_COLOR_BLUE + LOGMOD_VISIBILITY_FOREGROUND, 0 },
{ "TEST", LOGMOD_COLOR_MAGENTA + LOGMOD_VISIBILITY_INTENSITY, 0 }
};
// Register custom labels with your logger
logmod_logger_set_callback(logger, custom_labels, 2, my_callback);
// Use your custom log labels
logmod_log(HTTP, logger, "HTTP request received: %s", request_url);
logmod_log(TESTMODE, logger, "Test mode activated");
When using custom log labels, you need to:
- Define enum values starting at
LOGMOD_LEVEL_CUSTOM
- Create a const static array of
logmod_label
structures defining the properties for each label - Register these labels with your logger using
logmod_logger_set_callback
- Use the label names with the logging macros just like built-in levels
The callback you provide will be invoked for all log messages, allowing you to implement custom handling for your specific log labels.
By default, LogMod outputs colored log messages to the terminal. Each log level has a distinct color to help quickly identify message types:
- TRACE: Blue (intensity)
- DEBUG: Cyan
- INFO: Green
- WARN: Yellow
- ERROR: Red
- FATAL: Magenta
You can enable or disable colored output for a logger:
// Enable colored output
logmod_logger_set_color(logger, 1);
// Disable colored output
logmod_logger_set_color(logger, 0);
LogMod provides a utility for ANSI color formatting of arbitrary text:
// Encode text with color (red, bold, foreground)
const char *colored_text = logmod_encode(logger, "This text will be red and bold",
RED, BOLD, FOREGROUND);
Available colors, styles, and visibility options:
// Colors (used without the LOGMOD_COLOR_ prefix in the macro)
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE
// Styles (used without the LOGMOD_STYLE_ prefix in the macro)
REGULAR, BOLD, UNDERLINE, STRIKETHROUGH
// Visibility (used without the LOGMOD_VISIBILITY_ prefix in the macro)
FOREGROUND, BACKGROUND, INTENSITY, BACKGROUND_INTENSITY
The logmod_encode
macro automatically adds the necessary prefixes, so you can use the enum values directly without their prefixes.
To make logging thread-safe, you can set a custom lock function:
void my_lock_function(const struct logmod_logger *logger, int should_lock) {
// Implementation of your lock mechanism
// should_lock == 1: acquire lock
// should_lock == 0: release lock
}
// Set the lock function
logmod_set_lock(&logmod, my_lock_function);
You can set a custom callback for advanced logging scenarios:
logmod_err my_callback(
const struct logmod_logger *logger,
const struct logmod_label *const label,
const char *fmt,
va_list args)
{
// Get user data associated with this logger
void *user_data = logger->user_data;
// Check if it's a custom label
if (logger->level >= LOGMOD_LEVEL_CUSTOM) {
// Handle custom log labels
// ...
}
// Custom logging implementation
return LOGMOD_OK; // Stop default logging behavior
// OR
return LOGMOD_OK_CONTINUE; // Continue with default logging
}
// Set the callback with custom labels
size_t num_custom_labels = sizeof(custom_labels) / sizeof *custom_labels;
logmod_logger_set_callback(logger, custom_labels, num_custom_labels, my_callback);
// Set user data separately if needed
logmod_logger_set_data(logger, my_user_data);
Your callback function should return one of these values:
LOGMOD_OK
: Indicates the callback has fully handled the log message, and the default logging functionality should be skipped (no console or file output).LOGMOD_OK_CONTINUE
: Indicates the callback has processed the message but wants the default logging behavior (console/file output) to continue afterward. This is useful when you want to augment rather than replace the standard logging.- Any other error code: If your callback returns any other error code, logging will stop and the error will be propagated to the caller.
This flexibility allows you to implement callbacks that can either:
- Completely replace the default logging
- Perform additional actions before normal logging continues
- Filter or transform messages before they're written to console/file
You can configure various options for your logger:
// Set all options at once
struct logmod_logger_options options = {
.logfile = fopen("app.log", "a"),
.quiet = 0,
.color = 1
};
logmod_logger_set_options(logger, options);
// Or set individual options
logmod_logger_set_quiet(logger, 0); // Enable console output
logmod_logger_set_color(logger, 1); // Enable colored output
logmod_logger_set_logfile(logger, fp); // Set log file
To clean up the logging context, use the logmod_cleanup
function:
logmod_cleanup(&logmod);
LogMod is designed to be compatible with both C89 and C99 standards. The key differences are:
C99 introduced support for variable arguments in macros using __VA_ARGS__
. LogMod provides two versions of its logging macros:
-
C99 version (
logmod_log
): Uses variadic macros for a cleaner syntax:logmod_log(INFO, logger, "Value: %d", 42);
-
C89 version (
logmod_nlog
): Uses a tuple-based approach with an additional parameter to specify the number of arguments:logmod_nlog(INFO, logger, ("Value: %d", 42), 1);
The C89 version uses the LOGMOD_SPREAD_TUPLE_X
macros internally to unpack the tuple of arguments in a standard-compliant way.
logmod_err logmod_init(struct logmod *logmod, const char *const app_id, struct logmod_context table[], unsigned length);
Initializes the logging context with the specified application ID and logger table.
logmod
: Pointer to the logging context structure.app_id
: Application ID string.table
: Logger table array.length
: Length of the logger table array.
Returns LOGMOD_OK
on success, or an error code on failure.
logmod_err logmod_cleanup(struct logmod *logmod);
Cleans up the logging context.
logmod
: Pointer to the logging context structure. ReturnsLOGMOD_OK
on success
struct logmod_logger *logmod_get_logger(struct logmod *logmod, const char *const context_id);
Retrieves or creates a logger by context ID.
logmod
: Pointer to the logging context structure.context_id
: Context ID string. Returns a pointer to the logger, orNULL
on failure.
logmod_err logmod_set_lock(struct logmod *logmod, logmod_lock lock);
Sets the lock function for shared resources between loggers.
logmod
: Pointer to the logging context structure.lock
: Lock function pointer of typelogmod_lock
.
const char *logmod_encode(const struct logmod_logger *logger,
const char *buf,
enum logmod_colors color,
enum logmod_styles style,
enum logmod_visibility visibility);
Encodes a string with ANSI color formatting.
logger
: Pointer to the logger structure.buf
: String to encode.color
: Color value (e.g., RED instead of LOGMOD_COLOR_RED).style
: Text style (e.g., BOLD instead of LOGMOD_STYLE_BOLD).visibility
: Foreground/background visibility (e.g., FOREGROUND instead of LOGMOD_VISIBILITY_FOREGROUND). Returns the encoded string, or the original string if colors are disabled.
logmod_err logmod_logger_set_callback(
struct logmod_logger *logger,
const struct logmod_label *const custom_labels,
const size_t num_custom_labels,
logmod_callback callback);
Sets a custom callback function for the logger.
logger
: Pointer to the logger structure.custom_labels
: Custom log labels, or NULL to use defaults. When providing custom labels, they must correspond to enum values starting atLOGMOD_LEVEL_CUSTOM
.num_custom_labels
: Number of custom labels.callback
: Callback function of typelogmod_callback
.
logmod_err logmod_logger_set_data(struct logmod_logger *logger, void *user_data);
Sets the user data to be associated with the logger. This data can be accessed through the logger in the callback function.
logger
: Pointer to the logger structure.user_data
: Pointer to user-defined data. ReturnsLOGMOD_OK
on success.
logmod_err logmod_logger_set_options(struct logmod_logger *logger, struct logmod_logger_options options);
Sets the options for the logger.
logger
: Pointer to the logger structure.options
: Logger options structure.
logmod_err logmod_logger_set_quiet(struct logmod_logger *logger, int quiet);
Sets the quiet mode for the logger.
logger
: Pointer to the logger structure.quiet
: Quiet mode flag (1 to enable, 0 to disable). ReturnsLOGMOD_OK
on success.
logmod_err logmod_logger_set_color(struct logmod_logger *logger, int color);
Sets the color mode for the logger.
logger
: Pointer to the logger structure.color
: Color mode flag (1 to enable, 0 to disable). ReturnsLOGMOD_OK
on success.
logmod_err logmod_logger_set_logfile(struct logmod_logger *logger, FILE *logfile);
Sets the logfile for the logger.
logger
: Pointer to the logger structure.logfile
: Logfile pointer. ReturnsLOGMOD_OK
on success.
unsigned logmod_logger_get_counter(const struct logmod_logger *logger);
Retrieves the global log message counter shared between all loggers from the same logmod
instance. This counter increments each time a message is logged through any logger associated with the parent logmod instance.
logger
: Pointer to the logger structure. Returns the current value of the shared counter.
The counter is thread-safe when a lock function is provided via logmod_set_lock()
. This function can be useful for:
- Generating unique message IDs
- Tracking how many messages have been logged globally
- Implementing sequencing or correlation between different log contexts
// Example: Get current message count
unsigned msg_count = logmod_logger_get_counter(logger);
printf("Total logs so far: %u\n", msg_count);
const struct logmod_label *const logmod_logger_get_label(
const struct logmod_logger *logger, const unsigned level);
Retrieves the label information for a specific logging level.
logger
: Pointer to the logger structure.level
: The logging level to get the label for (can be a built-in level or a custom level). Returns a pointer to the label structure containing the name, color, and output specifications.
This function is useful for getting information about log levels, especially when working with custom log levels:
// Get label for a built-in level
const struct logmod_label *info_label = logmod_logger_get_label(logger, LOGMOD_LEVEL_INFO);
printf("Info label name: %s\n", info_label->name); // Outputs: "Info label name: INFO"
// Get label for a custom level
const struct logmod_label *http_label = logmod_logger_get_label(logger, LOGMOD_LEVEL_HTTP);
printf("Custom label name: %s\n", http_label->name); // Outputs: "Custom label name: HTTP"
long logmod_logger_get_level(const struct logmod_logger *logger, const char *const label);
Retrieves the level value for a specified label name. This is the reverse operation of logmod_logger_get_label()
.
logger
: Pointer to the logger structure.label
: The label name to search for (e.g., "INFO", "DEBUG", "HTTP"). Returns the level value as a long integer, or -1 if the label is not found or parameters are invalid.
This function is useful for converting label names to level values:
// Get level for a built-in label
long info_level = logmod_logger_get_level(logger, "INFO");
if (info_level >= 0) {
printf("INFO level value: %ld\n", info_level); // Outputs: "INFO level value: 2"
}
// Get level for a custom label
long http_level = logmod_logger_get_level(logger, "HTTP");
if (http_level >= 0) {
printf("HTTP level value: %ld\n", http_level); // Value depends on your custom levels
}
// Use the result for conditional logic
if (logmod_logger_get_level(logger, "WARN") == logmod_logger_get_level(logger, "ERROR")) {
// Handle case where WARN and ERROR have the same level
}
LogMod provides a global fallback logger that is automatically used when:
- You pass
NULL
as the logger parameter to logging functions - You want to log something before initializing your own logging context
This allows for immediate logging without explicit setup:
// Log using the fallback logger (no need to initialize anything)
logmod_log(INFO, NULL, "This uses the fallback logger");
The fallback logger uses:
- Application ID: Defined by
LOGMOD_FALLBACK_APPLICATION_ID
(defaults to "APPLICATION") - Context ID: Defined by
LOGMOD_FALLBACK_CONTEXT_ID
(defaults to "GLOBAL")
You can customize these values by defining them before including logmod.h:
#define LOGMOD_FALLBACK_APPLICATION_ID "MY_APP"
#define LOGMOD_FALLBACK_CONTEXT_ID "DEFAULT"
#include "logmod.h"
This feature is particularly useful during application startup or in code that doesn't have easy access to a logger instance.
This project is licensed under the MIT License - see the LICENSE file for details.