CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
Retention Rings

Macros

#define LOG_BOOT_DEFAULT_ENTRIES   4096
 Entries the boot window retains when no count is given.
 
#define LOG_BOOT_DEFAULT_DURATION   timeS(30)
 How long the boot window stays open when no duration is given.
 
#define LOG_DEBUGRING_DEFAULT_ENTRIES   256
 Entries a debug ring retains when no count is given.
 

Functions

void logBootWindowBegin (int maxlevel, uint32 maxentries, uint64 maxbytes, int64 duration)
 
void logBootWindowEnd (void)
 
bool logBootWindowActive (void)
 
uint32 logBootWindowCount (void)
 
bool logChanSetDebugRing (LogChannel *chan, int maxlevel, uint32 maxentries, int triglevel)
 
void logChanClearDebugRing (LogChannel *chan)
 
uint32 logChanDebugRingCount (LogChannel *chan)
 

Detailed Description

A retention ring holds recent entries that no destination asked for, so that something which happens later can decide they were worth keeping after all. Its default outcome is discard: entries expire out of a ring and cost nothing but the memory they occupied while they were in it.

There is one mechanism and two triggers:

The difference between the two is only in what fills a ring and what releases it. The boot window keeps the oldest entries, because startup diagnostics are what it is for and the interesting ones come first; the debug ring keeps the newest, because the context of a failure is whatever immediately preceded it.

A ring raises the channel's level ceiling. Entries that no destination wants are normally discarded at the call site, before an entry exists at all – so a ring has to be accounted for in that decision or it would never see anything. This is the cost of having one open: entries get built, copied and retained that would otherwise have cost next to nothing to check and discard.

Function Documentation

◆ logBootWindowActive()

bool logBootWindowActive ( void  )

Is the boot window open?

Returns
true if a destination registering now would be backfilled

◆ logBootWindowBegin()

void logBootWindowBegin ( int  maxlevel,
uint32  maxentries,
uint64  maxbytes,
int64  duration 
)

Open the boot window

Every record at or below maxlevel, on any channel, is retained from this call onwards. A destination registered while the window is open is backfilled from the ring first, filtered by its own level and channel rules, before it receives anything live.

Retention stops at whichever cap is reached first; the window itself stays open, so a destination registering later still gets whatever was retained. The window closes on logBootWindowEnd(), or on the deadline, and the ring is discarded then. Nothing is lost by that: the entries went to their local destinations when they were logged, and the ring is a second chance for a destination that may never arrive, not the system of record.

The backfill defines where a backfilled destination's log starts: records older than the end of it are not delivered to it a second time, whether they came from the ring or were still waiting in a queue. Records logged before the window opened, or too verbose for it to have retained, are dropped for that destination rather than arriving out of order ahead of the backfill. A destination the ring had nothing for is left alone, starting wherever it would have without a window open.

A forwarder is the exception to "registered": it has no level until a receiver subscribes, so there's nothing to backfill at registration – it gets the ring when the subscription arrives instead.

Parameters
maxlevelMost verbose level to retain, e.g. LOG_Verbose
maxentriesEntries to retain, or 0 for LOG_BOOT_DEFAULT_ENTRIES
maxbytesApproximate bytes to retain, or 0 for no byte cap
durationHow long the window stays open, 0 for LOG_BOOT_DEFAULT_DURATION, negative for no deadline at all
... // config parsing, subsystem startup
// ...and this one is backfilled with all of it
LogDest *dest = logfileRegister(LOG_Info, NULL, vfs, _SL("app.log"), &cfg, NULL);
struct LogDest LogDest
Opaque handle to a registered log destination.
Definition log.h:187
@ LOG_Verbose
Detailed informational messages.
Definition log.h:104
@ LOG_Info
Informational messages.
Definition log.h:103
LogDest * logfileRegister(int maxlevel, strref chanfilter, VFS *vfs, strref filename, const LogFileConfig *config, LogSerializer *ser)
void logBootWindowEnd(void)
void logBootWindowBegin(int maxlevel, uint32 maxentries, uint64 maxbytes, int64 duration)
#define _SL(s)
Inline ASCII string literal with compile-time embedded length (STR_LEN8). Content must be < 200 bytes...
Definition strliteral.h:207

◆ logBootWindowCount()

uint32 logBootWindowCount ( void  )

How many entries the boot window is currently holding

Returns
Retained entry count, 0 if no window is open

◆ logBootWindowEnd()

void logBootWindowEnd ( void  )

Close the boot window and discard what it retained

The application knows when it considers itself started, and knows it far better than any timer. Safe to call when no window is open.

◆ logChanClearDebugRing()

void logChanClearDebugRing ( LogChannel chan)

Take a channel's debug ring away

The subtree goes back to whatever it inherits, which is usually nothing. Anything the ring was holding is discarded, not released.

Parameters
chanChannel to clear, or NULL for the root channel

◆ logChanDebugRingCount()

uint32 logChanDebugRingCount ( LogChannel chan)

How many entries the ring covering a channel is currently holding

Parameters
chanChannel to inspect, or NULL for the root channel
Returns
Retained entry count, 0 if the channel has no ring

◆ logChanSetDebugRing()

bool logChanSetDebugRing ( LogChannel chan,
int  maxlevel,
uint32  maxentries,
int  triglevel 
)

Give a channel a retroactive debug ring

Off by default, everywhere. A channel has no ring until this is called for it or for one of its ancestors, and a process that never calls it pays nothing at all: the ring is what raises the channel's level ceiling, so without one the verbose records it would have kept are still discarded at the call site.

While a ring is configured, records on the channel that no destination wanted – and only those, so nothing is ever delivered twice – are retained, oldest evicted first. When a record at or below triglevel is logged on a channel the ring covers, everything it holds is released to the destinations that route the channel, ahead of the record that released it.

A released record is filtered as if it were the severity of the event that released it, not its own: a destination that would have seen the error sees the context leading up to it, and one that would not see the error does not get a burst of trace either.

The ring is inherited down the path, so one on net covers all of net/http/request. Setting one on the root channel (chan NULL) covers the whole process.

Parameters
chanChannel to cover, or NULL for the root channel
maxlevelMost verbose level to retain, e.g. LOG_Trace
maxentriesEntries to retain, or 0 for LOG_DEBUGRING_DEFAULT_ENTRIES
triglevelLevel that releases the ring, e.g. LOG_Error
Returns
true if the ring was configured
LogChannel * logChan(strref path)
@ LOG_Trace
Detailed trace messages (only available in debug builds)
Definition log.h:107
@ LOG_Error
Non-fatal errors requiring attention.
Definition log.h:100
bool logChanSetDebugRing(LogChannel *chan, int maxlevel, uint32 maxentries, int triglevel)