CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
Core Logging API

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

LogChannellogChan (strref path)
 
LogChannellogDeclareChan (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.)
 
LogChannelLogDefault
 Default log channel used when no channel is specified.
 

Detailed Description

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.

logStr(Info, _SL("Application started"));
logFmt(Warn, _SL("Invalid value: ${int}"), stvar(int32, value));
#define logStr(level, str)
Definition log.h:612
#define logFmt(level, fmt,...)
Definition log.h:643
#define _SL(s)
Inline ASCII string literal with compile-time embedded length (STR_LEN8). Content must be < 200 bytes...
Definition strliteral.h:207
#define stvar(typen, val)
Definition stvar.h:162

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:

LogChannel *netchan = logChan(_SL("net/http"));
logStrC(Info, netchan, _SL("Connection established"));
LogChannel * logChan(strref path)
#define logStrC(level, chan, str)
Definition log.h:624

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 cxcx/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:

// the application's own channels only...
logconsoleRegister(LOG_Info, NULL, NULL, NULL, &ccfg, NULL);
// ...and cx's, in a file of their own
logfileRegister(LOG_Diag, _SL("cx/**"), vfs, _SL("cx.log"), &fcfg, NULL);
LogDest * logconsoleRegister(int maxlevel, strref chanfilter, ConStream *out, ConStream *err, const LogConsoleConfig *config, LogSerializer *ser)
@ LOG_Info
Informational messages.
Definition log.h:103
@ LOG_Diag
Release build diagnostics not normally needed.
Definition log.h:105
LogDest * logfileRegister(int maxlevel, strref chanfilter, VFS *vfs, strref filename, const LogFileConfig *config, LogSerializer *ser)

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:

LogDest *dest = logfileRegister(LOG_Info, _SL("net/**"), vfs, _SL("app.log"), &config,
NULL);
// Later, unregister when done
bool logUnregisterDest(LogDest *dhandle)
struct LogDest LogDest
Opaque handle to a registered log destination.
Definition log.h:187

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:

logStr(Info, _SL("Starting operation"));
logStr(Info, _SL("Step 1 complete"));
logStr(Info, _SL("Step 2 complete"));
void logBatchBegin(void)
void logBatchEnd(void)

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.

Macro Definition Documentation

◆ LOG_Broadcast

#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.

Definition at line 123 of file log.h.

◆ LOG_Declared

#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().

Definition at line 138 of file log.h.

◆ LOG_Restricted

#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/&zwj;** sees all of net/http/request, while a bare ** sees none of it. Restriction changes permission, never reach.

Definition at line 131 of file log.h.

Typedef Documentation

◆ LogChanEnumCB

typedef bool(* LogChanEnumCB) (LogChannel *chan, void *ctx)

Called once per channel by logEnumChans()

Parameters
chanThe channel
ctxUser context passed to logEnumChans()
Returns
false to stop the walk

Definition at line 486 of file log.h.

◆ LogChannel

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().

◆ LogDestBatchDone

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.

Parameters
batchidThe batch that was completed
userdataUser-provided context pointer from logRegisterDest()

Definition at line 385 of file log.h.

◆ LogDestClose

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.

Parameters
userdataUser-provided context pointer from logRegisterDest()

Definition at line 393 of file log.h.

◆ LogDestMsg

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).

Parameters
recThe log record; valid only for the duration of the call
userdataUser-provided context pointer from logRegisterDest()

Definition at line 376 of file log.h.

◆ LogRecord

typedef struct LogRecord LogRecord

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.

◆ LogSite

typedef struct LogSite LogSite

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.

Aside: log macros in header functions
Because the site is a static object, a log macro cannot appear in a _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.

Enumeration Type Documentation

◆ 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.

Enumerator
LOG_Fatal 

Fatal errors, application cannot continue.

LOG_Error 

Non-fatal errors requiring attention.

LOG_Warn 

Warning conditions that may indicate problems.

LOG_Notice 

Normal but significant conditions.

LOG_Info 

Informational messages.

LOG_Verbose 

Detailed informational messages.

LOG_Diag 

Release build diagnostics not normally needed.

LOG_Debug 

Debug messages (compiled out of non-development builds)

LOG_Trace 

Detailed trace messages (only available in debug builds)

Definition at line 98 of file log.h.

◆ 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.

Enumerator
LOG_SiteAlways 

no rate limiting

LOG_SiteOnce 

emit only the first time this call site is reached

LOG_SiteEveryN 

emit every Nth time this call site is reached

LOG_SiteEveryT 

emit at most once per interval

Definition at line 216 of file log.h.

Function Documentation

◆ logBatchBegin()

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.

logStr(Info, _SL("Operation started"));
logStr(Info, _SL("Step 1 complete"));
logStr(Info, _SL("Step 2 complete"));
logBatchEnd(); // All three messages delivered together

◆ logBatchEnd()

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.

◆ logChan()

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.

