Flow 1.0.0
Flow project: Full implementation reference.
|
#include "flow/log/log_fwd.hpp"
#include "flow/log/detail/log_fwd.hpp"
#include "flow/log/detail/thread_lcl_str_appender.hpp"
#include "flow/util/util.hpp"
#include "flow/util/detail/util.hpp"
#include "flow/util/uniq_id_holder.hpp"
#include <chrono>
#include <string>
#include <typeinfo>
Go to the source code of this file.
Classes | |
class | flow::log::Component |
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... | |
struct | flow::log::Msg_metadata |
Simple data store containing all of the information generated at every logging call site by flow::log, except the message itself, which is passed to Logger::do_log() assuming Logger::should_log() had returned true . More... | |
class | flow::log::Logger |
Interface that the user should implement, passing the implementing Logger into logging classes (Flow's own classes like net_flow::Node; and user's own logging classes) at construction (plus free/static logging functions). More... | |
class | flow::log::Log_context |
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns this Logger and Component via get_logger() and get_log_component() public accessors. More... | |
Namespaces | |
namespace | flow |
Catch-all namespace for the Flow project: A collection of various production-quality modules written in modern C++17, originally by ygoldfel. | |
namespace | flow::log |
Flow module providing logging functionality. | |
Macros | |
#define | FLOW_LOG_WARNING(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_WARNING, ARG_stream_fragment) |
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by that Logger . More... | |
#define | FLOW_LOG_FATAL(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_FATAL, ARG_stream_fragment) |
Logs a FATAL message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_ERROR(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_ERROR, ARG_stream_fragment) |
Logs an ERROR message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_INFO(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_INFO, ARG_stream_fragment) |
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_DEBUG(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_DEBUG, ARG_stream_fragment) |
Logs a DEBUG message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_TRACE(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_TRACE, ARG_stream_fragment) |
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_DATA(ARG_stream_fragment) FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_DATA, ARG_stream_fragment) |
Logs a DATA message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() , if such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_WARNING_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_WARNING, ARG_stream_fragment) |
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_FATAL_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_FATAL, ARG_stream_fragment) |
Logs a FATAL message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_ERROR_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_ERROR, ARG_stream_fragment) |
Logs an ERROR message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_INFO_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_INFO, ARG_stream_fragment) |
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_DEBUG_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_DEBUG, ARG_stream_fragment) |
Logs a DEBUG message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_TRACE_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_TRACE, ARG_stream_fragment) |
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_DATA_WITHOUT_CHECKING(ARG_stream_fragment) FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_DATA, ARG_stream_fragment) |
Logs a DATA message into flow::log::Logger *get_logger() with flow::log::Component get_log_component() regardless of whether such logging is enabled by the flow::log::Logger. More... | |
#define | FLOW_LOG_SET_CONTEXT(ARG_logger_ptr, ARG_component_payload) |
For the rest of the block within which this macro is instantiated, causes all FLOW_LOG_...() invocations to log to ARG_logger_ptr with component flow::log::Component(ARG_component_payload) , instead of the normal get_logger() and get_log_component() , if there even such things are available in the block. More... | |
#define | FLOW_LOG_SET_LOGGER(ARG_logger_ptr) |
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_logger only. More... | |
#define | FLOW_LOG_SET_COMPONENT(ARG_component_payload) |
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_log_component only. More... | |
#define | FLOW_LOG_WITH_CHECKING(ARG_sev, ARG_stream_fragment) |
Logs a message of the specified severity into flow::log::Logger *get_logger() with flow::log::Component get_log_component() if such logging is enabled by said flow::log::Logger. More... | |
#define | FLOW_LOG_WITHOUT_CHECKING(ARG_sev, ARG_stream_fragment) |
Identical to FLOW_LOG_WITH_CHECKING() but foregoes the filter (Logger::should_log()) check. More... | |
#define | FLOW_LOG_DO_LOG(ARG_logger_ptr, ARG_component, ARG_sev, ARG_file_view, ARG_line, ARG_func_view, ARG_time_stamp, ARG_call_thread_nickname_str_moved, ARG_call_thread_id, ARG_stream_fragment) |
Lowest-level logging API accessible to the user, this is identical to FLOW_LOG_WITHOUT_CHECKING() but expects all pieces of metadata in addition to the message and log::Sev, plus the flow::log::Logger, to be supplied as macro arguments. More... | |
#define FLOW_LOG_DATA | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_DATA, ARG_stream_fragment) |
Logs a DATA message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_DATA severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_DATA_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_DATA, ARG_stream_fragment) |
Logs a DATA message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_DATA severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_DEBUG | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_DEBUG, ARG_stream_fragment) |
Logs a DEBUG message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_DEBUG severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_DEBUG_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_DEBUG, ARG_stream_fragment) |
Logs a DEBUG message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_DEBUG severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_DO_LOG | ( | ARG_logger_ptr, | |
ARG_component, | |||
ARG_sev, | |||
ARG_file_view, | |||
ARG_line, | |||
ARG_func_view, | |||
ARG_time_stamp, | |||
ARG_call_thread_nickname_str_moved, | |||
ARG_call_thread_id, | |||
ARG_stream_fragment | |||
) |
Lowest-level logging API accessible to the user, this is identical to FLOW_LOG_WITHOUT_CHECKING() but expects all pieces of metadata in addition to the message and log::Sev, plus the flow::log::Logger, to be supplied as macro arguments.
Internally, all other log-call-site macros ultimately build on top of this one.
ostream<<
semantics instead of having to instantiate an intermediate flow::util::String_ostream (which has performance overhead and is more verbose). Why not just use a higher-level macro – at least as high-level as FLOW_LOG_WITHOUT_CHECKING() – instead? Answer: In some cases there is a source of metadata, like file and line number, that comes from a different source than (e.g.) __FILE__
and __LINE__
at the log call site; e.g., when logging from another log API through flow::log.Logger
. Not doing so breaks (unenforced but nevertheless mandatory) rules of logging system. ARG_logger_ptr
is not null. Otherwise behavior is undefined.ARG_logger_ptr | A Logger* through which to log; not null. |
ARG_component | See Msg_metadata (reference copied into it). |
ARG_sev | See Msg_metadata (enum value copied into it). |
ARG_file_view | See Msg_metadata (String_view copied into it). Reminder: Underlying memory may need to remain valid asynchronously (read: indefinitely); usually it's a literal in static storage. |
ARG_line | See Msg_metadata (integer copied into it). |
ARG_func_view | See Msg_metadata (String_view copied into it). Reminder: Same as for ARG_file_view . |
ARG_time_stamp | See Msg_metadata (scalar copied into it). |
ARG_call_thread_nickname_str_moved | See Msg_metadata (this std::string is moved into it and thus made empty). |
ARG_call_thread_id | See Msg_metadata (scalar copied into it). |
ARG_stream_fragment | See FLOW_LOG_WITHOUT_CHECKING(). |
The implementation here must be as performant as humanly possible. Every single logged message (albeit only assuming severity [or any other filter] checks have passed, meaning a message is IN FACT logged) will execute this code.
In this implementation, one keeps reusing a thread-local string
, cleared each time this is invoked and then written to. (The clearing doesn't deallocate anything; it only updates an internal length integer to 0!) If Logger::logs_asynchronously() is false
, then the Logger synchronously outputs the message and has no need to make some intemediate copy of either the message or the metadata (time stamp, etc.). However, if it is true
(as for heavy-duty logger Async_file_logger), then Logger must make a copy of the aforementioned thread-local message string, so that it can be asynchronously logged later (probably by some other worker thread used by the Logger), and deallocated. This implementation allows for both work-flows; also see below to-do.
logs_asynchronously()
(i.e., synchronous) Logger flow. In the asynchronous flow, however, it involves an added message copy. Instead – as done in certain major server software author is familiar with – one could (perhaps in the async flow only) allocate a new string in each FLOW_LOG_DO_LOG() (in rare cases reallocating, even repeatedly, if more space is needed); then pass that pointer around, until it is asynchronously written out by Logger impl; then deallocate it. Thus, a copy is eliminated in the async workflow. A complicating factor is that the current system maintains a format state across separate log call sites in a given thread; this change would (if naively implemented at least) eliminate that feature – but this could be considered acceptable. (Still, do realize that, for example, in today's feature set one can set the chrono
I/O formatting to show short unit names like 2ms
instead of 2 milliseconds
; and one need do it only once; but with this change one would need to do it in every log call site. That would be, I can attest, rather annoying. Additionally, make sure the behavior is consistent in the sync and async work-flows.) A related wrinkle is that if we add special support for printf()
-style log call sites (a to-do in flow::log doc header as of this writing), then in that case since there is no expectation of such format statefulness in the first place, in that flow that particular concern isn't a concern. (Sub-to-do: If one did this, an extra-optimized further idea is to avoid the repeated allocs/deallocs by maintaining a pool of already-allocated buffers to be reused opportunistically.) Bottom line: I claim the existing thing is pretty good; the extra copy is IMO unlikely to affect real performance, because (1) it's only one copy in the context of quite a bit of similar copying and other ops going on while writing out the string; and (2) if the message is so verbose as to affect performance at all, then it will probably affect it regardless of the extra copy (in other words, its verbosity must be increased, or the filter verbosity must be decreased – avoiding this exta internal copy feels in my [ygoldfel] personal opinion like rearranging deck chairs on the Titanic). So, this to-do should probably be to-done at some point, but it doesn't feel urgent. And since there are quite a few subtleties involved, as shown above, it's natural to resist doing it until truly necessary.Fine_clock::now()
and a calendar time (boost.chrono docs say this unequivocally which is a confirmation). The pros include: (1) higher precision and resolution; (2) that time always flows forward and at a uniform rate without possibility of time going back or forward due to human/otherwise clock sets or rare events like daylight savings and leap seconds; or (3) to summarize, something more suited as a quick-and-dirty way to measure how long things take in the program, like an always-on, log-integrated version of perf::Checkpointing_timer. As of this writing all out-of-the-box log::Logger implementations and log::Config allow the output of human-readable as well as sec.usec-from-Epoch time stamps. One approach might be to replace the latter only with the high-rez clock's time stamps, perhaps optionally, while leaving the human-readable one alone. Note: There is an important test to perform here, which is the time cost of obtaining either time stamp. E.g., the high-rez time stamp might be much slower – or maybe the opposite! To test this, (1) add the POSIX-time clock into the perf::Clock_type enum
, with all associated (fairly straightforward) changes in flow::perf
; and (2) test the perf characteristics of this new clock. Certain code exists outside of Flow itself that already automatically tests all Clock_type
s, so it would quickly give the answer. (Secondary to-do: Be less vague about where this program resides, in this comment. I, ygoldfel, have the answer at any rate and am only omitting it here for boring process reasons.) #define FLOW_LOG_ERROR | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_ERROR, ARG_stream_fragment) |
Logs an ERROR message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_ERROR severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_ERROR_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_ERROR, ARG_stream_fragment) |
Logs an ERROR message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_ERROR severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_FATAL | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_FATAL, ARG_stream_fragment) |
Logs a FATAL message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_FATAL severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_FATAL_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_FATAL, ARG_stream_fragment) |
Logs a FATAL message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_FATAL severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_INFO | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_INFO, ARG_stream_fragment) |
Logs an INFO message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_INFO severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_INFO_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_INFO, ARG_stream_fragment) |
Logs an INFO message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_INFO severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_SET_COMPONENT | ( | ARG_component_payload | ) |
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_log_component
only.
If a free or static
member function or lambda is frequently called, and it needs to log, it will make perf aspects of this macro (and thus of FLOW_LOG_SET_CONTEXT()) significant to overall perf. Hence let's unpack what computation occurs.
struct
instance with a single Component
member thus constructed. As of this writing that constuctor: saves the pointer &(typeid(T))
, where T
is the particular enum class
type of ARG_component_payload
; and saves the integer payload of the enum
value ARG_component_payload
. A pointer and an integer are light-weight to copy, indeed. Additionally, in practice, both values are probably known at compile time; hence the compiler might further inline the "copy." Hence we conclude the copy operation is fast.struct
's two members are stored directly on the stack. There is no heap involved. Hence we conclude there is no hidden cost in messing with the heap by either using a lambda or whatever is stored inside the lambda.get_log_component()
is called (at every log call site in the free/whatever function, probably): It calls the aforementioned lambda's compiler-generated operator()()
. This just returns a pointer to the stack-stored struct
. Compilers tend to auto-inline (short) lambda operator()()
calls, so there's probably not even the overhead of a function call. Hence we conclude this is pretty fast.Looks fine perf-wise (at least in theory).
ARG_component_payload | See FLOW_LOG_SET_CONTEXT(). |
#define FLOW_LOG_SET_CONTEXT | ( | ARG_logger_ptr, | |
ARG_component_payload | |||
) |
For the rest of the block within which this macro is instantiated, causes all FLOW_LOG_...()
invocations to log to ARG_logger_ptr
with component flow::log::Component(ARG_component_payload)
, instead of the normal get_logger()
and get_log_component()
, if there even such things are available in the block.
This is useful, for example, in static
methods, where there is no get_logger()
or get_log_component()
function defined, but a flow::log::Logger and component payload are available (for example) via parameters. It's also useful if one wants to log to a different Logger*
for some reason and/or (perhaps more likely) a different component. Note that this creates or changes the meaning of the identifiers get_logger
and get_log_component
for the rest of the block; in fact you may call get_logger()
or get_log_component()
directly for whatever nefarious purposes you require, though it is suspected to be rare compared to just using FLOW_LOG_...()
normally.
Example:
ARG_logger_ptr | ARG_logger_ptr will be used as the Logger* in subsequent FLOW_LOG_...() invocations in this block. |
ARG_component_payload | Component(ARG_component_payload) , a light-weight holder of a copy of ARG_component_payload , will be used as the const Component& in subsequent FLOW_LOG_...() invocations in this block. |
#define FLOW_LOG_SET_LOGGER | ( | ARG_logger_ptr | ) |
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_logger
only.
ARG_logger_ptr | See FLOW_LOG_SET_CONTEXT(). |
#define FLOW_LOG_TRACE | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_TRACE, ARG_stream_fragment) |
Logs a TRACE message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but for the flow::log::Sev::S_TRACE severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_TRACE_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_TRACE, ARG_stream_fragment) |
Logs a TRACE message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING_WITHOUT_CHECKING() but for the flow::log::Sev::S_TRACE severity.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_WARNING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITH_CHECKING(::flow::log::Sev::S_WARNING, ARG_stream_fragment) |
Logs a WARNING message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
, if such logging is enabled by that Logger
.
Supplies context information to be potentially logged with message, like current time, source file/line/function, and thread ID/nickname info, in addition to the message, component, and severity. The severity checked against (and potentially logged in its own right) is flow::log::Sev:S_WARNING.
More precisely, checks whether logging warnings is currently enabled in the Logger*
returned by get_logger()
in the macro invocation's context; if not does nothing; if so constructs and logs the message as a warning via FLOW_LOG_WITHOUT_CHECKING(). Also, if get_logger()
is null, then the effect is the same as a get_logger()->should_log()
returning false
(meaning it is a no-op).
get_logger()
must exist, and if not null then get_logger()
(returning Logger*
) and get_log_component()
(returning const Component&
) must return a valid pointer and reference to Logger
and Component
, respectively. Most of the time these are available due to most logging code being in classes deriving from flow::log::Log_context which supplies those methods and takes (at construction) the values to return subsequently. In situations where this is impossible (such as static
members methods or in free functions) or insufficient (such as when one wants to use a different component vs. the one returned by flow::log::Log_context::get_log_component()), use FLOW_LOG_SET_CONTEXT().
We assume for this discussion that the notion of order of final output (to device/file/network/whatever) exists, and that the get_logger()
indeed safely outputs message M1 entirely before M2, or vice versa, for every pair of messages (in this context by message we mean message+metadata pair) ever passed to do_log()
. With that assumption in effect (IF indeed it is):
get_logger()
, if FLOW_LOG_*(M1);
is called before FLOW_LOG_*(M2);
, and both are successfully output by logger, then the final output order must have M1 precede M2, not vice versa.FLOW_LOG_*()
calls are chronologically disjoint, then again the final output must also have M1 precede M2 if M1 went first; and vice versa.FLOW_LOG_*()
calls chronologically overlap, then either final output order is possible.ARG_stream_fragment
in M1 vs. M2.)The time stamp is obtained as soon as practically possible within the body of this macro. Hence it reflects the time at the moment just after the pre-logging statement finished, just before the log call site executes. After this is when ARG_stream_fragment
is evaluated – assuming the filter check passes – and only after that might the final output get queued (or possibly synchronously output, depending on nature of get_logger()
). This is why it's technically possible that (even in the absence of system time going backwards) 2 messages from 2 different threads might appear in the final output with slightly out-of-order time stamps. Informally, this is unlikely, because the ARG_stream_fragment
evaluation would be on the order of a bunch of instructions that would complete in less time than the microsecond resolution of time stamp output, in most cases, or maybe a handful of microseconds. Anecdotally, I (ygoldfel) don't recall one instance of seeing this (out-of-order time stamps due to the concurrency race implied by the above mechanism). Nevertheless I mention it here for completeness, as well as to explain how it works.
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WITHOUT_CHECKING(). |
<<
and by implementing this as a variadic function template (C++11 feature).Thus, while it'd be no longer possible to write
one would instead write
which is fairly close and still reasonably readable. However, one would need to be mindful of performance; hopefully the optimizer would still inline everything instead of adding a number of function calls compared to this macro implementation whose side benefit is guaranteed inlining. Generally, though, macros are bad and should be eliminated where possible; just don't mess up speed in something as common as logging. In addition, if it's NOT inlined, the number of functions generated by the template's many instantiations would be rather large, though I suppose that's not necessarily worse than full-on inlining thereof – just different. (Still, consider how many different configurations would pop up as a result of all the different log messages!) Also keep in mind that fully inlining all of this would require the build engine to be capable of link-time optimization (FLTO), and the build script to enable FLTO. Unfortunately it would be impossible for the non-macro to refer to a get_logger()
in the call's context, so it would be necessary for this to be passed in as an argument, significantly lowering the ease of use of the API. That said, flow::log::Logger itself could simply implement all these APIs as class methods instead of their being free functions. Then one could even (when desired) write such things as
and therefore FLOW_LOG_SET_CONTEXT() (another "tricky" macro) could be eliminated due to lack of necessity. Finally, flow::log::Log_context (in the current design, to be derived from by all logging classes) would be used like this:
It might also be advisable, for code brevity in such commonly referenced APIs, to add trivial forwarding methods to flow::log::Log_context. This is slightly questionable, as it's quite a bit of boiler-plate (needed every time one might change this overall API) just to remove a few characters from each log call. The above call would become:
which is a little more compact. That can also be accomplished by having flow::log::Log_context implement flow::log::Logger itself. As a last note, __LINE__
(etc.) can only be made useful via a macro, so one would still be required to wrap around any API suggested above in a simple macro – but this would be far superior (in this particular dimension of avoiding macro insanity) to the level of macro-ness required at the moment. All in all, while I do hate macros, the present design seems reasonably strong, so the above rejiggering ideas don't feel like no-brainers.
#define FLOW_LOG_WARNING_WITHOUT_CHECKING | ( | ARG_stream_fragment | ) | FLOW_LOG_WITHOUT_CHECKING(::flow::log::Sev::S_WARNING, ARG_stream_fragment) |
Logs a WARNING message into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
regardless of whether such logging is enabled by the flow::log::Logger.
Analogous to FLOW_LOG_WARNING() but without checking for whether it is enabled (you should do so yourself; see the following note on this topic).
get_logger()
is null, this is a no-op. In practice, though, this case should have been eliminated as part of heeding the following warning:Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
#define FLOW_LOG_WITH_CHECKING | ( | ARG_sev, | |
ARG_stream_fragment | |||
) |
Logs a message of the specified severity into flow::log::Logger *get_logger()
with flow::log::Component get_log_component()
if such logging is enabled by said flow::log::Logger.
The behavior is identical to that by FLOW_LOG_WARNING() and similar, but one specifies the severity as an argument instead of it being hard-coded into the macro name itself.
ARG_sev | Severity (type log::Sev). |
ARG_stream_fragment | Same as in FLOW_LOG_WARNING(). |
Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
get_logger()
and get_log_component()
return values in such a way as to be reused by the FLOW_LOG_WITHOUT_CHECKING() invoked by the former macro if should_log() == true
. As it stands, they are called again inside the latter macro. In the most-common case, wherein Log_context is used for those two expressions, this should get inline-optimized to be maximally fast anyway. With FLOW_LOG_SET_*()
, though, it might be a bit slower than that. Technically, one can make their own get_logger
and get_log_component
identifiers that might do something slower still – though it's unlikely (and as of this writing unprecedented). So I would not call this pressing, but on the other hand... just do it! The implementation code will be somewhat hairier though. #define FLOW_LOG_WITHOUT_CHECKING | ( | ARG_sev, | |
ARG_stream_fragment | |||
) |
Identical to FLOW_LOG_WITH_CHECKING() but foregoes the filter (Logger::should_log()) check.
No-op if get_logger()
returns null. Internally, all other log-call-site macros ultimately build on top of this one except FLOW_LOG_DO_LOG().
Context information obtained and possibly logged is file/function/line, thread nickname/ID, time stamp, etc. The message is given as the <<
-using ostream
fragment in ARG_stream_fragment
argument. Example (note the 2nd argument containing a stream output fragment):
Logger
. Not doing so breaks (unenforced but nevertheless mandatory) rules of logging system.Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.
ARG_sev | Severity (type flow::log::Sev). |
ARG_stream_fragment | Fragment of code as if writing to a standard ostream . A terminating newline will be auto-appended to this eventually and therefore should generally not be included by the invoker. (Such a terminating newline would manifest as a blank line, likely.) |