Flow 1.0.1
Flow project: Full implementation reference.
config.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include "flow/log/log.hpp"
22#include <boost/unordered_map.hpp>
23#include <atomic>
24#include <typeinfo>
25#include <utility>
26
27namespace flow::log
28{
29
30// Types.
31
32/**
33 * Class used to configure the filtering and logging behavior of `Logger`s; its use in your custom `Logger`s is
34 * optional but encouraged; supports dynamically changing filter settings even while concurrent logging occurs.
35 *
36 * If you are reading this to know how to configure an existing Logger, then you don't need further background; just
37 * see this API to know how to configure such `Logger`s in a uniform way.
38 *
39 * If you are, instead, reading this when implementing a new custom Logger, then please see implementation
40 * recommendations in Logger doc header before continuing here.
41 *
42 * @todo Class Config doc header is wordy and might be hard to follow; rewrite for clarity/flow/size.
43 *
44 * ### Synopsis: How do I use it to configure a Logger? Just tell me! ###
45 * Really it's pretty easy to use it, but it's much easier to see how it's done by example rather than a
46 * formal-ish description (which is nevertheless below, in the next section of this doc header).
47 * To use Config with a `Config`-supporting Logger in your flow::log-using module M:
48 *
49 * - Declare the component `enum` inside the module M. Declare and define the associated index-to-name map for
50 * that `enum`. While it is possible to manually do this, we provide some tools to utterly minimize the
51 * boiler-plate required (which would be considerable otherwise). The files `config_enum_{start|end}.macros.[hc]pp`
52 * are those tools.
53 * - Start with config_enum_start_hdr.macros.hpp.
54 * - In module M, use this new component `enum` (totally separate from any others, such as Flow's
55 * flow::Flow_log_component) at log call sites. More precisely, either pass a value to Log_context ctor or
56 * to FLOW_LOG_SET_CONTEXT().
57 * - An example is the pervasive logging done by various Flow sub-modules with the exception of flow::log itself
58 * (which ironically doesn't do all that much logging, chicken/egg). Look for Log_context and
59 * FLOW_LOG_SET_CONTEXT() mentions.
60 * - Outside module M, in the program which uses module M -- perhaps along with other modules such as Flow itself --
61 * probably around startup, set up your new `Config`.
62 * Call Config::init_component_to_union_idx_mapping() and Config::init_component_names() to register M's
63 * `enum` -- AND again for every other logging module besides M (such as Flow itself). Call
64 * Config::configure_default_verbosity() and Config::configure_component_verbosity() (and friend) to configure
65 * verbosity levels. Finally, create the specific Logger and give it a pointer to this Config.
66 * - Examples of this can almost certainly be found in certain test programs outside of Flow code proper.
67 * Again, setting Config and specific `Logger` object's config shouldn't be done in libraries/modules but normally
68 * at the program level, such as around `main()`.
69 * - After this, the logging itself can begin (including from multiple threads). Concurrently with logging --
70 * which will often call output_whether_should_log() and potentially output_component_to_ostream() -- you
71 * may not safely call any of the above `init_*()` or `configure_*()` methods, with the following important
72 * exceptions:
73 * - You may call Config::configure_default_verbosity() safely concurrently with logging to dynamically change
74 * verbosity setting.
75 * - You may call Config::configure_component_verbosity() or Config::configure_component_verbosity_by_name()
76 * similarly.
77 *
78 * ### What Config controls and how ###
79 * Let's get into it more formally.
80 *
81 * Firstly, Config simply stores simple scalars controlling output behavior. For example, the public
82 * member #m_use_human_friendly_time_stamps controls the style of time stamps in the final output. It's just
83 * a data store for such things.
84 *
85 * Secondly, Config knows how to *understand* the *component* values supplied at every single log call site in
86 * your program. (This also ties in to the next thing we discuss, verbosity config.) See Component doc header.
87 * Now, here's how Config understands components. In your program, you will use various `flow::log`-using libraries
88 * or modules -- including (but not necessarily limited to!) Flow itself. Each module is likely to feature their own
89 * component table, in the form of an `enum class`. For example, Flow itself has `enum class Flow_log_component`
90 * (see common.hpp).
91 * - So, suppose there are two modules, M1 and M2 (e.g., M1 might be Flow itself). Each will define
92 * a component table `enum class`, C1 and C2, respectively. You are to *register* C1; and separately/similarly C2;
93 * using an init_component_to_union_idx_mapping() for each of C1 and C2. (All of this applies for any number
94 * of modules and enums, not just two.)
95 * - The Config maintains a single, merged *component union* table, which will (after registration) map
96 * *every* `C1::` enumeration member AND *every* `C2::` member to a distinct *component union index* integer.
97 * (For example, in the init_component_to_union_idx_mapping() call, you can specify that C1's members will map
98 * to union indices 1000, 1001, ...; and C2's to 2000, 2001, ....) These flat union indices between C1 and C2
99 * must never clash.
100 * - The Config also maintains a single, distinct *name* string, for each component. An optional feature is provided
101 * to auto-prepend a prefix configured for each registered `enum class` (e.g., "C1_" for all `C1::` members,
102 * "C2_" for all `C2::` members). One provides (for each of C1, C2) this optional prefix and a simple map from
103 * the `enum` members to their distinct string names. Use init_component_names().
104 * - After registering each of C1 and C2 with init_component_to_union_idx_mapping() and init_component_names(),
105 * in your *program* (e.g., in `main()`) but outside the modules M1 and M2 *themselves*, the Config now has:
106 * - The ability to map an incoming log::Component (as supplied to Logger::do_log()) to its index in the flat
107 * `Config`-internal union of all component `enum` values.
108 * - Having obtained that flat index, quickly obtain its distinct component name.
109 * - Conversely, suppose one passes in a distinct component name. Config can now quickly map that string to
110 * its flat index in the union table of all component `enum` values.
111 * - The following two features are built on this mapping of multiple individual component tables onto a single
112 * flat "union" table of components built inside the Config; as well as on the aforementioned mapping of
113 * union table index to component name string; and vice versa.
114 *
115 * Thirdly, and crucially, the verbosity filtering (Logger::should_log()) for the client Logger is entirely implemented
116 * via the output_whether_should_log() output method; so Logger::should_log() can simply forward to that method. Here
117 * is how one configures its behavior in Config. At construction, or in a subsequent configure_default_verbosity()
118 * call, one sets the *default verbosity*, meaning the most-verbose log::Sev that `should_log()` would let through
119 * when no per-component verbosity for the log call site's specified Component is configured. In addition, assuming
120 * more fine-grained (per-component) verbosity config is desired, one can call `configure_component_verbosity*()`
121 * to set the most-verbose log::Sev for when `should_log()` is passed that specific Component payload.
122 *
123 * Setting per-component verbosity can be done by its `enum` value. Or one can use the overload that takes the
124 * component's distinct (among *all* source component `enum` tables registered before) string name; in which case
125 * the aforementioned performant name-to-index mapping is used internally to set the proper union component's verbosity.
126 *
127 * (Note that order of calls matters: configure_default_verbosity() wipes out effects of any individual, per-component
128 * `configure_component_verbosity*()` executed prior to it. It is typical (in a given round of applying config) to
129 * first call configure_default_verbosity() and then make 0 or more `configure_component_verbosity*()` calls for various
130 * individual components.)
131 *
132 * Fourthly, output_component_to_ostream() is a significant helper for your Logger::do_log() when actually printing
133 * the ultimate character representation of the user's message and metadata. The metadata (Msg_metadata) includes
134 * a Component; that method will output its string representation to the `ostream` given to it. To do so it will
135 * use the aforementioned ability to quickly map the `C1::` or `C2::` member to the flat union index to that index's
136 * distinct name (the same name optionally used to configure verbosity of that component, as explained above).
137 *
138 * Or one can opt to print the flat numeric index of the component instead; in which case the reverse name-to-union-idx
139 * is not needed.
140 *
141 * ### Thread safety ###
142 * Formally, Config is thread-safe for all operations when concurrent access is to separate `Config`s.
143 * There are no `static` data involved. Formally, Config is generally NOT thread-safe when concurrent read and write
144 * access is w/r/t to a single Config; this includes read/write of any public data members and read/write in the form
145 * `const`/otherwise method calls. Informally, one could use an outside mutex, including in any Logger
146 * implementation that uses `*this`, but we recommend against this for performance reasons; and see below "exception."
147 *
148 * Also formally for a given `*this`: The logging phase is assumed to begin after all `init_*()` calls and any
149 * initial `configure_*()` calls; at this point output_whether_should_log() and output_component_to_ostream() may be
150 * used at will by any thread; but the pre-logging-phase non-`const` calls are no longer allowed.
151 *
152 * There is an important exception to the assertion that Config `*this` one must NOT call any write methods once
153 * the logging phase has begun. Informally, this exception should make it possible to use Config safely *and yet*
154 * dynamically allow changes to Config without any expensive outside mutex. The exception is as follows:
155 *
156 * Assume, as is proper, that you've called all needed init_component_to_union_idx_mapping() and init_component_names()
157 * before any concurrent logging and have now started to log -- you are in the logging phase.
158 * Now assume you want to change verbosity settings during this logging-at-large phase;
159 * this is common, for example, when some dynamic config file changes verbosity settings
160 * for your program. The following is safe: You may call configure_default_verbosity() and/or
161 * `configure_component_verbosity*()` while expecting the changes to take effect promptly in all threads; namely,
162 * output_whether_should_log() will reflect the change there; and output_component_to_ostream() does not care.
163 * Perf-wise, little to nothing is sacrified (internally, a lock-free implementation is used).
164 *
165 * Corner case: It is also safe to call configure_default_verbosity() and/or `configure_component_verbosity*()`
166 * concurrently with themselves. Naturally, it is a race as to which thread "wins." Moreover,
167 * configure_default_verbosity() with `reset == true` is equivalent to removing the verbosity setting for each
168 * individual component; but only each removal itself is atomic, not the overall batch operation; so concurrent
169 * execution with another `configure_*_verbosity()` call may result in an interleaved (but valid) verbosity table.
170 * Informally, we recommend against any design that would allow concurrent `configure_*()` calls on `*this`; it does
171 * not seem wise. It does however result in well-defined behavior as described. The real aim, though, is not this
172 * corner case but only the main case of a series of `configure_*()` calls in thread 1, while logging may be going on
173 * in other threads.
174 *
175 * ### Optional: Thread-local verbosity override ###
176 * In a pinch, it may be desirable -- *temporarily* and *in a given thread of execution only* -- to change the
177 * current verbosity config. To do so in a given scope `{}` simply do this:
178 *
179 * ~~~
180 * {
181 * // In this { scope } temporarily let-through only error messages or more severe.
182 * const auto overrider = flow::log::Config::this_thread_verbosity_override_auto(flow::log::Sev::S_WARNING);
183 *
184 * // Now let's create 3,000 threads each of which would normally log a few "starting thread" startup INFO messages!
185 * m_thread_pool = std::make_unique<flow::async::Cross_thread_task_loop>(get_logger(), "huge_pool", 3000);
186 * m_thread_pool->start();
187 * } // Previous thread-local verbosity is restored (whether there was one, or none) upon exit from {block}.
188 * ~~~
189 * You may also query the current setting via `*(this_thread_verbosity_override())`. Direct assignment of a #Sev
190 * to the latter is allowed, but generally it is both safer and easier to use the RAII
191 * pattern via this_thread_verbosity_override_auto() for setting/restoring the override.
192 *
193 * The value Sev::S_END_SENTINEL indicates the thread-local verbosity override is disabled; and is the initial (default)
194 * state in any thread. However, generally, it is recommended to use the RAII pattern via
195 * this_thread_verbosity_override_auto() instead of direct assignment, for safe and easy save/restore.
196 *
197 * Note there are no thread-safety concerns with this feature, as it is entirely thread-local.
198 */
200{
201public:
202 // Types.
203
204 /**
205 * Unsigned index into the flat union of component tables maintained by a Config, combining potentially multiple
206 * user component `enum` tables. Also suitable for non-negative offsets against such indices.
207 */
209
210 /**
211 * Short-hand for a function that takes a Component (storing a payload of some generic component `enum` member of
212 * the logging user's choice) and returns its corresponding flat union component index.
213 */
215
216 // Constants.
217
218 /// Recommended default/catch-all most-verbose-severity value if no specific config is given.
220
221 // Constructors/destructor.
222
223 /**
224 * Constructs a conceptually blank but functional set of Config. Namely, no component `enum`s are yet registered
225 * (call init_component_to_union_idx_mapping() and init_component_names() to register 0 or more such `enum` tables).
226 * The default verbosity is set to `most_verbose_sev_default`.
227 *
228 * While you can and should register `enum`s after this, if you don't then the object's outputs will act as follows:
229 * - output_whether_should_log() will simply return `true` if and only if `sev` is no more verbose than
230 * `most_verbose_sev_default` arg to this ctor. `component` is ignored in the decision.
231 * - output_component_to_ostream() will print nothing to the `ostream` arg and hence will return `false`.
232 *
233 * Note that -- particularly in a pinch and in simple applications -- this is perfectly reasonable, simple behavior.
234 * One doesn't always need per-component verbosity configuration abilities; and one definitely doesn't always need
235 * to print the component name/index in the log output. But if one does need these things, then you can
236 * register `enum`s as explained in class doc header.
237 *
238 * @param most_verbose_sev_default
239 * Same as in configure_default_verbosity().
240 */
241 explicit Config(Sev most_verbose_sev_default = S_MOST_VERBOSE_SEV_DEFAULT);
242
243 /**
244 * Copy-constructs `*this` to be equal to `src` config object.
245 *
246 * Performance-wise, this will copy internal per-component
247 * tables (in addition to a few scalars). These tables are conceptually unions of potentially multiple long
248 * `enum`s; so this probably shouldn't be done often, but typically Config is
249 * constructed at startup or during rare config change events.
250 *
251 * @warning If this copy construction occurs very soon after a configure_default_verbosity() or
252 * `configure_component_verbosity*()` call in a *different* thread completes, then it is possible
253 * that call's effect won't register in the resulting `*this`. In the case of
254 * configure_default_verbosity() with `reset == true` the clearing of the per-component verbosities may
255 * register only partially in that situation, though `*this` will still be valid. Since "very soon" cannot
256 * be formally defined, it is therefore best to make such a copy in the same thread as the last
257 * verbosity-modifying call on `src`. (On the other hand, even otherwise results in valid behavior, but
258 * it may not be quite as deterministic as preferred and clean.)
259 *
260 * @internal
261 * The warning above is due to the subtlety in the Atomic_raw_sev copy ctor doc header.
262 * @endinternal
263 *
264 * @param src
265 * Source object.
266 */
267 Config(const Config& src);
268
269 /**
270 * For now at least there's no reason for move-construction.
271 * @todo Reconsider providing a Config move constructor. I just didn't need to deal with it.
272 */
273 Config(Config&&) = delete;
274
275 // Methods.
276
277 /**
278 * For now at least there's no reason for copy assignment.
279 * @todo Reconsider providing a Config copy assignment. I just didn't need to deal with it.
280 */
281 void operator=(const Config&) = delete;
282
283 /**
284 * For now at least there's no reason for move assignment.
285 * @todo Reconsider providing a Config move assignment. I just didn't need to deal with it.
286 */
287 void operator=(Config&&) = delete;
288
289 /**
290 * A key output of Config, this computes the verbosity-filtering answer to Logger::should_log() based on the
291 * given log-call-site severity and component and the verbosity configuration in this Config, including
292 * the value at `*(this_thread_verbosity_override())`, the value from configure_default_verbosity(),
293 * and the config from `configure_component_verbosity*()`.
294 *
295 * Call this only after the full round of construction, init_component_to_union_idx_mapping(), and (initial)
296 * `configure_..._verbosity()`. In addition, it is specifically safe to concurrently set verbosity
297 * via configure_default_verbosity() and/or `configure_component_verbosity*()`.
298 *
299 * ### Thread safety on `*this` ###
300 * The last sentence means it's possible to change verbosities even while logging (which invokes us), as long
301 * as the `init_*()` stuff has all been completed. For formal details see notes on thread safety in class Config
302 * doc header.
303 *
304 * ### Algorithm for computing return value ###
305 * It's a matter of comparing `sev` to `S`, where `S` is the applicable log::Sev verbosity setting.
306 * Return `true` if and only if `sev <= S`.
307 *
308 * What is `S`? It is the first available value of the following three bits of config:
309 * -# `S = *(this_thread_verbosity_override())`...
310 * - ...unless it's Sev::S_END_SENTINEL (disabled, which is default); then:
311 * -# `S` = the verbosity configured via `configure_component_verbosity*()` for `component`.
312 * - ...unless no such per-component verbosity was set; then:
313 * -# `S` = the value given to configure_component_verbosity() or ctor, whichever happened later.
314 * - This is always available.
315 *
316 * @see this_thread_verbosity_override() and this_thread_verbosity_override_auto().
317 * @see configure_default_verbosity() and Config().
318 * @see configure_component_verbosity() and configure_component_verbosity_by_name().
319 *
320 * @param sev
321 * See Logger::should_log().
322 * @param component
323 * See Logger::should_log().
324 * @return `true` if we recommend to let the associated message be logged; `false` to suppress it.
325 */
326 bool output_whether_should_log(Sev sev, const Component& component) const;
327
328 /**
329 * An output of Config, this writes a string representation of the given component value to the given `ostream`,
330 * if possible. Returns `true` if it wrote anything, `false` otherwise.
331 * Call this only after the full round of construction, init_component_to_union_idx_mapping(), and
332 * init_component_names().
333 *
334 * If the component's type (`component.payload_type()`) has not been properly registered via
335 * init_component_to_union_idx_mapping(), it returns `false` and writes nothing. Otherwise, if no name was registered
336 * (either because it wasn't included in a init_component_names() call, or because in that call
337 * `output_components_numerically == true`), it will output the component's numeric index in the flat union table;
338 * and return `true`. Finally, if a name is indeed registered, it will output that string (details
339 * in init_component_names() doc header) and also return `true`.
340 *
341 * @param os
342 * Pointer (not null) to the `ostream` to which to possibly write.
343 * @param component
344 * The component value from the log call site. `component.empty()` (no component) is allowed.
345 * @return `true` if 1 or more characters have been written to `*os`; else `false`.
346 */
347 bool output_component_to_ostream(std::ostream* os, const Component& component) const;
348
349 /**
350 * Registers a generically-typed `enum class` that represents the full set of the calling module's possible
351 * component values that it will supply at subsequent log call sites from that module. The caller supplies:
352 * - The template argument `Component_payload`, which is an `enum` and is thus castable to the unsigned
353 * integer type `component_union_idx_t`.
354 * - The signed integer that shall be *added* to any log-call-site-supplied `enum` value in order to yield the
355 * flat-union index in `*this` merged table of all component `enum`s. For example, if we assume that no module
356 * will ever exceed 1,000 components in its `enum`, then module 1 can register its `enum` C1 with
357 * `enum_to_num_offset` 1,000, module 2 with 2,000, module 3 with 3,000, etc. Then the various C1 `enum` values
358 * 0, 1, ... will map to merged 1,000, 1,001, ...; C2's 0, 1, ... to 2,000, 2,001, ...; etc.
359 * - This can be negative, because why not? Be careful.
360 * - The "sparse size" of the `enum`. Details are below.
361 *
362 * @note If this is not acceptable -- maybe you want to pack them more tightly, or you have some other clever mapping
363 * in mind -- then Config might require a new feature (likely an overload of this method) which lets one simply
364 * provide the mapping in function (callback) form. In fact, in a previous version of Flow, this was provided;
365 * and in fact the present overload merely wrapped that more-general overload.
366 * We removed this primarily for perf reasons: Always using this numeric-offset technique allowed for an inlined
367 * implementation in the very-frequently-called (at every log call site) output_whether_should_log(). It would
368 * be possible to add it back in while *also* optimizing for the expected-to-be-used-typically offset technique,
369 * thus having essentially the best of both worlds (perf when possible, flexibility when necessary). However,
370 * since the "flexible" API appears unlikely to be needed, we decided it's over-engineering to keep it in --
371 * unless the need does appear in the future. In that case it should be possible to look in source control
372 * history and bring back its core elements (without removing the inlined offset-technique code path). At this
373 * stage this is not a formal to-do -- more of a note for posterity.
374 *
375 * Behavior is undefined if an index collision occurs here or in a subsequent `init_*()` or other relevant call.
376 * In particular take care to provide sufficient slack space (e.g., if you use `enum_to_num_offset` which are
377 * multiples of 5, then a collision will probably occur at some point).
378 *
379 * If one has called `init_component_to_union_idx_mapping<T>()` with the same `T` in the past, then behavior
380 * is undefined, so don't. (Informally, depending on whether/how one has called init_component_names() and
381 * `configure_component_verbosity*()`, this can actually be done safely and with well-defined results. However,
382 * I did not want to go down that rabbit hole. If it becomes practically necessary, which I doubt, we can revisit.
383 * This is not a formal to-do as of this writing.)
384 *
385 * @internal
386 * ### Rationale for `enum_sparse_length` ###
387 * A design is possible (and indeed was used for a very brief period of time) that avoids the need for this arg.
388 * Internally #m_verbosities_by_component can be a nice, elegant `unordered_map<component_union_idx_t, Sev>`, in which
389 * case we need not know anything about how many `enum` values there can be, and as long as the various `enum`s'
390 * numeric values don't clash -- which must and should easily be avoided by the user calling us here -- everything
391 * works fine with this non-sparse data structure. However, to make #m_verbosities_by_component a sparse `vector<>`
392 * (that uses `component_union_idx_t` as the key) -- yet never need to `resize()` it when
393 * configure_component_verbosity() is called -- one must know `enum_sparse_length` ahead of time to ensure there
394 * is enough space in the `vector` ahead of time. Why would one use such an ugly (and somewhat space-wasting)
395 * structure instead, you ask? Answer: Short version: for efficient thread safety of configure_component_verbosity()
396 * and output_whether_should_log(). Long version: See #m_verbosities_by_component doc header.
397 * @endinternal
398 *
399 * @see Component::payload_type() doc header.
400 *
401 * @tparam Component_payload
402 * See the doc header for the template param `Payload` on Component::payload().
403 * In addition, in our context, it must be convertible to `component_union_idx_t` (an unsigned integer).
404 * Informally, `Component_payload` must be a sane unsigned `enum` with end sentinel `S_END_SENTINEL`.
405 * The various input Component_payload types are distinguished via `typeid(Component_payload)`
406 * and further `type_index(typeid(Component_payload))`. I provide this implementation detail purely for
407 * general context; it should not be seen as relevant to how one uses the API.
408 * @param enum_to_num_offset
409 * For each further-referenced `Component_payload` value C, its flat union index shall be
410 * `component_union_idx_t(C) + enum_to_num_offset`.
411 * So this is the "base" index for the `enum` you are registering, in the final flat table of components.
412 * @param enum_sparse_length
413 * Formally, one plus the highest numeric value of a `Component_payload` value that will ever be passed to
414 * configure_component_verbosity() (directly; or indirectly if using configure_component_verbosity_by_name()).
415 * Informally, we recommend that you (a) use the config_enum_start_hdr.macros.hpp mechanism to create
416 * `Component_payload` type in the first place; and (b) therefore use
417 * `standard_component_payload_enum_sparse_length<Component_payload>()` for the present arg's value.
418 */
419 template<typename Component_payload>
421 size_t enum_sparse_length);
422
423 /**
424 * Registers the string names of each member of the `enum class Component_payload` earlier registered via
425 * `init_component_to_union_idx_mapping<Component_payload>()`. These are used subsequently (as of this writing)
426 * to (1) map name to index in one of the `configure_component_verbosity*()` methods; and
427 * (2) to map index to name in output_component_to_ostream().
428 *
429 * Behavior undefined if `init_component_to_union_idx_mapping<Component_payload>()` hasn't yet been called.
430 * Behavior undefined if `init_component_names<Component_payload>()` has already been called. (Informally, something
431 * safe might happen, depending, but in general it's a weird/bad idea, so don't.) Behavior undefined if
432 * any value in `component_names` is empty.
433 *
434 * The recommended (but not mandatory) way to auto-generate a `component_names` map (as normally doing so by hand is
435 * tedious) is to use `config_enum_{start|end}_macros.[hc]pp`. As an example, Flow itself does it in
436 * common.hpp and common.cpp, defining both flow::Flow_log_component (the `enum`) and
437 * flow::S_FLOW_LOG_COMPONENT_NAME_MAP (the `component_names` map). Basically, via macro magic it names each
438 * component according to the `enum` member identifier's own name.
439 *
440 * ### `component_names` meaning ###
441 * Some subtleties exist in interpreting `component_names`.
442 *
443 * Firstly, each value (string name) in `component_names` -- as well as `payload_type_prefix_or_empty` -- is
444 * internally pre-normalized before any other work. Name normalization consists of conversion to upper case according
445 * to the classic ("C") locale. output_component_to_ostream() will print in normalized form (if applicable);
446 * and configure_component_verbosity_by_name() will normalize the input arg string before lookup.
447 *
448 * If empty, `payload_type_prefix_or_empty` has no effect. Otherwise, its effect is as if it *were* empty, but
449 * as if `component_names[X]` had `payload_type_prefix_or_empty` prepended to its actual value at all times.
450 * Less formally, it's a constant to prefix every name; then if the program (perhaps around `main()`) simply manually
451 * provides a distinct, cosmetically useful "namespace-like" prefix in each init_component_names() call, then
452 * it can 100% guarantee no name clashes, even if accidentally one of module X's component names A happened to
453 * equal an unrelated module Y's component name B. For example, A = B = "UTIL" is fairly likely to collide otherwise.
454 * It won't be an issue, if they end up being called "X_UTIL" and "Y_UTIL" ultimately, by supplying
455 * prefixes "X_" and "Y_" X and Y's init_component_names() calls.
456 *
457 * Within `component_names` if a value (name) is present 2+ times, behavior is undefined.
458 * Furthermore if `payload_type_prefix_or_empty + X`, where `X` is in `component_names`, is already
459 * stored in `*this`, behavior is undefined. Either way it's a name collision which should be entirely avoidable
460 * using `payload_type_prefix_or_empty` as shown above.
461 *
462 * It is a multi-map, and key K is allowed to be present 2+ times mapping to 2+ distinct names.
463 * The reason this is supported is so one can (discouraged though it is -- but for historical reasons tends to
464 * come up at times) declare an `enum` that includes a few mutually "aliased" members:
465 * - `Sweet_components::S_COOL_ENGINE` <=> "COOL_ENGINE" <=> 5
466 * - `Sweet_components::S_ENGINE_ALIAS1` <=> "ENGINE_ALIAS1" <=> 5
467 * - `Sweet_components::S_ENGINE_ALIAS2` <=> "ENGINE_ALIAS2" <=> 5
468 * In that example, any of `S_{COOL_ENGINE|ENGINE_ALIAS{1|2}}` maps to the rather long name
469 * "COOL_ENGINE,ENGINE_ALIAS1,ENGINE_ALIAS2"; and *each* of "COOL_ENGINE", "ENGINE_ALIAS1",
470 * "ENGINE_ALIAS2" maps backwards to a single entry in the component-to-verbosity table. Hence if I configure
471 * verbosity X (using `configure_component_verbosity*()`) for COOL_ENGINE_ALIAS1, then verbosity X config will equally
472 * affect subsequent messages with specified component COOL_ENGINE and ENGINE_ALIAS2 as well.
473 *
474 * Detail: When concatenating component output names as just described, the prefix `payload_type_prefix_or_empty`
475 * is prepended only once. So, if the prefix is "SWEET-", then any one of the above 3 `enum` example members
476 * maps to the name "SWEET-COOL_ENGINE,ENGINE_ALIAS1,ENGINE_ALIAS2".
477 *
478 * `output_components_numerically`, if and only if set to `true`, suppresses the default behavior which is to
479 * memorize the string to output (in output_component_to_ostream()) for a given `enum` value;
480 * instead it doesn't memorize this forward mapping. As a result, output_component_to_ostream() will simply output
481 * the *numerical* value of the `enum` member from the flat union component table. This is a cosmetic output choice
482 * some prefer to the long-looking component names.
483 *
484 * In addition, even if `!output_components_numerically`, but a subsequent output_component_to_ostream() call
485 * encounters an `enum` value that you neglected to register via init_component_names() (omitting it in
486 * `component_names` in particular), then it will also be printed numerically as if `output_components_numerically`.
487 *
488 * Finally, even if `output_components_numerically == true`, the backwards mapping (from string name to component)
489 * is still memorized. Therefore one can still set configure_component_verbosity_by_name() by string name.
490 * Again, in practice, I have seen this: Config files will refer to component verbosities by component name, not
491 * unhelpful-looking number; but output log files still print them as numbers for brevity.
492 *
493 * @param component_names
494 * Mapping of each possible Component_payload value to its string representation, for both output and
495 * per-component config (namely verbosity config) subsequently. Details above.
496 * Empty names lead to undefined behavior.
497 * @param output_components_numerically
498 * If and only if `true`, output_component_to_ostream() will output the flat numeric index for all
499 * `Component_payload`-passing log call sites; else it will print the string name from the map
500 * (but if not *in* the map, then it'll fall back to the flat index again).
501 * @param payload_type_prefix_or_empty
502 * Optional prefix helpful as a disambiguating "namespace" to preprend to values in `component_names`.
503 * Details above.
504 * */
505 template<typename Component_payload>
506 void init_component_names(const boost::unordered_multimap<Component_payload, std::string>& component_names,
507 bool output_components_numerically = false,
508 util::String_view payload_type_prefix_or_empty = util::String_view());
509
510 /**
511 * Sets the default verbosity to the given value, to be used by subsequent output_whether_should_log() calls whenever
512 * one supplies it a component for which no per-component verbosity is configured at that time; optionally wipes out
513 * all existing per-component verbosities for a constructor-like reset.
514 *
515 * This is fairly intuitive; the only aspect one might find non-obvious is `reset == true` mode.
516 * In that mode all per-component verbosities are forgotten, as after construction. An intended use scenario is
517 * when reading a hypothetical config file describing new, dynamic *overall* verbosity settings to replace any
518 * existing ones. Such a config file would probably specify the catch-all (default) verbosity; then 0 or more
519 * per-component "exception" verbosities. Hence once would call this method with `reset == true` accordingly to
520 * reset everything and set the default; then one would call `configure_component_verbosity*()` for each "exception."
521 *
522 * `reset == true` is technically slower than otherwise, though it is doubtful one would call us frequently enough
523 * for it to matter. The perf cost of `!reset` is constant time and basically that of a scalar assignment.
524 * The perf cost of `reset == true` is that plus the cost of about N configure_component_verbosity() calls, where
525 * N is the highest flat-union-component-table implied by the `enum_sparse_length` arg
526 * to init_component_to_union_idx_mapping() calls to date. In practice doing this when
527 * outside config changes is unlikely to be a perf issue.
528 *
529 * ### Thread safety on `*this` ###
530 * If called, it must be called after all init_component_to_union_idx_mapping() calls have completed.
531 * It is safe to call concurrently with output_whether_should_log(), meaning dynamic config of verbosities is
532 * allowed. See formal details in thread safety notes in class Config doc header.
533 *
534 * @param most_verbose_sev_default
535 * The most-verbose (numerically highest) `Sev sev` value such that output_whether_should_log() will return
536 * `true`, when `Component component` is either null or has no per-component verbosity configured at that time.
537 * @param reset
538 * If `false` then per-component verbosities are left unchanged; else they are wiped out, meaning only the
539 * catch-all setting has subsequent effect in output_whether_should_log().
540 */
541 void configure_default_verbosity(Sev most_verbose_sev_default, bool reset);
542
543 /**
544 * Sets the per-component verbosity for the given component to the given value, to be used by subsequent
545 * output_whether_should_log() calls whenever one supplies it the same component value. See also
546 * configure_default_verbosity().
547 *
548 * This only works (and will return `true`) if `init_component_to_union_idx_mapping<Component_payload>()` has been
549 * called. Otherwise it returns `false` (caller may `assert()` against this result if it is felt justified).
550 *
551 * See class doc header section "What Config controls and how" for more discussion.
552 *
553 * @param most_verbose_sev
554 * The most-verbose (numerically highest) `Sev sev` value such that output_whether_should_log() will return
555 * `true`, when `Component component` is not null and has `Component::payload<Component_payload>()`
556 * return a value equal to `component_payload`.
557 * @param component_payload
558 * The component for which verbosity is being set.
559 * @return `true` on success; `false` otherwise. See above for more.
560 */
561 template<typename Component_payload>
562 bool configure_component_verbosity(Sev most_verbose_sev, Component_payload component_payload);
563
564 /**
565 * Like configure_component_verbosity(), but the component is to be specified by its registered string
566 * name, well suited to interpreting text config files. The meaning of verbosity is the same as in the other
567 * overload.
568 *
569 * This only works (and will return `true`) if init_component_names() has been called in such a way as to
570 * successfully associate a component in the flat union table with the name equal (after normalization of both sides)
571 * to `component_name`. If the name is unknown, it returns `false`.
572 *
573 * Name normalization consists of conversion to upper case according to the classic ("C") locale.
574 *
575 * See class doc header section "What Config controls and how" for more discussion.
576 *
577 * @param most_verbose_sev
578 * The most-verbose (numerically highest) `Sev sev` value such that output_whether_should_log() will return
579 * `true`, when `Component component` is not null and has an associated string name equal to `component_name`
580 * (post-normalization of both sides of comparison).
581 * @param component_name
582 * The component for which verbosity is being set.
583 * @return `true` on success; `false` otherwise. See above for more.
584 */
585 bool configure_component_verbosity_by_name(Sev most_verbose_sev,
586 util::String_view component_name);
587
588 /**
589 * Returns highest numeric value in the given component-payload `enum`, plus 1, assuming that
590 * `enum` was created using the config_enum_start_hdr.macros.hpp mechanism with all requirements followed by
591 * user. This is useful for most invocations of init_component_to_union_idx_mapping() for its
592 * `enum_sparse_length` argument.
593 *
594 * For example, if one wants to store a `vector` that uses `size_t(X)`, where `X` is a `Component_payload`,
595 * as an associative-key-like index, then the `vector` would have to be sized whatever the present method
596 * returns to guarantee no out-of-bounds error.
597 *
598 * @see init_component_to_union_idx_mapping(), particularly the `enum_sparse_length` argument.
599
600 * @tparam Component_payload
601 * An `enum class` type created by the mechanism prescribed in config_enum_start_hdr.macros.hpp, using that
602 * mechanism and with user following the documented requirements therein. Alternatively (though it's
603 * not recommended) the following is sufficient if one makes the type some other way:
604 * `Component_payload::S_END_SENTINEL` must have the highest numeric value, without ties;
605 * and the backing type is unsigned and with a bit width no higher than that of `size_t`
606 * (Component::enum_raw_t is what the aforementioned standard mechanism uses as of this writing).
607 * @return See above.
608 */
609 template<typename Component_payload>
611
612 /**
613 * Returns pointer to this thread's *mutable* verbosity override, for querying or assignment alike.
614 * The value of this override, at any given time, shall affect output_whether_should_log() return value.
615 * See output_whether_should_log().
616 *
617 * If you would like to query the current setting, use this method.
618 *
619 * If you would like to *modify* the current setting, it is safer and easier to use
620 * this_thread_verbosity_override_auto() which supplies RAII-style auto-restore.
621 *
622 * For each thread:
623 * - Let `S = *(this_thread_verbosity_override())`.
624 * - Originally `S == Sev::S_END_SENTINEL`.
625 * - output_whether_should_log() in this case will therefore disregard `S`, per its doc header.
626 * - You may set `S` to any valid log::Sev value via direct assignment.
627 * - output_whether_should_log() in this case will follow `S` and only `S`, per its doc header.
628 * - this_thread_verbosity_override_auto() is a convenient way of doing this, as it'll take care of the next
629 * step with minimal effort:
630 * - You may set `S` back to Sev::S_END_SENTINEL.
631 * - output_whether_should_log() will begin disregarding `S` again.
632 *
633 * @return See above. Note that, for any given thread, the returned pointer shall always be the same.
634 */
636
637 /**
638 * Sets `*(this_thread_verbosity_override()) = most_verbose_sev_or_none`; and returns an object that shall restore
639 * it to its current value when it goes out of scope. See class doc header for an example of use.
640 *
641 * It is recommended to use this method instead of direct assignment to the location this_thread_verbosity_override(),
642 * as then it'll be auto-restored.
643 *
644 * @see this_thread_verbosity_override() and output_whether_should_log().
645 *
646 * @param most_verbose_sev_or_none
647 * A value suitable for configure_default_verbosity(); or the special value Sev::S_END_SENTINEL which
648 * disables the override (meaning `C.output_whether_should_log()` shall actually follow the config in `Config
649 * C` and not any override).
650 * @return Object that, when it is destroyed, will restore the verbosity override to what it was before this
651 * call. (The object cannot be copied, to prevent a double-restore. It can however be moved-from.)
652 */
654
655 // Data. (Public!)
656
657 /**
658 * Config setting: If `true`, time stamps will include a (deterministically formatted) date, time, time zone, all in
659 * the OS's current time zone; else raw # of seconds passed since POSIX (Unix) Epoch (1970, Jan 1, 00:00, GMT).
660 * In both cases time is expressed with microsecond resolution (but the accuracy is only as good as the computer's
661 * clock hardware and OS software allow, presumably, though this isn't in the purview of class Config).
662 */
664
665private:
666 // Types.
667
668 /**
669 * The set of config stored for each distinct (as determined by Component::payload_type(), essentially
670 * C++ built-in `std::type_info`) component payload type (in English -- component `enum class`).
671 *
672 * As I write this, there is only one member, so we did not need a `struct`, but it *really* felt like
673 * it might get extended later -- and there is zero performance loss from this, and the extra code is minimal.
674 */
676 {
677 // Data.
678
679 /// See the `enum_to_num_offset` arg in init_component_to_union_idx_mapping() doc header.
681 };
682
683 /// Short-hand for fast-lookup map from distinct `Component_payload` type to the config for that component `enum`.
684 using Component_payload_type_to_cfg_map = boost::unordered_map<std::type_index, Component_config>;
685
686 /// How we store a log::Sev (a mere `enum` itself) in a certain data structure.
688
689 /**
690 * Trivial wrapper of `atomic<raw_sev_t>` which adds a couple of things to make it possible to construct, and
691 * therefore use, a `vector` of such `atomic`s.
692 *
693 * The primary reason it exists is that it's not possible to `push_back()` (and therefore `resize()`) or surprisingly
694 * even `reserve()` (hence `emplace_back()` is also impossible) onto a `vector<atomic<>>`. A copy constructor
695 * is required. Without one, I (ygoldfel) was able to *almost* get it to build, except it turns out that even
696 * `reserve()` (which essentially is required for anything that grows the sequence) needs to be able to copy
697 * `atomic<>`s -- albeit uninitialized ones -- even if one plans to construct in-place via `emplace_back()` (etc.).
698 * We hence provide a copy constructor ourselves in the wrapper; but note that it has certain stringent requirements
699 * for safe use; see its doc header.
700 *
701 * A side nicety is we can set the default constructor to, instead of keeping the value unspecified (probably
702 * uninitialized but definitely resulting in a valid object), init it to our special value `raw_sev_t(-1)`, which
703 * is used in Config::Component_union_idx_to_sev_map to represent the concept "index-not-in-map." That's mere
704 * syntactic sugar, but it works for us.
705 *
706 * @see I (ygoldfel) discovered and solved this all myself when trying to compile; but it's a generally easily
707 * Googlable situation and solution (that confirmed my findings); e.g.:
708 * https://stackoverflow.com/questions/12003024/error-with-copy-constructor-assignment-operator-for-a-class-which-has-stdatomi
709 */
710 class Atomic_raw_sev : public std::atomic<raw_sev_t>
711 {
712 public:
713 // Constructor/destructor.
714
715 /**
716 * Constructs the atomic to the given value, guaranteed to be `load()`ed as such in all subsequent accesses incl
717 * from other threads, even if `memory_order_relaxed` is used.
718 *
719 * @param init_val
720 * The value whose copy to set as the payload of this `atomic<>`.
721 */
722 Atomic_raw_sev(raw_sev_t init_val = raw_sev_t(-1));
723
724 /**
725 * Constructs the atomic to hold the value X held by the given other atomic accoding to a `memory_order_relaxed`
726 * load of the latter.
727 *
728 * There is a subtlety: One generally expects that a just-constructed `atomic<> X(a)` must yield
729 * `X.load(memory_order_relaxed) == a` no mater the thread where the load is done or how soon after construction.
730 * Yet the present ctor by definition essentially performs `atomic<> X(src.load(memory_order_relaxed))`, hence
731 * the subtlety: The `src.load()` needs to return the "true" value intended to be stored inside `src`, not some
732 * cached/whatever older value in the current thread. One way to guarantee this is to both construct (and
733 * optionally modify) `src` *only* in the present thread before calling the present ctor.
734 *
735 * Be very careful to ensure you've considered this when copy-contructing.
736 * Otherwise, it will probably work the *vast* majority of the
737 * time; yet it may silently result in `this->load(memory_order_relaxed)` returning some unexpected value,
738 * depending on whether the "master" value of `src` has propagated to the present thread. Formally speaking
739 * behavior is undefined outside of the aforementioned condition. In actual fact, we do guarantee this
740 * when growing Config::m_verbosities_by_component; and the other call site is the `default`
741 * Config copy constructor/etc., whose doc header mentions this limitation.
742 *
743 * @param src
744 * Other object. Behavior undefined if `(this == &src)`.
745 */
746 Atomic_raw_sev(const Atomic_raw_sev& src);
747 }; // class Atomic_raw_sev
748
749 /**
750 * Short-hand for fast-lookup, thread-safe-for-RW mapping from flat-union-component-table index to max
751 * allowed severity for that component.
752 *
753 * ### Rationale ###
754 * 2 things will jump out at many readers: 1: It is a `vector`, instead of the seemingly more elegant and essentially
755 * as-fast `unordered_map<component_union_idx_t, Sev>`. 2: It stores `atomic<raw_sev_t>` and not simply `Sev`.
756 * The reason for both is thread safety, to wit for 2 operations: writing to it via
757 * `configure_component_verbosity*()` vs. reading from it in output_whether_should_log().
758 * In one makes the two mutually safe for concurrent execution in a given `*this`, it becomes possible to safely
759 * configure per-component verbosity even while the rest of the system is concurrently logging like crazy.
760 * So how does this type accomplish that? Tackling the 2 decisions in order:
761 *
762 * Firstly assume for simplicity that `component_union_idx_t` is `size_t`; and that `Sev` and `raw_sev_t` are
763 * essentially the same thing (enough so for this discussion anyway).
764 * Now consider an `X` of this map type. If it were a `..._map<size_t, ...>`, then `X[A] = B;` might change the
765 * internal structure of the map (hash table, tree, whatever); accessing `X[C]` for *any* `C` would not be safe, as
766 * it could catch it in a halfway state among other dangers. If it is a `vector<...>`, however, then
767 * `X[A] = B;` either results in undefined-behavior -- if `X.size() >= A` -- or works fine, in that the buffer inside
768 * `X` does not change structure. (However, this does indeed require that `X` is sized to allow for all possible
769 * values of `A`. It also makes `X` sparse and hence larger than it otherwise would require. The latter is seen
770 * as likely negligible given the number of components used in reality vs. how much RAM modern computers have;
771 * to shrink it further and improve the set-all operation's speed is why we use single-byte `raw_sev_t` and not `Sev`.
772 * As for pre-sizing, that explains the need for init_component_to_union_idx_mapping() arg `enum_sparse_length`.
773 * TL;DR: It makes it so that the address in the reference `X[A]` never changes for a given `A`, as long as no
774 * `resize()` or equivalent occurs throughout.
775 *
776 * Given that, why `atomic<raw_sev_t>` and not `raw_sev_t` alone? Well, even though `X[A]` address never changes
777 * and is thus thread-safe at the `X` level, the `= B;` assignment itself isn't thread-safe against reading the value.
778 * Now, the extent to which it's not "thread-safe" depends on hairy low-level details; wrapping it in
779 * `atomic<>` allows one to explicitly control the level of thread safety and the associated performance trade-off
780 * (potentially significant since output_whether_should_log() is called at every single log call site).
781 * With `atomic<>` one can use `memory_order` specifications when storing and reading to control this.
782 * Further details are discussed at the read and write sites, but that's why `atomic<>` is used.
783 *
784 * ### Why the wrapper Atomic_raw_sev around the `atomic<>`? ###
785 * Short version: It's because `vector<>` effectively cannot be initialized due to `atomic<>` not being
786 * copy-constructible. (However Atomic_raw_sev, after construction, is identical to `atomic<raw_sev_t>`, effectively
787 * a `using`-alias at that point.) Long version: See doc header for Atomic_raw_sev.
788 *
789 * ### Performance ###
790 * The lookup by index could not be any faster: it adds the index to a base integer and done. This is even somewhat
791 * faster than an `unordered_map<>` which is also constant-time but a bit more involved. The writing and reading
792 * of the `atomic<>` can be faster than with an explicit mutex lock/unlock bracketing but can be somewhat slower than
793 * an unprotected assignment/read; or it can be exactly equal; the point is that is under our control
794 * via `memory_order` spec; again, see details at the read and write sites.
795 */
796 using Component_union_idx_to_sev_map = std::vector<Atomic_raw_sev>;
797
798 /* Ensure it's fine to use `vector` index (`size_t` by definition) as a conceptual equivalent of a
799 * `component_union_idx_t` key in a map. */
800 static_assert(std::numeric_limits<component_union_idx_t>::is_integer
801 && (!std::numeric_limits<component_union_idx_t>::is_signed)
802 && (!std::numeric_limits<size_t>::is_signed)
803 && (sizeof(size_t) >= sizeof(component_union_idx_t)),
804 "size_t must be able to act as equivalent to a component_union_idx_t key.");
805
806 /// Short-hand for fast-lookup map from normalized component name to its flat-union-component-table index.
807 using Component_name_to_union_idx_map = boost::unordered_map<std::string, component_union_idx_t>;
808
809 /// Short-hand for map that is essentially the inverse of `Component_name_to_union_idx_map`.
810 using Component_union_idx_to_name_map = boost::unordered_map<component_union_idx_t, std::string>;
811
812 // Methods.
813
814 /**
815 * Normalized version of given component name.
816 *
817 * @param name
818 * Source name.
819 * @return See above.
820 */
821 static std::string normalized_component_name(util::String_view name);
822
823 /**
824 * Normalizes given component name in place.
825 *
826 * @param name
827 * Pointer (not null) to string to potentially modify.
828 */
829 static void normalize_component_name(std::string* name);
830
831 /**
832 * Given a component in the form user provides it at log call sites, returns its index in the flat component union
833 * table, as registered via init_component_to_union_idx_mapping(); or `component_union_idx_t(-1)` if
834 * `component.payload_type()` was not registed.
835 *
836 * @param component
837 * A Component value as from a log call site. Undefined behavior if `component.empty()` (null Component).
838 * @return Index into the flat component union table (0 or higher); or `component_union_idx_t(-1)`. See above.
839 */
841
842 /**
843 * Helper that for the given flat-union-component-index saves the given per-component verbosity, or removes it.
844 *
845 * @param component_union_idx
846 * Index into #m_verbosities_by_component. -1 leads to undefined behavior. Out-of-bounds leads to undefined
847 * behavior. For rationale for the latter decision see doc header for #m_verbosities_by_component.
848 * @param most_verbose_sev_or_none
849 * Either a cast of a valid log::Sev value indicating the most-verbose (highest) severity allowed to
850 * pass the output_whether_should_log() filter; or -1 to remove the per-component verbosity.
851 * Note that `Sev::S_NONE` is valid in this context and would disable all logging for that component.
852 * Conversely -1 would mean the component is removed from the conceptual "map."
853 */
854 void store_severity_by_component(component_union_idx_t component_union_idx, raw_sev_t most_verbose_sev_or_none);
855
856 // Data.
857
858 /**
859 * Fast-lookup map from distinct `Component_payload` type to the config for that component `enum`.
860 * The key is `std::type_index(std::type_info)`, a/k/a Component::payload_type_index().
861 */
863
864 /**
865 * Most verbose (highest) log::Sev for which output_whether_should_log() will return true, when the input
866 * component is null or lacks a per-component configured verbosity. Note that some log systems will choose to
867 * use only this and not even allow any elements in #m_verbosities_by_component (see its doc header for definition
868 * of containing or lacking an element).
869 *
870 * ### Rationale: Why Atomic_raw_sev instead of just log::Sev? ###
871 * The reasoning is identical to that found in the discussion of why `atomic` is used in
872 * #Component_union_idx_to_sev_map; see its doc header. (We don't have to store a #raw_sev_t, as it's one lousy value
873 * and not a bulk container in this case, but we do anyway just to get the copyability for free without having
874 * to parameterize Atomic_raw_sev into a template. It's a tiny bit cheesy to avoid the latter just to keep the code
875 * briefer, but probably well within reasonableness.)
876 */
878
879 /**
880 * Maps from flat union component index to most verbose (highest) log::Sev for which output_whether_should_log() will
881 * return true, when the input component is not null and maps to that flat union index via component_to_union_idx().
882 * First, read doc header for Component_union_idx_to_sev_map which described why and how the data structure works
883 * as a map; then return here.
884 *
885 * Semantics are as follows:
886 * - If `m_verbosities_by_component[X]` equals -1 cast appropriately, then that component
887 * key is *not* in the conceptual map (`map::find()` would return `end()`). This means no verbosity is configured
888 * for that individual component; note this is common-place in practice. One should then fall back to
889 * #m_verbosity_default.
890 * - If `m_verbosities_by_component[X]` is out of range, same thing. However, that means they didn't properly
891 * call init_component_to_union_idx_mapping() to allow for `X`. Nevertheless, that is allowed when *reading*
892 * (in output_whether_should_log()) to avoid crashing for no great reason. It is however not allowed
893 * when *writing* (in `configure_*_verbosity()`), so they'd better get it right at that level.
894 * The idea is to be permissive at log call sites, which should not care about ANY of this and just want to
895 * log with their component in peace; but not-permissive when configuring the log system, done at the program
896 * driver level.
897 * - If `m_verbosities_by_component[X]` is in range and not -1, then that value cast to log::Sev is the verbosity
898 * for that individual component. (In particular, Sev::S_NONE which happens to be 0, means all logging is
899 * disabled for that component.)
900 *
901 * Note that some log systems will choose to not even allow any elements in #m_verbosities_by_component and leave
902 * the config to #m_verbosity_default exclusively; in this case `m_verbosities_by_component` is filled with
903 * -1 copies (or might even be empty, if they had never configured any components for whatever reason; by above
904 * semantics those have identical meanings).
905 */
907
908 /**
909 * Maps each flat union component index to its *output* component name as registered in init_component_names().
910 * Note that a given index not being present in this map doesn't mean it's not a real component; but
911 * init_component_names() caller may have intentionally not supplied it a forward-lookup name, so that instead
912 * the number itself would be output.
913 *
914 * @see init_component_names()
915 */
917
918 /**
919 * Maps each distinct component name as registered in init_component_names() to its flat union component index.
920 *
921 * @see init_component_names()
922 */
924}; // class Config
925
926// Template implementations.
927
928template<typename Component_payload>
930 size_t enum_sparse_length)
931{
932 using std::type_index;
933
934 assert(enum_sparse_length >= 1);
935
936 const component_union_idx_t component_union_idx_max = enum_to_num_offset + enum_sparse_length - 1;
937 assert(component_union_idx_max >= enum_to_num_offset);
938 assert(component_union_idx_max >= enum_sparse_length);
939
940 /* This would equal Component(C).payload_type_index(), where C is a value of type Component_payload. That is how
941 * this mapping would be used subsequently after this call. Component(C) is routinely provided at log call sites. */
942 const type_index idx(typeid(Component_payload));
943
944 /* It's not necessarily bad to replace an existing per-Component_paylod-offset. However in doc header I said behavior
945 * is undefined, as it requires looking into the various implications -- not currently worth it. So for now: */
947
948 m_component_cfgs_by_payload_type[idx] = { enum_to_num_offset };
949
950 /* Finally make the per-component verbosities table able to hold all keys component_to_union_idx(C) could ever
951 * return when C.payload_type() == typeid(Component_payload). See doc header for
952 * m_verbosities_by_component then come back here. */
953 if (component_union_idx_max >= m_verbosities_by_component.size())
954 {
955 /* Every single element starts at -1 which means "not in the conceptual map." Note this resize() is the only way
956 * elements are added to the vector. After this it's all atomic<>::store() to existing elements and
957 * atomic<>::load()s from there.
958 * (Note: OK, technically there's another way m_verbosities_by_component is set, in the copy ctor. See copy ctor's
959 * doc header.)
960 * (Note: This requires copying of a Atomic_raw_sev(raw_sev_t(-1)) N times, where N is the number of elements,
961 * if any, added to m_verbosities_by_component. That's why Atomic_raw_sev() has a copy ctor unlike the
962 * underlying atomic<raw_sev_t>. Its use can be unsafe, in terms of propagation across threads, as said in the
963 * copy ctor's doc header; but since we do the source construction and N copies in the same thread, per that doc
964 * header we are safe.) */
965 m_verbosities_by_component.resize(component_union_idx_max + 1); // Implied 2nd arg = Atomic_raw_sev(-1).
966 }
967} // Config::init_component_to_union_idx_mapping()
968
969template<typename Component_payload>
971 (const boost::unordered_multimap<Component_payload, std::string>& component_names,
972 bool output_components_numerically,
973 util::String_view payload_type_prefix_or_empty)
974{
975 using std::type_index;
976 using std::string;
977
978 /* There is much subtle (not super-exciting) stuff going on below. It helps to read the contract of the function
979 * in some detail; see doc header first, then continue here. */
980
981 /* Assuming for each distinct init_component_names() call they provide a distinct, non-empty one of these, it
982 * guarantees no 2 equal component names from different invocations ever ultimately collide. In other words
983 * it's a poor man's namespace. */
984 const string payload_type_prefix_normalized(normalized_component_name(payload_type_prefix_or_empty));
985
986 /* This looks straigtforward, but there's a surprisingly weird sublety. The doc header talks about it:
987 * component_names is a multi-map (meaning this iteration might yield the same enum_val 2+ times in a row).
988 * This is best explained by example. This is an explicitly legal component enum fragment:
989 * S_COOL_ENGINE = 5,
990 * S_ENGINE_ALIAS1 = 5,
991 * S_ENGINE_ALIAS2 = 5,
992 *
993 * Moreover, the typically-auto-generated-via-#macro index-to-name map fragment will be as follows:
994 * S_COOL_ENGINE -> "COOL_ENGINE"
995 * S_ENGINE_ALIAS1 -> "ENGINE_ALIAS1"
996 * S_ENGINE_ALIAS2 -> "ENGINE_ALIAS2"
997 *
998 * Looks fine, right? But, when indexing by enum class values, all three S_* keys cited are equal! They all,
999 * really, equal 5. So a non-multi-map would not be able to even store that mapping; one of the 3 names would
1000 * "win," the others being forgotten entirely. So it's a multi-map. So now what? To be continued inside
1001 * the loop. */
1002 for (auto const & enum_val_and_name : component_names)
1003 {
1004 const Component_payload enum_val = enum_val_and_name.first;
1005
1006 auto name_sans_prefix_normalized = enum_val_and_name.second;
1007 assert(!name_sans_prefix_normalized.empty()); // Advertised as not allowed. Implications would be too weird.
1008 normalize_component_name(&name_sans_prefix_normalized);
1009
1010 string name_normalized;
1011 name_normalized.reserve(payload_type_prefix_normalized.size() + name_sans_prefix_normalized.size());
1012 name_normalized += payload_type_prefix_normalized;
1013 name_normalized += name_sans_prefix_normalized;
1014
1015 auto const component_union_idx = component_to_union_idx(Component(enum_val));
1016
1017 // Presumably they didn't call init_component_to_union_idx_mapping<Component_payload>() yet, if this trips.
1018 assert(component_union_idx != component_union_idx_t(-1));
1019 // If this trips, they have allowed a name collision (behavior undefined, as promised).
1020 assert(!util::key_exists(m_component_union_idxs_by_name, name_normalized));
1021
1022 // Set up reverse lookup (for verbosity setting by name, at least).
1023 m_component_union_idxs_by_name[name_normalized] = component_union_idx;
1024 /* Ready! Let's analyze the multi-key corner case mentioned before, by example. This will, across 3 iterations,
1025 * result in the appropriate mappings:
1026 * "COOL_ENGINE" -> component_to_union_idx(S_COOL_ENGINE) -> X
1027 * "ENGINE_ALIAS1" -> component_to_union_idx(S_ENGINE_ALIAS1) -> X (same value)
1028 * "ENGINE_ALIAS2" -> component_to_union_idx(S_ENGINE_ALIAS2) -> X (same value)
1029 * This is the advertised behavior. For config purposes, any of the 3 names linked with 1 enum numerical value
1030 * refer to the same knob. */
1031
1032 // Set up forward lookup (for component -> string output, at least). They can choose to turn this off.
1033 if (!output_components_numerically)
1034 {
1035 string& output_name_so_far = m_component_names_by_union_idx[component_union_idx];
1036 if (output_name_so_far.empty())
1037 {
1038 assert(!name_normalized.empty());
1039 output_name_so_far = std::move(name_normalized);
1040 }
1041 else
1042 {
1043 assert(!name_sans_prefix_normalized.empty());
1044 output_name_so_far += ',';
1045 output_name_so_far += name_sans_prefix_normalized;
1046 }
1047 /* Ready! Let's analyze the multi-key corner case mentioned before, by example. This will, across 3 iterations,
1048 * result in the appropriate single mapping:
1049 * X -> "<prefix>COOL_ENGINE,ENGINE_ALIAS1,ENGINE_ALIAS2"
1050 * where X == component_to_union_idx(S_COOL_ENGINE == S_ENGINE_ALIAS1 == S_ENGINE_ALIAS2).
1051 * This is the advertised behavior. For output purposes, any of the enum values linked with 1 enum num value
1052 * refer to the same component name which contains all 3 individual strings comma-joined. */
1053 }
1054 } // for (enum_val_and_name : component_names)
1055} // Config::init_component_names()
1056
1057template<typename Component_payload>
1058bool Config::configure_component_verbosity(Sev most_verbose_sev, Component_payload component_payload)
1059{
1060 using std::type_index;
1061
1062 const auto component_union_idx = component_to_union_idx(Component(component_payload));
1063 if (component_union_idx == component_union_idx_t(-1))
1064 {
1065 /* If they called init_component_to_union_idx_mapping<Component_payload>(), this cannot happen.
1066 * If they did not, then this is the advertised behavior, but probably they're doing something wrong. */
1067 return false;
1068 }
1069 // else
1070
1071 store_severity_by_component(component_union_idx, raw_sev_t(most_verbose_sev));
1072 return true;
1073} // Config::configure_component_verbosity()
1074
1075template<typename Component_payload>
1077{
1078 return size_t(Component_payload::S_END_SENTINEL);
1079 /* To reiterate, if one just plops S_END_SENTINEL at the end of an `enum class` -- with no manually assigned value --
1080 * its value will be correct for our purposes. That does assume, though, that the immediately preceding member
1081 * has otherwise the highest numeric value (which is the case if, but not only if, one lists them in numeric order
1082 * as opposed to out of order). Additionally that assumes, essentially, its backing type is unsigned and no bigger
1083 * than our return type size_t. (A tigher, less restrictive requirement could be written out, but this isn't a
1084 * math proof over here.) In particular: Certainly all of these things are guaranteed if one uses
1085 * config_enum_start_hpp.macros.hpp, as documented therein, to create Component_payload. */
1086}
1087
1088} // namespace flow::log
A light-weight class, each object storing a component payload encoding an enum value from enum type o...
Definition: log.hpp:840
unsigned int enum_raw_t
The type Payload must be enum class Payload : enum_raw_t: an enum type encoded via this integer type.
Definition: log.hpp:845
Trivial wrapper of atomic<raw_sev_t> which adds a couple of things to make it possible to construct,...
Definition: config.hpp:711
Atomic_raw_sev(raw_sev_t init_val=raw_sev_t(-1))
Constructs the atomic to the given value, guaranteed to be load()ed as such in all subsequent accesse...
Definition: config.cpp:267
Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers...
Definition: config.hpp:200
boost::unordered_map< std::type_index, Component_config > Component_payload_type_to_cfg_map
Short-hand for fast-lookup map from distinct Component_payload type to the config for that component ...
Definition: config.hpp:684
void init_component_to_union_idx_mapping(component_union_idx_t enum_to_num_offset, size_t enum_sparse_length)
Registers a generically-typed enum class that represents the full set of the calling module's possibl...
Definition: config.hpp:929
static size_t standard_component_payload_enum_sparse_length()
Returns highest numeric value in the given component-payload enum, plus 1, assuming that enum was cre...
Definition: config.hpp:1076
void init_component_names(const boost::unordered_multimap< Component_payload, std::string > &component_names, bool output_components_numerically=false, util::String_view payload_type_prefix_or_empty=util::String_view())
Registers the string names of each member of the enum class Component_payload earlier registered via ...
Definition: config.hpp:971
bool m_use_human_friendly_time_stamps
Config setting: If true, time stamps will include a (deterministically formatted) date,...
Definition: config.hpp:663
Config(Sev most_verbose_sev_default=S_MOST_VERBOSE_SEV_DEFAULT)
Constructs a conceptually blank but functional set of Config.
Definition: config.cpp:31
static void normalize_component_name(std::string *name)
Normalizes given component name in place.
Definition: config.cpp:242
bool configure_component_verbosity_by_name(Sev most_verbose_sev, util::String_view component_name)
Like configure_component_verbosity(), but the component is to be specified by its registered string n...
Definition: config.cpp:127
Component_union_idx_to_name_map m_component_names_by_union_idx
Maps each flat union component index to its output component name as registered in init_component_nam...
Definition: config.hpp:916
std::vector< Atomic_raw_sev > Component_union_idx_to_sev_map
Short-hand for fast-lookup, thread-safe-for-RW mapping from flat-union-component-table index to max a...
Definition: config.hpp:796
Atomic_raw_sev m_verbosity_default
Most verbose (highest) log::Sev for which output_whether_should_log() will return true,...
Definition: config.hpp:877
static std::string normalized_component_name(util::String_view name)
Normalized version of given component name.
Definition: config.cpp:233
void operator=(const Config &)=delete
For now at least there's no reason for copy assignment.
bool output_component_to_ostream(std::ostream *os, const Component &component) const
An output of Config, this writes a string representation of the given component value to the given os...
Definition: config.cpp:145
boost::unordered_map< component_union_idx_t, std::string > Component_union_idx_to_name_map
Short-hand for map that is essentially the inverse of Component_name_to_union_idx_map.
Definition: config.hpp:810
Component_union_idx_to_sev_map m_verbosities_by_component
Maps from flat union component index to most verbose (highest) log::Sev for which output_whether_shou...
Definition: config.hpp:906
static Sev * this_thread_verbosity_override()
Returns pointer to this thread's mutable verbosity override, for querying or assignment alike.
Definition: config.cpp:250
static const Sev S_MOST_VERBOSE_SEV_DEFAULT
Recommended default/catch-all most-verbose-severity value if no specific config is given.
Definition: config.hpp:219
boost::unordered_map< std::string, component_union_idx_t > Component_name_to_union_idx_map
Short-hand for fast-lookup map from normalized component name to its flat-union-component-table index...
Definition: config.hpp:807
Component::enum_raw_t component_union_idx_t
Unsigned index into the flat union of component tables maintained by a Config, combining potentially ...
Definition: config.hpp:208
static util::Scoped_setter< Sev > this_thread_verbosity_override_auto(Sev most_verbose_sev_or_none)
Sets *(this_thread_verbosity_override()) = most_verbose_sev_or_none; and returns an object that shall...
Definition: config.cpp:260
Config(Config &&)=delete
For now at least there's no reason for move-construction.
void configure_default_verbosity(Sev most_verbose_sev_default, bool reset)
Sets the default verbosity to the given value, to be used by subsequent output_whether_should_log() c...
Definition: config.cpp:109
void operator=(Config &&)=delete
For now at least there's no reason for move assignment.
uint8_t raw_sev_t
How we store a log::Sev (a mere enum itself) in a certain data structure.
Definition: config.hpp:687
Component_name_to_union_idx_map m_component_union_idxs_by_name
Maps each distinct component name as registered in init_component_names() to its flat union component...
Definition: config.hpp:923
Config(const Config &src)
Copy-constructs *this to be equal to src config object.
bool configure_component_verbosity(Sev most_verbose_sev, Component_payload component_payload)
Sets the per-component verbosity for the given component to the given value, to be used by subsequent...
Definition: config.hpp:1058
bool output_whether_should_log(Sev sev, const Component &component) const
A key output of Config, this computes the verbosity-filtering answer to Logger::should_log() based on...
Definition: config.cpp:177
Component_payload_type_to_cfg_map m_component_cfgs_by_payload_type
Fast-lookup map from distinct Component_payload type to the config for that component enum.
Definition: config.hpp:862
void store_severity_by_component(component_union_idx_t component_union_idx, raw_sev_t most_verbose_sev_or_none)
Helper that for the given flat-union-component-index saves the given per-component verbosity,...
Definition: config.cpp:55
component_union_idx_t component_to_union_idx(const Component &component) const
Given a component in the form user provides it at log call sites, returns its index in the flat compo...
Definition: config.cpp:40
A simple RAII-pattern class template that, at construction, sets the specified location in memory to ...
Definition: util.hpp:120
Flow module providing logging functionality.
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:224
bool key_exists(const Container &container, const typename Container::key_type &key)
Returns true if and only if the given key is present at least once in the given associative container...
Definition: util.hpp:276
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
unsigned char uint8_t
Byte. Best way to represent a byte of binary data. This is 8 bits on all modern systems.
Definition: common.hpp:385
The set of config stored for each distinct (as determined by Component::payload_type(),...
Definition: config.hpp:676
component_union_idx_t m_enum_to_num_offset
See the enum_to_num_offset arg in init_component_to_union_idx_mapping() doc header.
Definition: config.hpp:680