Lightweight Logging Tool uLog: Enabling Microcontrollers to Elegantly “Log”

Summary: uLog is a tool that, once integrated into your embedded project, can immediately print out information such as “Where did I go?” and “Where did it go wrong?” It is compact, low overhead, and user-friendly.

What is uLog?

uLog is a logging library specifically designed for resource-constrained MCUs (Microcontrollers), consisting of only 1 header file + 1 source file, implemented entirely in C, with minimal reliance on external libraries. It brings the well-known Log4c/Log4j concept of “level + output” to the embedded world, stripping away heavyweight features while retaining the core CRITICAL, ERROR, WARNING, INFO, DEBUG, and TRACE log levels.

Tip: If <span>ULOG_ENABLED</span> is not defined at compile time, the entire logging code will not be included, resulting in zero cost.

What Problems Does It Solve?

Common Issues Traditional Approach uLog’s Solution
Large log size Writing your own <span>printf</span> or using a complete logging framework, resulting in code size of several KB or even MB Only requires about 2 KB (depending on compile options), extremely lightweight
Log level control is difficult Manual <span>#ifdef</span> and macro definitions, significant changes required Unified level thresholds, dynamically switchable at runtime
Multiple output targets Only a single path (e.g., UART), writing to files/memory requires a separate implementation Supports any number of subscribers (console, file, circular buffer, etc.), each with independent thresholds
Log overhead even when disabled Commenting out <span>printf</span>, the compiler still generates code <span>ULOG_ENABLED</span> undefined means log macros expand to empty statements, consuming no code space
Debug information is hard to trace Relying on manual <span>printf</span>, information is scattered uLog automatically includes timestamps and level tags, all in one line

Installation & Getting Started

In a nutshell: Just include <span>ulog.h</span> and <span>ulog.c</span> in your project, define <span>ULOG_ENABLED</span>, and let the macros do the rest.

  1. 1. Download the source code
    git clone https://github.com/rdpoor/ulog.git
  2. 2. Copy the files to your project
  • <span>ulog.h</span>include/
  • <span>ulog.c</span>src/
  • 3. Globally enable logging (optional)
    #define ULOG_ENABLED   // Place at the top of any source file, or add -DULOG_ENABLED in compile options
  • 4. Implement the logging handler function (using UART as an example)
    void my_uart_logger(ulog_level_t level, const char *msg) {
        // Assume you have a uart_write function
        char buf[128];
        snprintf(buf, sizeof(buf), "%s [%s]: %s\r\n",
                 get_timestamp(), ulog_level_name(level), msg);
        uart_write(buf, strlen(buf));
    }
  • 5. Initialize and subscribe in <span>main()</span>
    int main(void) {
        ULOG_INIT();                                   // Necessary initialization
        ULOG_SUBSCRIBE(my_uart_logger, ULOG_WARNING); // Only print WARNING and above
        
        int temp = 27;
        ULOG_INFO("Current temperature %d℃", temp);   // Will not print (INFO < WARNING)
        ULOG_ERROR("Temperature anomaly: %d℃", temp); // Will print
        
        // Dynamically lower the threshold to allow INFO to be printed
        ULOG_SUBSCRIBE(my_uart_logger, ULOG_INFO);
        ULOG_INFO("Reporting temperature again %d℃", temp);
        
        return 0;
    }

  • 6. Compile
    gcc -o demo main.c ulog.c -DULOG_ENABLED
  • Tip: If you want to write logs to a circular buffer, just implement a <span>my_buf_logger</span>, pass the pointer in, and read it when needed.

    Pros and Cons Overview

    Pros Description
    Extremely lightweight Only takes a few KB, almost no impact on MCU resources
    Flexible subscription Any number of output targets, each with independent thresholds
    Runtime adjustable Dynamic enabling/disabling through <span>ULOG_SUBSCRIBE</span>/<span>ULOG_UNSUBSCRIBE</span>
    No intrusion No code generated when not enabled
    Pure C implementation Compatible with all mainstream compilers, easy to port
    Cons Description
    Relatively simple functionality Does not have advanced features like configuration files or rolling files as in Log4j
    No built-in timestamp Must implement <span>get_timestamp()</span><span>, which may be a hurdle for beginners</span>
    Single-threaded safety Does not provide locks by default; must implement locking in multi-threaded/RTOS environments

    In Summary

    uLog is like a “sticky note” in the embedded world, transferring your habit of logging on a PC to resource-constrained MCUs. Its lightweight + flexible + no intrusion features allow you to debug without having to sift through source code, add <span>printf</span><span>, or reflash—just write a few lines of macros and a custom output function, and log information will appear immediately.</span>

    If you are struggling with “How can I print debug information from a microcontroller?” or want to unify log levels in RTOS projects, consider integrating uLog for a time-saving, efficient, and clean code solution.

    In a nutshell: Treat uLog as your embedded project’s “personal notebook” to jot down every “What did I just do?” moment.

    Project Address: https://github.com/rdpoor/ulog

    Leave a Comment