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