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