Flow 3.0.0
Flow project: Public API.
Public Member Functions | List of all members
flow::log::Log_context_mt Class Reference

Similar mission to Log_context but enables one to safely log under the possibility of a concurrent set_logger() (et al) that adds/replaces/removes active Logger* (et al). More...

#include <log.hpp>

Inheritance diagram for flow::log::Log_context_mt:
[legend]
Collaboration diagram for flow::log::Log_context_mt:
[legend]

Public Member Functions

 Log_context_mt (Logger *logger=nullptr)
 Identical to Log_context API. More...
 
template<typename Component_payload >
 Log_context_mt (Logger *logger, Component_payload component_payload)
 Identical to Log_context API. More...
 
 Log_context_mt (const Log_context_mt &src)
 Identical to Log_context API. More...
 
 Log_context_mt (Log_context_mt &&src)
 Identical to Log_context API. More...
 
Log_context_mt & operator= (const Log_context_mt &src)
 Identical to Log_context API; but safe against concurrent operations on a *this. More...
 
Log_context_mt & operator= (Log_context_mt &&src)
 Identical to Log_context API; but safe against concurrent operations on a *this. More...
 
void swap (Log_context_mt &other)
 Identical to Log_context API; but safe against concurrent operations on a *this. More...
 
Logger * set_logger (Logger *logger)
 Identical to Log_context API; but safe against concurrent operations on a *this. More...
 
template<typename Logging_task >
void log_while_locked (Logging_task &&logging_task) const
 Locks *this to be immutable, and under this lock executes user's logging_task(), providing it get_logger and get_log_component functions as arguments, so it can use them for logging operations without worry of the returned values becoming concurrently obsolete (potentially invalid). More...
 

Detailed Description

Similar mission to Log_context but enables one to safely log under the possibility of a concurrent set_logger() (et al) that adds/replaces/removes active Logger* (et al).

Specifically:

We restate: As there is no get_logger()/get_log_component(), actual logging work now must use log_while_locked() to make them (safely) available. And: we recommend the use of FLOW_LOG_..._LOCKED() macros in order to make that happen with reduced boiler-plate.

See also
Log_context doc header section "Setting the logger / thready safety."

There is a perf trade-off: essentially all operations will lock an internal mutex, proceed, then unlock. This will have a small cost when there is no lock contention (no simultaneous logging – log_while_locked() calls); and a larger cost when there is (when indeed there is simultaneous logging – therefore log_while_locked() calls). Informally: in our experience, as noted in the aforementioned doc header discussion, set_logger() is mainly used when the sub-class is instantiated statically or globally; and usually it is possible to simply avoid logging along fast-paths of such classes. By the time one has decided to actually FLOW_LOG_...() in those situations, it is usually stipulated that performance (in that specific place) is of lower concern.

Still: it requires care. If you need to provide/use set_logger(), with automatic concurrency support, be mindful of this potential source of slow-down in using Log_context_mt.

Note
In some cases perf is of such high concern that even FLOW_LOG_TRACE*() (or more verbose) is too costly to perform along a fast-path; even the should_log() check – generally coded to be quite speedy – is best avoided. Adding a log_while_locked() setup (whether explicitly or via ..._LOCKED()) would certainly not help matters. So what to do? Answer: There are various bespoke little tricks to use. One we have found to be acceptable is this: Maintaining an extra flag, something like m_skip_fast_path_verbose_logging, that is set to true or false whenever executing set_logger(); specifically to true if and only if Sev::S_TRACE results in Logger::should_log() == false at that time. Then bracket any FLOW_LOG_TRACE() (or higher verbosity log-call-site) with an if (!m_skip_fast_path_verbose_logging) {}. A single flag-check in the fast-path, when TRACE logging is disabled (by convention the case in production), is fast-enough – and it'll skip both the repeated should_log() checks – and, relevantly here, the now-also-required log_while_locked() setup. The downside is simply that enabling TRACE-logging (in debug/test scenarios) requires (for these hot-spot singletons/globals/whatever – not generally!), set_logger() in order to take effect. (Worst-case that means restart application; but other triggers could be added if desired.) We've found in practice it can be worthwhile (but only in very perf-sensitive contexts).

