tklog is a single-header/single-source (.h + .c) logging library for C programs. It provides thread-safe, flexible logging with compile-time configuration for minimal runtime overhead. Features include customizable log prefixes (level, timestamp, thread ID, file/line), multiple log levels, optional call-stack tracing, memory leak detection, and performance timing.
Designed for embedded and performance-critical applications, tklog avoids dynamic allocation in the hot path and supports cross-platform use (Linux, macOS, Windows via MinGW).
- Log Levels: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY.
- Customizable Prefixes (compile-time):
- Log level (e.g.,
[DEBUG]). - Relative timestamp (ms since program start).
- Thread ID (pthread_self()).
- File and line number, with optional call-stack tracing (e.g.,
a.c:12 → b.c:88).
- Log level (e.g.,
- Output Flexibility: Default to
stdoutviaprintf; customizable via callback function. - Optional Exit-on-Log: Automatically exit on high-severity logs (e.g., ERROR).
- Scope Tracing: Automatic call-stack tracking with
TKLOG_SCOPEmacro. - Memory Tracking: Override
malloc/freeetc. to detect leaks; dumps on exit or signals (SIGSEGV, SIGABRT, SIGINT). - Performance Timer: Track execution time for code blocks with
TKLOG_TIMERmacro; reports totals, averages, and call paths. - Thread-Safe: Uses
pthread_mutexfor serialization. - Cross-Platform Timing: High-resolution timers (QueryPerformanceCounter on Windows, CLOCK_MONOTONIC on POSIX).
- Zero Runtime Config: All behavior decided at compile-time via macros for optimal performance.
- C99 or later.
pthread(for threading; available on Linux/macOS/Windows via MinGW).- Standard C library.
- For memory/timer: Additional allocations (minimal).
- External dependency for timer:
verstable.h(a simple hash table library; not included—implement or replace).
- Copy
tklog.handtklog.cto your project. - Include
tklog.hin source files where logging is needed. - Compile
tklog.cinto your project (e.g.,gcc main.c tklog.c -lpthread -o app).
No installation or linking required beyond standard libs.
All configuration is compile-time via preprocessor macros. Define them in your build system (e.g., CMake add_compile_definitions) or before including tklog.h.
TKLOG_SHOW_LOG_LEVEL: Include log level in output (default: enabled).TKLOG_SHOW_TIME: Include relative timestamp (default: enabled).TKLOG_SHOW_THREAD: Include thread ID (default: enabled).TKLOG_SHOW_PATH: Include file:line (with call-stack ifTKLOG_SCOPEenabled; default: enabled).
Override defaults globally with TKLOG_STATIC_FLAGS (bitmask):
add_compile_definitions(TKLOG_STATIC_FLAGS=(TKLOG_SHOW_LOG_LEVEL|TKLOG_SHOW_TIME))
Enable/disable specific log levels:
TKLOG_DEBUG,TKLOG_INFO,TKLOG_NOTICE: Log these levels.TKLOG_WARNING,TKLOG_ERROR,TKLOG_CRITICAL,TKLOG_ALERT,TKLOG_EMERGENCY: Log or exit (see below).
Undefined levels expand to ((void)0) (no-op).
For high-severity levels, use TKLOG_EXIT_ON_<LEVEL> (e.g., TKLOG_EXIT_ON_ERROR) to log and exit(-1) instead of just logging. Overrides the plain TKLOG_<LEVEL>.
TKLOG_OUTPUT_FN: Custom output callback (default:tklog_output_stdiowriting tostdout).TKLOG_OUTPUT_USERPTR: User data for callback (default:NULL).TKLOG_MEMORY: Enable memory tracking (overrides stdlib allocators).TKLOG_MEMORY_PRINT_ON_EXIT: Dump leaks onatexit(requiresTKLOG_MEMORY).TKLOG_SCOPE: Enable scope-based call-stack tracing.TKLOG_TIMER: Enable performance timing (requiresverstable.hfor hash tables).
Example CMake:
target_compile_definitions(my_target PRIVATE
TKLOG_SHOW_LOG_LEVEL
TKLOG_SHOW_TIME
TKLOG_MEMORY
TKLOG_SCOPE
)Include tklog.h and use level-specific macros:
#include "tklog.h"
int main() {
tklog_info("Program started");
int x = 42;
tklog_debug("Value of x: %d", x);
if (x < 0) {
tklog_error("Negative value!");
}
return 0;
}Output (with defaults):
INFO | 0ms | tid 140735327123456 | main.c:8 | Program started
DEBUG | 0ms | tid 140735327123456 | main.c:10 | Value of x: 42
Define a callback:
bool my_logger(const char *msg, void *user) {
FILE *logfile = (FILE *)user;
return fputs(msg, logfile) != EOF;
}
// Before including tklog.h
#define TKLOG_OUTPUT_FN my_logger
#define TKLOG_OUTPUT_USERPTR logfileWrap code blocks to auto-push/pop call path:
// Just use these for time tracking:
tklog_timer_start();
[code]
tklog_timer_stop();Output:
0ms | 57 calls | 0.004ms avg | tsm.c:1206
0ms | 57 calls | 0.004ms avg | tsm.c:1206 to tsm.c:1243
0ms | 0 calls | 0.000ms avg | test_tsm.c:188
95ms | 53225 calls | 0.002ms avg | tsm.c:1011
95ms | 53225 calls | 0.002ms avg | tsm.c:1011 to tsm.c:1038
1ms | 182 calls | 0.007ms avg | tsm.c:1249
1ms | 181 calls | 0.007ms avg | tsm.c:1249 to tsm.c:1345
0ms | 1 calls | 0.015ms avg | tsm.c:1249 to tsm.c:1330
7ms | 196872 calls | 0.000ms avg | tsm.c:900
7ms | 196872 calls | 0.000ms avg | tsm.c:900 to tsm.c:904
2ms | 70000 calls | 0.000ms avg | tsm.c:943
0ms | 6 calls | 0.001ms avg | tsm.c:943 to tsm.c:954
2ms | 69994 calls | 0.000ms avg | tsm.c:943 to tsm.c:966
411ms | 69311 calls | 0.006ms avg | test_tsm.c:194
0ms | 2 calls | 0.002ms avg | test_tsm.c:194 to test_tsm.c:362
0ms | 25 calls | 0.011ms avg | test_tsm.c:194 to test_tsm.c:486
131ms | 17481 calls | 0.008ms avg | test_tsm.c:194 to test_tsm.c:253
1ms | 182 calls | 0.009ms avg | test_tsm.c:194 to test_tsm.c:370
0ms | 205 calls | 0.001ms avg | test_tsm.c:194 to test_tsm.c:315
45ms | 15994 calls | 0.003ms avg | test_tsm.c:194 to test_tsm.c:498
0ms | 6 calls | 0.002ms avg | test_tsm.c:194 to test_tsm.c:200
0ms | 1 calls | 0.003ms avg | test_tsm.c:194 to test_tsm.c:294
0ms | 57 calls | 0.010ms avg | test_tsm.c:194 to test_tsm.c:572
0ms | 1 calls | 0.419ms avg | test_tsm.c:194 to test_tsm.c:581
0ms | 628 calls | 0.000ms avg | test_tsm.c:194 to test_tsm.c:450
3ms | 6792 calls | 0.000ms avg | test_tsm.c:194 to test_tsm.c:283
3ms | 6794 calls | 0.000ms avg | test_tsm.c:194 to test_tsm.c:343
215ms | 1463 calls | 0.147ms avg | test_tsm.c:194 to test_tsm.c:424
0ms | 522 calls | 0.001ms avg | test_tsm.c:194 to test_tsm.c:278
7ms | 17108 calls | 0.000ms avg | test_tsm.c:194 to test_tsm.c:258
0ms | 2050 calls | 0.000ms avg | test_tsm.c:194 to test_tsm.c:505
10ms | 5810 calls | 0.002ms avg | tsm.c:1434
10ms | 5810 calls | 0.002ms avg | tsm.c:1434 to tsm.c:1489
96ms | 29101 calls | 0.003ms avg | tsm.c:1085
96ms | 29101 calls | 0.003ms avg | tsm.c:1085 to tsm.c:1129
0ms | 1399 calls | 0.000ms avg | tsm.c:981
0ms | 1399 calls | 0.000ms avg | tsm.c:981 to tsm.c:1005
1ms | 70000 calls | 0.000ms avg | test_tsm.c:190
1ms | 70000 calls | 0.000ms avg | test_tsm.c:190 to test_tsm.c:193
Automatically tracks allocations. Use like normal:
char *str = strdup("hello"); // Tracked
free(str); // Untracked → error log + exitOn exit/signals, dumps unfreed memory:
unfreed memory:
5ms | tid 123 | address 0x7f8b4000 | 6 bytes | at main.c:10
Dumps include timestamp, thread, address, size, and allocation path (with scopes). Warning: Nested allocs in tklog internals use original functions to avoid recursion.
Time code blocks and report aggregates:
tklog_timer_init(); // Once at startup
tklog_timer_start();
sleep(1);
tklog_timer_stop();
tklog_timer_print(); // At endOutput:
1000ms | 1 calls | 1000.000ms avg | main.c:10
1000ms | 1 calls | 1000.000ms avg | main.c:10 to main.c:12
Tracks per-location totals, counts, averages, and full call paths.
Clear data: tklog_timer_clear().
See examples/ (not included; create simple mains testing each feature).
- Timer requires
verstable.h(simple string-keyed hash table). - Memory tracking adds overhead; disable for production.
- Call-stack limited to 128-char paths; grows dynamically.
- No log rotation/file output (use custom callback).
- Windows support via MinGW; test thoroughly.
MIT License (assumed; adjust as needed). See LICENSE for details.
Fork, PR improvements (e.g., more platforms, JSON output).