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 "xtc_export.h"
34
35#include <stdarg.h>
36#include <stddef.h>
37#include <stdint.h>
38
39#include "xtc.h"
40
41typedef enum xtc_log_level {
42 XTC_LOG_TRACE = 0,
43 XTC_LOG_DEBUG = 1,
44 XTC_LOG_INFO = 2,
45 XTC_LOG_WARN = 3,
46 XTC_LOG_ERROR = 4,
47 XTC_LOG_FATAL = 5
48} xtc_log_level_t;
49
50typedef struct xtc_log xtc_log_t;
51
52/* Sink callback shape. buf is a complete formatted line including
53 * trailing newline. Return <0 to indicate the sink failed. */
54typedef int (*xtc_log_sink_fn)(void *user, xtc_log_level_t lvl,
55 const char *buf, size_t len);
56
57typedef struct xtc_log_opts {
58 int ring_size; /* records, default 4096 */
59 int record_max; /* per-record bytes, default 256 */
60 xtc_log_level_t floor; /* default INFO */
61 int sink_fd; /* if != -1, write to this fd */
62 xtc_log_sink_fn sink; /* if non-NULL, takes precedence */
63 void *sink_user;
65
66#define XTC_LOG_OPTS_DEFAULT { \
67 .ring_size = 4096, \
68 .record_max = 256, \
69 .floor = XTC_LOG_INFO, \
70 .sink_fd = 2, /* stderr */ \
71 .sink = NULL, \
72 .sink_user = NULL \
73}
74
75/*
76 * PUBLIC: int xtc_log_create __P((const xtc_log_opts_t *, xtc_log_t **));
77 * PUBLIC: void xtc_log_destroy __P((xtc_log_t *));
78 * PUBLIC: int xtc_log_set_floor __P((xtc_log_t *, xtc_log_level_t));
79 * PUBLIC: int xtc_log_set_default __P((xtc_log_t *));
80 * PUBLIC: xtc_log_t *xtc_log_default __P((void));
81 *
82 * PUBLIC: void xtc_log_write __P((xtc_log_t *, xtc_log_level_t, const char *, ...));
83 * PUBLIC: void xtc_log_vwrite __P((xtc_log_t *, xtc_log_level_t, const char *, va_list));
84 *
85 * PUBLIC: int xtc_log_drain __P((xtc_log_t *));
86 * PUBLIC: int xtc_log_drop_count __P((const xtc_log_t *));
87 */
88
89XTC_API int xtc_log_create(const xtc_log_opts_t *opts, xtc_log_t **out);
90XTC_API void xtc_log_destroy(xtc_log_t *log);
91
92XTC_API int xtc_log_set_floor(xtc_log_t *log, xtc_log_level_t lvl);
93
94/* Set the process-wide default logger. Subsequent macro-driven
95 * calls (XTC_LOG_INFO, etc.) route here. Returns the prior default
96 * (or NULL if none). */
97XTC_API int xtc_log_set_default(xtc_log_t *log);
98XTC_API xtc_log_t *xtc_log_default(void);
99
100/* Append a record to the ring. Non-blocking; if the ring is full
101 * the oldest record is dropped and a counter is bumped. */
102#if defined(__GNUC__) || defined(__clang__)
103# define XTC_LOG_PRINTF_FMT __attribute__((format(printf, 3, 4)))
104#else
105# define XTC_LOG_PRINTF_FMT /* MSVC: no prototype format attribute */
106#endif
107XTC_API void xtc_log_write(xtc_log_t *log, xtc_log_level_t lvl,
108 const char *fmt, ...)
109 XTC_LOG_PRINTF_FMT;
110XTC_API void xtc_log_vwrite(xtc_log_t *log, xtc_log_level_t lvl,
111 const char *fmt, va_list ap);
112
113/* Drain the ring synchronously: read every queued record and write
114 * it to the sink. Returns the number of records drained. Useful
115 * for tests; in production a dedicated consumer thread/proc would
116 * call this periodically. */
117XTC_API int xtc_log_drain(xtc_log_t *log);
118
119/* Number of records dropped because the ring was full. */
120XTC_API int xtc_log_drop_count(const xtc_log_t *log);
121
122/* Convenience macros: pass through the default logger. Calls below
123 * the default-logger's floor are eliminated cheaply via a runtime
124 * check (the default logger pointer is loaded once). */
125#define XTC_LOG(lvl, ...) \
126 xtc_log_write(xtc_log_default(), (lvl), __VA_ARGS__)
127#define XTC_LOG_TRACE_F(...) XTC_LOG(XTC_LOG_TRACE, __VA_ARGS__)
128#define XTC_LOG_DEBUG_F(...) XTC_LOG(XTC_LOG_DEBUG, __VA_ARGS__)
129#define XTC_LOG_INFO_F(...) XTC_LOG(XTC_LOG_INFO, __VA_ARGS__)
130#define XTC_LOG_WARN_F(...) XTC_LOG(XTC_LOG_WARN, __VA_ARGS__)
131#define XTC_LOG_ERROR_F(...) XTC_LOG(XTC_LOG_ERROR, __VA_ARGS__)
132#define XTC_LOG_FATAL_F(...) XTC_LOG(XTC_LOG_FATAL, __VA_ARGS__)
133
134#endif /* XTC_LOG_H */