Parameters
pathChannel path, e.g. net/http/request
Returns
Channel handle, or NULL if the logging system is not initialized
LogChannel *netchan = logChan(_SL("net/http"));
logStrC(Info, netchan, _SL("Connection established"));

◆ logDeclareChan()

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.

Parameters
pathChannel path
flagsLOG_Broadcast, LOG_Restricted, or 0 for the LOG_Restricted default
Returns
Channel handle, or NULL if the logging system is not initialized
logDeclareChan(_SL("audit"), 0); // audit and its subtree are restricted
logDeclareChan(_SL("audit/public"), LOG_Broadcast); // ...except this
LogChannel * logDeclareChan(strref path, flags_t flags)
#define LOG_Broadcast
Definition log.h:123

◆ logDestAddFilter()

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.

Parameters
dhandleDestination handle returned from logRegisterDest()
patternChannel path pattern (see logRegisterDest())
excludetrue for an exclude rule, false for an include rule
Returns
false if the destination is not registered
LogDest *dest = logRegisterDest(LOG_Debug, _SL("net/**"), myMsgFunc, NULL, NULL, NULL);
logDestAddFilter(dest, _SL("net/http/**"), true); // ...but not the HTTP subtree
LogDest * logRegisterDest(int maxlevel, strref chanfilter, LogDestMsg msgfunc, LogDestBatchDone batchfunc, LogDestClose closefunc, void *userdata)
bool logDestAddFilter(LogDest *dhandle, strref pattern, bool exclude)
@ LOG_Debug
Debug messages (compiled out of non-development builds)
Definition log.h:106

◆ logDestSetFilter()

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.

Parameters
dhandleDestination handle
patternChannel path pattern (see logRegisterDest()), or NULL for every unrestricted channel
Returns
false if the destination is not registered
logDestSetFilter(dest, _SL("net/**"));
bool logDestSetFilter(LogDest *dhandle, strref pattern)

◆ logDestSetLevel()

bool logDestSetLevel ( LogDest *  dhandle,
int  maxlevel 
)

Change the most verbose level a destination receives

Parameters
dhandleDestination handle
maxlevelMaximum log level to receive, or -1 to receive nothing
Returns
false if the destination is not registered
logDestSetLevel(dest, LOG_Debug); // turn this one up at runtime
bool logDestSetLevel(LogDest *dhandle, int maxlevel)

◆ logEnumChans()

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.

Parameters
cbCalled once per channel
ctxPassed to the callback
logEnumChans(printChannel, NULL);
void logEnumChans(LogChanEnumCB cb, void *ctx)

◆ logFlush()

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.

◆ logRecordRender()

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.

Parameters
outReceives the rendered text; any existing value is destroyed first
recRecord to render
string line = 0;
logRecordRender(&line, rec);
...
strDestroy(&line);
void logRecordRender(string *out, const LogRecord *rec)

◆ logRegisterDest()

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 it
  • net/* matches the immediate children of net
  • net/&zwj;** matches net and its entire subtree
  • ** matches everything, and is what a NULL filter means

Restricted 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:

logDeclareChan(_SL("audit"), 0); // audit and its subtree are restricted
logRegisterDest(LOG_Info, NULL, ...); // `**`: sees nothing under audit
logRegisterDest(LOG_Info, _SL("*/**"), ...); // still nothing: no literal `audit`
logRegisterDest(LOG_Info, _SL("audit/**"), ...); // names audit: the whole subtree
logRegisterDest(LOG_Info, _SL("audit/db"), ...); // names audit: that one channel

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/&zwj;** still covers audit/db, but only a pattern spelling out audit/keys reaches that branch.

Add further include/exclude rules with logDestAddFilter().

Parameters
maxlevelMaximum log level to receive (e.g., LOG_Info receives Fatal through Info)
chanfilterChannel path pattern, or NULL for every unrestricted channel
msgfuncCallback invoked for each log message
batchfuncOptional callback invoked when a batch completes
closefuncOptional callback invoked when destination is unregistered
userdataUser context pointer passed to all callbacks
Returns
Destination handle for later unregistration, or NULL on failure
LogDest *dest = logRegisterDest(LOG_Info, _SL("net/**"), myMsgFunc, NULL, NULL, &mydata);

◆ logRestart()

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.

◆ logShutdown()

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.

◆ logUnregisterDest()

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.

Parameters
dhandleDestination handle returned from logRegisterDest()
Returns
true if destination was found and removed, false otherwise

◆ logWouldLog()

bool logWouldLog ( int  level,
LogChannel chan 
)
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.

Parameters
levelLog level constant, e.g. LOG_Debug
chanChannel to test, or NULL for the default channel
Returns
true if at least one destination would receive a message at this level
if (logWouldLog(LOG_Debug, netchan))
logFmtC(Debug, netchan, _SL("state: ${string}"), stvar(string, expensiveDump()));
bool logWouldLog(int level, LogChannel *chan)
Definition log.h:560
#define logFmtC(level, chan, fmt,...)
Definition log.h:656

Definition at line 560 of file log.h.

References LogDefault.