Flow 1.0.0
Flow project: Full implementation reference.
ostream_log_msg_writer.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
21#include "flow/log/log_fwd.hpp"
22/* Subtlety: The following is technically not required -- log_fwd.hpp is enough to fwd-declare Config, and we
23 * only mention Config* here. However in practice we are likely indirectly included by a *_logger.hpp, and guys such
24 * as (e.g.) Simple_ostream_logger all take Config* as ctor arg, and in practice the user almost always will want
25 * to instantiate a Config and thus require the following header file. Of course, we could just make them
26 * #include it themselves, and generally that is fine/good/a goal of _fwd.hpp pattern. (Even if the user is
27 * directly using Ostream_log_msg_writer, the same reasoning applies: probably they need to instantiate a Config.)
28 * So just for convenience for the vast majority of users, just give 'em this. (Truthfully this is partially to
29 * avoid breaking some user code written before the _fwd.hpp pattern was applied to Flow. However in my (ygoldfel)
30 * opinion it would still be a decent argument even without that. */
31#include "flow/log/config.hpp"
32#include <array>
33// Normally we use boost.chrono in Flow; see explanation in class doc header below re. why std::chrono here.
34#include <chrono>
35#include <ostream>
36#include <boost/io/ios_state.hpp>
37#include <vector>
38
39namespace flow::log
40{
41
42// Types.
43
44/**
45 * Utility class, each object of which wraps a given `ostream` and outputs discrete messages to it adorned with time
46 * stamps and other formatting such as separating newlines. A Config object controls certain aspects of its behavior;
47 * pass this to the constructor. The Config may come from the user as passed to the Logger (if any) using `*this`;
48 * or that Logger (if any) can make its own from scratch.
49 *
50 * It is assumed that between the first and last calls to log() no other code whatsoever writes to the underlying
51 * stream (as passed to ctor) at any point -- including any formatters. At `*this` destruction formatter state
52 * is restored.
53 *
54 * ### Rationale ###
55 * This optional (but recommended for most Logger implementations, though technically not limited to their use)
56 * utility class typically sits between some "physical" output medium -- a buffer in memory, a file, etc. --
57 * that can be written to using an `ostream` (e.g., `std::ostringstream`, util::String_ostream, `std::ofstream`);
58 * and a Logger implementation that promises to output log messages to that output medium. When Logger::do_log()
59 * is called, the message string passed to it will contain the user's original message plus various Msg_metadata
60 * nuggets (like time stamp, severity, source code location) either generated by FLOW_LOG_WITHOUT_CHECKING()
61 * implementation or provided by user. The idea is the Logger is to decide on the output format of the various
62 * metadata plus the message. Therefore, the present class outputs all those pieces (as appropriate) plus
63 * separating newlines. Any Logger is free to use any format it wants; and some `Logger`s might not even generate
64 * any output character stream but rather forward everything elsewhere (maybe even over the network); but
65 * Ostream_log_msg_writer provides a reasonable output format to a character stream, to be used or not as desired.
66 *
67 * ### Thread safety ###
68 * For a given `*this`, the class is not safe for concurrent calls to log(). The using code must provide any such
69 * guarantees.
70 *
71 * See thread safety notes and to-dos regarding config in Simple_ostream_logger doc header. These apply here also.
72 *
73 * @internal
74 * Impl notes
75 * ----------
76 * ### Human-readable time stamps, caching ###
77 * At least optionally, log() adds a human-readable time stamp readout with the date, time (including seconds.usec
78 * or better), and time zone. Just getting that in a decent, predictable form, without localized weirdness, and
79 * with sub-second precision can be non-trivial; `std::chrono` does not do it as of C++17; boost.chrono does but
80 * only as of a certain quietly added features called time-point I/O v2 (as opposed to v1). That in itself was
81 * workable; however:
82 *
83 * When logging volume is very high, processor use by logging can be high, and as a percentage of cycles used,
84 * we noticed that a high proportion was used in formatting the `time_point` into the `ostream` by boost.chrono.
85 * Therefore we eventually moved to fmt, an output formatting library that is lightning-fast and whose API is
86 * now folded into C++20 standard lib (as of this writing we are on C++17); not unlike boost.chrono long ago became
87 * `std::chrono`. fmt provides flexibly formattable `time_point` output as well, so we can get everything we want
88 * just how we want it (in contrast with boost.chrono where it was merely a stroke of luck that the output
89 * was acceptable).
90 *
91 * Moreover, we accomplish a slight additional speedup by *caching* the formatted output across log() calls;
92 * in each log() printing it up to but excluding seconds; then adding the seconds plus sub-seconds in a separate,
93 * less expensive fmt call. The cached output needs to be updated once the previous cached value is no longer
94 * applicable even up to the precision that is actually used.
95 *
96 * ### Why standard library time-points instead of boost.chrono? ###
97 * In the rest of Flow we tend to prefer boost.chrono to `std::chrono`; they are extremely similar, but
98 * boost.chrono adds a few features including some extra clocks which are useful. However fmt accepts `std::chrono`
99 * time points, so for this specific purpose we store an `std::chrono` time point in Msg_metadata. At that point
100 * it was also reasonable to just use `std::chrono` for other time-related needs in this small class.
101 */
103 private boost::noncopyable
104{
105public:
106 // Types.
107
108 // Constructors/destructor.
109
110 /**
111 * Constructs object wrapping the given `ostream`. Does not write any characters to stream (so, for instance,
112 * if it happens to be an `ofstream` then it is fine if it's not yet open or even associated with a path). It may
113 * however do stateful things such as format setting.
114 *
115 * If any non-default formatting has been applied to `os`, subsequent behavior is undefined.
116 *
117 * @param config
118 * Controls behavior of `*this`. See thread safety notes in class doc header.
119 * This is saved *by reference* inside `*this`; it must remain valid through the last log() call.
120 * @param os
121 * Stream to which to write subsequently via log().
122 */
123 explicit Ostream_log_msg_writer(const Config& config, std::ostream& os);
124
125 /// Restores formatting state of `os` to how it was before entry to constructor.
126 ~Ostream_log_msg_writer() noexcept;
127
128 // Methods.
129
130 /**
131 * Logs to the wrapped `ostream` the given message and associated metadata like severity and time stamp; plus
132 * a newline.
133 *
134 * @param metadata
135 * See `*metadata` in Logger::do_log().
136 * @param msg
137 * The message. No terminating newline is assumed (though there is no rule against it; but
138 * if one is present, a blank line will appear in wrapped `ostream`).
139 */
140 void log(const Msg_metadata& metadata, util::String_view msg);
141
142private:
143
144 // Types.
145
146 /// Short-hand for an `ostream` state saver.
147 using Ostream_state = boost::io::ios_all_saver;
148
149 // Methods.
150
151 /**
152 * log() implementation with time stamp = seconds.microseconds since Epoch.
153 *
154 * @param metadata
155 * See log().
156 * @param msg
157 * See log().
158 */
160
161 /**
162 * log() implementation with time stamp = date/time with microsecond+ resolution/time zone, in
163 * current OS time zone.
164 *
165 * @param metadata
166 * See log().
167 * @param msg
168 * See log().
169 */
171
172 /**
173 * Remainder of log() functionality after either do_log_with_epoch_time_stamp() or
174 * do_log_with_human_friendly_time_stamp(), once either has logged the time stamp.
175 *
176 * @param metadata
177 * See log().
178 * @param msg
179 * See log().
180 */
181 void log_past_time_stamp(const Msg_metadata& metadata, util::String_view msg);
182
183 // Constants.
184
185 /// Mapping from Sev to its brief string description.
186 static const std::vector<util::String_view> S_SEV_STRS;
187
188 /// Jan 1, 1970, 00:00.
189 static const boost::chrono::system_clock::time_point S_POSIX_EPOCH;
190
191 /// Example human-readable time stamp output, up to but excluding seconds, for compile-time size calculations.
193
194 /**
195 * Example human-readable time stamp output, starting with seconds, for compile-time size calculations.
196 *
197 * ### Subtlety ###
198 * fmt docs say `%S` will use the precision "available"; in practice in our tested Linuxes it is
199 * nanoseconds; in the past I (ygoldfel) have also seen microseconds printed in this situation; might be
200 * some artifact of an internal time-formatting system API; whatever. However, it is unlikely it will in
201 * practice be better than nanoseconds; hence for our size-reserving purposes the template string with
202 * nanoseconds should suffice.
203 *
204 * (It also says that if there is no sub-second value, then it won't print the sub-second portion; but this
205 * appears to be poorly worded documentation: It prints `.000000000` just fine.)
206 */
208
209 // Data.
210
211 /// Reference to the config object passed to constructor. See notes on thread safety.
213
214 /// Pointer to function that log() will forward to when invoked.
216
217 /// Reference to stream to which to log messages.
218 std::ostream& m_os;
219
220 /// Formatter state of #m_os at construction. Used at least in destructor (as of this writing) to restore it.
222
223 /// Buffer storing the last log() time stamp in human-friendly form, including a NUL at the end.
224 std::array<char, S_HUMAN_FRIENDLY_TIME_STAMP_MIN_SZ_TEMPLATE.size()
227
228 /// Length of string in #m_last_human_friendly_time_stamp_str (excluding any NUL).
230
231 /**
232 * As used in the caching-for-perf algorithm in do_log_with_human_friendly_time_stamp(),
233 * this is the seconds-precision version of high-precision Msg_metadata::m_called_when, when
234 * do_log_with_human_friendly_time_stamp() last had to perform full formatted-output into
235 * #m_last_human_friendly_time_stamp_str.
236 */
237 std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> m_cached_rounded_time_stamp;
238}; // class Ostream_log_msg_writer
239
240} // namespace flow::log
Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers...
Definition: config.hpp:200
Utility class, each object of which wraps a given ostream and outputs discrete messages to it adorned...
const Function< void(Ostream_log_msg_writer *, const Msg_metadata &, util::String_view)> m_do_log_func
Pointer to function that log() will forward to when invoked.
static const std::vector< util::String_view > S_SEV_STRS
Mapping from Sev to its brief string description.
size_t m_last_human_friendly_time_stamp_str_sz
Length of string in m_last_human_friendly_time_stamp_str (excluding any NUL).
static const boost::chrono::system_clock::time_point S_POSIX_EPOCH
Jan 1, 1970, 00:00.
std::array< char, S_HUMAN_FRIENDLY_TIME_STAMP_MIN_SZ_TEMPLATE.size()+S_HUMAN_FRIENDLY_TIME_STAMP_REST_SZ_TEMPLATE.size()+1 > m_last_human_friendly_time_stamp_str
Buffer storing the last log() time stamp in human-friendly form, including a NUL at the end.
boost::io::ios_all_saver Ostream_state
Short-hand for an ostream state saver.
void do_log_with_human_friendly_time_stamp(const Msg_metadata &metadata, util::String_view msg)
log() implementation with time stamp = date/time with microsecond+ resolution/time zone,...
static constexpr util::String_view S_HUMAN_FRIENDLY_TIME_STAMP_REST_SZ_TEMPLATE
Example human-readable time stamp output, starting with seconds, for compile-time size calculations.
static constexpr util::String_view S_HUMAN_FRIENDLY_TIME_STAMP_MIN_SZ_TEMPLATE
Example human-readable time stamp output, up to but excluding seconds, for compile-time size calculat...
Ostream_log_msg_writer(const Config &config, std::ostream &os)
Constructs object wrapping the given ostream.
std::chrono::time_point< std::chrono::system_clock, std::chrono::seconds > m_cached_rounded_time_stamp
As used in the caching-for-perf algorithm in do_log_with_human_friendly_time_stamp(),...
void log_past_time_stamp(const Msg_metadata &metadata, util::String_view msg)
Remainder of log() functionality after either do_log_with_epoch_time_stamp() or do_log_with_human_fri...
const Config & m_config
Reference to the config object passed to constructor. See notes on thread safety.
~Ostream_log_msg_writer() noexcept
Restores formatting state of os to how it was before entry to constructor.
Ostream_state m_clean_os_state
Formatter state of m_os at construction. Used at least in destructor (as of this writing) to restore ...
void do_log_with_epoch_time_stamp(const Msg_metadata &metadata, util::String_view msg)
log() implementation with time stamp = seconds.microseconds since Epoch.
void log(const Msg_metadata &metadata, util::String_view msg)
Logs to the wrapped ostream the given message and associated metadata like severity and time stamp; p...
std::ostream & m_os
Reference to stream to which to log messages.
Flow module providing logging functionality.
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