Flow 1.0.1
Flow project: Full implementation reference.
Classes | Namespaces | Macros
log.hpp File 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...
 

Macro Definition Documentation

◆ FLOW_LOG_DATA

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 242 of file log.hpp.

◆ FLOW_LOG_DATA_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 372 of file log.hpp.

◆ FLOW_LOG_DEBUG

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 212 of file log.hpp.

◆ FLOW_LOG_DEBUG_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 336 of file log.hpp.

◆ FLOW_LOG_DO_LOG

#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.

Note
From public user's point of view: It's flow::log::Logger::should_log() that allows the message argument to be built using 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.
Warning
If invoking this directly, API user must manually ensure the severity/component is enabled in the Logger. Not doing so breaks (unenforced but nevertheless mandatory) rules of logging system.
If invoking this directly, API user must manually ensure ARG_logger_ptr is not null. Otherwise behavior is undefined.
Parameters
ARG_logger_ptrA Logger* through which to log; not null.
ARG_componentSee Msg_metadata (reference copied into it).
ARG_sevSee Msg_metadata (enum value copied into it).
ARG_file_viewSee 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_lineSee Msg_metadata (integer copied into it).
ARG_func_viewSee Msg_metadata (String_view copied into it). Reminder: Same as for ARG_file_view.
ARG_time_stampSee Msg_metadata (scalar copied into it).
ARG_call_thread_nickname_str_movedSee Msg_metadata (this std::string is moved into it and thus made empty).
ARG_call_thread_idSee Msg_metadata (scalar copied into it).
ARG_stream_fragmentSee FLOW_LOG_WITHOUT_CHECKING().

Implementation discussion

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.

Todo:
An alternative copy-free implementation of the asynchronous FLOW_LOG_DO_LOG() work-flow is possible. The basic idea of using a thread-local non-reallocating work string is just fine in the non-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.
Todo:
Time stamp subtlety: It might not be crazy to log not just the time stamp of entry to this macro but also some indication how long it took to evaluate the rest and finally output it to the ultimate device/whatever. It could be an optional differential or something, like "timestamp+diff," with diff in microseconds or something.
Todo:
Time stamp source: The current implementation uses the system clock to generate time stamps (a/k/a POSIX time), but consider optionally or mandatorily using the high-resolution clock instead (or additionally?). This has pros and cons; all or most time stamps elsewhere use the system clock also, so this allows for easy cross-referencing against other systems and logs. There's also the question of how to express an absolute time, as usually the high-resolution clock starts at system startup – not as humanly useful as a "calendar" (UTC) time, which – while useful humanly – is not steady/monotonic/etc. There is no reasonable conversion between 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_types, 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.)

Definition at line 683 of file log.hpp.

◆ FLOW_LOG_ERROR

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 182 of file log.hpp.

◆ FLOW_LOG_ERROR_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 300 of file log.hpp.

◆ FLOW_LOG_FATAL

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 167 of file log.hpp.

◆ FLOW_LOG_FATAL_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 282 of file log.hpp.

◆ FLOW_LOG_INFO

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 197 of file log.hpp.

◆ FLOW_LOG_INFO_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 318 of file log.hpp.

◆ FLOW_LOG_SET_COMPONENT

#define FLOW_LOG_SET_COMPONENT (   ARG_component_payload)
Value:
[[maybe_unused]] \
const auto get_log_component = [component = ::flow::log::Component(ARG_component_payload)] \
() -> const ::flow::log::Component & \
{ \
return component; \
}
A light-weight class, each object storing a component payload encoding an enum value from enum type o...
Definition: log.hpp:840

Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_log_component only.

Impl/perf discussion

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.

  • When this macro (and thus FLOW_LOG_SET_CONTEXT()) is called (usually near the top of free/whatever function): It creates a lambda with one capture, a flow::log::Component, constructed via its 1-arg ctor form. That lambda object is, in fact, identical to a 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.
    • The quasi-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.
  • When 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).

Parameters
ARG_component_payloadSee FLOW_LOG_SET_CONTEXT().

Definition at line 451 of file log.hpp.

◆ FLOW_LOG_SET_CONTEXT

#define FLOW_LOG_SET_CONTEXT (   ARG_logger_ptr,
  ARG_component_payload 
)
Value:
FLOW_LOG_SET_LOGGER(ARG_logger_ptr); \
FLOW_LOG_SET_COMPONENT(ARG_component_payload);
#define FLOW_LOG_SET_LOGGER(ARG_logger_ptr)
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_logger only.
Definition: log.hpp:415

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:

