|
CX Framework
Cross-platform C utility framework
|
Data Structures | |
| struct | LogChannel |
| struct | LogSite |
| struct | LogRecord |
Macros | |
| #define | LOG_Broadcast 0x00000001 |
| #define | LOG_Restricted 0x00000002 |
| #define | LOG_Declared 0x00000004 |
Typedefs | |
| typedef struct LogChannel | LogChannel |
| typedef struct LogDest | LogDest |
| Opaque handle to a registered log destination. | |
| typedef struct LogSite | LogSite |
| typedef struct LogCtx | LogCtx |
| Opaque handle to a log context; see logctx.h. | |
| typedef struct LogRecord | LogRecord |
| typedef void(* | LogDestMsg) (const LogRecord *rec, void *userdata) |
| typedef void(* | LogDestBatchDone) (uint32 batchid, void *userdata) |
| typedef void(* | LogDestClose) (void *userdata) |
| typedef bool(* | LogChanEnumCB) (LogChannel *chan, void *ctx) |
Enumerations | |
| enum | LOG_LEVEL_ENUM { LOG_Fatal , LOG_Error , LOG_Warn , LOG_Notice , LOG_Info , LOG_Verbose , LOG_Diag , LOG_Debug , LOG_Trace , LOG_Count } |
| enum | LOG_SITE_GATE { LOG_SiteAlways = 0 , LOG_SiteOnce , LOG_SiteEveryN , LOG_SiteEveryT } |
Functions | |
| LogChannel * | logChan (strref path) |
| LogChannel * | logDeclareChan (strref path, flags_t flags) |
| void | logRecordRender (string *out, const LogRecord *rec) |
| LogDest * | logRegisterDest (int maxlevel, strref chanfilter, LogDestMsg msgfunc, LogDestBatchDone batchfunc, LogDestClose closefunc, void *userdata) |
| bool | logDestAddFilter (LogDest *dhandle, strref pattern, bool exclude) |
| bool | logDestSetFilter (LogDest *dhandle, strref pattern) |
| bool | logDestSetLevel (LogDest *dhandle, int maxlevel) |
| void | logEnumChans (LogChanEnumCB cb, void *ctx) |
| bool | logUnregisterDest (LogDest *dhandle) |
| void | logFlush (void) |
| void | logShutdown (void) |
| void | logRestart (void) |
| void | logBatchBegin (void) |
| void | logBatchEnd (void) |
| bool | logWouldLog (int level, LogChannel *chan) |
Variables | |
| strref | LogLevelNames [] |
| Array of log level names as strings (e.g., "Fatal", "Error", etc.) | |
| strref | LogLevelAbbrev [] |
| Array of single-character log level abbreviations (e.g., "F", "E", etc.) | |
| LogChannel * | LogDefault |
| Default log channel used when no channel is specified. | |
A log call names a level, a message, and whatever typed arguments go with it. It never carries, acquires, or looks up a logger object: where a record goes is a property of its channel and of the destinations that asked for that channel, configured in one place and invisible at the call site.
Logging at a level nobody is listening to is cheap enough to leave in place, and a level disabled at build time by DEBUG_LEVEL is free – it and everything it would have computed are compiled out entirely (see Log Message Macros).
A record is not a line. A log call formats nothing: it copies its arguments, hands the entry off and returns. The template is expanded later, on the thread that does the writing, and only if a text destination actually needs it. The same record reaches a structured destination as named fields instead, so JSON and human-readable output are one call site and one system rather than two parallel ones (see LogRecord and Log Serializers).
Channels: Channels are hierarchical /-separated paths that logs are routed by. They are interned, so the same path always gives back the same channel, and permanent, so the pointer can be cached at a call site:
A source file that always logs to one channel can bind it with LOG_CHANNEL instead; see the log_macros group.
The cx channel: everything the framework logs about itself goes under cx – cx/net, cx/log/stats, and so on – and that channel is declared LOG_Restricted before anything else can name it. A destination reaches the subtree only by naming it literally, so a program that registers a plain console sees its own logging and nothing else, and one that wants the framework's view asks for it:
Destinations: A destination is where records are written – a file, a console, a memory buffer. It is registered with a maximum level and a channel pattern, and any number of them can be active at once:
Patterns are matched once, when the destination is bound, so filtering can be as expressive as it likes without costing a call site anything.
Threads: writing happens on a drain thread rather than on the thread that logged. There is one per group, and every destination is in the default group unless it is moved, so a program that never mentions groups behaves as though there were a single background thread. Moving the expensive destinations into a group of their own is what keeps a slow one – a file rotating, a disk flushing – from holding up the console (see Drain Groups).
Batching: Batch multiple log messages together to ensure they appear consecutively in output, even while other threads are logging:
Beyond this: Log Context attaches correlation fields to everything logged on a thread, Retention Rings retains records nothing asked for so a later failure can claim them, and Volume Control and Backpressure decides what gives when there is more to log than there is room for.
| #define LOG_Broadcast 0x00000001 |
Channel is visible to any destination rule that matches its path
This is the default, and it is inherited down the path from the root, so interning a channel from a call site never makes output disappear. It is also the flag that re-opens a subtree beneath a restricted parent.
| #define LOG_Declared 0x00000004 |
Channel policy was set here by logDeclareChan(), rather than inherited or merely named
A receiver of forwarded records uses this to decide whose policy wins: a channel this process declared for itself keeps its own flags, and one it only knows about because a remote instance mentioned it takes the sender's. Set automatically; never pass it to logDeclareChan().
| #define LOG_Restricted 0x00000002 |
Channel is restricted: a destination rule reaches it only by naming this node literally
This is the default for an explicit logDeclareChan(), because declaring a channel is the act of carving out a stream. The restriction gates the subtree at this node, not at every channel beneath it: with net restricted, a rule of net/‍** sees all of net/http/request, while a bare ** sees none of it. Restriction changes permission, never reach.
| typedef bool(* LogChanEnumCB) (LogChannel *chan, void *ctx) |
Called once per channel by logEnumChans()
| chan | The channel |
| ctx | User context passed to logEnumChans() |
| typedef struct LogChannel LogChannel |
Log channel for filtering and organizing log messages
Channels are the routing key of the log system. They are named by a /-separated hierarchical path (net/http/request), interned in a process-wide registry so that the same path always yields the same channel, and permanent for the lifetime of the process – a channel pointer cached at a call site never dangles, even across logShutdown()/logRestart().
Obtain one with logChan(); declare policy for one with logDeclareChan().
| typedef void(* LogDestBatchDone) (uint32 batchid, void *userdata) |
Callback function type for batch completion notification
Called after all messages in a batch have been delivered to the destination. Destinations can use this to flush buffers or perform cleanup after a batch.
| batchid | The batch that was completed |
| userdata | User-provided context pointer from logRegisterDest() |
| typedef void(* LogDestClose) (void *userdata) |
Callback function type for destination cleanup
Called when a destination is unregistered. The destination should release any resources it holds.
| userdata | User-provided context pointer from logRegisterDest() |
| typedef void(* LogDestMsg) (const LogRecord *rec, void *userdata) |
Callback function type for log destinations
This function is called for each log record that passes the destination's level and channel filters. Records with the same batchid should be kept together when possible (e.g., not split across log file rotations).
| rec | The log record; valid only for the duration of the call |
| userdata | User-provided context pointer from logRegisterDest() |
One log record as a destination sees it
A record is not a formatted line. It carries the message template and a copy of the arguments that were logged with it, so that a text destination renders a sentence and a structured destination emits named fields – from the same record, with no parallel API and no possibility of a structured-only record reaching a console.
Formatting therefore happens here, on the drain thread, rather than at the call site. Call logRecordRender() to get the flat text; repeat calls within one dispatch are cheap because the rendering is shared across destinations.
Structured destinations read args directly. Keyed arguments (stvark()) become named fields; unkeyed ones are positional and belong to the template. Note that the two are disjoint – a keyed argument is never matched by an unkeyed placeholder, so both have to be read through their own accessors.
ctx carries the fields that were in scope on the logging thread (see Log Context). They are fields exactly like keyed arguments are, from a different source, and logRecordRender() makes them available to the template under their keys – so ${string:reqid} resolves against the context when the call site did not supply a reqid argument of its own.
Per-call-site state, declared by the log macros in a block of their own
Every log macro opens a block containing one of these. Its address is a stable identity for the call site: unique, never reused, valid for the lifetime of the process, and disclosing nothing – unlike __FILE__/__LINE__, which cx deliberately keeps out of the binary by default. Source location is a separate, opt-in concern and nothing here depends on it.
The counters exist for the rate-limiting macro variants (logStrOnce(), logStrEveryN(), logStrEveryT()); a call site that never uses one only ever contributes its address.
_meta_inline function: C forbids an inline definition with external linkage from containing one, so gcc and clang reject it outright. A header function that wants to log has to be plain static instead – which is no loss, since _meta_inline is for small fragments that are really just stronger-typed macros. Code that genuinely needs to log without a site can call _logStr() or _logFmt() with a NULL one; it forgoes rate limiting and any per-site behavior, and the resulting record carries no call site identity. | enum LOG_LEVEL_ENUM |
Log severity levels
Levels are ordered from most to least severe. When registering a destination with a maximum level, all messages at that level and below (more severe) will be delivered.
| enum LOG_SITE_GATE |
Rate-limiting policy applied to a call site
Always a compile-time constant at the call site, so the ungated case folds away entirely.
| void logBatchBegin | ( | void | ) |
Begin a log batch
Groups subsequent log messages into a batch that will be delivered together. Batches can be nested; only when the outermost batch ends will messages be sent.
| void logBatchEnd | ( | void | ) |
End a log batch
Completes a log batch started with logBatchBegin(). When the outermost batch ends, all batched messages are queued for delivery to destinations.
| LogChannel * logChan | ( | strref | path | ) |
Look up a log channel by path, creating it if it does not exist yet
The same path always returns the same channel, so a call site can look it up once, cache the pointer, and never look it up again. Channels are permanent: the pointer stays valid for the lifetime of the process, even across logShutdown() and logRestart().
Paths are /-separated and hierarchical; every ancestor along the path is interned too. Path components may not contain /, *, [ or ]. A channel created this way inherits its parent's visibility, so naming a path that nobody has configured behaves exactly like an unnamed log line.
| path | Channel path, e.g. net/http/request |
| LogChannel * logDeclareChan | ( | strref | path, |
| flags_t | flags | ||
| ) |
Declare policy for a log channel
Like logChan(), but attaches policy to the channel rather than merely naming it. With no flags the channel becomes LOG_Restricted, gating itself and its subtree: declaring a channel is the act of carving out a stream that goes somewhere specific, so a destination has to ask for it by name. Pass LOG_Broadcast to re-open a subtree beneath a restricted parent.
Declaring a channel that already exists changes its policy, including for channels beneath it that already exist.
| path | Channel path |
| flags | LOG_Broadcast, LOG_Restricted, or 0 for the LOG_Restricted default |
| bool logDestAddFilter | ( | LogDest * | dhandle, |
| strref | pattern, | ||
| bool | exclude | ||
| ) |
Add an include or exclude rule to a registered destination
Rules compose with most-specific-wins precedence: the matching rule with the most path components before its first wildcard decides, and an exclude wins a tie. A destination with no rules at all receives every unrestricted channel.
| dhandle | Destination handle returned from logRegisterDest() |
| pattern | Channel path pattern (see logRegisterDest()) |
| exclude | true for an exclude rule, false for an include rule |
| bool logDestSetFilter | ( | LogDest * | dhandle, |
| strref | pattern | ||
| ) |
Replace a destination's channel filter
Clears every rule the destination has and installs this one. Takes effect from the next record; anything already queued for the destination is delivered under the old filter.
| dhandle | Destination handle |
| pattern | Channel path pattern (see logRegisterDest()), or NULL for every unrestricted channel |
| bool logDestSetLevel | ( | LogDest * | dhandle, |
| int | maxlevel | ||
| ) |
Change the most verbose level a destination receives
| dhandle | Destination handle |
| maxlevel | Maximum log level to receive, or -1 to receive nothing |
| void logEnumChans | ( | LogChanEnumCB | cb, |
| void * | ctx | ||
| ) |
Walk every channel this process has seen
The registry is what a program has actually named so far, which grows as call sites are first reached. A channel is permanent once used, so this never sees one disappear.
| cb | Called once per channel |
| ctx | Passed to the callback logEnumChans(printChannel, NULL);
void logEnumChans(LogChanEnumCB cb, void *ctx) |
| void logFlush | ( | void | ) |
Flush all pending log messages
Blocks until all queued log messages have been processed by all destinations. Useful before critical operations or shutdown to ensure logs are written.
| void logRecordRender | ( | string * | out, |
| const LogRecord * | rec | ||
| ) |
Render a record to flat text
Runs the template through the formatter with the record's arguments. Text destinations use this instead of receiving a pre-formatted string, which is what moves the cost of formatting off the thread that logged the message.
The result is cached for the duration of the dispatch, so several text destinations receiving the same record only pay for one rendering between them.
| out | Receives the rendered text; any existing value is destroyed first |
| rec | Record to render string line = 0;
logRecordRender(&line, rec);
...
strDestroy(&line);
void logRecordRender(string *out, const LogRecord *rec) |
| LogDest * logRegisterDest | ( | int | maxlevel, |
| strref | chanfilter, | ||
| LogDestMsg | msgfunc, | ||
| LogDestBatchDone | batchfunc, | ||
| LogDestClose | closefunc, | ||
| void * | userdata | ||
| ) |
Register a new log destination
Registers callbacks that will receive log messages matching the specified level and channel filter. Multiple destinations can be registered simultaneously.
The channel filter is a path pattern, matched once when the destination is bound rather than per message, so its expressiveness costs nothing at a call site:
net matches the channel net exactly, and nothing beneath itnet/* matches the immediate children of netnet/‍** matches net and its entire subtree** matches everything, and is what a NULL filter meansRestricted channels. Matching a restricted channel (LOG_Restricted) is not enough on its own: the pattern also has to name the gate, meaning the part of it before its first wildcard must spell out the restricted channel's own path. A wildcard cannot stand in for any of those components, so ** – which names nothing at all – never reaches a restricted subtree:
Gates nest. Declaring audit/keys restricted as well puts a second gate below the first, and a pattern must clear the deepest one that applies – so audit/‍** still covers audit/db, but only a pattern spelling out audit/keys reaches that branch.
Add further include/exclude rules with logDestAddFilter().
| maxlevel | Maximum log level to receive (e.g., LOG_Info receives Fatal through Info) |
| chanfilter | Channel path pattern, or NULL for every unrestricted channel |
| msgfunc | Callback invoked for each log message |
| batchfunc | Optional callback invoked when a batch completes |
| closefunc | Optional callback invoked when destination is unregistered |
| userdata | User context pointer passed to all callbacks |
| void logRestart | ( | void | ) |
Restart the logging system after shutdown
Reinitializes the logging system after a previous logShutdown() call. This allows logging to resume after being explicitly stopped.
| void logShutdown | ( | void | ) |
Shutdown the logging system
Flushes all pending logs and unregisters all destinations. Channels are permanent and are deliberately kept, so a cached channel pointer stays valid across a shutdown/restart cycle. After shutdown, logging calls will be ignored until logRestart() is called.
| bool logUnregisterDest | ( | LogDest * | dhandle | ) |
Unregister a log destination
Removes the destination from the logging system and calls its close callback if provided. The destination handle becomes invalid after this call.
| dhandle | Destination handle returned from logRegisterDest() |
|
inline |
Test whether anything is listening to a channel at a given level
Guards computation that is only needed in order to log. This is the same check the log macros make internally, so it costs one relaxed atomic load and no allocation.
| level | Log level constant, e.g. LOG_Debug |
| chan | Channel to test, or NULL for the default channel |
Definition at line 560 of file log.h.
References LogDefault.