Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
sapi_log.c
Go to the documentation of this file.
1
20
21#include <string.h>
22
24
28typedef struct
29{
30 const char *name;
31 sapi_log_level_t level;
33
36static const sapi_log_backend_t *s_backend = NULL;
37
42
46 { "DEBUG", SAPI_LOG_LEVEL_DEBUG },
47 { "INFO", SAPI_LOG_LEVEL_INFO },
48 { "WARNING", SAPI_LOG_LEVEL_WARNING },
49 { "ERROR", SAPI_LOG_LEVEL_ERROR }
50};
51
53
67static void sapi_log_append_event_field(sapi_string_t *line, const char *key, const char *value);
76static void sapi_log_append_event_field_u32(sapi_string_t *line, const char *key, uint32_t value);
77
78
81{
82 sapi_status_t lifecycle_status;
83
84 if (backend == NULL)
85 {
87 }
88 /* REQ-LIFECYCLE-001 (ADR-026): registering a backend is a setup-only
89 * action - refuse once the application's setup phase has been locked. */
90 lifecycle_status = sapi_lifecycle_check_setup_allowed();
91 if (lifecycle_status != SAPI_STATUS_OK)
92 {
93 return lifecycle_status;
94 }
95 s_backend = backend;
96 return SAPI_STATUS_OK;
97}
98
100{
101 if ((s_backend == NULL) || (s_backend->init == NULL))
102 {
103 return SAPI_STATUS_OK;
104 }
105 return s_backend->init();
106}
107
108void sapi_log_write(sapi_log_level_t level, const char *tag, const char *message)
109{
110 if ((s_backend == NULL) || (s_backend->write == NULL))
111 {
112 /* No backend registered: intentionally a silent no-op, not an
113 * error - REQ-OAL-LOG-001 requires logging to never affect the
114 * caller's control flow. */
115 return;
116 }
117 if ((int)level < (int)s_min_level)
118 {
119 /* Below the runtime threshold (sapi_log_set_level()) - dropped. */
120 return;
121 }
122 s_backend->write(level, tag, message);
123}
124
126{
128
129 if ((name != NULL) && (out_level != NULL))
130 {
131 size_t i;
132
133 for (i = 0U; i < (sizeof(s_level_names) / sizeof(s_level_names[0])); i++)
134 {
135 if (strcmp(name, s_level_names[i].name) == 0)
136 {
137 *out_level = s_level_names[i].level;
138 status = SAPI_STATUS_OK;
139 break;
140 }
141 }
142 }
143 return status;
144}
145
147{
148 /* Only an upper bound is checked: the enum's underlying type is
149 * unsigned (values 0..3), so `>= SAPI_LOG_LEVEL_DEBUG` would be a
150 * tautology. A wrapped/garbage value lands above ERROR and is ignored. */
151 if ((int)min_level <= (int)SAPI_LOG_LEVEL_ERROR)
152 {
153 s_min_level = min_level;
154 }
155}
156
161
163{
164 const char *result;
165
166 switch (level)
167 {
169 result = "DEBUG";
170 break;
172 result = "INFO";
173 break;
175 result = "WARNING";
176 break;
178 result = "ERROR";
179 break;
180 default:
181 /* Defensive only - not reachable through the public enum. */
182 result = "UNKNOWN";
183 break;
184 }
185 return result;
186}
187
189 const char *site,
190 uint32_t cycle,
191 const char *source,
192 const char *destination,
193 const char *type,
194 const char *info,
195 const char *extra_fields)
196{
197 char line_storage[SAPI_LOG_EVENT_LINE_MAX_LEN];
198 char ts_storage[24];
199 sapi_string_t line;
200 sapi_string_t ts;
201 sapi_timestamp_ms_t now_ms = 0U;
202 const char *ts_cstr = NULL;
203 const char *line_cstr = NULL;
204
205 if ((s_backend == NULL) || (s_backend->write == NULL) || ((int)level < (int)s_min_level))
206 {
207 /* Same silent-no-op contract as sapi_log_write() above - and the
208 * same runtime-threshold drop (sapi_log_set_level()). Bail before
209 * the formatting work below when there is nowhere for it to go, or
210 * when it would be dropped anyway. */
211 return;
212 }
213
214 (void)sapi_string_init(&line, line_storage, sizeof(line_storage));
215 (void)sapi_string_init(&ts, ts_storage, sizeof(ts_storage));
216
217 /* Site: written directly (no leading space/key), first field. */
218 (void)sapi_string_concat(&line, "Site=");
219 (void)sapi_string_concat(&line, (site != NULL) ? site : "");
220
221 /* TIMESTAMP: best-effort - stays 0 (its declared initializer) if no
222 * sapi_timer backend is registered or the call otherwise fails; a
223 * timer problem must never prevent this event from being logged
224 * (REQ-OAL-LOG-001), so the field is degraded, not the whole call. */
225 (void)sapi_timer_now(&now_ms);
226 (void)sapi_string_from_u64(&ts, now_ms);
227 (void)sapi_string_c_str(&ts, &ts_cstr);
228 sapi_log_append_event_field(&line, "Timestamp", (ts_cstr != NULL) ? ts_cstr : "0");
229
231 sapi_log_append_event_field_u32(&line, "Cycle", cycle);
232 sapi_log_append_event_field(&line, "Source", source);
233 sapi_log_append_event_field(&line, "Destination", destination);
234 sapi_log_append_event_field(&line, "Type", type);
235 sapi_log_append_event_field(&line, "Info", info);
236
237 if ((extra_fields != NULL) && (extra_fields[0] != '\0'))
238 {
239 /* extra_fields is already caller-formatted Key=Value text (see
240 * this function's own doc) - appended verbatim behind a single
241 * separating space, not wrapped in another key, so it reads as
242 * more of the same space-separated Key=Value convention rather
243 * than a nested field. An empty string is treated exactly like
244 * NULL (no field, no trailing space) so an empty
245 * sapi_log_fields_t builder round-trips cleanly. */
246 (void)sapi_string_concat(&line, " ");
247 (void)sapi_string_concat(&line, extra_fields);
248 }
249
250 (void)sapi_string_c_str(&line, &line_cstr);
251 s_backend->write(level, source, (line_cstr != NULL) ? line_cstr : "");
252}
253
255{
256 if (fields != NULL)
257 {
258 (void)sapi_string_init(&fields->str, fields->storage, sizeof(fields->storage));
259 }
260}
261
269static bool sapi_log_fields_begin_pair(sapi_log_fields_t *fields, const char *key)
270{
271 bool ok = (fields != NULL) && (key != NULL);
272
273 if (ok)
274 {
275 if (sapi_string_length(&fields->str) > 0U)
276 {
277 (void)sapi_string_concat(&fields->str, " ");
278 }
279 (void)sapi_string_concat(&fields->str, key);
280 (void)sapi_string_concat(&fields->str, "=");
281 }
282 return ok;
283}
284
285sapi_log_fields_t *sapi_log_fields_add_str(sapi_log_fields_t *fields, const char *key, const char *value)
286{
287 if (sapi_log_fields_begin_pair(fields, key))
288 {
289 (void)sapi_string_concat(&fields->str, (value != NULL) ? value : "");
290 }
291 return fields;
292}
293
294sapi_log_fields_t *sapi_log_fields_add_u32(sapi_log_fields_t *fields, const char *key, uint32_t value)
295{
296 if (sapi_log_fields_begin_pair(fields, key))
297 {
298 (void)sapi_string_append_u32(&fields->str, value);
299 }
300 return fields;
301}
302
303sapi_log_fields_t *sapi_log_fields_add_i32(sapi_log_fields_t *fields, const char *key, int32_t value)
304{
305 if (sapi_log_fields_begin_pair(fields, key))
306 {
307 (void)sapi_string_append_i32(&fields->str, value);
308 }
309 return fields;
310}
311
312sapi_log_fields_t *sapi_log_fields_add_u64(sapi_log_fields_t *fields, const char *key, uint64_t value)
313{
314 if (sapi_log_fields_begin_pair(fields, key))
315 {
316 (void)sapi_string_append_u64(&fields->str, value);
317 }
318 return fields;
319}
320
321sapi_log_fields_t *sapi_log_fields_add_i64(sapi_log_fields_t *fields, const char *key, int64_t value)
322{
323 if (sapi_log_fields_begin_pair(fields, key))
324 {
325 (void)sapi_string_append_i64(&fields->str, value);
326 }
327 return fields;
328}
329
330sapi_log_fields_t *sapi_log_fields_add_hex_u32(sapi_log_fields_t *fields, const char *key,
331 uint32_t value, uint8_t min_digits)
332{
333 if (sapi_log_fields_begin_pair(fields, key))
334 {
335 (void)sapi_string_concat(&fields->str, "0x");
336 (void)sapi_string_append_hex_u32(&fields->str, value, min_digits);
337 }
338 return fields;
339}
340
341sapi_log_fields_t *sapi_log_fields_add_bool(sapi_log_fields_t *fields, const char *key, bool value)
342{
343 if (sapi_log_fields_begin_pair(fields, key))
344 {
345 (void)sapi_string_concat(&fields->str, value ? "true" : "false");
346 }
347 return fields;
348}
349
351{
352 const char *out = "";
353
354 if (fields != NULL)
355 {
356 const char *tmp = NULL;
357
358 if (sapi_string_c_str(&fields->str, &tmp) == SAPI_STATUS_OK)
359 {
360 out = tmp;
361 }
362 }
363 return out;
364}
365
367 const char *site,
368 uint32_t cycle,
369 const char *source,
370 const char *destination,
371 const char *type,
372 const char *info,
373 sapi_log_fields_t *fields)
374{
375 /* c_str() yields "" for a NULL/empty builder; sapi_log_write_event()
376 * now treats "" exactly like NULL, so no special-casing needed. */
377 sapi_log_write_event(level, site, cycle, source, destination, type, info,
378 sapi_log_fields_c_str(fields));
379}
380
381
382/****Local functions ****/
383
384static void sapi_log_append_event_field(sapi_string_t *line, const char *key, const char *value)
385{
386 (void)sapi_string_concat(line, " ");
387 (void)sapi_string_concat(line, key);
388 (void)sapi_string_concat(line, "=");
389 (void)sapi_string_concat(line, (value != NULL) ? value : "");
390}
391
392static void sapi_log_append_event_field_u32(sapi_string_t *line, const char *key, uint32_t value)
393{
394 char num_storage[16];
395 sapi_string_t num;
396 const char *num_cstr = NULL;
397
398 (void)sapi_string_init(&num, num_storage, sizeof(num_storage));
399 (void)sapi_string_from_u32(&num, value);
400 (void)sapi_string_c_str(&num, &num_cstr);
401 sapi_log_append_event_field(line, key, num_cstr);
402}
sapi_status_t sapi_lifecycle_check_setup_allowed(void)
Convenience check for a setup-only constructor: call this as one of the first checks in any function ...
sapi_status_t sapi_log_register_backend(const sapi_log_backend_t *backend)
Registers the backend implementation used by sapi_log_init()/ sapi_log_write() (ADR-005 section 2....
Definition sapi_log.c:80
void sapi_log_write_event(sapi_log_level_t level, const char *site, uint32_t cycle, const char *source, const char *destination, const char *type, const char *info, const char *extra_fields)
Emits one structured inter-channel event/message-trail log line - for a message actually sent/receive...
Definition sapi_log.c:188
const char * sapi_log_level_to_string(sapi_log_level_t level)
Renders a sapi_log_level_t as a fixed, human-readable tag - "DEBUG"/"INFO"/"WARNING"/"ERROR" - used a...
Definition sapi_log.c:162
sapi_log_level_t
Log message severity, passed to sapi_log_write().
Definition sapi_log.h:34
const char * sapi_log_fields_c_str(sapi_log_fields_t *fields)
NUL-terminated view of the accumulated pairs.
Definition sapi_log.c:350
sapi_log_level_t sapi_log_get_level(void)
Returns the current minimum severity (see sapi_log_set_level()). REQ-OAL-LOG-015.
Definition sapi_log.c:157
void sapi_log_write_event_fields(sapi_log_level_t level, const char *site, uint32_t cycle, const char *source, const char *destination, const char *type, const char *info, sapi_log_fields_t *fields)
sapi_log_write_event() with a builder passed directly as the extra fields - no sapi_log_fields_c_str(...
Definition sapi_log.c:366
void sapi_log_set_level(sapi_log_level_t min_level)
Sets the minimum severity that sapi_log_write() and sapi_log_write_event() forward to the backend - a...
Definition sapi_log.c:146
sapi_status_t sapi_log_level_from_string(const char *name, sapi_log_level_t *out_level)
Parses one of the canonical level spellings ("DEBUG", "INFO", "WARNING", "ERROR" - exactly as sapi_lo...
Definition sapi_log.c:125
sapi_status_t sapi_log_init(void)
Initializes the logging backend. Safe to call once at startup.
Definition sapi_log.c:99
void sapi_log_fields_reset(sapi_log_fields_t *fields)
(Re)initialises a builder to empty. MUST be called before the first sapi_log_fields_add_*(); safe to ...
Definition sapi_log.c:254
void sapi_log_write(sapi_log_level_t level, const char *tag, const char *message)
Emits one log message. Non-blocking; never fails the caller's control flow even if the message is dro...
Definition sapi_log.c:108
#define SAPI_LOG_EVENT_LINE_MAX_LEN
Max length, in bytes and not counting the NUL terminator, of one sapi_log_write_event() line - fields...
Definition sapi_log.h:122
sapi_log_fields_t * sapi_log_fields_add_str(sapi_log_fields_t *fields, const char *key, const char *value)
Appends one key=value pair (preceded by a single separating space only when the builder is already no...
Definition sapi_log.c:285
@ SAPI_LOG_LEVEL_DEBUG
Definition sapi_log.h:35
@ SAPI_LOG_LEVEL_ERROR
Definition sapi_log.h:38
@ SAPI_LOG_LEVEL_INFO
Definition sapi_log.h:36
@ SAPI_LOG_LEVEL_WARNING
Definition sapi_log.h:37
sapi_status_t
Common result/status codes.
Definition sapi_status.h:27
@ SAPI_STATUS_INVALID_PARAM
Definition sapi_status.h:29
@ SAPI_STATUS_OK
Definition sapi_status.h:28
sapi_status_t sapi_string_from_u64(sapi_string_t *dest, uint64_t value)
Bounded base-10 itoa equivalent for uint64_t. Replaces dest's content.
sapi_status_t sapi_string_init(sapi_string_t *str, char *storage, size_t capacity)
Binds a string to caller-owned storage. Initial length is 0 (empty string).
Definition sapi_string.c:10
sapi_status_t sapi_string_c_str(sapi_string_t *str, const char **out_cstr)
Ensures a NUL terminator is present within capacity (without incrementing length) and returns a point...
Definition sapi_string.c:37
sapi_status_t sapi_string_append_hex_u32(sapi_string_t *dest, uint32_t value, uint8_t min_digits)
Bounded append of a uint32_t formatted as lowercase hexadecimal (no "0x" prefix - the caller prepends...
sapi_status_t sapi_string_append_i64(sapi_string_t *dest, int64_t value)
Bounded base-10 append of an int64_t; a leading '-' is emitted for negative values....
size_t sapi_string_length(const sapi_string_t *str)
Returns a string's current length (not counting a NUL terminator).
Definition sapi_string.c:28
sapi_status_t sapi_string_concat(sapi_string_t *dest, const char *src)
Bounded strcat equivalent. Never calls strlen(src) - scans for a NUL only up to dest's remaining capa...
sapi_status_t sapi_string_append_u32(sapi_string_t *dest, uint32_t value)
Bounded base-10 append of a uint32_t to dest's existing content (unlike sapi_string_from_u32(),...
sapi_status_t sapi_string_append_u64(sapi_string_t *dest, uint64_t value)
Bounded base-10 append of a uint64_t. See sapi_string_append_u32().
sapi_status_t sapi_string_append_i32(sapi_string_t *dest, int32_t value)
Bounded base-10 append of an int32_t; a leading '-' is emitted for negative values....
sapi_status_t sapi_string_from_u32(sapi_string_t *dest, uint32_t value)
Bounded base-10 itoa equivalent for uint32_t. Replaces dest's content.
sapi_status_t sapi_timer_now(sapi_timestamp_ms_t *out_now_ms)
Returns the current monotonic time base used by all timers.
Definition sapi_timer.c:130
uint64_t sapi_timestamp_ms_t
Definition sapi_types.h:30
static const sapi_clocksync_backend_t * s_backend
Process-wide application setup-phase lock (ADR-026).
static bool sapi_log_fields_begin_pair(sapi_log_fields_t *fields, const char *key)
Appends the "[ ]key=" prefix of one pair (leading space only if the builder already holds something)....
Definition sapi_log.c:269
static sapi_log_level_t s_min_level
Minimum severity forwarded to the backend (sapi_log_set_level()). SAPI_LOG_LEVEL_DEBUG = nothing filt...
Definition sapi_log.c:41
static void sapi_log_append_event_field(sapi_string_t *line, const char *key, const char *value)
Appends " Key=Value" to *line (a leading space, then key, "=", then value or "" if value is NULL) - t...
Definition sapi_log.c:384
static void sapi_log_append_event_field_u32(sapi_string_t *line, const char *key, uint32_t value)
Formats value in base 10 and appends it as "Key=value" via sapi_log_append_event_field()....
Definition sapi_log.c:392
static const sapi_log_level_name_t s_level_names[]
The four canonical spellings, kept in sync with sapi_log_level_to_string().
Definition sapi_log.c:45
OS Abstraction Layer - Logging/diagnostics service.
OS-backend adaptation surface for the Logging/diagnostics service (ADR-005, ADR-021).
Bounded, checked string manipulation (ADR-006). Replaces strcpy/strcat/sprintf/atoi/strtok-style unbo...
OS Abstraction Layer - Timer service.
Backend vtable: an integrator's implementation of the logging sink (ADR-005). Unlike every other OAL ...
sapi_status_t(*) init(void)
Backend implementation of sapi_log_init(). May be NULL.
void(*) write(sapi_log_level_t level, const char *tag, const char *message)
Backend implementation of sapi_log_write(). May be NULL.
Bounded builder for sapi_log_write_event()'s extra_fields (or info) argument: accumulates space-separ...
Definition sapi_log.h:232
sapi_string_t str
Definition sapi_log.h:233
char storage[SAPI_LOG_EVENT_LINE_MAX_LEN]
Definition sapi_log.h:234
One canonical level spelling <-> value pair for sapi_log_level_from_string().
Definition sapi_log.c:29
Bounded string: buf.length is the string length, not counting a NUL.
Definition sapi_string.h:39