ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
log.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file log.h
6 * @brief Abstract logging whose disabled levels cost nothing at all (PC_LOG_LEVEL).
7 *
8 * Instrumentation is only worth leaving in the source permanently if a build that does not want it
9 * pays nothing for it - not a branch, not a call, and not a format string sitting in flash. A
10 * runtime `if (level >= threshold)` fails that last part: every message is still linked in, and on
11 * a device flash is the scarce resource.
12 *
13 * So the filter is the preprocessor. A call below PC_LOG_LEVEL expands to a form that names its
14 * arguments only inside `sizeof(...)` - an unevaluated context - which emits no code and no string
15 * literal, yet still runs the compiler's printf format checking over them and marks the arguments
16 * used (so a variable read only by a log does not warn). Enable the level and the same line starts
17 * logging, with no source change.
18 *
19 * Where an emitted line goes is the caller's choice: it is handed to services/system/logbuf's ring when
20 * PC_ENABLE_LOGBUF is on, and to a sink callback registered with pc_log_set_sink() (Serial,
21 * syslog, a websocket console) if there is one.
22 *
23 * @author Douglas Quigg (dstroy0)
24 * @date 2026
25 */
26
27#ifndef PROTOCORE_LOG_H
28#define PROTOCORE_LOG_H
29
30#include "protocore_config.h"
32#include <stdint.h>
33
34/** @brief Receives an emitted line, already formatted. @p level is a PC_LOG_LEVEL_* value. */
35typedef void (*pc_log_sink_fn)(uint8_t level, const char *line);
36
37/**
38 * @brief Declared, never defined: only ever named inside `sizeof`, so no call is ever generated.
39 *
40 * It exists to mark a discarded statement's arguments as used, so a variable read only by a log
41 * does not warn its way into being deleted.
42 */
43int pc_log_discard_args(const pc_field *spec, ...);
44
45/** @brief The discarded form: marks arguments used, emits nothing. */
46#define PC_LOG_DISCARD(...) \
47 do \
48 { \
49 (void)sizeof(pc_log_discard_args(__VA_ARGS__)); \
50 } while (0)
51
52#if PC_LOG_LEVEL < PC_LOG_LEVEL_NONE
53
54/**
55 * @brief Build @p spec into a line and route it to the logbuf ring and/or the registered sink.
56 *
57 * A spec, not a format string: the shape of the message is decided when the code is written, so
58 * nothing here parses anything at runtime, and a build whose logs declare no float field links no
59 * float formatter.
60 */
61void pc_log_frame(uint8_t level, const pc_field *spec, ...);
62
63/** @brief Install (or clear, with nullptr) the sink emitted lines are handed to. */
64void pc_log_set_sink(pc_log_sink_fn cb);
65
66#else
67
68/** @brief No level is emitted, so there is nothing to sink; kept so callers still compile. */
69static inline void pc_log_set_sink(pc_log_sink_fn cb)
70{
71 (void)cb;
72}
73
74#endif // PC_LOG_LEVEL < PC_LOG_LEVEL_NONE
75
76#if PC_LOG_LEVEL <= PC_LOG_LEVEL_DEBUG
77#define PC_LOGD(...) pc_log_frame(PC_LOG_LEVEL_DEBUG, __VA_ARGS__)
78#else
79#define PC_LOGD(...) PC_LOG_DISCARD(__VA_ARGS__)
80#endif
81
82#if PC_LOG_LEVEL <= PC_LOG_LEVEL_INFO
83#define PC_LOGI(...) pc_log_frame(PC_LOG_LEVEL_INFO, __VA_ARGS__)
84#else
85#define PC_LOGI(...) PC_LOG_DISCARD(__VA_ARGS__)
86#endif
87
88#if PC_LOG_LEVEL <= PC_LOG_LEVEL_WARN
89#define PC_LOGW(...) pc_log_frame(PC_LOG_LEVEL_WARN, __VA_ARGS__)
90#else
91#define PC_LOGW(...) PC_LOG_DISCARD(__VA_ARGS__)
92#endif
93
94#if PC_LOG_LEVEL <= PC_LOG_LEVEL_ERROR
95#define PC_LOGE(...) pc_log_frame(PC_LOG_LEVEL_ERROR, __VA_ARGS__)
96#else
97#define PC_LOGE(...) PC_LOG_DISCARD(__VA_ARGS__)
98#endif
99
100#endif // PROTOCORE_LOG_H
Declarative frame builder: a frame is a static table of typed fields, built by one engine.
void(* pc_log_sink_fn)(uint8_t level, const char *line)
Receives an emitted line, already formatted. level is a PC_LOG_LEVEL_* value.
Definition log.h:35
int pc_log_discard_args(const pc_field *spec,...)
Declared, never defined: only ever named inside sizeof, so no call is ever generated.
User-facing configuration for ProtoCore.
One field of a frame. Frames are static const pc_field[], so they live in rodata.
Definition frame.h:80