Zephyr OS Log Filtering: Separate Compiled-Out Messages from Runtime Suppression
A runtime logging command cannot recover a message that the firmware did not compile into its executable path. When a Zephyr debug message is missing, first establish whether that call can exist in the built image, then examine runtime admission and the destination backend. Raising every logging setting at once hides the distinction and can create an unnecessarily noisy diagnostic build.
This article follows Zephyr 4.4.0 and proposes a missing-message investigation. It does not claim that a particular board or shell command was tested. The goal is to identify the gate that removes one expected message, rather than to redesign the application's logging transport.

Identify one message and its source
Choose one known call site. Record the source file, severity, registered module or instance, and condition that should execute the surrounding code. Confirm that the event actually reaches that code using an appropriate independent observation. A missing branch execution is not a logging-filter failure.
Keep the build directory and executable together. A developer can inspect a current source tree while a device still runs an older image. If the message text changed between revisions, searching the wrong artifact may create a misleading diagnosis before filtering is even considered.
Write the expectation narrowly: this event should produce this severity from this source in this build. Avoid starting with “debug logging is broken,” which merges registration, compilation, filtering, backend state, and transport delivery into one claim.
Resolve the compile-time gate first
The tagged filtering configuration distinguishes default module levels, an override level, a maximum compiled level, and runtime startup filtering. The default level applies where a module does not explicitly choose its own level. The runtime startup setting does not replace compile-time eligibility.
Read the generated configuration and the module registration together. Do not interpret a fragment containing a requested setting as proof of the final value. Also record whether an override is enabled: a global override changes the normal reasoning about a module's own level.
The actual logging macro checks are useful when settings interact. In this version, the constant check has an override branch and a separate normal module/max-level branch. Avoid reducing that expression to an invented rule that every option is always applied as the same independent minimum.
For an ambiguous build, inspect compiler output or a suitably isolated preprocessed example using the actual project configuration. Treat that as a proposed verification step, not a result already obtained. Finding a string in a binary alone is weaker evidence than demonstrating the corresponding executable path and effective gate.
Examine runtime filtering only when it exists
Next confirm whether runtime filtering is enabled in this image. Minimal logging does not provide that feature. A runtime-level procedure copied from a different build may therefore be inapplicable even though ordinary log messages still appear.
For a runtime-filtered build, inspect the active source and destination settings using the facilities actually enabled by the application. Do not assume a logging shell is present. If it is present, verify the commands and source naming for that firmware rather than inventing a universal command sequence.
Use a controlled before-and-after test for the selected source. Record the initial setting, requested change, reported effective setting, and the event used to exercise the call site. Change one gate at a time. This makes it possible to distinguish “the runtime change was rejected or inapplicable” from “the change was accepted but an earlier gate still excludes the message.”
A backend can still reject an admitted message
Creation and delivery are separate decisions. The logger processing implementation checks active backends and their applicable filter levels before forwarding messages. Seeing the same source on one destination does not prove that another destination must receive it.
A hypothetical device might retain verbose diagnostics on one backend while a service console receives only higher-severity messages. In that case, the presence of the debug event in the first output supports its existence, but the second output still requires its own filter and transport investigation. This is an illustrative configuration, not a report of a deployed system.
Record which backend each observation refers to. “The level is debug” is incomplete if it omits the source, destination, and time of the observation. Startup filtering and a later application reconfiguration can also produce different results during the same boot.
Use a small diagnostic matrix
A practical proposed test uses a single source with two deliberately chosen severities and an event that reliably reaches both call sites. Keep production secrets and private payloads out of the test messages. Exercise a compiled-out case, a runtime-suppressed case, and a destination-suppressed case where the configuration supports them.
For each case, write down the expected absence or presence before running it. Preserve the effective configuration and outputs afterward. Do not infer the removal stage solely from absence; combine the observation with the relevant build or runtime evidence.
Once the gate is identified, restore the intended diagnostic policy and repeat the original event. Broad debug overrides can increase output volume and expose information that was intentionally omitted from normal operation. Retaining them indefinitely should be a separate engineering decision.
Close the investigation with the right claim
A defensible conclusion names the exact stage: the message was excluded during compilation, suppressed by an effective runtime source level, filtered for one backend, or lost later in an unresolved delivery path. If the evidence does not separate those possibilities, leave the conclusion open.
This method keeps missing-message troubleshooting specific to Zephyr's actual filtering mechanism. It also prevents a common mistake: using a successful runtime command as evidence that every debug statement in the source is now observable.