{"id":151,"date":"2025-09-25T13:35:56","date_gmt":"2025-09-25T11:35:56","guid":{"rendered":"https:\/\/microsproject.dev\/?p=151"},"modified":"2025-09-25T14:07:55","modified_gmt":"2025-09-25T12:07:55","slug":"building-a-lightweight-logging-framework-for-micros-part-1-why-logging-matters-in-an-rtos","status":"publish","type":"post","link":"https:\/\/microsproject.dev\/index.php\/2025\/09\/25\/building-a-lightweight-logging-framework-for-micros-part-1-why-logging-matters-in-an-rtos\/","title":{"rendered":"Building a Lightweight Logging Framework for MicrOS \u2013 Part 1: Why Logging Matters in an RTOS"},"content":{"rendered":"\n<p>When working with microcontrollers, debugging can feel like peering through a keyhole. Unlike desktop development, there\u2019s no rich OS to lean on, no debugger always attached, and no full-featured console to tell you what went wrong. If your system crashes, often the only trace left is whatever text you managed to push out before everything froze.<\/p>\n\n\n\n<p>That\u2019s why a good <strong>logging framework<\/strong> is one of the first things I wanted in MicrOS \u2014 my small embedded operating system. In this series, I\u2019ll walk you through how I built a lightweight but flexible logging API for MicrOS, starting from the simplest ideas and growing it into a colorful, structured system with per-module filtering and runtime flexibility.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Why Not Just <code>printf<\/code>?<\/h2>\n\n\n\n<p>The simplest way to log on a microcontroller is to sprinkle <code>printf()<\/code> calls wherever you need to see something. And in very small projects, that works fine. But once you scale up, it quickly becomes a problem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>No severity levels<\/strong>: You can\u2019t tell \u201cdebug spam\u201d apart from real errors.<\/li>\n\n\n\n<li><strong>No filtering<\/strong>: When you want only errors, you still get everything.<\/li>\n\n\n\n<li><strong>Runtime cost<\/strong>: Even disabled <code>printf<\/code>s eat cycles and code space.<\/li>\n\n\n\n<li><strong>Poor readability<\/strong>: Raw strings are hard to scan when things get busy.<\/li>\n<\/ul>\n\n\n\n<p>In an RTOS like MicrOS, I wanted something closer to what you\u2019d expect in a professional system:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Control over <strong>how much detail<\/strong> I see (globally and per module).<\/li>\n\n\n\n<li>Clear, <strong>structured messages<\/strong> with severity and source information.<\/li>\n\n\n\n<li><strong>Zero cost<\/strong> when logs are disabled.<\/li>\n\n\n\n<li>The ability to eventually add color, timestamps, and different output backends.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Design Goals for MicrOS Logging<\/h2>\n\n\n\n<p>I set out a few rules for the system:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Global configuration<\/strong> \u2013 one macro defines the maximum log level for the whole OS (<code>CONFIG_MICROS_LOG_LEVEL<\/code>).<\/li>\n\n\n\n<li><strong>Per-module configuration<\/strong> \u2013 each source file can declare its own verbosity (<code>MICROS_LOG_REGISTER(main, MICROS_LOG_LEVEL_INFO)<\/code>).<\/li>\n\n\n\n<li><strong>Convenient macros<\/strong> \u2013 log with <code>E(\"...\")<\/code>, <code>W(\"...\")<\/code>, <code>I(\"...\")<\/code>, <code>D(\"...\")<\/code>.<\/li>\n\n\n\n<li><strong>Minimal overhead<\/strong> \u2013 if a log is above the global level, it gets compiled out.<\/li>\n\n\n\n<li><strong>Clarity<\/strong> \u2013 each message should clearly show severity, module, and (when needed) file\/line.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The First Building Block: Log Levels<\/h2>\n\n\n\n<p>At the heart of the API is a simple enum:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>typedef enum {\n    MICROS_LOG_LEVEL_NONE  = 0,\n    MICROS_LOG_LEVEL_ERROR = 1,\n    MICROS_LOG_LEVEL_WARN  = 2,\n    MICROS_LOG_LEVEL_INFO  = 3,\n    MICROS_LOG_LEVEL_DEBUG = 4\n} micros_log_level_t;\n<\/code><\/pre>\n\n\n\n<p>This gives us a spectrum from completely silent (<code>NONE<\/code>) to full verbosity (<code>DEBUG<\/code>).<\/p>\n\n\n\n<p>A global ceiling is set at build time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#define CONFIG_MICROS_LOG_LEVEL MICROS_LOG_LEVEL_DEBUG\n<\/code><\/pre>\n\n\n\n<p>So if you compile with <code>INFO<\/code>, you\u2019ll never even build debug messages into your binary.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">A Simple Logging Function<\/h2>\n\n\n\n<p>Before adding colors or fancy formatting, we just need a function to send messages out:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void _log(micros_log_level_t level,\n          const char* module_name,\n          const char* file,\n          int line,\n          const char* fmt,\n          ...);\n<\/code><\/pre>\n\n\n\n<p>This function will later handle formatting and writing to <code>stdout<\/code>\/<code>stderr<\/code>. For now, it\u2019s just a placeholder we can build around.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Registering a Module<\/h2>\n\n\n\n<p>Each C file that wants to log needs to \u201cintroduce itself\u201d:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MICROS_LOG_REGISTER(main, MICROS_LOG_LEVEL_INFO);\n<\/code><\/pre>\n\n\n\n<p>This macro stores the module\u2019s name (<code>\"main\"<\/code>) and its default verbosity. It means we can later filter logs differently per module.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using the API<\/h2>\n\n\n\n<p>Once a module is registered, logging is as easy as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I(\"System is starting up...\");\nE(\"Fatal error: %d\", code);\n<\/code><\/pre>\n\n\n\n<p>The API will decide (based on global and module levels) whether to actually print the message.<\/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<p>So far we\u2019ve defined:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <strong>log levels<\/strong>.<\/li>\n\n\n\n<li>The <strong>log function prototype<\/strong>.<\/li>\n\n\n\n<li>The <strong>per-module registration<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p>That\u2019s enough to sketch out the framework, but it doesn\u2019t yet make logs easy to read. Everything is still monochrome, and debug logs don\u2019t tell you where they came from.<\/p>\n\n\n\n<p>In <strong>Part 2<\/strong>, we\u2019ll fix that: we\u2019ll add <strong>structured formatting and VT100 colors<\/strong>, so logs pop off the screen and errors can\u2019t be missed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udc49 Stay tuned for <strong>Part 2: Coloring and Structuring Logs<\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When working with microcontrollers, debugging can feel like peering through a keyhole. Unlike desktop development, there\u2019s no rich OS to lean on, no debugger always attached, and no full-featured console to tell you what went wrong. If your system crashes, often the only trace left is whatever text you managed to push out before everything [&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-151","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\/151","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=151"}],"version-history":[{"count":1,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/151\/revisions"}],"predecessor-version":[{"id":152,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/151\/revisions\/152"}],"wp:attachment":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/media?parent=151"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/categories?post=151"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/tags?post=151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}