libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_log.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 * Use of this source code is governed by the ISC License.
5 *
6 * src/inc/xtc_log.h
7 * Predictable, async-safe structured logger.
8 *
9 * Design goals:
10 * - Predictable: no malloc on the hot path; preallocated ring
11 * buffer per logger; bounded latency.
12 * - Cooperative: log calls don't block the loop; a background
13 * consumer drains the ring and writes to the sink.
14 * - Structured: severity + message + key/value pairs.
15 * - Lossy under pressure: when the ring is full we drop the
16 * oldest record and bump a counter so callers know about it.
17 * Better than blocking the loop.
18 * - Thread-safe: writers and the consumer can be on different
19 * OS threads; ring uses lock-free MPSC enqueue.
20 *
21 * Sinks supported:
22 * - stderr/stdout (default)
23 * - file descriptor (preopened by caller)
24 * - custom callback (user-supplied function)
25 *
26 * Levels follow the usual syslog ordering: TRACE < DEBUG < INFO < WARN
27 * < ERROR < FATAL. At configure-time the floor is set; calls
28 * below the floor are eliminated by the macro to a no-op.
29 */
30
31#ifndef XTC_LOG_H
32#define XTC_LOG_H
33
34#include "xtc_export.h"
35
36#include <stdarg.h>
37#include <stddef.h>
38#include <stdint.h>
39
40#include "xtc.h"
41
42typedef enum xtc_log_level {
43 XTC_LOG_TRACE = 0,
44 XTC_LOG_DEBUG = 1,
45 XTC_LOG_INFO = 2,
46 XTC_LOG_WARN = 3,
47 XTC_LOG_ERROR = 4,
48 XTC_LOG_FATAL = 5
49} xtc_log_level_t;
50
51typedef struct xtc_log xtc_log_t;
52
53/* Sink callback shape. buf is a complete formatted line including
54 * trailing newline. Return <0 to indicate the sink failed. */
55typedef int (*xtc_log_sink_fn)(void *user, xtc_log_level_t lvl,
56 const char *buf, size_t len);
57
58typedef struct xtc_log_opts {
59 size_t size; /* sizeof(xtc_log_opts_t) */
60 int ring_size; /* records, default 4096 */
61 int record_max; /* per-record bytes, default 256 */
62 xtc_log_level_t floor; /* default INFO */
63 int sink_fd; /* if != -1, write to this fd */
64 xtc_log_sink_fn sink; /* if non-NULL, takes precedence */
65 void *sink_user;
67
68#define XTC_LOG_OPTS_DEFAULT { \
69 .size = sizeof(xtc_log_opts_t), \
70 .ring_size = 4096, \
71 .record_max = 256, \
72 .floor = XTC_LOG_INFO, \
73 .sink_fd = 2, /* stderr */ \
74 .sink = NULL, \
75 .sink_user = NULL \
76}
77
78/*
79 * PUBLIC: int xtc_log_create __P((const xtc_log_opts_t *, xtc_log_t **));
80 * PUBLIC: void xtc_log_destroy __P((xtc_log_t *));
81 * PUBLIC: int xtc_log_set_floor __P((xtc_log_t *, xtc_log_level_t));
82 * PUBLIC: int xtc_log_set_default __P((xtc_log_t *));
83 * PUBLIC: xtc_log_t *xtc_log_default __P((void));
84 *
85 * PUBLIC: void xtc_log_write __P((xtc_log_t *, xtc_log_level_t, const char *, ...));
86 * PUBLIC: void xtc_log_vwrite __P((xtc_log_t *, xtc_log_level_t, const char *, va_list));
87 *
88 * PUBLIC: int xtc_log_drain __P((xtc_log_t *));
89 * PUBLIC: int xtc_log_drop_count __P((const xtc_log_t *));
90 */
91
92XTC_API int xtc_log_create(const xtc_log_opts_t *opts, xtc_log_t **out);
93XTC_API void xtc_log_destroy(xtc_log_t *log);
94
95XTC_API int xtc_log_set_floor(xtc_log_t *log, xtc_log_level_t lvl);
96
97/* Set the process-wide default logger. Subsequent macro-driven
98 * calls (XTC_LOG_INFO, etc.) route here. Returns the prior default
99 * (or NULL if none). */
100XTC_API int xtc_log_set_default(xtc_log_t *log);
101XTC_API xtc_log_t *xtc_log_default(void);
102
103/* Append a record to the ring. Non-blocking; if the ring is full
104 * the oldest record is dropped and a counter is bumped. */
105#if defined(__GNUC__) || defined(__clang__)
106# define XTC_LOG_PRINTF_FMT __attribute__((format(printf, 3, 4)))
107#else
108# define XTC_LOG_PRINTF_FMT /* MSVC: no prototype format attribute */
109#endif
110XTC_API void xtc_log_write(xtc_log_t *log, xtc_log_level_t lvl,
111 const char *fmt, ...)
112 XTC_LOG_PRINTF_FMT;
113XTC_API void xtc_log_vwrite(xtc_log_t *log, xtc_log_level_t lvl,
114 const char *fmt, va_list ap);
115
116/* Drain the ring synchronously: read every queued record and write
117 * it to the sink. Returns the number of records drained. Useful
118 * for tests; in production a dedicated consumer thread/proc would
119 * call this periodically. */
120XTC_API int xtc_log_drain(xtc_log_t *log);
121
122/* Number of records dropped because the ring was full. */
123XTC_API int xtc_log_drop_count(const xtc_log_t *log);
124
125/* Convenience macros: pass through the default logger. Calls below
126 * the default-logger's floor are eliminated cheaply via a runtime
127 * check (the default logger pointer is loaded once). */
128#define XTC_LOG(lvl, ...) \
129 xtc_log_write(xtc_log_default(), (lvl), __VA_ARGS__)
130#define XTC_LOG_TRACE_F(...) XTC_LOG(XTC_LOG_TRACE, __VA_ARGS__)
131#define XTC_LOG_DEBUG_F(...) XTC_LOG(XTC_LOG_DEBUG, __VA_ARGS__)
132#define XTC_LOG_INFO_F(...) XTC_LOG(XTC_LOG_INFO, __VA_ARGS__)
133#define XTC_LOG_WARN_F(...) XTC_LOG(XTC_LOG_WARN, __VA_ARGS__)
134#define XTC_LOG_ERROR_F(...) XTC_LOG(XTC_LOG_ERROR, __VA_ARGS__)
135#define XTC_LOG_FATAL_F(...) XTC_LOG(XTC_LOG_FATAL, __VA_ARGS__)
136
137#endif /* XTC_LOG_H */