Constructor & Destructor Documentation

◆ Log_context_mt() [1/4]

flow::log::Log_context_mt::Log_context_mt ( Logger *  logger = nullptr)
explicit

Identical to Log_context API.

Parameters
loggerSee above.

◆ Log_context_mt() [2/4]

template<typename Component_payload >
flow::log::Log_context_mt::Log_context_mt ( Logger *  logger,
Component_payload  component_payload 
)
explicit

Identical to Log_context API.

Template Parameters
Component_payloadSee above.
Parameters
loggerSee above.
component_payloadSee above.

◆ Log_context_mt() [3/4]

flow::log::Log_context_mt::Log_context_mt ( const Log_context_mt &  src)
explicit

Identical to Log_context API.

Parameters
srcSee above.

◆ Log_context_mt() [4/4]

flow::log::Log_context_mt::Log_context_mt ( Log_context_mt &&  src)

Identical to Log_context API.

Parameters
srcSee above.

Member Function Documentation

◆ log_while_locked()

template<typename Logging_task >
void flow::log::Log_context_mt::log_while_locked ( Logging_task &&  logging_task) const

Locks *this to be immutable, and under this lock executes user's logging_task(), providing it get_logger and get_log_component functions as arguments, so it can use them for logging operations without worry of the returned values becoming concurrently obsolete (potentially invalid).

