A minimal, lightweight logging library for C, ideal for embedded systems and small projects.
- Multiple Log Levels: Supports
critical,error,warning,info, anddebuglevels. - Compile-Time Filtering: Easily set the maximum log level at compile time to control binary size.
- Self-Contained: No external dependencies (no printf library required).
- Custom Backends: Redirect log output to any destination (e.g., serial port, file, memory buffer) via a simple callback API.
- Minimal Footprint: Lightweight implementation with internal formatting (~1.8KB compiled size).
- Flexible Formatting: Supports
%d,%u,%x,%X,%s,%c,%%format specifiers.
git clone https://github.com/your-username/log-c.git
cd log-cTo build the library and run the tests, simply use make:
make testThis will compile the log-c library and run the test suite to ensure everything is working correctly.
#include "log_c.h"
#include <stdio.h>
// Output callback function
void my_output(const char* message, size_t length) {
// Send to your output device (UART, file, etc.)
fwrite(message, 1, length, stdout);
}
int main(void) {
// Set the output callback before logging
log_set_output_callback(my_output);
// Use logging macros
loginfo("Application started");
logdebug("Debug value: %d", 42);
logerror("Error occurred: %s", "example error");
return 0;
}The library requires you to provide an output callback function that handles the formatted log messages. The callback signature is:
typedef void (*log_output_callback_t)(const char* message, size_t length);The callback receives:
- A pointer to the formatted message buffer (includes level prefix like
[info]and newline) - The length of the message in bytes
Example for UART:
void uart_log_output(const char* message, size_t length) {
for (size_t i = 0; i < length; i++) {
uart_write(message[i]);
}
}
int main(void) {
uart_init();
log_set_output_callback(uart_log_output);
loginfo("UART logging initialized");
}Example for File:
void file_log_output(const char* message, size_t length) {
FILE* log_file = fopen("app.log", "a");
if (log_file) {
fwrite(message, 1, length, log_file);
fclose(log_file);
}
}logcritical(fmt, ...)- Log critical messages (unrecoverable errors)logerror(fmt, ...)- Log error messageslogwarning(fmt, ...)- Log warning messagesloginfo(fmt, ...)- Log informational messageslogdebug(fmt, ...)- Log debug messages
void log_set_output_callback(log_output_callback_t callback)- Set the output callbackbool log_is_output_configured(void)- Check if output callback is configured
// Set maximum message buffer size (default: 256)
#define LOG_MAX_MESSAGE_SIZE 512Control which log levels are compiled into your binary:
// Set before including log_c.h
#define LOG_LEVEL LOG_LEVEL_DEBUG // Include all messages
#include "log_c.h"Available levels:
LOG_LEVEL_OFF- Disable all loggingLOG_LEVEL_CRITICAL- Only critical messagesLOG_LEVEL_ERROR- Critical and error messagesLOG_LEVEL_WARNING- Critical, error, and warning messagesLOG_LEVEL_INFO- All except debug (default)LOG_LEVEL_DEBUG- All messages
Compile-time filtering completely eliminates unused log calls from the binary, saving code space.
New Feature: Dynamically control log verbosity without recompilation!
The library now supports runtime filtering in addition to compile-time optimization. This enables you to change which messages are output without reflashing firmware.
- Compile-time level (
LOG_LEVEL): Maximum level compiled into binary (eliminates code) - Runtime level (
log_set_level()): Current filtering threshold (can be changed anytime)
// Compile with DEBUG to have maximum flexibility
#define LOG_LEVEL LOG_LEVEL_DEBUG
#include "log_c.h"
int main(void) {
log_set_output_callback(my_output);
// Start with INFO level
log_set_level(LOG_LEVEL_INFO);
loginfo("Prints"); // ✓ Allowed
logdebug("Hidden"); // ✗ Suppressed at runtime
// Enable debug dynamically (no recompilation!)
log_set_level(LOG_LEVEL_DEBUG);
logdebug("Now prints"); // ✓ Now allowed
// Reduce verbosity
log_set_level(LOG_LEVEL_ERROR);
loginfo("Hidden"); // ✗ Suppressed
logerror("Prints"); // ✓ Still allowed
}// Set runtime log level (clamped to compile-time max)
void log_set_level(log_level_e level);
// Get current runtime level
log_level_e log_get_level(void);
// Get compile-time maximum level
log_level_e log_get_compile_time_level(void);- Debugging: Enable debug logs without reflashing
- Production: Reduce verbosity after initialization
- Interactive control: Change levels via CLI commands
- Performance: Minimize overhead in critical sections
Compile with LOG_LEVEL_DEBUG for development builds to have maximum runtime flexibility, then compile with a lower level for production to save code space.
Supported format specifiers:
%d,%i- Signed integer%u- Unsigned integer%x- Lowercase hexadecimal%X- Uppercase hexadecimal%s- String%c- Character%%- Literal percent sign
Note: Float, long long, and width specifiers are not supported to keep the library minimal.
The library has been updated to remove the printf dependency and use a callback-based API.
Old API (deprecated):
void my_putchar(char c) {
uart_write(c);
}
logc_set_putchar(my_putchar);New API:
void my_output(const char* msg, size_t len) {
for (size_t i = 0; i < len; i++) {
uart_write(msg[i]);
}
}
log_set_output_callback(my_output);The library itself is thread-safe for logging (no shared mutable state). However, your output callback must be thread-safe if you plan to log from multiple threads or interrupt contexts.
The self-contained log-c library compiles to approximately 1.8KB (ARM Cortex-M4, -O0), compared to ~4-6KB when using printf. This makes it ideal for resource-constrained embedded systems.
This project is licensed under the MIT License. See the LICENSE file for details.