Flow 1.0.0
Flow project: Full implementation reference.
ostream_log_msg_writer.cpp
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
20#include "flow/log/config.hpp"
22#include "flow/util/fmt.hpp"
23#include <chrono>
24
25namespace flow::log
26{
27
28// Static initializations.
29
30const std::vector<util::String_view> Ostream_log_msg_writer::S_SEV_STRS({ "null", // Never used (sentinel).
31 "fatl", "eror", "warn",
32 "info", "debg", "trce", "data" });
33const boost::chrono::system_clock::time_point Ostream_log_msg_writer::S_POSIX_EPOCH
34 = boost::chrono::system_clock::from_time_t(0);
35
36// Implementations.
37
39 m_config(config),
40 m_do_log_func(m_config.m_use_human_friendly_time_stamps
41 ? (&Ostream_log_msg_writer::do_log_with_human_friendly_time_stamp)
42 : (&Ostream_log_msg_writer::do_log_with_epoch_time_stamp)),
43 m_os(os),
44 m_clean_os_state(m_os), // Memorize this before any messing with formatting.
45 m_last_human_friendly_time_stamp_str_sz(0)
46{
47 using std::setfill;
48
49 /* Note: The ostream here is totally unrelated to ostream used elsewhere to create msg in the first place.
50 * (In fact, this class shouldn't have to know anything about that;
51 * it receives a msg that is already put together. I only mention it here in case you've been reading Logger
52 * stuff and get confused.) In particular, any formatters we use don't affect that other ostream. So even
53 * if we accidentally left std::hex formatter enabled at the end of this method, that would in no way cause hex
54 * output of integer values in the Logger's user's message (such as passed to FLOW_LOG_INFO() and friends).
55 * On the other hand, leaving std::hex on *would* indeed affect the users of m_os, which would indeed
56 * be uncivilized of us; consider m_os might be std::cout for example. However, we've been assured that no one
57 * uses m_os until we're done with it; so we only restore the original formatting in our destructor. */
58
59 // Set some global formatting. Since we've been promised no cross-use of m_os by outside code, this is enough.
61 {
62 m_os << setfill('0');
63 }
64} // Ostream_log_msg_writer::Ostream_log_msg_writer()
65
66/* As promised m_clean_os_state dtor will auto-restore any formatting changes we'd made to m_os throughout.
67 * This class uses boost::basic_ios_all_saver() (aka Ostream_state) which is explicitly marked as noexcept(false)
68 * in boost 1.84. The fix is to override the noexcept(false) specification to noexcept(true) in this class
69 * and let it call std::terminate in case it throws (IMO it shouldn't throw, but I didn't study it to the depth). */
71{
72}
73
75{
76 m_do_log_func(this, metadata, msg);
77}
78
80{
81 using std::setw;
82 using std::chrono::duration_cast;
83 using std::chrono::microseconds;
84
85 /* Get POSIX time stamp, a ubiquitous (if not 100% unambiguous, due to leap seconds) way to time-stamp a log
86 * line. Show it as seconds.microseconds with appropriate padding. POSIX/UTC time is relative to 1/1/1970 00:00.
87 *
88 * Note: On at least some systems, we can get even better precision than microseconds; at least empirically in some
89 * Linuxes one can see (1) system_clock::duration is nanoseconds; and (2) the last 3 digits are not 000.
90 * To not worry about it showing up as SSSSSS000 just go with microseconds. */
91 const auto since_epoch = duration_cast<microseconds>(metadata.m_called_when.time_since_epoch());
92
93 const auto usec_since_epoch = since_epoch.count();
94 const microseconds::rep sec = usec_since_epoch / 1000000;
95 const microseconds::rep usec = usec_since_epoch % 1000000;
96
97 // sec.usec, padded with zeroes up to 6 digits. We've already set 0 as fill character. setw(6) affects only one <<.
98 m_os << sec << '.'
99 << setw(6) << usec << ' ';
100 // Formatting is essentially back to default now, except '0' fill character which only matters with setw(non-zero).
101
102 log_past_time_stamp(metadata, msg);
103} // Ostream_log_msg_writer::do_log_with_epoch_time_stamp()
104
106{
107 using util::String_view;
108 using std::chrono::time_point_cast;
109 using std::chrono::seconds;
110 using std::chrono::system_clock;
111
112 // See background in class doc header Impl section; then come back here.
113
114 // Keep consistent with S_HUMAN_FRIENDLY_TIME_STAMP_*_SZ_TEMPLATE values.
115 constexpr String_view TIME_STAMP_FORMAT("{0:%Y-%m-%d %H:%M:}{1:%S} {0:%z} ");
116 // Offset from start of human-friendly time stamp output, where the seconds.subseconds output begins.
117 constexpr size_t SECONDS_START = S_HUMAN_FRIENDLY_TIME_STAMP_MIN_SZ_TEMPLATE.size();
118
119 /* We just want to print m_called_when in a certain format, including %S which is seconds.subseconds.
120 * However the part up-to (not including) %S rarely changes, yet it is somewhat expensive to compute
121 * each time. So we remember the formatted string printed in the last call to the present method in
122 * m_last_human_friendly_time_stamp_str; and in the present call merely splice-in the %S part, saving some cycles.
123 *
124 * Naturally m_last_human_friendly_time_stamp_str becomes obsolete ~every minute, so we need to re-output it fully,
125 * when we detect that m_called_when when m_last_human_friendly_time_stamp_str was last re-output was such that
126 * the up-to-%S part would have been different and thus is now obsolete. So step 1 is detecting whether that's
127 * the case. To do so we save m_cached_rounded_time_stamp each time this re-output occurs; and in step 1
128 * compare whether the # of whole seconds in this saved value is different from # of whole seconds in the new
129 * m_called_when. (We could perhaps store/compare minutes, but that's an extra conversion, and anyway ~every second
130 * is rare enough.) */
131 const auto rounded_time_stamp = time_point_cast<seconds>(metadata.m_called_when);
132 if (m_cached_rounded_time_stamp != rounded_time_stamp)
133 {
134 /* Need to re-output the whole thing. In the %S part, output the same thing we would in the
135 * fast path (the else{} below).
136 *
137 * Subtlety: Use localtime() to ensure output in the present time zone, not UTC.
138 * It cannot be used for %S to get sub-second resolution, as to_time_t() yields 1-second resolution, so
139 * just use m_called_when for %S and localtime() for the rest.
140 * @todo Maybe there is some way to just force it to print full-precision m_called_when in local time zone?
141 * (boost.chrono did it, with a switch that would cause it to use either local time zone or UTC in the output;
142 * but we are not using boost.chrono output anymore for perf reasons; plus one cannot specify a format
143 * which is a nice feature we can use to expand Ostream_log_writer capabilities in the future.) */
144 const auto end = fmt::format_to(m_last_human_friendly_time_stamp_str.begin(),
145 static_cast<const std::string_view&>(TIME_STAMP_FORMAT),
146 fmt::localtime(system_clock::to_time_t(metadata.m_called_when)),
147 metadata.m_called_when);
148 *end = '\0'; // Perhaps unnecessary, but it's cheap enough and nice for debugging.
149
150 m_cached_rounded_time_stamp = rounded_time_stamp;
152 }
153 else
154 {
155 // m_last_human_friendly_time_stamp_str_sz is already correct, except the %S part needs to be overwritten:
156 fmt::format_to(m_last_human_friendly_time_stamp_str.begin() + SECONDS_START,
157 "{:%S}", metadata.m_called_when);
158 }
159
161
162 log_past_time_stamp(metadata, msg);
163} // Ostream_log_msg_writer::do_log_with_human_friendly_time_stamp()
164
166{
167 using std::flush;
168
169 /* time_stamp [<sevr>]: T<thread nickname or ID>: <component>: <file>:<function>(<line>): <msg>
170 * Note: The "<component>: " part is optional at the discretion of Config m_config. For example the component
171 * may be null, or it may not have been registered in Config. */
172
173 assert(metadata.m_msg_sev != Sev::S_NONE); // S_NONE can be used only as a sentinel.
174
175 m_os << '[' << S_SEV_STRS[static_cast<size_t>(metadata.m_msg_sev)] << "]: T";
176 if (metadata.m_call_thread_nickname.empty())
177 {
178 m_os << metadata.m_call_thread_id;
179 }
180 else
181 {
182 m_os << metadata.m_call_thread_nickname;
183 }
184 m_os << ": ";
185
186 // As noted, this part may be omitted by Config.
188 {
189 m_os << ": ";
190 }
191
193 << ": "
194 << msg << '\n'
195 << flush;
196} // Ostream_log_msg_writer::log_past_time_stamp()
197
198} // namespace flow::log
Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers...
Definition: config.hpp:200
bool m_use_human_friendly_time_stamps
Config setting: If true, time stamps will include a (deterministically formatted) date,...
Definition: config.hpp:663
bool output_component_to_ostream(std::ostream *os, const Component &component) const
An output of Config, this writes a string representation of the given component value to the given os...
Definition: config.cpp:145
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.
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_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.
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.
@ S_NONE
Sentinel log level that must not be specified for any actual message (at risk of undefined behavior s...
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
std::chrono::system_clock::time_point m_called_when
Time stamp from as close as possible to entry into the log call site (usually FLOW_LOG_WARNING() or s...
Definition: log.hpp:1119
util::Thread_id m_call_thread_id
Thread ID of the thread from which the log call was invoked; or a default-constructed (no-thread) suc...
Definition: log.hpp:1144
unsigned int m_msg_src_line
Copy of integer that would have come from built-in __LINE__ macro which is auto-invoked by all FLOW_L...
Definition: log.hpp:1109
Sev m_msg_sev
Severity of message, typically determined by choice of macro (e.g., FLOW_LOG_WARNING() vs.
Definition: log.hpp:1058
std::string m_call_thread_nickname
Thread nickname, as for Logger::this_thread_set_logged_nickname(), of the thread from which the log c...
Definition: log.hpp:1134
util::String_view m_msg_src_file
Pointer/length into static-storage string that would have come from built-in __FILE__ macro which is ...
Definition: log.hpp:1103
Component m_msg_component
Component of message, as of this writing coming from either Log_context constructor or FLOW_LOG_SET_C...
Definition: log.hpp:1052
util::String_view m_msg_src_function
Analogous to m_msg_src_file but coming from __FUNCTION__, not __FILE__. See m_msg_src_file perf notes...
Definition: log.hpp:1112
#define FLOW_UTIL_WHERE_AM_I_FROM_ARGS(ARG_file, ARG_function, ARG_line)
Helper macro, same as FLOW_UTIL_WHERE_AM_I(), but takes the source location details as arguments inst...
Definition: util.hpp:112