|
CX Framework
Cross-platform C utility framework
|
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) |
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.
| bool logBootWindowActive | ( | void | ) |
Is the boot window open?
| 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.
| maxlevel | Most verbose level to retain, e.g. LOG_Verbose |
| maxentries | Entries to retain, or 0 for LOG_BOOT_DEFAULT_ENTRIES |
| maxbytes | Approximate bytes to retain, or 0 for no byte cap |
| duration | How long the window stays open, 0 for LOG_BOOT_DEFAULT_DURATION, negative for no deadline at all logBootWindowBegin(LOG_Verbose, 0, 0, 0);
... // config parsing, subsystem startup
// ...and this one is backfilled with all of it
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 |
| uint32 logBootWindowCount | ( | void | ) |
How many entries the boot window is currently holding
| 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.
| 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.
| chan | Channel to clear, or NULL for the root channel |
| uint32 logChanDebugRingCount | ( | LogChannel * | chan | ) |
How many entries the ring covering a channel is currently holding
| chan | Channel to inspect, or NULL for the root channel |
| 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.
| chan | Channel to cover, or NULL for the root channel |
| maxlevel | Most verbose level to retain, e.g. LOG_Trace |
| maxentries | Entries to retain, or 0 for LOG_DEBUGRING_DEFAULT_ENTRIES |
| triglevel | Level that releases the ring, e.g. LOG_Error |