Flow 1.0.1
Flow project: Full implementation reference.
simple_ostream_logger.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include "flow/log/log.hpp"
24#include <boost/array.hpp>
25#include <boost/shared_ptr.hpp>
26
27namespace flow::log
28{
29
30// Types.
31
32/**
33 * An implementation of Logger that logs messages to the given `ostream`s (e.g., `cout` or
34 * an `ofstream` for a file). Protects against garbling due to simultaneous logging from multiple
35 * threads.
36 *
37 * Vaguely speaking, this Logger is suitable for console (`cout`, `cerr`) output; and, in a pinch outside of a
38 * heavy-duty production/server environment, for file (`ofstream`) output. For heavy-duty file logging one
39 * should use Async_file_logger. The primary reason is performance; this is discussed in the Logger class doc header.
40 * A secondary reason is additional file-logging-specific utilities -- such as rotation -- are now or in the future
41 * going to be in Async_file_logger, as its purpose is heavy-duty file logging specifically.
42 *
43 * ### Thread safety ###
44 * As noted above, simultaneous logging from multiple threads is safe from output corruption, in that
45 * simultaneous do_log() calls for the same Logger targeting the same stream will log serially to each other.
46 *
47 * Additionally, changes to the Config (which controls should_log() behavior among other things), pointer to which
48 * is passed to constructor, are thread-safe against should_log() and do_log() if and only if class Config doc header
49 * describes them as such. Short version: You can't modify the Config anymore (while should_log() and do_log())
50 * except you can dynamically modify verbosity via Config::configure_default_verbosity() and
51 * Config::configure_component_verbosity() or Config::configure_component_verbosity_by_name().
52 *
53 * There are no other mutable data (state), so that's that.
54 *
55 * ### Thread safety: Corner case: Don't cross the `ostream`s! ###
56 * There is another, arguably subtle, thread safety issue.
57 * Bottom line/summary: If you pass `ostream` S (including `cout` and `cerr`)
58 * into object Simple_ostream_logger S1 constructor, then don't
59 * access S from any other code, including outside code printing to S, AND including indirect access via any other
60 * Simple_ostream_logger S2 that isn't S1 -- at least not when S1 might be logging.
61 *
62 * Now in more detail: Suppose S is an underlying `ostream`; for example S could be `cerr`.
63 * Suppose Simple_ostream_logger S1 is constructed with S, meaning it will always print to `cerr`. Finally,
64 * suppose some other unrelated code *also* accesses the stream S *while* a log statement is printing
65 * to S through S1; for example that code might be writing some raw printouts to standard error by means
66 * of `cerr <<`. Hence, S is being write-accessed from 2 threads.
67 * At best, this can cause garbled output. (This is unavoidable but could be seen as acceptable functionally.)
68 * However the danger doesn't end there; internally S1 code will use stateful formatters to affect the output.
69 * Write ops via `ostream` formatters are *not* thread-safe against use of formatters on the same `ostream` from
70 * another thread. This can cause very-hard-to-identify crashes, such as double-frees reported
71 * from deep inside `ostream`-related library code. Garbled output may be okay in a pinch, but crashes and other
72 * concurrency-corruption issues are never okay!
73 *
74 * One's reaction to that might be "of course!" -- it's simply messing with an `ostream` from 2+ different threads
75 * simultaneously: it should be expected to break. However, an essentially identical problem -- which one
76 * might assume wouldn't exist (but one would be wrong) -- is the following. Suppose everything is as described
77 * in the previous paragraph, except that the "outside code accessing stream from another thread simultaneously"
78 * isn't unrelated but rather simply *another* Simple_ostream_logger S2 that happened to be
79 * constructed against `ostream` S, same as S1 was. This, too, can break dangerously in the same ways and for
80 * the same reason. That is, Simple_ostream_logger takes no special measures to prevent this; all built-in
81 * thread-safety measures apply to multi-threaded uses of each given `*this` -- not across different `*this`es.
82 */
84 public Logger
85{
86public:
87 // Constructors/destructor.
88
89 /**
90 * Constructs logger to subsequently log to the given standard `ostream` (or 2 thereof).
91 *
92 * @warning It is strongly recommended that numeric format state (for integers) in `os` and `os_for_err` be at
93 * default values (e.g, no `std::hex` or non-default fill character) at entry to this constructor.
94 * Otherwise the contents of prefixes within log output will be formatted in some undefined way.
95 *
96 * Note that usually one SHOULD avoid writing anything
97 * to `os` or `os_for_err` once they've been passed to Simple_ostream_logger. Logs should not (usually) be mixed with
98 * other output, as (1) it would look odd; and (2) interleaving may occur, since there is no mutex making logging
99 * mutually exclusive against other output to the same stream(s). Update: It might be worse that that; see
100 * thread safety notes in Simple_ostream_logger doc header. Reiterating: Really one and only one
101 * Simple_ostream_logger should access a given `ostream`, and no other code should.
102 *
103 * The case `&os == &os_for_err` (in other words they are the same stream) is not only allowed but likely common,
104 * so that post-factum redirect of `stderr` into `stdout` (often done via `2>&1` on command line) becomes unnecessary.
105 *
106 * @param config
107 * Controls behavior of this Logger. In particular, it affects should_log() logic (verbosity default and
108 * per-component) and output format (such as time stamp format). See thread safety notes in class doc header.
109 * This is saved in #m_config.
110 * @param os
111 * `ostream` to which to log messages of severity strictly less severe than Sev::S_WARNING.
112 * @param os_for_err
113 * `ostream` to which to log messages of severity Sev::S_WARNING or more severe.
114 */
115 explicit Simple_ostream_logger(Config* config,
116 std::ostream& os = std::cout, std::ostream& os_for_err = std::cerr);
117
118 // Methods.
119
120 /**
121 * Implements interface method by returning `true` if the severity and component (which is allowed to be null)
122 * indicate it should. As of this writing not thread-safe against changes to `*m_config`.
123 *
124 * @param sev
125 * Severity of the message.
126 * @param component
127 * Component of the message. Reminder: `component.empty() == true` is allowed.
128 * @return See above.
129 */
130 bool should_log(Sev sev, const Component& component) const override;
131
132 /**
133 * Implements interface method by returning `false`, indicating that this Logger will not need the contents of
134 * `*metadata` and `msg` passed to do_log(), as the latter will output them synchronously.
135 *
136 * @return See above.
137 */
138 bool logs_asynchronously() const override;
139
140 /**
141 * Implements interface method by synchronously logging the message and some subset of the metadata in a fashion
142 * controlled by #m_config.
143 *
144 * @param metadata
145 * All information to potentially log in addition to `msg`.
146 * @param msg
147 * The message.
148 */
149 void do_log(Msg_metadata* metadata, util::String_view msg) override;
150
151 // Data. (Public!)
152
153 /// Reference to the config object passed to constructor. Note that object is mutable; see notes on thread safety.
155
156private:
157
158 // Types.
159
160 /// Short-hand for ref-counted pointer to a given Ostream_log_msg_writer. See #m_os_writers for ref-count rationale.
161 using Ostream_log_msg_writer_ptr = boost::shared_ptr<Ostream_log_msg_writer>;
162
163 // Data.
164
165 /**
166 * Stream writers via which to log messages, each element corresponding to a certain set of possible severities
167 * (as of this writing, warnings or worse versus all others).
168 * 2+ pointers stored may point to the same object (see constructor body), if they share the same `ostream`.
169 * In that case, ref-counted pointer mechanics will ensure the shared Ostream_log_msg_writer is freed when
170 * #m_os_writers `array` is freed, when `*this` is destroyed.
171 */
172 boost::array<Ostream_log_msg_writer_ptr, 2> m_os_writers;
173
174 /// Mutex protecting against log messages being logged concurrently and thus being garbled.
176}; // class Simple_ostream_logger
177
178} // namespace flow::log
A light-weight class, each object storing a component payload encoding an enum value from enum type o...
Definition: log.hpp:840
Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers...
Definition: config.hpp:200
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
An implementation of Logger that logs messages to the given ostreams (e.g., cout or an ofstream for a...
bool logs_asynchronously() const override
Implements interface method by returning false, indicating that this Logger will not need the content...
boost::array< Ostream_log_msg_writer_ptr, 2 > m_os_writers
Stream writers via which to log messages, each element corresponding to a certain set of possible sev...
void do_log(Msg_metadata *metadata, util::String_view msg) override
Implements interface method by synchronously logging the message and some subset of the metadata in a...
Config *const m_config
Reference to the config object passed to constructor. Note that object is mutable; see notes on threa...
Simple_ostream_logger(Config *config, std::ostream &os=std::cout, std::ostream &os_for_err=std::cerr)
Constructs logger to subsequently log to the given standard ostream (or 2 thereof).
boost::shared_ptr< Ostream_log_msg_writer > Ostream_log_msg_writer_ptr
Short-hand for ref-counted pointer to a given Ostream_log_msg_writer. See m_os_writers for ref-count ...
bool should_log(Sev sev, const Component &component) const override
Implements interface method by returning true if the severity and component (which is allowed to be n...
util::Mutex_non_recursive m_log_mutex
Mutex protecting against log messages being logged concurrently and thus being garbled.
Flow module providing logging functionality.
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:224
boost::mutex Mutex_non_recursive
Short-hand for non-reentrant, exclusive mutex. ("Reentrant" = one can lock an already-locked-in-that-...
Definition: util_fwd.hpp:215
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
Simple data store containing all of the information generated at every logging call site by flow::log...
Definition: log.hpp:1048