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