{"id":155,"date":"2025-09-27T13:38:00","date_gmt":"2025-09-27T11:38:00","guid":{"rendered":"https:\/\/microsproject.dev\/?p=155"},"modified":"2025-09-25T14:07:55","modified_gmt":"2025-09-25T12:07:55","slug":"building-a-lightweight-logging-framework-for-micros-part-3-compile-time-and-per-module-filtering","status":"publish","type":"post","link":"https:\/\/microsproject.dev\/index.php\/2025\/09\/27\/building-a-lightweight-logging-framework-for-micros-part-3-compile-time-and-per-module-filtering\/","title":{"rendered":"Building a Lightweight Logging Framework for MicrOS \u2013 Part 3: Compile-Time and Per-Module Filtering"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In <a href=\"https:\/\/microsproject.dev\/index.php\/2025\/09\/26\/building-a-lightweight-logging-framework-for-micros-part-2-coloring-and-structuring-logs\/\" target=\"_blank\" rel=\"noreferrer noopener\">Part 2<\/a>, we added colors, severity tags, and file\/line information to MicrOS logs. Now we\u2019re going to take it further: instead of just printing everything, we\u2019ll give developers <strong>fine-grained control<\/strong> over what gets logged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is crucial in embedded systems, where every byte of flash and every CPU cycle counts. We want <strong>debug detail when we need it<\/strong>, but <strong>zero overhead when we don\u2019t<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Two Layers of Filtering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MicrOS logging supports <strong>two filters<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Global filter<\/strong>\n<ul class=\"wp-block-list\">\n<li>Controlled by <code>CONFIG_MICROS_LOG_LEVEL<\/code>.<\/li>\n\n\n\n<li>Defines the <em>absolute maximum<\/em> verbosity compiled into the system.<\/li>\n\n\n\n<li>Example: if you set it to <code>INFO<\/code>, then all <code>D()<\/code> logs vanish at compile time.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Per-module filter<\/strong>\n<ul class=\"wp-block-list\">\n<li>Defined by <code>MICROS_LOG_REGISTER(module, level)<\/code>.<\/li>\n\n\n\n<li>Lets each source file decide how chatty it should be.<\/li>\n\n\n\n<li>Example: <code>boot<\/code> might default to <code>INFO<\/code>, while <code>scheduler<\/code> runs at <code>DEBUG<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The effective log level is the intersection of both.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Global Ceiling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At build time, you choose the global level:<\/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 class=\"wp-block-paragraph\">This controls which macros are compiled in. For example, if the global level is <code>INFO<\/code>, then the <code>D()<\/code> macro literally expands to nothing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#if CONFIG_MICROS_LOG_LEVEL &gt;= MICROS_LOG_LEVEL_DEBUG\n#define D(...) _log(MICROS_LOG_LEVEL_DEBUG, _micros_log_module_name, __FILE__, __LINE__, __VA_ARGS__)\n#else\n#define D(...) do {} while (0)\n#endif\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So debug logs don\u2019t even exist in the binary.<\/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 class=\"wp-block-paragraph\">Each source file declares itself like this:<\/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 class=\"wp-block-paragraph\">This expands to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>static const char* _micros_log_module_name = \"main\";\nstatic const micros_log_level_t _micros_log_level = MICROS_LOG_LEVEL_INFO;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So now the macros know both <strong>which module is speaking<\/strong> and <strong>how chatty it\u2019s allowed to be<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Logging Macros<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s how an info log works:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#if CONFIG_MICROS_LOG_LEVEL &gt;= MICROS_LOG_LEVEL_INFO\n#define I(...) \\\n    do { \\\n        if (_micros_log_level &gt;= MICROS_LOG_LEVEL_INFO) { \\\n            _log(MICROS_LOG_LEVEL_INFO, _micros_log_module_name, __FILE__, __LINE__, __VA_ARGS__); \\\n        } \\\n    } while (0)\n#else\n#define I(...) do {} while (0)\n#endif\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, we check the <strong>global level<\/strong> (<code>CONFIG_MICROS_LOG_LEVEL<\/code>).<\/li>\n\n\n\n<li>Then, at runtime, we check the <strong>module\u2019s level<\/strong> (<code>_micros_log_level<\/code>).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This way, you can build with <code>DEBUG<\/code> globally but keep certain modules quieter by registering them with <code>INFO<\/code> or <code>WARN<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Real Output: <code>init_functions<\/code> in QEMU<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what it looks like when running the <code>init_functions<\/code> sample:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;D] &#91;main] --\/home\/grzegorz\/projects\/micros\/samples\/init_functions\/main.c:22-- System clock initialized - simulated\n&#91;D] &#91;main] --\/home\/grzegorz\/projects\/micros\/samples\/init_functions\/main.c:26-- UART initialized - simulated\n&#91;D] &#91;main] --\/home\/grzegorz\/projects\/micros\/samples\/init_functions\/main.c:31-- System initialized successfully - simulated\n&#91;D] &#91;main] --\/home\/grzegorz\/projects\/micros\/samples\/init_functions\/main.c:35-- Very early init function in section .init_array.50 - simulated\n&#91;I] &#91;main] MicrOS Initialization Framework Example\n&#91;I] &#91;main] System is up and running!\n&#91;I] &#91;main] Main function is exiting now.\n&#91;I] &#91;boot] Main function returned with code 0\n&#91;D] &#91;main] --\/home\/grzegorz\/projects\/micros\/samples\/init_functions\/main.c:39-- Very late fini function in section .fini_array - simulated\n&#91;I] &#91;boot] System halted after main return\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notice:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>main<\/code> is set to <code>DEBUG<\/code>, so we see detailed traces with file\/line.<\/li>\n\n\n\n<li><code>boot<\/code> is set to <code>INFO<\/code>, so only info-level logs are printed \u2014 no debug noise.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is the power of per-module filtering: you see the details where you need them, but keep the rest of the system quiet.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Efficiency Matters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Why not just check levels at runtime? Because:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Logs above the global level are compiled out completely \u2192 <strong>zero code size<\/strong>.<\/li>\n\n\n\n<li>The runtime check only applies inside enabled macros \u2192 <strong>minimal cost<\/strong>.<\/li>\n\n\n\n<li>Disabled logs don\u2019t even reach <code>_log()<\/code> \u2192 <strong>no wasted formatting<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This makes the system suitable even for tiny Cortex-M0 devices with &lt;32 KB of flash.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Where We Stand<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, MicrOS logging provides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Severity levels with colors<\/strong> (from Part 2).<\/li>\n\n\n\n<li><strong>Global filtering<\/strong> to keep the binary lean.<\/li>\n\n\n\n<li><strong>Per-module filtering<\/strong> for fine-grained verbosity.<\/li>\n\n\n\n<li><strong>Minimal runtime overhead<\/strong> when logs are disabled.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is already a professional-grade logging system for an 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\">Coming Up Next<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In <strong>Part 4<\/strong>, we\u2019ll look at how to extend the framework:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Changing log levels at <strong>runtime<\/strong> (via UART shell or debug CLI).<\/li>\n\n\n\n<li>Routing logs to <strong>different backends<\/strong> (UART, RTT, memory buffer, network).<\/li>\n\n\n\n<li>Adding <strong>timestamps<\/strong> for precise sequencing.<\/li>\n\n\n\n<li>Even exploring <strong>structured logs<\/strong> for automated testing.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s where logging goes from <em>debugging tool<\/em> to <em>system-level instrumentation<\/em>.<\/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 Stay tuned for <strong>Part 4: Extending the Framework<\/strong> \u2014 where we make MicrOS logs dynamic, flexible, and even more powerful.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Part 2, we added colors, severity tags, and file\/line information to MicrOS logs. Now we\u2019re going to take it further: instead of just printing everything, we\u2019ll give developers fine-grained control over what gets logged. This is crucial in embedded systems, where every byte of flash and every CPU cycle counts. We want debug detail [&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-155","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\/155","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=155"}],"version-history":[{"count":1,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/155\/revisions"}],"predecessor-version":[{"id":156,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/155\/revisions\/156"}],"wp:attachment":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/media?parent=155"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/categories?post=155"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/tags?post=155"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}