Flow 1.0.0
Flow project: Full implementation reference.
Classes | Public Types | Public Member Functions | Static Public Member Functions | Public Attributes | Static Public Attributes | Private Types | Private Member Functions | Static Private Member Functions | Private Attributes | List of all members
flow::log::Config Class Reference

Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers is optional but encouraged; supports dynamically changing filter settings even while concurrent logging occurs. More...

#include <config.hpp>

Collaboration diagram for flow::log::Config:
[legend]

Classes

class  Atomic_raw_sev
 Trivial wrapper of atomic<raw_sev_t> which adds a couple of things to make it possible to construct, and therefore use, a vector of such atomics. More...
 
struct  Component_config
 The set of config stored for each distinct (as determined by Component::payload_type(), essentially C++ built-in std::type_info) component payload type (in English – component enum class). More...
 

Public Types

using component_union_idx_t = Component::enum_raw_t
 Unsigned index into the flat union of component tables maintained by a Config, combining potentially multiple user component enum tables. More...
 
using Component_to_union_idx_func = Function< component_union_idx_t(const Component &)>
 Short-hand for a function that takes a Component (storing a payload of some generic component enum member of the logging user's choice) and returns its corresponding flat union component index. More...
 

Public Member Functions

 Config (Sev most_verbose_sev_default=S_MOST_VERBOSE_SEV_DEFAULT)
 Constructs a conceptually blank but functional set of Config. More...
 
 Config (const Config &src)
 Copy-constructs *this to be equal to src config object. More...
 
 Config (Config &&)=delete
 For now at least there's no reason for move-construction. More...
 
void operator= (const Config &)=delete
 For now at least there's no reason for copy assignment. More...
 
void operator= (Config &&)=delete
 For now at least there's no reason for move assignment. More...
 
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 the given log-call-site severity and component and the verbosity configuration in this Config, including the value at *(this_thread_verbosity_override()), the value from configure_default_verbosity(), and the config from configure_component_verbosity*(). More...
 
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 ostream, if possible. More...
 
template<typename Component_payload >
void init_component_to_union_idx_mapping (component_union_idx_t enum_to_num_offset, size_t enum_sparse_length)
 Registers a generically-typed enum class that represents the full set of the calling module's possible component values that it will supply at subsequent log call sites from that module. More...
 
template<typename Component_payload >
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 init_component_to_union_idx_mapping<Component_payload>(). More...
 
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() calls whenever one supplies it a component for which no per-component verbosity is configured at that time; optionally wipes out all existing per-component verbosities for a constructor-like reset. More...
 
template<typename Component_payload >
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 output_whether_should_log() calls whenever one supplies it the same component value. More...
 
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 name, well suited to interpreting text config files. More...
 

Static Public Member Functions

template<typename Component_payload >
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 created using the config_enum_start_hdr.macros.hpp mechanism with all requirements followed by user. More...
 
static Sevthis_thread_verbosity_override ()
 Returns pointer to this thread's mutable verbosity override, for querying or assignment alike. More...
 
static util::Scoped_setter< Sevthis_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 restore it to its current value when it goes out of scope. More...
 

Public Attributes

bool m_use_human_friendly_time_stamps
 Config setting: If true, time stamps will include a (deterministically formatted) date, time, time zone, all in the OS's current time zone; else raw # of seconds passed since POSIX (Unix) Epoch (1970, Jan 1, 00:00, GMT). More...
 

Static Public Attributes

static const Sev S_MOST_VERBOSE_SEV_DEFAULT = Sev::S_INFO
 Recommended default/catch-all most-verbose-severity value if no specific config is given. More...
 

Private Types

using Component_payload_type_to_cfg_map = boost::unordered_map< std::type_index, Component_config >
 Short-hand for fast-lookup map from distinct Component_payload type to the config for that component enum. More...
 
using raw_sev_t = uint8_t
 How we store a log::Sev (a mere enum itself) in a certain data structure. More...
 
using Component_union_idx_to_sev_map = std::vector< Atomic_raw_sev >
 Short-hand for fast-lookup, thread-safe-for-RW mapping from flat-union-component-table index to max allowed severity for that component. More...
 
using Component_name_to_union_idx_map = boost::unordered_map< std::string, component_union_idx_t >
 Short-hand for fast-lookup map from normalized component name to its flat-union-component-table index. More...
 
using Component_union_idx_to_name_map = boost::unordered_map< component_union_idx_t, std::string >
 Short-hand for map that is essentially the inverse of Component_name_to_union_idx_map. More...
 

Private Member Functions

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 component union table, as registered via init_component_to_union_idx_mapping(); or component_union_idx_t(-1) if component.payload_type() was not registed. More...
 
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, or removes it. More...
 

Static Private Member Functions

static std::string normalized_component_name (util::String_view name)
 Normalized version of given component name. More...
 
static void normalize_component_name (std::string *name)
 Normalizes given component name in place. More...
 

Private Attributes

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. More...
 
Atomic_raw_sev m_verbosity_default
 Most verbose (highest) log::Sev for which output_whether_should_log() will return true, when the input component is null or lacks a per-component configured verbosity. More...
 
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_should_log() will return true, when the input component is not null and maps to that flat union index via component_to_union_idx(). More...
 
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_names(). More...
 
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 index. More...
 

Detailed Description

Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers is optional but encouraged; supports dynamically changing filter settings even while concurrent logging occurs.

If you are reading this to know how to configure an existing Logger, then you don't need further background; just see this API to know how to configure such Loggers in a uniform way.

If you are, instead, reading this when implementing a new custom Logger, then please see implementation recommendations in Logger doc header before continuing here.

Todo:
Class Config doc header is wordy and might be hard to follow; rewrite for clarity/flow/size.

Synopsis: How do I use it to configure a Logger? Just tell me!

Really it's pretty easy to use it, but it's much easier to see how it's done by example rather than a formal-ish description (which is nevertheless below, in the next section of this doc header). To use Config with a Config-supporting Logger in your flow::log-using module M:

What Config controls and how

Let's get into it more formally.

Firstly, Config simply stores simple scalars controlling output behavior. For example, the public member m_use_human_friendly_time_stamps controls the style of time stamps in the final output. It's just a data store for such things.

Secondly, Config knows how to understand the component values supplied at every single log call site in your program. (This also ties in to the next thing we discuss, verbosity config.) See Component doc header. Now, here's how Config understands components. In your program, you will use various flow::log-using libraries or modules – including (but not necessarily limited to!) Flow itself. Each module is likely to feature their own component table, in the form of an enum class. For example, Flow itself has enum class Flow_log_component (see common.hpp).

Thirdly, and crucially, the verbosity filtering (Logger::should_log()) for the client Logger is entirely implemented via the output_whether_should_log() output method; so Logger::should_log() can simply forward to that method. Here is how one configures its behavior in Config. At construction, or in a subsequent configure_default_verbosity() call, one sets the default verbosity, meaning the most-verbose log::Sev that should_log() would let through when no per-component verbosity for the log call site's specified Component is configured. In addition, assuming more fine-grained (per-component) verbosity config is desired, one can call configure_component_verbosity*() to set the most-verbose log::Sev for when should_log() is passed that specific Component payload.

Setting per-component verbosity can be done by its enum value. Or one can use the overload that takes the component's distinct (among all source component enum tables registered before) string name; in which case the aforementioned performant name-to-index mapping is used internally to set the proper union component's verbosity.

(Note that order of calls matters: configure_default_verbosity() wipes out effects of any individual, per-component configure_component_verbosity*() executed prior to it. It is typical (in a given round of applying config) to first call configure_default_verbosity() and then make 0 or more configure_component_verbosity*() calls for various individual components.)

Fourthly, output_component_to_ostream() is a significant helper for your Logger::do_log() when actually printing the ultimate character representation of the user's message and metadata. The metadata (Msg_metadata) includes a Component; that method will output its string representation to the ostream given to it. To do so it will use the aforementioned ability to quickly map the C1:: or C2:: member to the flat union index to that index's distinct name (the same name optionally used to configure verbosity of that component, as explained above).

Or one can opt to print the flat numeric index of the component instead; in which case the reverse name-to-union-idx is not needed.

Thread safety

Formally, Config is thread-safe for all operations when concurrent access is to separate Configs. There are no static data involved. Formally, Config is generally NOT thread-safe when concurrent read and write access is w/r/t to a single Config; this includes read/write of any public data members and read/write in the form const/otherwise method calls. Informally, one could use an outside mutex, including in any Logger implementation that uses *this, but we recommend against this for performance reasons; and see below "exception."

Also formally for a given *this: The logging phase is assumed to begin after all init_*() calls and any initial configure_*() calls; at this point output_whether_should_log() and output_component_to_ostream() may be used at will by any thread; but the pre-logging-phase non-const calls are no longer allowed.

There is an important exception to the assertion that Config *this one must NOT call any write methods once the logging phase has begun. Informally, this exception should make it possible to use Config safely and yet dynamically allow changes to Config without any expensive outside mutex. The exception is as follows:

Assume, as is proper, that you've called all needed init_component_to_union_idx_mapping() and init_component_names() before any concurrent logging and have now started to log – you are in the logging phase. Now assume you want to change verbosity settings during this logging-at-large phase; this is common, for example, when some dynamic config file changes verbosity settings for your program. The following is safe: You may call configure_default_verbosity() and/or configure_component_verbosity*() while expecting the changes to take effect promptly in all threads; namely, output_whether_should_log() will reflect the change there; and output_component_to_ostream() does not care. Perf-wise, little to nothing is sacrified (internally, a lock-free implementation is used).

Corner case: It is also safe to call configure_default_verbosity() and/or configure_component_verbosity*() concurrently with themselves. Naturally, it is a race as to which thread "wins." Moreover, configure_default_verbosity() with reset == true is equivalent to removing the verbosity setting for each individual component; but only each removal itself is atomic, not the overall batch operation; so concurrent execution with another configure_*_verbosity() call may result in an interleaved (but valid) verbosity table. Informally, we recommend against any design that would allow concurrent configure_*() calls on *this; it does not seem wise. It does however result in well-defined behavior as described. The real aim, though, is not this corner case but only the main case of a series of configure_*() calls in thread 1, while logging may be going on in other threads.

Optional: Thread-local verbosity override

In a pinch, it may be desirable – temporarily and in a given thread of execution only – to change the current verbosity config. To do so in a given scope {} simply do this:

{
// In this { scope } temporarily let-through only error messages or more severe.
// Now let's create 3,000 threads each of which would normally log a few "starting thread" startup INFO messages!
m_thread_pool = std::make_unique<flow::async::Cross_thread_task_loop>(get_logger(), "huge_pool", 3000);
m_thread_pool->start();
} // Previous thread-local verbosity is restored (whether there was one, or none) upon exit from {block}.
static util::Scoped_setter< Sev > this_thread_verbosity_override_auto(Sev most_verbose_sev_or_none)
Sets *(this_thread_verbosity_override()) = most_verbose_sev_or_none; and returns an object that shall...
Definition: config.cpp:260
@ S_WARNING
Message indicates a "bad" condition that is not frequent enough to be of severity Sev::S_TRACE.

You may also query the current setting via *(this_thread_verbosity_override()). Direct assignment of a Sev to the latter is allowed, but generally it is both safer and easier to use the RAII pattern via this_thread_verbosity_override_auto() for setting/restoring the override.

The value Sev::S_END_SENTINEL indicates the thread-local verbosity override is disabled; and is the initial (default) state in any thread. However, generally, it is recommended to use the RAII pattern via this_thread_verbosity_override_auto() instead of direct assignment, for safe and easy save/restore.

Note there are no thread-safety concerns with this feature, as it is entirely thread-local.

Definition at line 199 of file config.hpp.

Member Typedef Documentation

◆ Component_name_to_union_idx_map

using flow::log::Config::Component_name_to_union_idx_map = boost::unordered_map<std::string, component_union_idx_t>
private

Short-hand for fast-lookup map from normalized component name to its flat-union-component-table index.

Definition at line 807 of file config.hpp.

◆ Component_payload_type_to_cfg_map

using flow::log::Config::Component_payload_type_to_cfg_map = boost::unordered_map<std::type_index, Component_config>
private

Short-hand for fast-lookup map from distinct Component_payload type to the config for that component enum.

Definition at line 684 of file config.hpp.

◆ Component_to_union_idx_func

Short-hand for a function that takes a Component (storing a payload of some generic component enum member of the logging user's choice) and returns its corresponding flat union component index.

Definition at line 214 of file config.hpp.

◆ component_union_idx_t

Unsigned index into the flat union of component tables maintained by a Config, combining potentially multiple user component enum tables.

Also suitable for non-negative offsets against such indices.

Definition at line 208 of file config.hpp.

◆ Component_union_idx_to_name_map

using flow::log::Config::Component_union_idx_to_name_map = boost::unordered_map<component_union_idx_t, std::string>
private

Short-hand for map that is essentially the inverse of Component_name_to_union_idx_map.

Definition at line 810 of file config.hpp.

◆ Component_union_idx_to_sev_map

Short-hand for fast-lookup, thread-safe-for-RW mapping from flat-union-component-table index to max allowed severity for that component.

Rationale

2 things will jump out at many readers: 1: It is a vector, instead of the seemingly more elegant and essentially as-fast unordered_map<component_union_idx_t, Sev>. 2: It stores atomic<raw_sev_t> and not simply Sev. The reason for both is thread safety, to wit for 2 operations: writing to it via configure_component_verbosity*() vs. reading from it in output_whether_should_log(). In one makes the two mutually safe for concurrent execution in a given *this, it becomes possible to safely configure per-component verbosity even while the rest of the system is concurrently logging like crazy. So how does this type accomplish that? Tackling the 2 decisions in order:

Firstly assume for simplicity that component_union_idx_t is size_t; and that Sev and raw_sev_t are essentially the same thing (enough so for this discussion anyway). Now consider an X of this map type. If it were a ..._map<size_t, ...>, then X[A] = B; might change the internal structure of the map (hash table, tree, whatever); accessing X[C] for any C would not be safe, as it could catch it in a halfway state among other dangers. If it is a vector<...>, however, then X[A] = B; either results in undefined-behavior – if X.size() >= A – or works fine, in that the buffer inside X does not change structure. (However, this does indeed require that X is sized to allow for all possible values of A. It also makes X sparse and hence larger than it otherwise would require. The latter is seen as likely negligible given the number of components used in reality vs. how much RAM modern computers have; to shrink it further and improve the set-all operation's speed is why we use single-byte raw_sev_t and not Sev. As for pre-sizing, that explains the need for init_component_to_union_idx_mapping() arg enum_sparse_length. TL;DR: It makes it so that the address in the reference X[A] never changes for a given A, as long as no resize() or equivalent occurs throughout.

Given that, why atomic<raw_sev_t> and not raw_sev_t alone? Well, even though X[A] address never changes and is thus thread-safe at the X level, the = B; assignment itself isn't thread-safe against reading the value. Now, the extent to which it's not "thread-safe" depends on hairy low-level details; wrapping it in atomic<> allows one to explicitly control the level of thread safety and the associated performance trade-off (potentially significant since output_whether_should_log() is called at every single log call site). With atomic<> one can use memory_order specifications when storing and reading to control this. Further details are discussed at the read and write sites, but that's why atomic<> is used.

Why the wrapper Atomic_raw_sev around the atomic<>?

Short version: It's because vector<> effectively cannot be initialized due to atomic<> not being copy-constructible. (However Atomic_raw_sev, after construction, is identical to atomic<raw_sev_t>, effectively a using-alias at that point.) Long version: See doc header for Atomic_raw_sev.

Performance

The lookup by index could not be any faster: it adds the index to a base integer and done. This is even somewhat faster than an unordered_map<> which is also constant-time but a bit more involved. The writing and reading of the atomic<> can be faster than with an explicit mutex lock/unlock bracketing but can be somewhat slower than an unprotected assignment/read; or it can be exactly equal; the point is that is under our control via memory_order spec; again, see details at the read and write sites.

Definition at line 796 of file config.hpp.

◆ raw_sev_t

How we store a log::Sev (a mere enum itself) in a certain data structure.

Definition at line 687 of file config.hpp.

Constructor & Destructor Documentation

◆ Config() [1/3]

flow::log::Config::Config ( Sev  most_verbose_sev_default = S_MOST_VERBOSE_SEV_DEFAULT)
explicit

Constructs a conceptually blank but functional set of Config.

Namely, no component enums are yet registered (call init_component_to_union_idx_mapping() and init_component_names() to register 0 or more such enum tables). The default verbosity is set to most_verbose_sev_default.

While you can and should register enums after this, if you don't then the object's outputs will act as follows:

Note that – particularly in a pinch and in simple applications – this is perfectly reasonable, simple behavior. One doesn't always need per-component verbosity configuration abilities; and one definitely doesn't always need to print the component name/index in the log output. But if one does need these things, then you can register enums as explained in class doc header.

Parameters
most_verbose_sev_defaultSame as in configure_default_verbosity().

Definition at line 31 of file config.cpp.

◆ Config() [2/3]

flow::log::Config::Config ( const Config src)
default

Copy-constructs *this to be equal to src config object.

Performance-wise, this will copy internal per-component tables (in addition to a few scalars). These tables are conceptually unions of potentially multiple long enums; so this probably shouldn't be done often, but typically Config is constructed at startup or during rare config change events.

Warning
If this copy construction occurs very soon after a configure_default_verbosity() or configure_component_verbosity*() call in a different thread completes, then it is possible that call's effect won't register in the resulting *this. In the case of configure_default_verbosity() with reset == true the clearing of the per-component verbosities may register only partially in that situation, though *this will still be valid. Since "very soon" cannot be formally defined, it is therefore best to make such a copy in the same thread as the last verbosity-modifying call on src. (On the other hand, even otherwise results in valid behavior, but it may not be quite as deterministic as preferred and clean.)

The warning above is due to the subtlety in the Atomic_raw_sev copy ctor doc header.

Parameters
srcSource object.

◆ Config() [3/3]

flow::log::Config::Config ( Config &&  )
delete

For now at least there's no reason for move-construction.

Todo:
Reconsider providing a Config move constructor. I just didn't need to deal with it.

Member Function Documentation

◆ component_to_union_idx()

Config::component_union_idx_t flow::log::Config::component_to_union_idx ( const Component component) const
private

Given a component in the form user provides it at log call sites, returns its index in the flat component union table, as registered via init_component_to_union_idx_mapping(); or component_union_idx_t(-1) if component.payload_type() was not registed.

Parameters
componentA Component value as from a log call site. Undefined behavior if component.empty() (null Component).
Returns
Index into the flat component union table (0 or higher); or component_union_idx_t(-1). See above.

Definition at line 40 of file config.cpp.

References m_component_cfgs_by_payload_type, flow::log::Component::payload_enum_raw_value(), and flow::log::Component::payload_type_index().

Referenced by configure_component_verbosity(), init_component_names(), output_component_to_ostream(), and output_whether_should_log().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ configure_component_verbosity()

template<typename Component_payload >
bool flow::log::Config::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 output_whether_should_log() calls whenever one supplies it the same component value.

See also configure_default_verbosity().

This only works (and will return true) if init_component_to_union_idx_mapping<Component_payload>() has been called. Otherwise it returns false (caller may assert() against this result if it is felt justified).

See class doc header section "What Config controls and how" for more discussion.

Parameters
most_verbose_sevThe most-verbose (numerically highest) Sev sev value such that output_whether_should_log() will return true, when Component component is not null and has Component::payload<Component_payload>() return a value equal to component_payload.
component_payloadThe component for which verbosity is being set.
Returns
true on success; false otherwise. See above for more.

Definition at line 1058 of file config.hpp.

References component_to_union_idx(), and store_severity_by_component().

Here is the call graph for this function:

◆ configure_component_verbosity_by_name()

bool flow::log::Config::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 name, well suited to interpreting text config files.

The meaning of verbosity is the same as in the other overload.

This only works (and will return true) if init_component_names() has been called in such a way as to successfully associate a component in the flat union table with the name equal (after normalization of both sides) to component_name. If the name is unknown, it returns false.

Name normalization consists of conversion to upper case according to the classic ("C") locale.

See class doc header section "What Config controls and how" for more discussion.

Parameters
most_verbose_sevThe most-verbose (numerically highest) Sev sev value such that output_whether_should_log() will return true, when Component component is not null and has an associated string name equal to component_name (post-normalization of both sides of comparison).
component_nameThe component for which verbosity is being set.
Returns
true on success; false otherwise. See above for more.

Definition at line 127 of file config.cpp.

References m_component_union_idxs_by_name, normalized_component_name(), and store_severity_by_component().

Here is the call graph for this function:

◆ configure_default_verbosity()

void flow::log::Config::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() calls whenever one supplies it a component for which no per-component verbosity is configured at that time; optionally wipes out all existing per-component verbosities for a constructor-like reset.

This is fairly intuitive; the only aspect one might find non-obvious is reset == true mode. In that mode all per-component verbosities are forgotten, as after construction. An intended use scenario is when reading a hypothetical config file describing new, dynamic overall verbosity settings to replace any existing ones. Such a config file would probably specify the catch-all (default) verbosity; then 0 or more per-component "exception" verbosities. Hence once would call this method with reset == true accordingly to reset everything and set the default; then one would call configure_component_verbosity*() for each "exception."

reset == true is technically slower than otherwise, though it is doubtful one would call us frequently enough for it to matter. The perf cost of !reset is constant time and basically that of a scalar assignment. The perf cost of reset == true is that plus the cost of about N configure_component_verbosity() calls, where N is the highest flat-union-component-table implied by the enum_sparse_length arg to init_component_to_union_idx_mapping() calls to date. In practice doing this when outside config changes is unlikely to be a perf issue.

Thread safety on *this

If called, it must be called after all init_component_to_union_idx_mapping() calls have completed. It is safe to call concurrently with output_whether_should_log(), meaning dynamic config of verbosities is allowed. See formal details in thread safety notes in class Config doc header.

Parameters
most_verbose_sev_defaultThe most-verbose (numerically highest) Sev sev value such that output_whether_should_log() will return true, when Component component is either null or has no per-component verbosity configured at that time.
resetIf false then per-component verbosities are left unchanged; else they are wiped out, meaning only the catch-all setting has subsequent effect in output_whether_should_log().

Definition at line 109 of file config.cpp.

References m_verbosities_by_component, m_verbosity_default, and store_severity_by_component().

Here is the call graph for this function:

◆ init_component_names()

template<typename Component_payload >
void flow::log::Config::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 init_component_to_union_idx_mapping<Component_payload>().

These are used subsequently (as of this writing) to (1) map name to index in one of the configure_component_verbosity*() methods; and (2) to map index to name in output_component_to_ostream().

Behavior undefined if init_component_to_union_idx_mapping<Component_payload>() hasn't yet been called. Behavior undefined if init_component_names<Component_payload>() has already been called. (Informally, something safe might happen, depending, but in general it's a weird/bad idea, so don't.) Behavior undefined if any value in component_names is empty.

The recommended (but not mandatory) way to auto-generate a component_names map (as normally doing so by hand is tedious) is to use config_enum_{start|end}_macros.[hc]pp. As an example, Flow itself does it in common.hpp and common.cpp, defining both flow::Flow_log_component (the enum) and flow::S_FLOW_LOG_COMPONENT_NAME_MAP (the component_names map). Basically, via macro magic it names each component according to the enum member identifier's own name.

component_names meaning

Some subtleties exist in interpreting component_names.

Firstly, each value (string name) in component_names – as well as payload_type_prefix_or_empty – is internally pre-normalized before any other work. Name normalization consists of conversion to upper case according to the classic ("C") locale. output_component_to_ostream() will print in normalized form (if applicable); and configure_component_verbosity_by_name() will normalize the input arg string before lookup.

If empty, payload_type_prefix_or_empty has no effect. Otherwise, its effect is as if it were empty, but as if component_names[X] had payload_type_prefix_or_empty prepended to its actual value at all times. Less formally, it's a constant to prefix every name; then if the program (perhaps around main()) simply manually provides a distinct, cosmetically useful "namespace-like" prefix in each init_component_names() call, then it can 100% guarantee no name clashes, even if accidentally one of module X's component names A happened to equal an unrelated module Y's component name B. For example, A = B = "UTIL" is fairly likely to collide otherwise. It won't be an issue, if they end up being called "X_UTIL" and "Y_UTIL" ultimately, by supplying prefixes "X_" and "Y_" X and Y's init_component_names() calls.

Within component_names if a value (name) is present 2+ times, behavior is undefined. Furthermore if payload_type_prefix_or_empty + X, where X is in component_names, is already stored in *this, behavior is undefined. Either way it's a name collision which should be entirely avoidable using payload_type_prefix_or_empty as shown above.

It is a multi-map, and key K is allowed to be present 2+ times mapping to 2+ distinct names. The reason this is supported is so one can (discouraged though it is – but for historical reasons tends to come up at times) declare an enum that includes a few mutually "aliased" members:

  • Sweet_components::S_COOL_ENGINE <=> "COOL_ENGINE" <=> 5
  • Sweet_components::S_ENGINE_ALIAS1 <=> "ENGINE_ALIAS1" <=> 5
  • Sweet_components::S_ENGINE_ALIAS2 <=> "ENGINE_ALIAS2" <=> 5 In that example, any of S_{COOL_ENGINE|ENGINE_ALIAS{1|2}} maps to the rather long name "COOL_ENGINE,ENGINE_ALIAS1,ENGINE_ALIAS2"; and each of "COOL_ENGINE", "ENGINE_ALIAS1", "ENGINE_ALIAS2" maps backwards to a single entry in the component-to-verbosity table. Hence if I configure verbosity X (using configure_component_verbosity*()) for COOL_ENGINE_ALIAS1, then verbosity X config will equally affect subsequent messages with specified component COOL_ENGINE and ENGINE_ALIAS2 as well.

Detail: When concatenating component output names as just described, the prefix payload_type_prefix_or_empty is prepended only once. So, if the prefix is "SWEET-", then any one of the above 3 enum example members maps to the name "SWEET-COOL_ENGINE,ENGINE_ALIAS1,ENGINE_ALIAS2".

output_components_numerically, if and only if set to true, suppresses the default behavior which is to memorize the string to output (in output_component_to_ostream()) for a given enum value; instead it doesn't memorize this forward mapping. As a result, output_component_to_ostream() will simply output the numerical value of the enum member from the flat union component table. This is a cosmetic output choice some prefer to the long-looking component names.

In addition, even if !output_components_numerically, but a subsequent output_component_to_ostream() call encounters an enum value that you neglected to register via init_component_names() (omitting it in component_names in particular), then it will also be printed numerically as if output_components_numerically.

Finally, even if output_components_numerically == true, the backwards mapping (from string name to component) is still memorized. Therefore one can still set configure_component_verbosity_by_name() by string name. Again, in practice, I have seen this: Config files will refer to component verbosities by component name, not unhelpful-looking number; but output log files still print them as numbers for brevity.

Parameters
component_namesMapping of each possible Component_payload value to its string representation, for both output and per-component config (namely verbosity config) subsequently. Details above. Empty names lead to undefined behavior.
output_components_numericallyIf and only if true, output_component_to_ostream() will output the flat numeric index for all Component_payload-passing log call sites; else it will print the string name from the map (but if not in the map, then it'll fall back to the flat index again).
payload_type_prefix_or_emptyOptional prefix helpful as a disambiguating "namespace" to preprend to values in component_names. Details above.

Definition at line 970 of file config.hpp.

References component_to_union_idx(), flow::util::key_exists(), m_component_names_by_union_idx, m_component_union_idxs_by_name, normalize_component_name(), and normalized_component_name().

Here is the call graph for this function:

◆ init_component_to_union_idx_mapping()

template<typename Component_payload >
void flow::log::Config::init_component_to_union_idx_mapping ( component_union_idx_t  enum_to_num_offset,
size_t  enum_sparse_length 
)

Registers a generically-typed enum class that represents the full set of the calling module's possible component values that it will supply at subsequent log call sites from that module.

The caller supplies:

  • The template argument Component_payload, which is an enum and is thus castable to the unsigned integer type component_union_idx_t.
  • The signed integer that shall be added to any log-call-site-supplied enum value in order to yield the flat-union index in *this merged table of all component enums. For example, if we assume that no module will ever exceed 1,000 components in its enum, then module 1 can register its enum C1 with enum_to_num_offset 1,000, module 2 with 2,000, module 3 with 3,000, etc. Then the various C1 enum values 0, 1, ... will map to merged 1,000, 1,001, ...; C2's 0, 1, ... to 2,000, 2,001, ...; etc.
    • This can be negative, because why not? Be careful.
  • The "sparse size" of the enum. Details are below.
Note
If this is not acceptable – maybe you want to pack them more tightly, or you have some other clever mapping in mind – then Config might require a new feature (likely an overload of this method) which lets one simply provide the mapping in function (callback) form. In fact, in a previous version of Flow, this was provided; and in fact the present overload merely wrapped that more-general overload. We removed this primarily for perf reasons: Always using this numeric-offset technique allowed for an inlined implementation in the very-frequently-called (at every log call site) output_whether_should_log(). It would be possible to add it back in while also optimizing for the expected-to-be-used-typically offset technique, thus having essentially the best of both worlds (perf when possible, flexibility when necessary). However, since the "flexible" API appears unlikely to be needed, we decided it's over-engineering to keep it in – unless the need does appear in the future. In that case it should be possible to look in source control history and bring back its core elements (without removing the inlined offset-technique code path). At this stage this is not a formal to-do – more of a note for posterity.

Behavior is undefined if an index collision occurs here or in a subsequent init_*() or other relevant call. In particular take care to provide sufficient slack space (e.g., if you use enum_to_num_offset which are multiples of 5, then a collision will probably occur at some point).

If one has called init_component_to_union_idx_mapping<T>() with the same T in the past, then behavior is undefined, so don't. (Informally, depending on whether/how one has called init_component_names() and configure_component_verbosity*(), this can actually be done safely and with well-defined results. However, I did not want to go down that rabbit hole. If it becomes practically necessary, which I doubt, we can revisit. This is not a formal to-do as of this writing.)

Rationale for enum_sparse_length

A design is possible (and indeed was used for a very brief period of time) that avoids the need for this arg. Internally m_verbosities_by_component can be a nice, elegant unordered_map<component_union_idx_t, Sev>, in which case we need not know anything about how many enum values there can be, and as long as the various enums' numeric values don't clash – which must and should easily be avoided by the user calling us here – everything works fine with this non-sparse data structure. However, to make m_verbosities_by_component a sparse vector<> (that uses component_union_idx_t as the key) – yet never need to resize() it when configure_component_verbosity() is called – one must know enum_sparse_length ahead of time to ensure there is enough space in the vector ahead of time. Why would one use such an ugly (and somewhat space-wasting) structure instead, you ask? Answer: Short version: for efficient thread safety of configure_component_verbosity() and output_whether_should_log(). Long version: See m_verbosities_by_component doc header.

See also
Component::payload_type() doc header.
Template Parameters
Component_payloadSee the doc header for the template param Payload on Component::payload(). In addition, in our context, it must be convertible to component_union_idx_t (an unsigned integer). Informally, Component_payload must be a sane unsigned enum with end sentinel S_END_SENTINEL. The various input Component_payload types are distinguished via typeid(Component_payload) and further type_index(typeid(Component_payload)). I provide this implementation detail purely for general context; it should not be seen as relevant to how one uses the API.
Parameters
enum_to_num_offsetFor each further-referenced Component_payload value C, its flat union index shall be component_union_idx_t(C) + enum_to_num_offset. So this is the "base" index for the enum you are registering, in the final flat table of components.
enum_sparse_lengthFormally, one plus the highest numeric value of a Component_payload value that will ever be passed to configure_component_verbosity() (directly; or indirectly if using configure_component_verbosity_by_name()). Informally, we recommend that you (a) use the config_enum_start_hdr.macros.hpp mechanism to create Component_payload type in the first place; and (b) therefore use standard_component_payload_enum_sparse_length<Component_payload>() for the present arg's value.

Definition at line 929 of file config.hpp.

References flow::util::key_exists(), m_component_cfgs_by_payload_type, and m_verbosities_by_component.

Here is the call graph for this function:

◆ normalize_component_name()

void flow::log::Config::normalize_component_name ( std::string *  name)
staticprivate

Normalizes given component name in place.

Parameters
namePointer (not null) to string to potentially modify.

Definition at line 242 of file config.cpp.

Referenced by init_component_names().

Here is the caller graph for this function:

◆ normalized_component_name()

std::string flow::log::Config::normalized_component_name ( util::String_view  name)
staticprivate

Normalized version of given component name.

Parameters
nameSource name.
Returns
See above.

Definition at line 233 of file config.cpp.

Referenced by configure_component_verbosity_by_name(), and init_component_names().

Here is the caller graph for this function:

◆ operator=() [1/2]

void flow::log::Config::operator= ( Config &&  )
delete

For now at least there's no reason for move assignment.

Todo:
Reconsider providing a Config move assignment. I just didn't need to deal with it.

◆ operator=() [2/2]

void flow::log::Config::operator= ( const Config )
delete

For now at least there's no reason for copy assignment.

Todo:
Reconsider providing a Config copy assignment. I just didn't need to deal with it.

◆ output_component_to_ostream()

bool flow::log::Config::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 ostream, if possible.

Returns true if it wrote anything, false otherwise. Call this only after the full round of construction, init_component_to_union_idx_mapping(), and init_component_names().

If the component's type (component.payload_type()) has not been properly registered via init_component_to_union_idx_mapping(), it returns false and writes nothing. Otherwise, if no name was registered (either because it wasn't included in a init_component_names() call, or because in that call output_components_numerically == true), it will output the component's numeric index in the flat union table; and return true. Finally, if a name is indeed registered, it will output that string (details in init_component_names() doc header) and also return true.

Parameters
osPointer (not null) to the ostream to which to possibly write.
componentThe component value from the log call site. component.empty() (no component) is allowed.
Returns
true if 1 or more characters have been written to *os; else false.

Definition at line 145 of file config.cpp.

References component_to_union_idx(), flow::log::Component::empty(), and m_component_names_by_union_idx.

Referenced by flow::log::Ostream_log_msg_writer::log_past_time_stamp().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ output_whether_should_log()

bool flow::log::Config::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 the given log-call-site severity and component and the verbosity configuration in this Config, including the value at *(this_thread_verbosity_override()), the value from configure_default_verbosity(), and the config from configure_component_verbosity*().

Call this only after the full round of construction, init_component_to_union_idx_mapping(), and (initial) configure_..._verbosity(). In addition, it is specifically safe to concurrently set verbosity via configure_default_verbosity() and/or configure_component_verbosity*().

Thread safety on *this

The last sentence means it's possible to change verbosities even while logging (which invokes us), as long as the init_*() stuff has all been completed. For formal details see notes on thread safety in class Config doc header.

Algorithm for computing return value

It's a matter of comparing sev to S, where S is the applicable log::Sev verbosity setting. Return true if and only if sev <= S.

What is S? It is the first available value of the following three bits of config:

  1. S = *(this_thread_verbosity_override())...
  2. S = the verbosity configured via configure_component_verbosity*() for component.
    • ...unless no such per-component verbosity was set; then:
  3. S = the value given to configure_component_verbosity() or ctor, whichever happened later.
    • This is always available.
See also
this_thread_verbosity_override() and this_thread_verbosity_override_auto().
configure_default_verbosity() and Config().
configure_component_verbosity() and configure_component_verbosity_by_name().
Parameters
sevSee Logger::should_log().
componentSee Logger::should_log().
Returns
true if we recommend to let the associated message be logged; false to suppress it.

Definition at line 177 of file config.cpp.

References component_to_union_idx(), flow::log::Component::empty(), m_verbosities_by_component, m_verbosity_default, flow::log::S_END_SENTINEL, and this_thread_verbosity_override().

Here is the call graph for this function:

◆ standard_component_payload_enum_sparse_length()

template<typename Component_payload >
size_t flow::log::Config::standard_component_payload_enum_sparse_length
static

Returns highest numeric value in the given component-payload enum, plus 1, assuming that enum was created using the config_enum_start_hdr.macros.hpp mechanism with all requirements followed by user.

This is useful for most invocations of init_component_to_union_idx_mapping() for its enum_sparse_length argument.

For example, if one wants to store a vector that uses size_t(X), where X is a Component_payload, as an associative-key-like index, then the vector would have to be sized whatever the present method returns to guarantee no out-of-bounds error.

See also
init_component_to_union_idx_mapping(), particularly the enum_sparse_length argument.
Template Parameters
Component_payloadAn enum class type created by the mechanism prescribed in config_enum_start_hdr.macros.hpp, using that mechanism and with user following the documented requirements therein. Alternatively (though it's not recommended) the following is sufficient if one makes the type some other way: Component_payload::S_END_SENTINEL must have the highest numeric value, without ties; and the backing type is unsigned and with a bit width no higher than that of size_t (Component::enum_raw_t is what the aforementioned standard mechanism uses as of this writing).
Returns
See above.

Definition at line 1076 of file config.hpp.

◆ store_severity_by_component()

void flow::log::Config::store_severity_by_component ( component_union_idx_t  component_union_idx,
raw_sev_t  most_verbose_sev_or_none 
)
private

Helper that for the given flat-union-component-index saves the given per-component verbosity, or removes it.

Parameters
component_union_idxIndex into m_verbosities_by_component. -1 leads to undefined behavior. Out-of-bounds leads to undefined behavior. For rationale for the latter decision see doc header for m_verbosities_by_component.
most_verbose_sev_or_noneEither a cast of a valid log::Sev value indicating the most-verbose (highest) severity allowed to pass the output_whether_should_log() filter; or -1 to remove the per-component verbosity. Note that Sev::S_NONE is valid in this context and would disable all logging for that component. Conversely -1 would mean the component is removed from the conceptual "map."

Definition at line 55 of file config.cpp.

References m_verbosities_by_component.

Referenced by configure_component_verbosity(), configure_component_verbosity_by_name(), and configure_default_verbosity().

Here is the caller graph for this function:

◆ this_thread_verbosity_override()

Sev * flow::log::Config::this_thread_verbosity_override ( )
static

Returns pointer to this thread's mutable verbosity override, for querying or assignment alike.

The value of this override, at any given time, shall affect output_whether_should_log() return value. See output_whether_should_log().

If you would like to query the current setting, use this method.

If you would like to modify the current setting, it is safer and easier to use this_thread_verbosity_override_auto() which supplies RAII-style auto-restore.

For each thread:

Returns
See above. Note that, for any given thread, the returned pointer shall always be the same.

Definition at line 250 of file config.cpp.

References flow::log::S_END_SENTINEL.

Referenced by output_whether_should_log(), and this_thread_verbosity_override_auto().

Here is the caller graph for this function:

◆ this_thread_verbosity_override_auto()

util::Scoped_setter< Sev > flow::log::Config::this_thread_verbosity_override_auto ( Sev  most_verbose_sev_or_none)
static

Sets *(this_thread_verbosity_override()) = most_verbose_sev_or_none; and returns an object that shall restore it to its current value when it goes out of scope.

See class doc header for an example of use.

It is recommended to use this method instead of direct assignment to the location this_thread_verbosity_override(), as then it'll be auto-restored.

See also
this_thread_verbosity_override() and output_whether_should_log().
Parameters
most_verbose_sev_or_noneA value suitable for configure_default_verbosity(); or the special value Sev::S_END_SENTINEL which disables the override (meaning C.output_whether_should_log() shall actually follow the config in Config C and not any override).
Returns
Object that, when it is destroyed, will restore the verbosity override to what it was before this call. (The object cannot be copied, to prevent a double-restore. It can however be moved-from.)

Definition at line 260 of file config.cpp.

References this_thread_verbosity_override().

Here is the call graph for this function:

Member Data Documentation

◆ m_component_cfgs_by_payload_type

Component_payload_type_to_cfg_map flow::log::Config::m_component_cfgs_by_payload_type
private

Fast-lookup map from distinct Component_payload type to the config for that component enum.

The key is std::type_index(std::type_info), a/k/a Component::payload_type_index().

Definition at line 862 of file config.hpp.

Referenced by component_to_union_idx(), and init_component_to_union_idx_mapping().

◆ m_component_names_by_union_idx

Component_union_idx_to_name_map flow::log::Config::m_component_names_by_union_idx
private

Maps each flat union component index to its output component name as registered in init_component_names().

Note that a given index not being present in this map doesn't mean it's not a real component; but init_component_names() caller may have intentionally not supplied it a forward-lookup name, so that instead the number itself would be output.

See also
init_component_names()

Definition at line 916 of file config.hpp.

Referenced by init_component_names(), and output_component_to_ostream().

◆ m_component_union_idxs_by_name

Component_name_to_union_idx_map flow::log::Config::m_component_union_idxs_by_name
private

Maps each distinct component name as registered in init_component_names() to its flat union component index.

See also
init_component_names()

Definition at line 923 of file config.hpp.

Referenced by configure_component_verbosity_by_name(), and init_component_names().

◆ m_use_human_friendly_time_stamps

bool flow::log::Config::m_use_human_friendly_time_stamps

Config setting: If true, time stamps will include a (deterministically formatted) date, time, time zone, all in the OS's current time zone; else raw # of seconds passed since POSIX (Unix) Epoch (1970, Jan 1, 00:00, GMT).

In both cases time is expressed with microsecond resolution (but the accuracy is only as good as the computer's clock hardware and OS software allow, presumably, though this isn't in the purview of class Config).

Definition at line 663 of file config.hpp.

Referenced by flow::log::Ostream_log_msg_writer::Ostream_log_msg_writer().

◆ m_verbosities_by_component

Component_union_idx_to_sev_map flow::log::Config::m_verbosities_by_component
private

Maps from flat union component index to most verbose (highest) log::Sev for which output_whether_should_log() will return true, when the input component is not null and maps to that flat union index via component_to_union_idx().

First, read doc header for Component_union_idx_to_sev_map which described why and how the data structure works as a map; then return here.

Semantics are as follows:

  • If m_verbosities_by_component[X] equals -1 cast appropriately, then that component key is not in the conceptual map (map::find() would return end()). This means no verbosity is configured for that individual component; note this is common-place in practice. One should then fall back to m_verbosity_default.
  • If m_verbosities_by_component[X] is out of range, same thing. However, that means they didn't properly call init_component_to_union_idx_mapping() to allow for X. Nevertheless, that is allowed when reading (in output_whether_should_log()) to avoid crashing for no great reason. It is however not allowed when writing (in configure_*_verbosity()), so they'd better get it right at that level. The idea is to be permissive at log call sites, which should not care about ANY of this and just want to log with their component in peace; but not-permissive when configuring the log system, done at the program driver level.
  • If m_verbosities_by_component[X] is in range and not -1, then that value cast to log::Sev is the verbosity for that individual component. (In particular, Sev::S_NONE which happens to be 0, means all logging is disabled for that component.)

Note that some log systems will choose to not even allow any elements in m_verbosities_by_component and leave the config to m_verbosity_default exclusively; in this case m_verbosities_by_component is filled with -1 copies (or might even be empty, if they had never configured any components for whatever reason; by above semantics those have identical meanings).

Definition at line 906 of file config.hpp.

Referenced by configure_default_verbosity(), init_component_to_union_idx_mapping(), output_whether_should_log(), and store_severity_by_component().

◆ m_verbosity_default

Atomic_raw_sev flow::log::Config::m_verbosity_default
private

Most verbose (highest) log::Sev for which output_whether_should_log() will return true, when the input component is null or lacks a per-component configured verbosity.

Note that some log systems will choose to use only this and not even allow any elements in m_verbosities_by_component (see its doc header for definition of containing or lacking an element).

Rationale: Why Atomic_raw_sev instead of just log::Sev?

The reasoning is identical to that found in the discussion of why atomic is used in 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 and not a bulk container in this case, but we do anyway just to get the copyability for free without having to parameterize Atomic_raw_sev into a template. It's a tiny bit cheesy to avoid the latter just to keep the code briefer, but probably well within reasonableness.)

Definition at line 877 of file config.hpp.

Referenced by configure_default_verbosity(), and output_whether_should_log().

◆ S_MOST_VERBOSE_SEV_DEFAULT

const Sev flow::log::Config::S_MOST_VERBOSE_SEV_DEFAULT = Sev::S_INFO
static

Recommended default/catch-all most-verbose-severity value if no specific config is given.

Definition at line 219 of file config.hpp.

Referenced by flow::log::Verbosity_config::parse(), and flow::log::Verbosity_config::Verbosity_config().


The documentation for this class was generated from the following files: