{"id":157,"date":"2025-09-28T13:41:00","date_gmt":"2025-09-28T11:41:00","guid":{"rendered":"https:\/\/microsproject.dev\/?p=157"},"modified":"2025-09-25T14:07:55","modified_gmt":"2025-09-25T12:07:55","slug":"building-a-lightweight-logging-framework-for-micros-part-4-extending-the-framework","status":"publish","type":"post","link":"https:\/\/microsproject.dev\/index.php\/2025\/09\/28\/building-a-lightweight-logging-framework-for-micros-part-4-extending-the-framework\/","title":{"rendered":"Building a Lightweight Logging Framework for MicrOS \u2013 Part 4: Extending the Framework"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In <a href=\"https:\/\/microsproject.dev\/index.php\/2025\/09\/27\/building-a-lightweight-logging-framework-for-micros-part-3-compile-time-and-per-module-filtering\/\" target=\"_blank\" rel=\"noreferrer noopener\">Part 3<\/a>, we gave MicrOS logging compile-time filtering and per-module verbosity control. That already makes it a powerful tool for day-to-day development. But what if we want to push further?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this final part of the series, I\u2019ll explore <strong>runtime flexibility, multiple backends, timestamps, and structured logs<\/strong>. These are the kinds of features that turn a logging system from a developer aid into a <strong>serious instrumentation layer<\/strong> for your embedded OS.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Runtime Log Level Control<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Right now, MicrOS log levels are <strong>fixed at compile time<\/strong>. That\u2019s great for efficiency, but sometimes you need to <strong>turn up the volume<\/strong> without rebuilding the firmware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A common approach is to expose a simple command interface over UART:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void shell_cmd_log_level(const char* module, const char* level) {\n    micros_log_level_t new_level = parse_level(level);\n    log_set_module_level(module, new_level);\n    I(\"Log level for %s set to %s\", module, level);\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This requires a runtime table of modules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>typedef struct {\n    const char* name;\n    micros_log_level_t level;\n} micros_log_module_t;\n\nstatic micros_log_module_t modules&#91;] = {\n    { \"main\", MICROS_LOG_LEVEL_INFO },\n    { \"boot\", MICROS_LOG_LEVEL_INFO },\n    { \"scheduler\", MICROS_LOG_LEVEL_DEBUG },\n};\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now you can type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>log set scheduler info\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the scheduler module will quiet down without reflashing.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Multiple Backends<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Today, <code>_log()<\/code> always writes to <code>stdout<\/code>\/<code>stderr<\/code>. But what if you want:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>UART output for normal use,<\/li>\n\n\n\n<li><strong>SEGGER RTT<\/strong> for fast debugging,<\/li>\n\n\n\n<li>or a <strong>ring buffer<\/strong> in RAM that you can dump on a crash?<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">We can abstract the backend:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>typedef void (*log_backend_fn)(const char* formatted);\n\nstatic log_backend_fn current_backend = uart_backend;\n\nvoid log_set_backend(log_backend_fn backend) {\n    current_backend = backend;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now <code>_log()<\/code> just formats the string and hands it off:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>current_backend(buffer);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Switching from UART to RTT is a single call.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Adding Timestamps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Logs are much more useful if you know <em>when<\/em> they happened. Since MicrOS already has a system tick counter, we can prepend it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uint32_t micros_log_timestamp(void) {\n    extern volatile uint32_t tick_counter;\n    return tick_counter;\n}\n\nfprintf(output, \"&#91;%s] &#91;%s] &#91;%lu ms] \", level_str, module_name,\n        micros_log_timestamp());\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output in QEMU might look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;I] &#91;main] &#91;123 ms] System is up and running!\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Structured Logs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For humans, colors and text are great. But for <strong>automated testing<\/strong>, it\u2019s often better to have structured logs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Imagine this in JSON:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"level\": \"INFO\",\n  \"module\": \"boot\",\n  \"file\": \"boot.c\",\n  \"line\": 120,\n  \"time_ms\": 456,\n  \"message\": \"Main function returned with code 0\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This could be streamed over UART, captured in CI, and parsed by test scripts.<br>You might even use <strong>CBOR<\/strong> for a binary-compact version.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Vision: Logging as Instrumentation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, MicrOS logging isn\u2019t just about debugging. It becomes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A <strong>diagnostic tool<\/strong> during bring-up.<\/li>\n\n\n\n<li>A <strong>runtime control knob<\/strong> for adjusting verbosity.<\/li>\n\n\n\n<li>A <strong>data channel<\/strong> for structured system telemetry.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For a small RTOS, this is incredibly powerful \u2014 and all of it builds on the simple foundations we laid in Part 1.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up the Series<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Over four parts, we went from nothing to a flexible, colorful logging framework:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Why Logging Matters<\/strong> \u2013 log levels, module registration.<\/li>\n\n\n\n<li><strong>Coloring and Structuring<\/strong> \u2013 severity tags, VT100 colors, file\/line info.<\/li>\n\n\n\n<li><strong>Filtering<\/strong> \u2013 compile-time ceiling and per-module verbosity.<\/li>\n\n\n\n<li><strong>Extending<\/strong> \u2013 runtime control, backends, timestamps, structured logs.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The best part is: the core API is still <strong>lightweight<\/strong>. On a Cortex-M, if you set <code>CONFIG_MICROS_LOG_LEVEL=ERROR<\/code>, all the debug\/info\/warn macros disappear completely.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add a <strong>simple UART shell<\/strong> for changing log levels at runtime.<\/li>\n\n\n\n<li>Implement a <strong>ring buffer backend<\/strong> for post-mortem debugging.<\/li>\n\n\n\n<li>Experiment with <strong>structured JSON\/CBOR logs<\/strong> for CI integration.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">And of course \u2014 keep testing in QEMU and on real boards to refine performance.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udc49 That\u2019s the end of this series, but the beginning of MicrOS logging as a serious tool. If you want to dig into the code, experiment with your own backends, or suggest features, contributions are welcome!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Part 3, we gave MicrOS logging compile-time filtering and per-module verbosity control. That already makes it a powerful tool for day-to-day development. But what if we want to push further? In this final part of the series, I\u2019ll explore runtime flexibility, multiple backends, timestamps, and structured logs. These are the kinds of features that [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,7],"tags":[],"class_list":["post-157","post","type-post","status-publish","format-standard","hentry","category-development","category-logging"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/comments?post=157"}],"version-history":[{"count":1,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/157\/revisions"}],"predecessor-version":[{"id":158,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/157\/revisions\/158"}],"wp:attachment":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/media?parent=157"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/categories?post=157"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/tags?post=157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}