See also
FLOW_LOG_INFO_LOCKED(), FLOW_LOG_TRACE_LOCKED(), et al: They use log_while_locked() enabling its use pithily and safely.
FLOW_LOG_SET_LOCKED_CONTEXT(), reminiscent of FLOW_LOG_SET_CONTEXT() (explained below).
Note
You can also declare your own method log_while_locked() that forwards to a Log_context_mt of choice.
Warning
logging_task() must be careful not to invoke anything that would lock *this; this includes set_logger(), assignment, swap, and log_while_locked() – and FLOW_LOG_..._LOCKED() which calls the latter. For this reason, it is typically better to not use log_while_locked() directly but rather FLOW_LOG_..._LOCKED() (and don't do anything similarly-illegal inside the args to ..._LOCKED() either).
Along those lines, it also possible to deadlock if logging_task() locks another Log_context_mt, while in another thread the two are locked in the opposite order (AB/BA deadlock). If all threads stick to FLOW_LOG_..._LOCKED() (without illegal stuff inside the ()), then this bad thing cannot happen.

An example of what is safe:

// Thread 1: main():
std::optional<Async_file_logger> lgr{std::in_place, ...};
// s_concurrently_logging_guy derives from Log_context_mt.
s_concurrently_logging_guy.set_logger(&*lgr);
...
s_concurrently_logging_guy.set_logger(nullptr);
// s_concurrently_logging_guy is fine logging as shown below; by now its Logger* is null, so
// *lgr disappearing does not affect it.
lgr.reset();
// main() exits here, even while s_concurrently_logging_guy is still potentially logging for a bit.
// Thread 2: inside s_concurrently_logging_guy:
...
FLOW_LOG_INFO_LOCKED("Periodic logging of something interesting [" << ... << "].");
// ^-- This did basically: log_while_locked([&](auto&& get_logger, auto&& get_log_component)
// { FLOW_LOG_INFO("Periodic logging of ...."); });
// So either get_logger() is null, or get_logger() is &*lgr, inside the lambda.
#define FLOW_LOG_INFO_LOCKED(ARG_stream_fragment)
A-la FLOW_LOG_WARNING_LOCKED() but with the different log::Sev.
Definition: log.hpp:286

An example of what would not compile:

// Thread 2: inside s_concurrently_logging_guy:
...
// Compile error: No get_logger() or get_log_component() available.
FLOW_LOG_INFO("Periodic logging of something interesting [" << ... << "].");
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:200

Use outside a Log_context_mt sub-class / Whether to save Logger* past lock

Just as sometimes one needs to log in a static (etc.) function and, without concurrency concerns, would use FLOW_LOG_SET_CONTEXT() giving it a known Logger* and Component, you may want to do a similar thing – but now you've got concurrency concerns. In this case a const Log_context_mt* – perhaps log_ctx_mt_ptr – is required by definition. How to actually use it though?

It is possible, but usually not recommended/possibly an anti-pattern, to grab get_logger()'s value and use it outside the lock. You might be tempted to do something like this:

Logger* logger_ptr;
log_ctx_mt_ptr->log_while_locked([&](auto&& get_logger, auto&&) { logger_ptr = get_logger(); });
FLOW_LOG_SET_CONTEXT(logger_ptr, ...); // ... could be, say, a hard-coded Component. That is fine.
FLOW_LOG_INFO("Let's log."); // Not fine: *logger_ptr might be wrong -- or even destroyed -- here.
Logger * get_logger() const
Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect.
Definition: log.cpp:260
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1392
#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:473

Usually don't do that ("that" = use a Logger* outside the lock, having grabbed it inside the lock).

Note
It is not disallowed, and if you can guarantee that *logger_ptr will remain in-existence and generally-OK-to-use throughout such a snippet, then it is well-defined behavior and not dangerous. In our experience we've found cases where it was okay and unavoidable, such as Thread_local_state_registry<T> distributing Logger* to individual per-thread T objects: (1) as-needed pull (constructing new T{}) or (2) via user-triggered push (Thread_local_state_registry::set_logger() that updates each existing T).

As for the original premise here – lacking a Log_context_mt super-class – consider using FLOW_LOG_SET_LOCKED_CONTEXT() instead. This would be fine:

// Now naked `log_while_locked` is available, so you can just:
FLOW_LOG_INFO_LOCKED("Let's log.");
#define FLOW_LOG_SET_LOCKED_CONTEXT(ARG_log_ctx_mt_ptr)
It is to FLOW_LOG_SET_CONTEXT() what FLOW_LOG_(sev)_LOCKED is to plain FLOW_LOG_(sev): given a const ...
Definition: log.hpp:537

You can also always use log_ctx_mt_ptr->log_while_locked(F) directly in that situation. In general at that point, though, it becomes tempting to do something complex in F(), and the aforementioned warnings come into play. Informally we suggest using FLOW_LOG_..._LOCKED() when possible; FLOW_LOG_SET_LOCKED_CONTEXT() makes that reasonably easy in this situation.

Template Parameters
Logging_taskRecommended signature in your code: verbatim: void (auto&& get_logger, auto&& get_log_component). If the args are named literally get_logger and get_log_component, then FLOW_LOG_...()s inside will compile and work properly.
Parameters
logging_taskSee above.

◆ operator=() [1/2]

Log_context_mt & flow::log::Log_context_mt::operator= ( const Log_context_mt &  src)

Identical to Log_context API; but safe against concurrent operations on a *this.

Parameters
srcSee above.
Returns
See above.

◆ operator=() [2/2]

Log_context_mt & flow::log::Log_context_mt::operator= ( Log_context_mt &&  src)

Identical to Log_context API; but safe against concurrent operations on a *this.

Parameters
srcSee above.
Returns
See above.

◆ set_logger()

Logger * flow::log::Log_context_mt::set_logger ( Logger *  logger)

Identical to Log_context API; but safe against concurrent operations on a *this.

Parameters
loggerSee above.
Returns
See above.

◆ swap()

void flow::log::Log_context_mt::swap ( Log_context_mt &  other)

Identical to Log_context API; but safe against concurrent operations on a *this.

Parameters
otherSee above.

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