This document describes the features of the logging library. There are optional and core features.
Core Features - are mandatory features requred by the library for the normal operation:
Print - provides formatted printing to streams and buffers (not exposed to the users and used by other features internally)
Outputs - printing endpoints logic and stdout output
Levels - severity filters per output
Events - containers to distribute logging info across outputs
Lock - logic to inject external thread-safety mechanism
Logging - logic that generates an event and dispatch it to outputs
Static Configuration - compile-time logic to enable/disable optional features and configure core features
Optional Features - are configurable optional features that can extend the core library capabilities:
Color - add ANSI colors to the output
Time - add time stamps
Prefix - add custom data after the time stamp
Extra Outputs - additional user-defined outputs, including files
Source Location - prints file:line location of a logging call
Level Style - full or short severity level name
Topics - label based message filtering
Dynamic Configuration - run-time configuration of all features
Warnings Stubs for Non-Enabled Features - generate stubs for disabled features with warning message or just fail linking if the function is disabled.
Core Features
Static Configuration
Part of features are configured compile-time. You can use defines in the compiler options, e.g. -DULOG_BUILD_COLOR=1.
For CMake projects, you can use the add_compile_definitions function.
target_compile_definitions(micrologPRIVATEULOG_BUILD_COLOR=1)For Meson projects, you can use the meson command.
add_global_arguments('-DULOG_BUILD_COLOR=1', language: 'c')Note: For meson, you might want to adjust the compiler argument -fmacro-prefix-map=OLD_PATH=NEW_PATH to to get the right file paths, e.g. for meson:
add_global_arguments('-fmacro-prefix-map=../=',language: 'c')See also:
feature.
The full list of build options for static configuration is shown bellow:
Build OptionDefaultPurposeULOG_BUILD_COLOR0Compile color code pathsULOG_BUILD_PREFIX_SIZE0Prefix buffer logicULOG_BUILD_EXTRA_OUTPUTS0Extra output backendsULOG_BUILD_SOURCE_LOCATION1File:line outputULOG_BUILD_LEVEL_SHORT0Print levels with short names, e.g. 'E'ULOG_BUILD_TIME0Timestamp supportULOG_BUILD_TOPICS_MODEULOG_BUILD_TOPICS_MODE_OFFTopic allocation modeULOG_BUILD_TOPICS_STATIC_NUM0Number of static topics (0 = disabled)ULOG_BUILD_DYNAMIC_CONFIG0Runtime togglesULOG_BUILD_WARN_NOT_ENABLED1Warning stubsULOG_BUILD_CONFIG_HEADER_ENABLED0Use external configuration headerULOG_BUILD_CONFIG_HEADER_NAME"ulog_config.h"Configuration header nameULOG_BUILD_DISABLED0Disable microlog completelyWARNING! Do not use ULOG_BUILD_* options with a precompiled microlog library. Use dynamic configuration instead.
Logging, Levels and Outputs
There are 8 log severity levels (by ascending severity): ULOG_LEVEL_0 ... ULOG_LEVEL7. To log a message there is a general macro:
ulog(ulog_levellevel, constchar*fmt, ...);By default these generic levels are aliased with:
ULOG_LEVEL_TRACE - for tracing the execution path (ULOG_LEVEL_0)
ULOG_LEVEL_DEBUG - for debug information (ULOG_LEVEL_1)
ULOG_LEVEL_INFO - for general information (ULOG_LEVEL_2)
ULOG_LEVEL_WARN - for important information (ULOG_LEVEL_3)
ULOG_LEVEL_ERROR - for information about recoverable errors (ULOG_LEVEL_4)
ULOG_LEVEL_FATAL - for information about condition causing the system failure (ULOG_LEVEL_5)
Leveles ULOG_LEVEL_6 and ULOG_LEVEL_7 are not used by default
The library provides also default level macros for logging:
ulog_trace(constchar*fmt, ...); ulog_debug(constchar*fmt, ...); ulog_info(constchar*fmt, ...); ulog_warn(constchar*fmt, ...); ulog_error(constchar*fmt, ...); ulog_fatal(constchar*fmt, ...);Each function takes a printf format string followed by additional arguments:
ulog_info("Info message %f", 3.0)The user can also define custom levels by using the ulog_level_set_new_levels(ulog_level_descriptor *levels) function. The default levels can be restored by calling ulog_level_reset_levels(). E.g.
static ulog_level_descriptor syslog_levels = { .max_level = ULOG_LEVEL_7, // allow 0..7 .names = {"DEBUG", "INFO", "NOTICE", "WARN", "ERR", "CRIT", "ALERT", "EMERG"}, }; #defineLOG_NOTICEULOG_LEVEL_2// ...ulog_level_set_new_levels(&syslog_levels); ulog(LOG_NOTICE, "This is a notice message"); // Output: NOTICE src/main.c:12: This is a notice messageulog_level_reset_levels(); ulog(ULOG_LEVEL_2, "This is a default level message"); // Output: INFO src/main.c:14: This is a default level messageThe default log level is ULOG_LEVEL_TRACE, such that nothing is ignored. And by default there is only one available output - stdout. To configure its severity the user can use these two functions:
ulog_output_level_set(ULOG_OUTPUT_STDOUT, ULOG_LEVEL_INFO); // orulog_output_level_set_all(ULOG_LEVEL_TRACE);In this case the stdout-printed line will be:
INFO src/main.c:66: Info message 3.000000The current log level of an output can be acquired using the ulog_output_level_get(ulog_output_id output, ulog_level *level) function:
ulog_levellvl; ulog_output_level_get(ULOG_OUTPUT_STDOUT, &lvl); printf("loglevel: %s\n", ulog_level_to_string(lvl));Events
The events care information depending on the static configuration. The whole list of possible data:
Message
Message format arguments
Topic
Time
File
Line
Level
The data is accessible via getters (see header file for details):
ulog_event_get_message(...)
ulog_event_get_topic(...)
ulog_event_get_time(...)
ulog_event_get_file(...)
ulog_event_get_line(...)
ulog_event_get_level(...)
Lock
If the log will be written to from multiple threads a lock function can be set. To do this use the ulog_lock_set_fn() function.
The lock function must match the ulog_lock_fn type and return non-ULOG_STATUS_OK value on error:
typedefulog_status (*ulog_lock_fn)(boollock, void*udata);The function is passed the boolean true if the lock should be acquired or false if the lock should be released and the given udata value.
ulog_statuslock_function(boollock, void*lock_arg) { pthread_mutex_t*mutex= (pthread_mutex_t*) lock_arg; // retrieve the mutexintresult=-1; if (lock) { result=pthread_mutex_lock(mutex); } else { result=pthread_mutex_unlock(mutex); } return (result==0) ? ULOG_STATUS_OK : ULOG_STATUS_ERROR; } . . . pthread_mutex_tmutex; // We pass the mutex as the lock_argpthread_mutex_init(&mutex, NULL); . . . ulog_lock_set_fn(lock_function, mutex);For platform-specific convenience helpers (pthread, Windows, FreeRTOS, ThreadX, Zephyr, CMSIS‑RTOS2, macOS unfair lock) and the syslog level extension, see extensions/README.md.
Cleanup
If the library is used with dynamic features (e.g. topics, extra outputs, dynamic configuration) it is recommended to call the ulog_cleanup() function before program exit to free all allocated resources.
. . . ulog_cleanup();The clean up can be also used to remove all topics and outputs if needed during the program execution even if the allocation mode is static.
Optional Features
Disable
Static configuration options: ULOG_BUILD_DISABLED
Values (bool): 0/1
Default: 0.
NOTE: ULOG_BUILD_DISABLED=1 overrides all other ULOG_BUILD_* feature flags.
This feature allows disabling all logging calls at compile time for zero-overhead logging. To enable this feature, define ULOG_BUILD_DISABLED=1 in the compiler options.
When the feature is enabled, all logging macros are replaced with ((void)0) and function calls return disabled status codes. Important: Logging macros become true no-ops - their arguments are not evaluated, providing complete zero-overhead when disabled.
Example:
intexpensive_calculation() { /* ... */ } // When ULOG_BUILD_DISABLED=1:ulog_info("Result: %d", expensive_calculation()); // expensive_calculation() is NOT calledWhen the feature is enabled all logging macros are replaced with empty stubs or return negative status codes:
FunctionReturn Value When Disabledulog_cleanupULOG_STATUS_DISABLEDulog_color_configULOG_STATUS_DISABLEDulog_event_get_file""ulog_event_get_levelULOG_LEVEL_0ulog_event_get_line-1ulog_event_get_messageULOG_STATUS_DISABLEDulog_event_get_timeNULLulog_event_get_topicULOG_TOPIC_ID_INVALIDulog_event_to_cstrULOG_STATUS_DISABLEDulog_event_to_cstr_coloredULOG_STATUS_DISABLEDulog_level_configULOG_STATUS_DISABLEDulog_level_reset_levelsULOG_STATUS_DISABLEDulog_level_set_new_levelsULOG_STATUS_DISABLEDulog_level_to_string"?"ulog_lock_set_fnULOG_STATUS_DISABLEDulog_log(void)0ulog_output_addULOG_OUTPUT_INVALIDulog_output_add_fileULOG_OUTPUT_INVALIDulog_output_level_getULOG_STATUS_DISABLEDulog_output_level_setULOG_STATUS_DISABLEDulog_output_level_set_allULOG_STATUS_DISABLEDulog_output_removeULOG_STATUS_DISABLEDulog_prefix_configULOG_STATUS_DISABLEDulog_prefix_set_fnULOG_STATUS_DISABLEDulog_source_location_configULOG_STATUS_DISABLEDulog_time_configULOG_STATUS_DISABLEDulog_topic_addULOG_TOPIC_ID_INVALIDulog_topic_configULOG_STATUS_DISABLEDulog_topic_get_idULOG_TOPIC_ID_INVALIDulog_topic_level_getULOG_STATUS_DISABLEDulog_topic_level_setULOG_STATUS_DISABLEDulog_topic_removeULOG_STATUS_DISABLEDConfiguration Header
Static configuration options: ULOG_BUILD_CONFIG_HEADER_ENABLED, ULOG_BUILD_CONFIG_HEADER_NAME
Values (bool, string): 0/1, ANY
Default: 0, "ulog_config.h"
As an alternative to defining build options individually via compiler flags, you can define ULOG_BUILD_CONFIG_HEADER_ENABLED=1 to include a single header file named ulog_config.h that contains all configuration options. This approach simplifies configuration management by centralizing all build options in one file.
When ULOG_BUILD_CONFIG_HEADER_ENABLED is defined:
The library will include ulog_config.hYou can overwrite it with ULOG_BUILD_CONFIG_HEADER_NAME="my_ulog_conf.h"
All other ULOG_BUILD_* macros must be defined in this header file
Defining other ULOG_BUILD_* macros via compiler flags will cause a compilation error
Example ulog_config.h:
#pragma once // Define all build options in one place#defineULOG_BUILD_COLOR 1 #defineULOG_BUILD_PREFIX_SIZE 64 #defineULOG_BUILD_EXTRA_OUTPUTS 8 #defineULOG_BUILD_SOURCE_LOCATION 1 #defineULOG_BUILD_LEVEL_SHORT 0 #defineULOG_BUILD_TIME 1 #defineULOG_BUILD_TOPICS_MODE ULOG_BUILD_TOPICS_MODE_STATIC #defineULOG_BUILD_TOPICS_STATIC_NUM 10 #defineULOG_BUILD_DYNAMIC_CONFIG 0 #defineULOG_BUILD_WARN_NOT_ENABLED 1Usage with CMake:
target_compile_definitions(micrologPRIVATEULOG_BUILD_CONFIG_HEADER_ENABLED=1) target_include_directories(micrologPRIVATEpath/to/config/directory)This approach is particularly useful when you have multiple configurations or want to keep configuration separate from build scripts.
Topics
Static configuration options: ULOG_BUILD_TOPICS_MODE, ULOG_BUILD_TOPICS_STATIC_NUM
Values (enum, int): ULOG_BUILD_TOPICS_MODE_OFF, ULOG_BUILD_TOPICS_MODE_STATIC, ULOG_BUILD_TOPICS_MODE_DYNAMIC, 0...UINT_MAX
Default: ULOG_BUILD_TOPICS_MODE_OFF, 0.
The feature is controlled by ULOG_BUILD_TOPICS_MODE. It allows to filter log messages by subsystems, e.g. "network", "storage", etc. Use ULOG_BUILD_TOPICS_MODE_STATIC with ULOG_BUILD_TOPICS_STATIC_NUM for a fixed number of topics, or ULOG_BUILD_TOPICS_MODE_DYNAMIC for runtime allocation.
There are two mechanism of working with the topics:
Dynamic allocation - slightly slower than static allocation
Static allocation - faster
If you want to use dynamic topics, set ULOG_BUILD_TOPICS_MODE to ULOG_BUILD_TOPICS_MODE_DYNAMIC. For static allocation set ULOG_BUILD_TOPICS_MODE to ULOG_BUILD_TOPICS_MODE_STATIC and define ULOG_BUILD_TOPICS_STATIC_NUM to the desired number of topics.
Printing the log message with the topic is done by the set of function-like macros similar to ulog_xxx, but with the topic as the first argument:
ulog_topic_log(ulog_levellevel, constchar*topic_name, constchar*fmt, ...); ulog_topic_trace(constchar*topic_name, constchar*fmt, ...); ulog_topic_debug(constchar*topic_name, constchar*fmt, ...); ulog_topic_info(constchar*topic_name, constchar*fmt, ...); ulog_topic_warn(constchar*topic_name, constchar*fmt, ...); ulog_topic_error(constchar*topic_name, constchar*fmt, ...); ulog_topic_fatal(constchar*topic_name, constchar*fmt, ...); // or short versions:ulog_t(ulog_levellevel, constchar*topic_name, constchar*fmt, ...); ulog_t_trace(constchar*topic_name, constchar*fmt, ...); ulog_t_debug(constchar*topic_name, constchar*fmt, ...); ulog_t_info(constchar*topic_name, constchar*fmt, ...); ulog_t_warn(constchar*topic_name, constchar*fmt, ...); ulog_t_error(constchar*topic_name, constchar*fmt, ...); ulog_t_fatal(constchar*topic_name, constchar*fmt, ...);All topic must be added before usage via ulog_topic_add. At this step it is possible to select particular output for the topic (e.g. print topic Credentials only to a local file, or Operator Info only to ULOG_OUTPUT_STDOUT)
For example (static topics):
ulog_topic_add("network", ULOG_OUTPUT_ALL, ULOG_LEVEL_TRACE); // Added and initialized to TRACE levelulog_topic_add("storage", ULOG_OUTPUT_ALL, ULOG_LEVEL_INFO); // Added and initialized to INFO levelulog_topic_info("network", "Connected to server"); ulog_topic_warn("storage", "No free space");or dynamic topics:
// by default all topics are disabledulog_topic_enable("storage"); ulog_topic_error("storage", "No free space"); ulog_topic_enable_all(); ulog_topic_trace("network", "Disconnected from server"); ulog_topic_fatal("video", "No signal");The logging level of each topic is set to the value given as parameter. It is possible to alter this behavior by calling ulog_topic_level_set(). All topics below the level set by ulog_output_level_set() will not generate log.
For example:
// Both topic logging levels are set to ULOG_LEVEL_TRACEulog_topic_add("network", ULOG_OUTPUT_ALL, ULOG_LEVEL_TRACE); ulog_topic_add("storage", ULOG_OUTPUT_ALL, ULOG_LEVEL_TRACE); // Both topics generate log as global logging level is set to ULOG_LEVEL_TRACEulog_topic_info("network", "Connected to server"); ulog_topic_warn("storage", "No free space"); ulog_output_level_set_all(ULOG_LEVEL_INFO); // All outputs are set to INFOulog_topic_level_set("storage", ULOG_LEVEL_WARN); // Storage is set to WARNulog_topic_info("storage", "No free space"); // generatedulog_topic_info("network", "Connected to server"); // filtered out topiculog_topic_debug("storage", "No free space"); // filtered out level DEBUG < INFOThe current loglevel of each topic can be acquired using the ulog_topic_level_get() function:
ulog_levellvl; ulog_topic_level_get("network", &lvl); printf("network loglevel: %s\n", ulog_level_to_string(lvl));Topics can be removed by using the ulog_topic_remove() function.
Extra Outputs
Static configuration options: ULOG_BUILD_EXTRA_OUTPUTS
Values (int): 0...INT32_MAX
Default: 0.
The feature is controlled by the following define:
ULOG_BUILD_EXTRA_OUTPUTS - The maximum number of extra logging outputs that can be added. Each extra output requires some memory. When it is 0, the only available output is STDOUT. Default is 0.
File Output
One or more file pointers where the log will be written can be provided to the library by using the ulog_output_add_file() function. The data written to the file output is of the following format (with the full time stamp):
2047-03-11 20:18:26 TRACE src/main.c:11: Hello world vs 20:18:26 TRACE src/main.c:11: Hello worldTo write to a file open a file and pass it to the ulog_output_add_file function.
ulog_topic_add("Outputs", ULOG_OUTPUT_ALL, ULOG_LEVEL_TRACE); FILE*fp=fopen("log.txt", "w"); if (fp) { ulog_output_idfile_output=ulog_output_add_file(fp, ULOG_LEVEL_INFO); if (file_output!=ULOG_OUTPUT_INVALID) { ulog_topic_info("Outputs", "File output added"); ulog_output_level_set(file_output, ULOG_LEVEL_TRACE); ulog_topic_trace("Outputs", "File output level set to TRACE"); } ulog_output_remove(file_output); // For demo purposesfclose(fp); // For demo purposes }Outputs can be removed by using the ulog_output_remove() function.
User Defined Output
One or more output handler functions which are called with the log data can be provided to the library by using the ulog_output_add() function. Use ulog_event_to_cstr to convert the event to a plain string, or ulog_event_to_cstr_colored to include ANSI colour codes. Note that ulog_event_to_cstr_colored always emits colour codes regardless of the runtime colour configuration.
voidarduino_output_handler(ulog_event*ev, void*arg) { staticcharbuffer[128]; intresult=ulog_event_to_cstr(ev, buffer, sizeof(buffer)); if (result==0) { Serial.println(buffer); } } . . . ulog_output_idard_output=ulog_output_add(arduino_output_handler, NULL, ULOG_LEVEL_INFO); if (ard_output!=ULOG_OUTPUT_INVALID) { ulog_info("Will be printed to Arduino serial"); ulog_output_remove(ard_output); // For demo purposes }WARNING: The handler function is called with the lock acquired, so if you are using logging inside the handler, it may cause a deadlocks: e.g.
voidfaulty_output_handler(ulog_event*ev, void*arg) { ulog_info("This might cause a deadlock or unexpected behavior"); }Prefix
Static configuration options: ULOG_BUILD_PREFIX_SIZE
Values (int): 0...INT_MAX
Default: 0.
Sets a prefix function that can be used to customize the log output. The function is called with the log event and should fill a string (prefix) that will be printed right before the log level. It can be used to add custom data to the log messages, e.g. millisecond time.
Requires ULOG_BUILD_PREFIX_SIZE to be more than 0.
voidprefix_handler(ulog_event*ev, char*prefix, size_tprefix_size) { snprintf(prefix, prefix_size, ", %03d ms", millis()); } // . . .ulog_prefix_set_fn(prefix_handler);The output will be:
19:51:42, 005 ms ERROR src/main.c:38: Error messageWARNING: The handler function is called with the lock acquired, so if you are using logging inside the handler, it may cause a deadlocks. E.g.:
voidfaulty_prefix(ulog_event*ev, char*prefix, size_tprefix_size) { ulog_info("This might cause a deadlock or unexpected behavior"); }Time
Static configuration options: ULOG_BUILD_TIME
Values (bool): 0/1
Default: 0.
Prints a time stamp in from of all log messages. Your platform must support time.h.
The time to the file output will be written with the date, while time to the console and other outputs will be written with the time only.
log.txt: 2021-03-11 20:18:26 TRACE src/main.c:11: Hello world console: 20:18:26 TRACE src/main.c:11: Hello worldColor
Static configuration options: ULOG_BUILD_COLOR
Values (bool): 0/1
Default: 0.
Use ANSI color escape codes when printing to stdout. If the terminal supports, the output will be colorized.
TRACE - white
DEBUG - cyan
INFO - green
WARN - yellow
ERROR - red
FATAL - red on white background
UNUSED levels (6,7) - yellow on red and white on red background
Source Location
Static configuration options: ULOG_BUILD_SOURCE_LOCATION
Values (bool): 0/1
Default: 1.
Hide or show the file name and line number. See output examples below:
ULOG_BUILD_SOURCE_LOCATION=0: TRACE Hello world
ULOG_BUILD_SOURCE_LOCATION=1: TRACE src/main.c:11: Hello world
Level Style
Static configuration options: ULOG_BUILD_LEVEL_SHORT
Values (bool): 0/1
Default: 0.
Allows to use short level strings, e.g. "T" for "TRACE", "I" for "INFO":
ULOG_BUILD_LEVEL_SHORT=0: TRACE src/main.c:11: Hello world
ULOG_BUILD_LEVEL_SHORT=1: T src/main.c:11: Hello world
Dynamic Configuration
Static configuration options: ULOG_BUILD_DYNAMIC_CONFIG
Values (bool): 0/1
Default: 0.
Most of the library features are configured compile time to reduce the code size and complexity. However, if the code size is not a concern, you can enable Dynamic Config by defining ULOG_BUILD_DYNAMIC_CONFIG=1. When the feature is enables all other features are enabled too in some default mode described in bellow. All Dynamic Config functions named like: ulog_FEATURE_config. The default configuration is following:
Build ConfigDefault ValueULOG_BUILD_PREFIX_SIZE64ULOG_BUILD_EXTRA_OUTPUTS8ULOG_BUILD_TIME1ULOG_BUILD_SOURCE_LOCATION1ULOG_BUILD_COLOR1ULOG_BUILD_LEVEL_SHORT0ULOG_BUILD_TOPICS_MODEULOG_BUILD_TOPICS_MODE_DYNAMICULOG_BUILD_WARN_NOT_ENABLED0Topics Configuration
If Dynamic Config enabled topics are created runtime in the dynamic allocation mode.
Configuration functions:
ulog_status ulog_topic_config(bool enabled) - show or hide topics in the log output when using ulog_topic_xxx macros. Returns ULOG_STATUS_OK on success, ULOG_STATUS_BUSY if the logger is currently locked, or ULOG_STATUS_ERROR if the feature is not compiled in.
Example output with and without topics:
ulog_topic_info("WORLD", "Hello");enabled=true: INFO [WORLD] src/main.c:13: Hello
enabled=false: INFO src/main.c:13: Hello
Prefix Configuration
If Dynamic Config enabled, ULOG_BUILD_PREFIX_SIZE is set to 64, so the prefix will be limited to: 63 characters + 1 null terminator.
Functions to configure the prefix:
ulog_status ulog_prefix_config(bool enabled) - enable or disable prefix in the log output. Returns ULOG_STATUS_OK / ULOG_STATUS_BUSY / ULOG_STATUS_ERROR (feature disabled).
Time Configuration
Functions to configure the timestamp:
ulog_status ulog_time_config(bool enabled) - enable or disable time in the log output. Returns ULOG_STATUS_OK / ULOG_STATUS_BUSY / ULOG_STATUS_ERROR (feature disabled).
Color Configuration
Functions to configure:
ulog_status ulog_color_config(bool enabled) - enable or disable ANSI color escape codes when printing to stdout. Returns ULOG_STATUS_OK / ULOG_STATUS_BUSY / ULOG_STATUS_ERROR (feature disabled).
Source Location Configuration
Functions to configure:
ulog_status ulog_source_location_config(bool enabled) - show or hide file and line in the log output. Returns ULOG_STATUS_OK / ULOG_STATUS_BUSY / ULOG_STATUS_ERROR (feature disabled).
Level Configuration
Functions to configure:
ulog_status ulog_level_config(ulog_level_config_style style) - enable or disable short level strings (e.g. "T" for "TRACE", "I" for "INFO"). Returns ULOG_STATUS_OK / ULOG_STATUS_BUSY / ULOG_STATUS_ERROR (feature disabled).
The style argument can be one of the following values:
ULOG_LEVEL_CONFIG_STYLE_DEFAULT: long level strings (default)
ULOG_LEVEL_CONFIG_STYLE_SHORT: short level strings