Flow 1.0.0
Flow project: Full implementation reference.
|
A light-weight class, each object storing a component payload encoding an enum
value from enum
type of user's choice, and a light-weight ID of that enum
type itself.
More...
#include <log.hpp>
Public Types | |
using | enum_raw_t = unsigned int |
The type Payload must be enum class Payload : enum_raw_t : an enum type encoded via this integer type. More... | |
Public Member Functions | |
Component () | |
Constructs a Component that stores no payload; meaning an unspecified-component Component that returns empty() == true . More... | |
template<typename Payload > | |
Component (Payload payload) | |
Constructs a Component with the given payload of arbitrary type, so long as that type is an enum class : Component::enum_raw_t . More... | |
Component (const Component &src) | |
Copies the source Component into *this . More... | |
Component (Component &&src_moved) | |
Constructs *this equal to src_moved . More... | |
Component & | operator= (const Component &src) |
Overwrites *this with a copy of src . More... | |
template<typename Payload > | |
Component & | operator= (Payload new_payload) |
Equivalent to operator=(Component<Payload>(new_payload)) modulo possibly minor perf differences. More... | |
Component & | operator= (Component &&src_moved) |
Makes *this equal to src_moved . More... | |
bool | empty () const |
Returns true if *this is as if default-constructed (a null Component); false if as if constructed via the 1-arg constructor (a non-null Component). More... | |
template<typename Payload > | |
Payload | payload () const |
Returns reference to immutable payload stored in *this ; undefined behavior if empty() == true . More... | |
const std::type_info & | payload_type () const |
Returns typeid(Payload) , where Payload was the template param used when calling the originating one-arg constructor or equivalent; undefined behavior if empty() == true . More... | |
std::type_index | payload_type_index () const |
Convenience accessor that returns std::type_index(payload_type()) , which can be used most excellently to store things in associative containers (like std::map ) keyed by disparate Component payload enum types. More... | |
enum_raw_t | payload_enum_raw_value () const |
Returns the numeric value of the enum payload stored by this Component, originating in the one-arg constructor; undefined behavior if empty() == true . More... | |
Private Attributes | |
std::type_info const * | m_payload_type_or_null |
The typeid() of the Payload passed to the 1-arg constructor; if 0-arg ctor was used (empty() is true ) then it is null. More... | |
enum_raw_t | m_payload_enum_raw_value |
The internally stored integer representation of the enum value passed to the 1-arg constructor; meaningless and ignored if 0-arg ctor was used (empty() is true ). More... | |
A light-weight class, each object storing a component payload encoding an enum
value from enum
type of user's choice, and a light-weight ID of that enum
type itself.
A Component is supplied by the user, at every logging call site, along with the message. Log_context (or FLOW_LOG_SET_CONTEXT() in relatively rare scenarios) makes this easier, so typically user need not literally type out a component at every logging call site, meaning there is a "context" that already stores it and need not be re-specified.
A Component can be either empty, as when default-constructed, indicated by empty() returning true
; or non-empty, in which case it actually stores something interesting. In the latter case, construct it with the templated one-arg constructor. The template arg Payload
must, always, be user's own enum class
. In order to have this Payload
interpreted correctly, one can (and usually should) use the log::Config facility which is used by out-of-the-box Logger implementations including Simple_ostream_logger (useful for console output) and Async_file_logger (for heavy-duty file logging, including rotation support). However, this is technically optional: one can implement their own Logger which might not use the Config facility and thus deal with the meaning of Component in some completely different way. I'd start with an existing Logger implementation however; and if writing one's own Logger, then still have it use log::Config, unless that system is somehow insufficient or inappropriate.
Tip: Arguably the best way to see how to use all this together, just see flow::Flow_log_component and how Flow's own code (which, itself, logs!) uses the log system. This is discussed in more detail in the class Config doc header. Lastly, some clarifying discussion may be found (as of this writing) in the Component::type_info() doc header.
A Component is immutable as of this writing, except one can trivially overwrite a Component via an assignment operator. The latter write operation is not thread-safe w.r.t. a given *this
, though in practice this is unlikely to ever matter.
Again – a Component conceptually must store only 2 things:
enum
value.enum class
type from which this value originates.Why these 2 values?
Logger
s do use it), in particular, has such a structure.The operations required to implement, then, are:
enum class
value of arbitrary type (one such type per logging module).enum
value itself.The first version of Component did this by simply storing (and thus being, in terms of data, equal to) boost::any
. The constructor would simply load the enum
value of type T as the any
payload. The accessors would any_cast<T>()
and then trivially access the value itself and/or its typeid()
.
This was delightful in terms of code simplicity. However, due to the extreme prevalence of Logger::should_log() checks in code – at every log call site, including high-verbosity ones that do not typically result in messages being logged, such as for Sev::S_TRACE – certain perf aspects of boost::any
involved non-trivial perf costs. Namely:
any
constructor performs a new
when storing the arbitrarily-typed value, even though in our case it's always just an enum
. Copying an any
similarly will need to new
.any
accessors must do virtual
lookups, to resolve to the particular enum class
type.Hence the current solution optimized those issues away by making use of the fact we know the stored thing is always an enum class
. Hence:
enum
, which is non-polymorphic, the typeid()
operation does not even need to do the virtual
resolution. In fact its result is known at compile time. (This can even lead to further optimizations by the compiler.)Some discussion for perspective:
any
was elegant in terms of code simplicity; it is analogous to runtime dynamic typing of languages like Python.enum
.boost::any
is just too slow, because most operations are not done as frequently as should-log checks. It does mean it may be too slow in production in this particular context.std::any
(appearing first in C++17) in some or all gcc versions optimized-away these perf problems by storing certain values directly inside it, when those values were small enough to "fit." (Our enum
s presumably would fit that bill, being glorified int
s.) This is believable: Many std::string
impls will directly store strings of sufficiently small length; e.g., <=15 bytes would fit into the same area as the pointers and length data members required for arbitrarily-long string that do require heap allocation. Probably the same can be done for any
. So keep that in mind if using any
functionality in other contexts. using flow::log::Component::enum_raw_t = unsigned int |
flow::log::Component::Component | ( | ) |
Constructs a Component that stores no payload; meaning an unspecified-component Component that returns empty() == true
.
Every Logger and all other systems must accept a message with such a null Component. However, one can configure a given Logger to ensure such messages not be logged (filtered out via should_log()
). Point is, any log call site that supplied a null Component must still work, meaning not cause undefined behavior.
flow::log::Component::Component | ( | Payload | payload | ) |
Constructs a Component with the given payload of arbitrary type, so long as that type is an enum class : Component::enum_raw_t
.
(enum_raw_t is an unsigned integer type.) The resulting Component will return empty() == false
.
Payload | The type of the component value stored inside *this . This is required to be a type satisfying the requirements in the doc header for enum_raw_t. |
payload | The payload value copied into *this and whenever a Component itself is copied (or moved). |
Definition at line 1729 of file log.hpp.
References operator=(), and payload().
|
default |
Copies the source Component into *this
.
This involves a single payload copy (and not even that if src.empty()
).
src | Object to copy. |
|
default |
Constructs *this
equal to src_moved
.
In this implementation it is equivalent to the copy constructor.
src_moved | Object to move. |
bool flow::log::Component::empty | ( | ) | const |
Returns true
if *this
is as if default-constructed (a null Component); false
if as if constructed via the 1-arg constructor (a non-null Component).
Definition at line 168 of file log.cpp.
References m_payload_type_or_null.
Referenced by flow::log::Config::output_component_to_ostream(), flow::log::Config::output_whether_should_log(), payload_enum_raw_value(), and payload_type().
Makes *this
equal to src_moved
.
In this implementation it is equivalent to copy assignment.
src_moved | Object to move. |
*this
. Overwrites *this
with a copy of src
.
src | Object to copy. |
*this
. Referenced by Component().
Component & flow::log::Component::operator= | ( | Payload | new_payload | ) |
Equivalent to operator=(Component<Payload>(new_payload))
modulo possibly minor perf differences.
new_payload | See non-default constructor. |
*this
. Definition at line 1743 of file log.hpp.
References m_payload_enum_raw_value, and m_payload_type_or_null.
Payload flow::log::Component::payload |
Returns reference to immutable payload stored in *this
; undefined behavior if empty() == true
.
Payload | See one-arg ctor doc header. |
Definition at line 1736 of file log.hpp.
References m_payload_enum_raw_value.
Referenced by Component().
Component::enum_raw_t flow::log::Component::payload_enum_raw_value | ( | ) | const |
Returns the numeric value of the enum
payload stored by this Component, originating in the one-arg constructor; undefined behavior if empty() == true
.
static_cast<enum_raw_t>(payload)
, where payload
was passed to originating 1-arg ctor. Definition at line 187 of file log.cpp.
References empty(), and m_payload_enum_raw_value.
Referenced by flow::log::Config::component_to_union_idx().
const std::type_info & flow::log::Component::payload_type | ( | ) | const |
Returns typeid(Payload)
, where Payload
was the template param used when calling the originating one-arg constructor or equivalent; undefined behavior if empty() == true
.
flow::log user that relies fully on an out-of-the-box Logger, or on a custom Logger that nevertheless fully uses the log::Config facility, is unlikely to need this information. (It is used internally by log::Config.) However, a custom Logger that decided to use an alternative Component mechanism (as opposed to how log::Config and reliant Logger
s do) can use this payload_type() method to distinguish between potential source enum
s that were passed to the Logger at each given log call site.
For example, consider that Flow itself, for its own logging, uses the flow::Flow_log_component enum
at all log call sites. Imagine you, the user, have decided to generate your own flow::log messages and always use your own enum class cool_project::Cool_log_component
at your own logging call sites. Then, a typical component specification will look like this respectively:
Now, this->get_log_component().payload_type()
will equal that of Flow_log_component
and Cool_log_component
, respectively, within those 2 classes. In particular, a custom Logger implementation can use payload_type()
– and in particular the derived payload_type_index()
– to interpret the Component
s of values from the 2 entirely disparate enum
s in different ways. More in particular, the out-of-the-box Logger
s use Config
to do all of that without your having to worry about it (but your own Logger would potentially have to worry about it particularly if not using log::Config... though we suggest that you should, barring excellent design counter-reasons).
Definition at line 173 of file log.cpp.
References empty(), and m_payload_type_or_null.
Referenced by payload_type_index().
std::type_index flow::log::Component::payload_type_index | ( | ) | const |
Convenience accessor that returns std::type_index(payload_type())
, which can be used most excellently to store things in associative containers (like std::map
) keyed by disparate Component payload enum
types.
(E.g., such a map might have one entry for Flow's own flow::Flow_log_component; one for the example cool_project::Cool_log_component
enumeration mentioned in the payload_type() doc header; and so on for any logging modules in your process. Again, log::Config will do that for you, if you choose to use it in your custom Logger.)
I (ygoldfel) considered not storing a type_info
– and hence not even providing payload_type() API – but directly storing type_index
only (and hence only providing payload_type_index()). The plus would have been eliminating computation in type_index()
construction, in the payload_type_index() accessor which is executed very frequently. The minus would have been the lack of type_info
access, so for instance the name of the enum
type could not be printed.
I almost did this; but since type_index()
ctor is inline
d, and all possible type_info
s are actually generated at compile time before the program proper executes, the accessor likely reduces to a constant anyway in optimized code. That said, if profiler results contradict this expectation, we can perform this optimization anyway. However – that would be a breaking change once a Flow user uses payload_type() publicly.
Definition at line 179 of file log.cpp.
References payload_type().
Referenced by flow::log::Config::component_to_union_idx().
|
private |
The internally stored integer representation of the enum
value passed to the 1-arg constructor; meaningless and ignored if 0-arg ctor was used (empty() is true
).
Definition at line 1030 of file log.hpp.
Referenced by operator=(), payload(), and payload_enum_raw_value().
|
private |
The typeid()
of the Payload
passed to the 1-arg constructor; if 0-arg ctor was used (empty() is true
) then it is null.
Why is it a pointer and not something else? Answer: It cannot be a direct member: type_info
is not copyable. It cannot be a non-const
reference for the same reason. It cannot be a const
reference, because Component is mutable via assignment. (In any case, all possible type_info
objects are known before program start and are 1-1 with all possible types; hence the desire to store a "copy" is wrong-headed perf-wise or otherwise; there is just no reason for it.)
Definition at line 1024 of file log.hpp.
Referenced by empty(), operator=(), and payload_type().