// Suppose flow_node is a flow::Node object, which derives from Log_context.
FLOW_LOG_SET_CONTEXT(flow_node.get_logger(), My_project_components::S_COOL_MODULE);
// Logs with component S_COOL_MODULE to logger set above.
FLOW_LOG_WARNING("Something horrible happened: [" << error_str << "].");
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:152
#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_....
Definition: log.hpp:405
Note
It will not compile if used 2+ times in the same block at the same nesting level. (The same applies to mixing with FLOW_LOG_SET_COMPONENT() or FLOW_LOG_SET_LOGGER().) However, one can create sub-blocks to work around this (likely very infrequent) situation.
Parameters
ARG_logger_ptrARG_logger_ptr will be used as the Logger* in subsequent FLOW_LOG_...() invocations in this block.
ARG_component_payloadComponent(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.

Definition at line 405 of file log.hpp.

◆ FLOW_LOG_SET_LOGGER

#define FLOW_LOG_SET_LOGGER (   ARG_logger_ptr)
Value:
[[maybe_unused]] \
const auto get_logger \
= [logger_ptr_copy = static_cast<::flow::log::Logger*>(ARG_logger_ptr)] \
() -> ::flow::log::Logger* { return logger_ptr_copy; }
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291

Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_logger only.

Parameters
ARG_logger_ptrSee FLOW_LOG_SET_CONTEXT().

Definition at line 415 of file log.hpp.

◆ FLOW_LOG_TRACE

#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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 227 of file log.hpp.

◆ FLOW_LOG_TRACE_WITHOUT_CHECKING

#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.

Note
Same warnings and notes as for FLOW_LOG_WARNING_WITHOUT_CHECKING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 354 of file log.hpp.

◆ 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().

Final log output subleties

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):

  • In a given thread T, and assuming the same 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.
    • In addition, assuming monotonically increasing time stamps, as time flows (which is the case most of the time with exceptions due to computer-clock adjustments and similar), M1's time stamp will be earlier (ignoring rounding) than M2's.
  • If, instead, M1 is logged in thread T1, while M2 is logged in thread T2:
    • If the 2 FLOW_LOG_*() calls are chronologically disjoint, then again the final output must also have M1 precede M2 if M1 went first; and vice versa.
      • Time stamps (under a well behaved clock again), again, will match this order.
    • If the 2 FLOW_LOG_*() calls chronologically overlap, then either final output order is possible.
      • Moreover if M1 precedes M2 in the output, formally time stamps might be in the opposite order (even assuming, again, a well behaved clock). (Informally, given how this is implemented internally, this is unlikely to be observed in practice, barring a wide absolute discrepancy between how long it takes to evaluate 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.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WITHOUT_CHECKING().
Todo:
We can avoid using macros for this and similar APIs by requiring the user to use commas instead of the usual << and by implementing this as a variadic function template (C++11 feature).

Thus, while it'd be no longer possible to write

FLOW_LOG_WARNING("Result: [" << std::setfill('0') << num << "].");

one would instead write

flow::log::warning("Result: [", std::setfill('0'), num, "].");

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

Logger some_logger(...); // Some Logger that is not available through get_logger() as would be more typical.
some_logger.warning("Error detected: [", err_num, "].");

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:

get_logger()->warning("Error detected: [", err_num, "].");

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:

log_warning("Error detected: [", err_num, "]."); // Invoke superclass Log_context's Logger::warning() method.

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.

Definition at line 152 of file log.hpp.

◆ FLOW_LOG_WARNING_WITHOUT_CHECKING

#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).

Note
If 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:
Warning
If invoking this directly, API user must manually ensure the severity is enabled in the logger. Not doing so breaks (unenforced but nevertheless mandatory) rules of logging system.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Definition at line 264 of file log.hpp.

◆ FLOW_LOG_WITH_CHECKING

#define FLOW_LOG_WITH_CHECKING (   ARG_sev,
  ARG_stream_fragment 
)
Value:
( \
::flow::log::Logger const * const FLOW_LOG_W_CHK_logger = get_logger(); \
if (FLOW_LOG_W_CHK_logger && FLOW_LOG_W_CHK_logger->should_log(ARG_sev, get_log_component())) \
{ \
FLOW_LOG_WITHOUT_CHECKING(ARG_sev, ARG_stream_fragment); \
} \
)
virtual bool should_log(Sev sev, const Component &component) const =0
Given attributes of a hypothetical message that would be logged, return true if that message should b...
#define FLOW_UTIL_SEMICOLON_SAFE(ARG_func_macro_definition)
Use this to create a semicolon-safe version of a "void" functional macro definition consisting of at ...
Definition: util_fwd.hpp:1079

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.

Note
It is important that ARG_stream_fragment is actually evaluated only if Logger::should_log() is true. Otherwise resources are wasted on constructing a message string that never gets logged. That's a (the?) reason Logger:should_log() and Logger::do_log() are mutually decoupled in that interface.
Parameters
ARG_sevSeverity (type log::Sev).
ARG_stream_fragmentSame as in FLOW_LOG_WARNING().

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Todo:
In FLOW_LOG_WITH_CHECKING(), save 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.

Definition at line 489 of file log.hpp.

◆ FLOW_LOG_WITHOUT_CHECKING

#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):

"Failed [" << n_times << "] times; bailing out!");
#define FLOW_LOG_WITHOUT_CHECKING(ARG_sev, ARG_stream_fragment)
Identical to FLOW_LOG_WITH_CHECKING() but foregoes the filter (Logger::should_log()) check.
Definition: log.hpp:532
@ S_WARNING
Message indicates a "bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
Note
This macro is the lowest-level API that user should invoke to actually log, except FLOW_LOG_DO_LOG(). Usually she'd invoke something higher-level like FLOW_LOG_WARNING(), etc.; but at times more control is desirable for performance or other reasons – but even then one should not call anything below this level without an extremely excellent reason. See FLOW_LOG_DO_LOG() for discussion of the latter.
Warning
If invoking this directly, API user must manually ensure the severity is enabled in the Logger. Not doing so breaks (unenforced but nevertheless mandatory) rules of logging system.

Severity selection

Before selecting a severity for your log call site, please consider the discussion in the flow::log::Sev doc header.

Parameters
ARG_sevSeverity (type flow::log::Sev).
ARG_stream_fragmentFragment 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.)

Definition at line 532 of file log.hpp.