CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
log_private.h
1#pragma once
2
3#include <cx/container.h>
4#include <cx/thread.h>
5#include <cx/utils/lazyinit.h>
6#include "log.h"
7#include "logctx.h"
8#include "loggroup.h"
9#include "logring.h"
10#include "logserializer.h"
11#include "logvolume.h"
12
13#define LOG_INITIAL_QUEUE_SIZE 32
14#define LOG_MAX_QUEUE_SIZE 262144
15
16extern atomic(bool) _log_running;
17extern Mutex _log_run_lock;
18
19// One include or exclude rule of a destination's channel filter. litdepth is the number of path
20// components before the rule's first wildcard, which is both its specificity and what decides
21// whether it can pass a restricted channel's gate.
22//
23// comps is the pattern already split on '/', for the same reason litdepth is precomputed: a
24// routing recompute evaluates every rule of every destination against every channel, and none of
25// that work depends on the channel.
26typedef struct LogFilterRule {
27 string pattern;
28 sa_string comps;
29 uint32 litdepth;
30 bool exclude;
31} LogFilterRule;
32
33typedef struct LogDest {
34 LogFilterRule* rules;
35 uint32 nrules;
36
37 // A second, independent rule set that a channel must *also* match. Empty for a destination
38 // configured only locally.
39 //
40 // `rules` is what this process has decided the destination may ever receive, and `subrules`
41 // is what a remote subscriber has asked for.
42 LogFilterRule* subrules;
43 uint32 nsubrules;
44 LogDestMsg msgfunc;
45 LogDestBatchDone batchfunc;
46 LogDestClose closefunc;
47 void* userdata;
48 int maxlevel;
49 uint32 idx; // stable slot index in the destination table
50 LogGroup* group; // drain group whose thread does this destination's work; never NULL
51
52 // Where this destination's history begins, for one backfilled from the boot ring.
53 //
54 // The ring captures an entry when it is logged, not when it is delivered, so an entry can be
55 // in the ring *and* still sitting in a queue. Backfilling would then deliver it twice: once
56 // from the ring, once when the drain thread gets to the queue. Recording how far the backfill
57 // reached and dropping anything at or below it closes that, and needs no lock -- both fields
58 // are written while the destination is still private to the registering thread and published
59 // with it.
60 //
61 // The rule this states is that the backfill *defines* where the destination's log starts.
62 // Records older than the end of it either came from it or predate this destination entirely;
63 // either way they are not delivered a second time.
64 uint64 backfillseq;
65 bool backfilled; // ...and whether there was a backfill at all, since 0 is a valid sequence
66
67 // This destination sends records off the machine, which is what engages loop prevention: it
68 // never binds to the cx/net subtree (logRoutingChanRow) and never receives a record marked
69 // localonly (logDispatchRecord). Set by logforwardRegister(); nothing else may set it.
70 bool remote;
71} LogDest;
72saDeclarePtr(LogDest);
74
75// A queued record, before any destination has seen it.
76//
77// The entry keeps the message template and a copy of the arguments rather than a rendered
78// string, so formatting happens once on the drain thread instead of on whichever thread logged
79// the message, and structured destinations get fields rather than a sentence to re-parse. An
80// entry from logStr() carries its message in the same field but is marked not-a-template, because
81// a literal message that happens to contain ${...} must survive rendering unchanged.
82//
83// An entry is refcounted because it is delivered to every drain group interested in it and
84// destroyed by whichever of them finishes last.
85typedef struct LogEntry LogEntry;
86
87// One entry's presence in one group's queue. The queue element is a chain of these: a chain is
88// one push, which is what keeps a batch from being interleaved with another thread's entries.
89//
90// Most entries reach exactly one group, so an entry carries one of these inline and only the
91// second and later groups allocate; see LogEntry.inlnode and logQueueNodeIsInline().
92typedef struct LogQueueNode LogQueueNode;
93typedef struct LogQueueNode {
94 LogQueueNode* next;
95 LogEntry* ent; // holds a reference
96} LogQueueNode;
97saDeclarePtr(LogQueueNode);
98
99typedef struct LogEntry {
100 // Chain used only while a batch is being accumulated on the logging thread, before the
101 // entries reach any queue. Once enqueued a batch is held together by LogQueueNode instead,
102 // because each group sees a different subset of the batch and one pointer cannot express two
103 // chains.
104 //
105 // It is deliberately not shared with inlnode.next. logRingRelease() rebuilds _next on entries
106 // it takes from a debug ring, and one of those can still be sitting in a group's queue, so
107 // aliasing the two would let the logging thread corrupt a chain a drain thread is walking.
108 LogEntry* _next;
109
110 // Queue node for the first group this entry reaches, so that the overwhelmingly common
111 // single-group case allocates nothing beyond the entry itself and transfers the creator's
112 // reference rather than taking a second one.
113 //
114 // Only the fan-out that follows the entry's creation claims it, on the creating thread, which
115 // is what makes the claim race-free with no flag to guard it. An entry being fanned out a
116 // second time -- logRingRelease() replaying a debug ring -- allocates its nodes, because it
117 // cannot know whether the first fan-out queued this one.
118 LogQueueNode inlnode;
119
120 // One per queue node. The creator's reference is transferred into the inline node when it is
121 // claimed, or released outright when no group wanted the entry.
122 atomic(uint32) refs;
123 int64 timestamp;
124 uint64 seq; // monotonic global sequence; see logNextSeq()
125 LogChannel* chan;
126 const LogSite* site; // call site identity, NULL for a dynamically generated entry
127 string msgtmpl;
128 stvar* args; // points into the tail of this allocation, NULL when nargs is 0
129 LogCtx* ctx; // snapshot of the logging thread's context; owned
130 int nargs;
131 int level;
132 uint32 batchid; // assigned at enqueue, so every group sees the same one
133 uint32 sample; // sampling rate this entry survived; 0 or 1 for none
134 int trigger; // severity that released this entry from a ring; -1 if it never was
135 string origin; // instance this entry was forwarded from; empty for a local one
136 uint8 hops; // instances this entry has been forwarded through
137 bool istmpl; // msgtmpl is a format template, not a literal message
138 bool localonly; // must not reach a destination that leaves the machine
139} LogEntry;
140saDeclarePtr(LogEntry);
141
142// Is this node the one embedded in the entry it refers to?
143//
144// Such a node is part of the entry's allocation, so it is never freed on its own -- and because
145// releasing the entry may free the node's own storage, nothing may touch the node after that
146// release.
147_meta_inline bool logQueueNodeIsInline(_In_ const LogQueueNode* node)
148{
149 return node == &node->ent->inlnode;
150}
151
152// Immutable snapshot of the destination table and the per-channel routing masks. A new version is
153// built under _log_op_lock and published with a single atomic pointer store; drain threads take a
154// reference once per batch and walk it with no lock held. Slots are stable: an entry is NULL if
155// the slot is free, and a slot is not handed out again until its previous occupant has been
156// reclaimed.
157//
158// The masks live here rather than inline on the channel because at the design's floor of 128
159// destinations they are multi-word, and a multi-word mask cannot be read atomically -- the
160// version pointer is what makes them consistent to read at any width. The channel dimension is
161// sized with slack so that interning a channel, which is a lazy per-call-site event, does not
162// produce a version at all.
163typedef struct LogRouting {
164 uint32 ndest; // number of destination slots
165 uint32 nchan; // channel rows this version has room for
166 uint32 nwords; // (ndest + 63) / 64, minimum 2
167 LogDest** dests; // ndest entries, NULL for a free slot
168 uint64* destmask; // nchan * nwords, row for channel i starts at i * nwords
169 // storage for both arrays follows the header
170} LogRouting;
171
172// Idle state of one drain thread. The thread publishes the generation it is working at before it
173// touches a routing version, and LOG_QUIESCENT while it is asleep holding no references at all.
174#define LOG_QUIESCENT 0
175typedef struct LogDrain {
176 atomic(uint32) epoch;
177} LogDrain;
178saDeclarePtr(LogDrain);
179
180// A named drain group. The struct is permanent -- the registry survives
181// logShutdown()/logRestart() the same way the channel registry does -- but the queue and thread
182// exist only while the log system is running.
183typedef struct LogGroup {
184 string name;
185 uint32 idx;
186 PrQueue queue;
187 Thread* thread;
188 LogDrain* drain;
189 Event doneevent;
190
191 // Excludes destination callbacks for the destinations that belong to this group. The drain
192 // thread holds it across its dispatch loop; logWriteSync() and logPanicFlush() take it to
193 // deliver from a thread that is not the drain.
194 Mutex dispatchlock;
195
196 atomic(uint32) depth; // entries waiting in this group's queue
197 atomic(uint32) peak; // high-water mark of the above
198 hashtable dedup; // LogSite* -> LogDedupState*, drain-thread private
199 int64 dedupdue; // when the earliest open dedup window closes, 0 if none
200} LogGroup;
201
202// Groups are looked up on the enqueue path with no lock, so the table is a fixed array published
203// by count rather than a growable one: a group is written into its slot before the count that
204// makes it visible is bumped, and groups are never removed.
205extern LogGroup* _log_grouptab[LOG_GROUP_MAX];
206extern atomic(uint32) _log_ngroups;
207
208// Protects the configuration side of the log system: _log_dests, _log_channels, the group table,
209// the routing version pointer and the retire list. Drain threads never take this lock, so
210// creating a channel or registering a destination cannot block behind a destination doing I/O.
211extern Mutex _log_op_lock;
212extern sa_LogDest _log_dests;
213extern hashtable _log_channels; // channel path -> LogChannel*
214extern sa_LogChannel _log_chans; // indexed by LogChannel.idx
215
216extern LazyInitState _logInitState;
217
218// Monotonic global sequence number, assigned once per entry when the entry is created.
219//
220// Wall-clock timestamps are not an ordering: they can jump, and two entries logged on different
221// threads in the same microsecond are indistinguishable by time. A single atomic increment gives
222// exact cross-thread ordering, which is what recovers the true order of entries that reached the
223// queue out of order -- the per-thread overflow chain in logqueue.c is exactly that case, since
224// it holds entries back and re-pushes them behind later ones.
225uint64 logNextSeq(void);
226
227// Wrap-aware ordering test, the same discipline TCP sequence numbers use: correct as long as the
228// two are less than half the counter's range apart, which holds for anything being collated
229// within a batch window.
230//
231// Not really needed anymore with 64-bit sequence numbers, but keep anyway just in case somebody
232// keeps a process running for like 1000 years or something.
233_meta_inline bool logSeqBefore(uint64 a, uint64 b)
234{
235 return (int64)(a - b) < 0;
236}
237
238void logCheckInit(void);
239
240// Builds an entry, deep-copying the arguments so the caller's temporaries can go out of scope.
241// Assigns a fresh sequence number, once, for the life of the entry: replaying an entry never
242// renumbers it, which is what lets a backfill be compared against one. Returns NULL if the
243// allocation could not be satisfied.
244_Ret_opt_valid_ LogEntry*
245logEntryCreate(int level, int64 timestamp, _In_ LogChannel* chan, _In_opt_ const LogSite* site,
246 _In_opt_ strref tmpl, int nargs, _In_opt_ stvar* args, _In_opt_ LogCtx* ctx);
247// Fills in the destination-facing view of an entry. cache may be NULL, in which case every
248// destination that renders the record pays for its own rendering.
249void logEntryToRecord(_Out_ LogRecord* rec, _In_ const LogEntry* ent, uint32 batchid,
250 _In_opt_ LogRenderCache* cache);
251
252// Delivers a chain of entries to every drain group interested in it, consuming the caller's
253// reference on each. The chain is threaded through LogEntry._next.
254//
255// `fresh` says these entries have never been fanned out before, so their inline queue nodes are
256// free to claim. It is true for the enqueue path, where the entries were just created on this
257// thread, and false for a replay (logRingRelease), where an entry may already be queued.
258void logFanout(_In_opt_ LogEntry* head, bool fresh);
259
260// Entry references. A fan-out takes one per queue node; the last release destroys the entry.
261_Ret_valid_ LogEntry* logEntryAcquire(_In_ LogEntry* ent);
262void logEntryRelease(_In_ LogEntry* ent);
263
264// Pushes a chain of queue nodes onto one group's queue as a single unit, so nothing interleaves
265// with a batch. Never blocks; on overflow the chain is held on a per-thread, per-group list, and
266// if that is full too the chain is dropped -- except for entries at or above the synchronous
267// level, which are written from this thread instead.
268void logQueueAdd(_In_ LogGroup* group, _In_ LogQueueNode* head, uint32 nents);
269// Releases the entries a chain of queue nodes refers to and frees the nodes.
270void logQueueFreeNodes(_In_opt_ LogQueueNode* head);
271// Number of entries in a chain of queue nodes.
272uint32 logQueueCount(_In_opt_ LogQueueNode* head);
273
274// Drain groups (loggroup.c). logGroupInit() runs under the run lock or lazy init and creates the
275// queues; the threads are started separately, because starting one takes _log_op_lock.
276void logGroupInit(void);
277void logGroupStartAll(void);
278void logGroupStopAll(void);
279void logGroupShutdown(void);
280int logGroupThread(_Inout_ Thread* self);
281
282// Delivers one record to every destination of this group that its channel routes to. Shared by
283// the drain loop, the deduplicator's summary records and logPanicFlush(). `sent` accumulates the
284// destinations that were reached, for the batch-done callbacks.
285void logDispatchRecord(_In_ LogGroup* grp, _In_opt_ LogRouting* routing, _In_ const LogRecord* rec,
286 _Inout_ sa_LogDest* sent);
287// The live routing version, with no grace-period reference taken. Only for a caller that is not
288// a registered drain thread and accepts the risk: logPanicFlush() in a dying process.
289_Ret_opt_valid_ LogRouting* logRoutingCurrentUnsafe(void);
290
291// Volume control (logvolume.c, logdedup.c).
292extern atomic(uint64) _log_stat_enqueued;
293extern atomic(uint64) _log_stat_dropped;
294extern atomic(uint64) _log_stat_sampled;
295extern atomic(uint64) _log_stat_suppressed;
296extern atomic(uint64) _log_stat_sync;
297extern atomic(int32) _log_synclevel;
298
299// Decides whether a record on this channel survives sampling, counting it if it does not. Runs
300// at the call site, before an entry exists.
301bool logSamplePasses(_In_ LogChannel* chan, int level, _Out_ uint32* rate);
302
303// Drain-side deduplication. Returns false if the record should not be delivered. Both are called
304// only from a drain thread, which is what makes the per-group table lock-free.
305bool logDedupPasses(_In_ LogGroup* grp, _In_ const LogRecord* rec);
306// Emits summaries for every window that has closed. Called with a routing version in hand.
307void logDedupFlush(_In_ LogGroup* grp, _In_opt_ LogRouting* routing, _Inout_ sa_LogDest* sent,
308 bool all);
309void logDedupDestroy(_Inout_ LogGroup* grp);
310// How long the drain thread may sleep before a dedup window needs closing; timeForever if none.
311int64 logDedupWait(_In_ LogGroup* grp);
312
313// Periodic self-logging of the statistics (logvolume.c). Called by a drain thread when it goes
314// idle; does nothing until the configured interval has elapsed.
315void logStatsTick(_In_ LogGroup* grp);
316
317// Retention rings (logring.c).
318//
319// A ring holds references to entries no destination asked for, until something releases them.
320// keepoldest stops accepting once the ring is full instead of evicting, which is what the boot
321// window wants and the retroactive debug ring does not.
322typedef struct LogRing LogRing;
323_Ret_valid_ LogRing* logRingCreate(int maxlevel, uint32 cap, uint64 maxbytes, int trigger,
324 bool keepoldest);
325int logRingMaxLevel(_In_ LogRing* ring);
326void logRingDestroy(_Inout_ LogRing** pring);
327void logRingClear(_Inout_ LogRing* ring);
328void logRingReconfigure(_Inout_ LogRing* ring, int maxlevel, uint32 cap, uint64 maxbytes,
329 int trigger);
330uint32 logRingCount(_Inout_ LogRing* ring);
331// Retains an entry, taking a reference. False if the ring did not want it.
332bool logRingPush(_Inout_ LogRing* ring, _In_ LogEntry* ent);
333// Flattens the ring oldest-first into an array the caller owns and must pass to
334// logRingFreeTaken(). With drain set the ring is emptied and its references move to the caller.
335uint32 logRingTake(_Inout_ LogRing* ring, _Outptr_ LogEntry*** out, bool drain);
336void logRingFreeTaken(_Pre_valid_ _Post_invalid_ LogEntry** ents, uint32 n);
337
338// Most verbose level any open ring retains, or -1. Folded into every channel's ceiling by the
339// routing table, because an entry no destination wants is otherwise dropped before it exists.
340extern atomic(int32) _log_bootlevel;
341
342// Offers a freshly created entry to whatever rings want it. Called on the logging thread.
343void logRingCapture(_In_ LogEntry* ent);
344// Most verbose level the ring covering this channel retains, or -1 if it has none.
345int logChanRingLevel(_In_ LogChannel* chan);
346// Backfills a destination that is not published yet from the boot ring.
347void logRingReplay(_In_ LogDest* dest);
348// Finishes closing a boot window that hit its deadline. Called by a drain thread when idle.
349void logRingTick(_In_ LogGroup* grp);
350void logRingShutdown(void);
351
352// Writes a chain of queue nodes from the calling thread, skipping anything less severe than
353// minlevel. The backpressure path for entries a full queue would otherwise drop (logpanic.c).
354void logWriteSync(_In_ LogGroup* grp, _In_opt_ LogQueueNode* head, int minlevel);
355
356// Is this thread inside a callback the log system owns -- a drain dispatch, or a forwarder's
357// call into an application transport? Anything logged while it is costs nothing extra but is
358// marked localonly, which is loop prevention's second layer (logthread.c).
359bool logInLocalScope(void);
360
361// Builds a context node directly, rather than by entering one on a thread. Used by the injector
362// to attach a forwarded record's fields without disturbing the injecting thread's own context.
363// Takes a reference to `parent`; the returned node is owned by the caller.
364_Ret_valid_ LogCtx* logCtxCreate(_In_opt_ LogCtx* parent, int n, _In_opt_ const stvar* vars);
365
366// Channel registry (logchan.c). The registry is permanent and is built exactly once.
367void logChanInit(void);
368// Is this channel cx's own transport, `cx/net` or anything beneath it? Loop prevention's first
369// layer: a destination that leaves the machine never binds to one of these.
370bool logChanIsTransport(_In_ const LogChannel* chan);
371// Interns a channel named by a forwarded record, applying the sender's policy flags only where
372// this process has not declared the path itself. See logInject().
373_Ret_opt_valid_ LogChannel* logChanApplyRemote(_In_ strref path, flags_t flags);
374uint32 logChanLitDepth(_In_ strref pattern);
375bool logChanMatch(_In_ strref pattern, _In_opt_ strref path);
376// Splits a channel path or filter pattern into the component form the matcher works in. The
377// output is initialized by this call and belongs to the caller.
378void logChanSplitPath(_Inout_ sa_string* _Nonnull out, _In_opt_ strref path);
379// Does this destination's filter reach this channel? Evaluated at bind time only.
380bool logChanRuleMatch(_In_ LogDest* dest, _In_ LogChannel* chan);
381// Same, for a caller that is testing many destinations against one channel and has already split
382// its path with logChanSplitPath().
383bool logChanRuleMatchComps(_In_ LogDest* dest, _In_ LogChannel* chan, _In_ sa_string* _Nonnull comps);
384
385// Routing table versioning and reclamation (logrouting.c). Everything but the drain-side epoch
386// calls must be called with _log_op_lock held.
387void logRoutingInit(void);
388// Rebuilds every channel's routing mask and level ceiling from _log_dests, publishes the new
389// version and retires the previous one. Called whenever a destination or a channel declaration
390// changes what matches what. Returns the generation the new version was published at.
391uint32 logRoutingPublish(void);
392// Computes the row for a channel that is about to become reachable, growing the table only if
393// there is no room left. Adding a channel never changes any other channel's row.
394void logRoutingAddChan(_Inout_ LogChannel* chan);
395void logRoutingRetireDest(_In_ LogDest* dest, uint32 gen);
396bool logRoutingSlotPending(uint32 idx);
397// Frees everything that has become unreachable and whose grace period has expired.
398void logRoutingSweep(void);
399// Unconditional teardown; only valid once every drain thread has exited.
400void logRoutingShutdown(void);
401
402_Ret_opt_valid_ LogDrain* logDrainRegister(void);
403void logDrainUnregister(_Pre_valid_ _Post_invalid_ LogDrain* drain);
404// Publishes the generation the drain thread is about to work at and returns the routing version
405// to use for this batch. Must be paired with logDrainIdle() before the thread sleeps.
406_Ret_opt_valid_ LogRouting* logDrainEnter(_Inout_ LogDrain* drain);
407void logDrainIdle(_Inout_ LogDrain* drain);
408
409// As logRegisterDest(), for a destination that sends records off the machine. Sets LogDest.remote
410// before the destination is published, so loop prevention is in force from its first record.
411_Ret_opt_valid_ LogDest*
412logRegisterRemoteDest(int maxlevel, _In_opt_ strref chanfilter, _In_ LogDestMsg msgfunc,
413 _In_opt_ LogDestBatchDone batchfunc, _In_opt_ LogDestClose closefunc,
414 _In_opt_ void* userdata);
415
416// does NOT free dhandle, the caller is responsible for retiring it
417bool logUnregisterDestLocked(_In_ LogDest* dhandle);
418// inserts into a free slot of the destination table; does not publish
419void logDestInsertLocked(_In_ LogDest* dest);
420void logDestAddRuleLocked(_Inout_ LogDest* dest, _In_opt_ strref pattern, bool exclude);
421void logDestFreeRules(_Inout_ LogDest* dest);
422// Replaces the destination's subscription rule set, the one a channel must match in addition to
423// the local rules. An empty list clears it. Does not publish; the caller does.
424void logDestSetSubRulesLocked(_Inout_ LogDest* dest, _In_opt_ const sa_string* patterns);
425// Same, taking the configuration lock and republishing. NULL or an empty list clears the set.
426bool logDestSetSubFilter(_In_ LogDest* dhandle, _In_opt_ const sa_string* patterns);
427// Applies a subscription's rule set and level in one step, and backfills the destination from the
428// boot ring if a window is still open. Caller must hold no locks that the destination group may
429// need.
430bool logDestSubscribe(_In_ LogDest* dhandle, _In_opt_ const sa_string* patterns, int maxlevel);
Generic type-safe containers with runtime type system integration.
#define saDeclarePtr(name)
Definition sarray.h:100
struct LogDest LogDest
Opaque handle to a registered log destination.
Definition log.h:187
void(* LogDestBatchDone)(uint32 batchid, void *userdata)
Definition log.h:385
struct LogCtx LogCtx
Opaque handle to a log context; see logctx.h.
Definition log.h:227
void(* LogDestMsg)(const LogRecord *rec, void *userdata)
Definition log.h:376
void(* LogDestClose)(void *userdata)
Definition log.h:393
#define LOG_GROUP_MAX
Definition loggroup.h:49
#define stvar(typen, val)
Definition stvar.h:162
Threading system aggregated header.
Thread-safe lazy initialization.
Core logging system API.
Per-thread log context: fields every record on this thread inherits.
Drain groups: which thread does a destination's work.
Bounded retention rings: the boot window, and the retroactive debug ring.
Log record serializers, independent of where the result is written.
Volume control: what happens when there is too much to log.
Definition event.h:53
State tracker for lazy initialization.
Definition lazyinit.h:40
Definition log.h:208
Definition mutex.h:60