|
Flow 3.0.0
Flow project: Public API.
|
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>
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... | |
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:
*this.log_while_locked(F), where you provide F() that performs work, including logging work, with certainty that the Logger* and Component stores in *this cannot change. If used as prescribed (see log_while_locked() doc header), then anything depending on get_logger and/or get_log_component – including FLOW_LOG_INFO(), FLOW_LOG_TRACE(), etc. – will work properly.FLOW_LOG_INFO_LOCKED(), FLOW_LOG_TRACE_LOCKED(), etc., each of which is equivalent to its non-_LOCKED counterpart but as-if executed inside log_while_locked().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.
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.
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).
|
explicit |
Identical to Log_context API.
| logger | See above. |
|
explicit |
Identical to Log_context API.
| Component_payload | See above. |
| logger | See above. |
| component_payload | See above. |
|
explicit |
Identical to Log_context API.
| src | See above. |
| flow::log::Log_context_mt::Log_context_mt | ( | Log_context_mt && | src | ) |
Identical to Log_context API.
| src | See above. |
| 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).
log_while_locked() that forwards to a Log_context_mt of choice.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). 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:
An example of what would not compile:
Logger* past lockJust 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:
Usually don't do that ("that" = use a Logger* outside the lock, having grabbed it inside the lock).
*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:
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.
| Logging_task | Recommended 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. |
| logging_task | See above. |
| 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.
| src | See above. |
| 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.
| src | See above. |
Identical to Log_context API; but safe against concurrent operations on a *this.
| logger | See above. |
| void flow::log::Log_context_mt::swap | ( | Log_context_mt & | other | ) |
Identical to Log_context API; but safe against concurrent operations on a *this.
| other | See above. |