Flow 3.0.0
Flow project: Full implementation reference.
log_fwd.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/// @cond
22// -^- Doxygen, please ignore the following.
23#define BOOST_FILESYSTEM_NO_DEPRECATED 1
24/// @endcond
25#include <boost/filesystem.hpp>
26#include <boost/filesystem/fstream.hpp>
27#include <boost/static_string.hpp>
28
29/**
30 * Flow module providing logging functionality. While originally intended to
31 * be used from within the flow::net_flow module's implementation, it can be used by general user code as well.
32 * All other Flow modules expose flow::log concepts when logging is relevant, so one way or another the user
33 * is likely to have at least limited contact with flow::log if they use Flow at all. (In particular, classes
34 * and free/`static` functions in various Flow modules often take a Logger pointer as a constructor
35 * or function argument respectively. Hence to use such APIs one must instantiate a concrete Logger, the simplest
36 * choice being probably Simple_ostream_logger.)
37 *
38 * (The general user [in their own, non-Flow-related code] may well
39 * prefer to use another logging system directly instead; or use boost.log.
40 * However, we humbly recommend taking a look at flow::log as a possibility, as it is highly usable, yet fast, yet
41 * small and elegant enough to ensure complete visibility into its low-level behavior, including how that affects
42 * performance. Note also that this system easily integrates with others via log::Logger interface, so one can stack
43 * this on top of perhaps a lower-level logging facility. The performance cost of this is essentially 1-2
44 * `virtual` pointer lookups per log-message call.)
45 *
46 * Log philosophy of Flow is as follows. The following is a list of goals; and for each item, sub-item(s)
47 * explain how this goal is accomplished.
48 *
49 * - Flow is a library or set of libraries -- not an application or set of applications -- so logging has to flexibly
50 * work with any logging system the user code may use.
51 * - The Logger interface provides a way to efficiently log to an arbitrary logging output; the flow::log
52 * user must provide a simple Logger implementation that fills in what Logger::should_log() and
53 * Logger::do_log() mean.
54 * - To ensure maximum simplicity and usability right out of the box,
55 * flow::log provides a Logger implementation, Simple_ostream_logger, which will log to standard `ostream`s
56 * (including `cout` and `cerr`). The user can just use this out of the box and not even have to implement
57 * the (already trivial) Logger interface. (Probably most larger projects would need to go beyond this.)
58 * - The logging system has to provide both error messages and informational or trace messages, and these
59 * should be easily distinguishable once passed on to the user's supplied arbitrary logging output(s).
60 * - There are at least 3 message severities, and they are passed to the Logger implementation, so
61 * that this can be translated as necessary into the user's logging output(s).
62 * - The logging system is not a replacement for error reporting by Flow APIs.
63 * - flow::Error_code and flow::error provide at least error code reporting (in addition to log calls)
64 * where possible and applicable.
65 * - The logging should be possible to entirely enable, entirely disable, or some reasonable point
66 * between these extremes.
67 * - The Logger interface's Logger::should_log() method allows filtering messages by severity (e.g., allow
68 * only WARNING messages) in any desired fashion. There is now also support for setting filtering config
69 * by "component" supplied essentially at the log message call site.
70 * - If a given message is not ultimately logged due its severity, it should induce no performance or resource
71 * penalty (such as that incurred by assembling the message string) beyond the severity check itself.
72 * - The `FLOW_LOG_...()` macros work with the `Logger::should_log()`/`Logger::do_log()` APIs to only
73 * construct the ultimate message string if the resulting message would be logged.
74 * - Logging should be simple without lots of extraneous features.
75 * - A few simple-to-use macros are the main way to log messages; see those first.
76 * - Every logged message just has a severity and component, which are `enum`s, and the text of the message.
77 * File/function/line and thread ID info are auto-obtained and maintained along with the
78 * messages themselves, via the `FLOW_LOG_...()` macros. The user need not worry about such mundane but tricky
79 * details.
80 * - The `FLOW_LOG_...()` macros assume that `get_logger()` (in the context where the macro is expanded)
81 * returns a `Logger*` implementation, which is then used for the logging call(s) associated with the
82 * message -- typically first the Logger::should_log() check and then possibly the Logger::do_log()
83 * message output. Deriving from the supplied Log_context utility class is an easy way to make this
84 * `get_logger()` available all over the Flow modules' implementations (and should indeed be a technique for the
85 * user's own logging code, if indeed they decided to use flow::log themselves).
86 *
87 * @internal
88 *
89 * log_fwd.hpp is separate from log.hpp due to C++ circular dependency nonsense. See util_fwd.hpp for some
90 * discussion on this same concept.
91 *
92 * @todo There are third-party logging systems including boost.log. The to-do is to
93 * investigate using boost.log, partially or entirely replacing the manually implemented system.
94 * (To be honest I am rather partial to the simple but effective system already implemented, in particular
95 * the performance-focused trickery.) Boost is invaluable
96 * in many ways, and Flow modules use it extremely extensively, but my intuition says that in this case it may be
97 * irrelevant. Meanwhile, boost.log can, of course, be used within user's Logger interface implementation,
98 * but that's not what this to-do is about; it's about actually placing it all over Flow's implementation and
99 * APIs *in place of* Logger, log::Sev, etc. (Also, after a 5-minute glance at boost.log,
100 * I am noticing that some of its ideas I seem to have independently and
101 * unknowingly replicated here. It is, however, simple stuff, not rocket science.) My intuition is I hesitate to
102 * force-marry the Flow user to boost.log; the very simple Logger (abstract) interface is seemingly a gentler
103 * thing to force the Flow user into, at least relatively speaking.
104 *
105 * @todo Lacking feature: message IDs. This is discussed a bit more in the log::Msg_metadata doc header.
106 *
107 * @todo Lacking feature: `printf()`-style logging call sites, in contrast to the currently supported `ostream`
108 * fragment call sites. So, like, `FLOW_LOG_WARNING_FMT("Hello, Mr. %s!", my_name.c_str())` could be used
109 * analogously to `FLOW_LOG_WARNING("Hello, Mr " << my_name << "!!")` (with `std::string my_name`).
110 * In terms of implementation, there is more to this than meets the eye perhaps; as `ostream`s potentially
111 * store (formatting) state between totally separate invocations of `ostream<<`, whereas `printf()` style
112 * functions normally do not. Internally, this likely allows for specially optimized logging code.
113 *
114 * @todo Lacking feature: boost.format-style logging call sites. This is conceptually similar to -- and possibly even
115 * entirely subsuming in backwards-compatible fashion -- the to-do just above for `printf()`-style logging.
116 * Likely both to-dos should be designed/implemented in one shot. Moreover, it is possible (and would be
117 * absolutely delightful if true) that boost.format's `format` class can be used entirely transparently on top
118 * of the `ostream`-focused existing flow::log API (such as FLOW_LOG_WARNING() and buddies)!
119 *
120 * @todo Lacking feature: log message rate-limiting. This could actually mean a few things. One is being able
121 * to rate-limit the messages produced a given log call site per unit time; this might be the minimum for this to-do.
122 * This could be done by a dirty macro hack; or it could be done in more civilized fashion (while minding perf)
123 * by configuring it by message ID (but message IDs are optional and not implemented as of this writing anyway).
124 * Another rate-limiting thing is general controls on frequency of logging; though arguably that is more applicable
125 * to individual `Logger` implementations rather than as a mandatory-general mechanism.
126 *
127 * @todo Lacking feature: compiler hints for optimizing away log filter checks. This is inspired by a certain
128 * other proprietary logging API in which we noticed attempts to give hints to the compiler as to how likely or
129 * unlikely the verbosity check is to return a true or false value, seemingly so that the compiler might
130 * optimize out the check and hence the entire log statement in some conditions; or *always* log others and
131 * skip the check in the optimized code. I omit any details here, but that's the general idea. I don't know
132 * how effective this is given that verbosities are configurable dynamically potentially; but look into it.
133 */
134namespace flow::log
135{
136
137// Types.
138
139// Find doc headers near the bodies of these compound types.
140
141class Async_file_logger;
142class Buffer_logger;
143class Component;
144class Config;
145class Logger;
146class Log_context;
147class Log_context_mt;
148struct Msg_metadata;
149class Ostream_log_msg_writer;
150class Simple_ostream_logger;
151
152/* (The @namespace line shouldn't be needed, but there must be some Doxygen bug causing that doc header to get
153 * merged into the succeeding item's. Who knows? @todo Look into it more in copious spare time.
154 *
155 * Update: That was with Doxygen 1.8.11 through 1.8.15. In 1.8.16, it's still true; plus another bug appeared:
156 * The first sentence (up to the first period) is supposed to be interpreted, with certain Doxygen config,
157 * as @brief, meaning the brief summary of the namespace; this feature is used constantly, not just in this
158 * spot. Well, for some reason that Doxygen version took the @brief to be blank (shows up as blank in the
159 * namespaces list page and the namespace page itself), whereas before it was fine. Since it's not happening
160 * for anything else, I made it an explicit @brief here and called it a day; I guess the Doxygen parser is
161 * getting doubly confused; not sure. @todo Sigh, untangle this at some point; maybe Doxygen gets fixed;
162 * file bug against Doxygen.) */
163
164/**
165 * @namespace flow::log::fs
166 * @brief Short-hand for `namespace boost::filesystem`.
167 */
168namespace fs = boost::filesystem;
169
170/**
171 * Alias for a heap-free, array-backed, trivial-destructor-having string used for certain simple strings
172 * in flow::log. The precipitating reason for introducing this to flow::log is that certain things, like
173 * string in Msg_metadata, in a thread-local context would be destroyed by a non-trivial destructor; but ideally
174 * we want logging to work -- all else being equal -- even during `thread_local` deinit.
175 *
176 * The width was chosen so as to be big enough to handle all or most payloads for all flow::log use-cases
177 * (truncation naturally is used when required). For the record, as of this writing, the longest known real
178 * payloads are ~40 -- let us say 64 -- characters; typical loop/thread nicknames are usually shorter; and the
179 * OS-thread-name sink alongside truncates at 15. So 128 gives ample headroom, and truncation is cosmetic-only
180 * when it does occur.
181 */
182using Fixed_string = boost::static_string<128>;
183
184/**
185 * Enumeration containing one of several message severity levels, ordered from highest to
186 * lowest. Generally speaking, volume/verbosity is inversely proportional to severity, though
187 * this is not enforced somehow.
188 *
189 * As the underlying type is `size_t`, and the values are guaranteed to be 0, 1, ... going from lowest
190 * to highest verbosity (highest to lowest severity), you may directly use log::Sev values to index into arrays that
191 * arrange one-to-one values in the same order.
192 *
193 * The supplied `ostream<<` operator, together with this `enum`, is suitable for util::istream_to_enum().
194 * `ostream>>` operator is built on the latter and is also supplied. Hence I/O of `Sev` is available out of the box;
195 * this enables parsing in boost.program_options among other things.
196 *
197 * ### Formal semantics of log::Sev ###
198 * From the point of view of all Flow code, the *formal* semantics of log::Sev are as follows:
199 * - Sev::S_NONE has value 0 and is a start-sentinel.
200 * - Non-sentinel severities follow, starting at 1 with no gaps.
201 * - Ordinal comparison is meaningful; lower is termed as more severe/less verbose; higher is therefore
202 * termed as less severe/more verbose. These are, formally, only terminology.
203 * - Sev::S_END_SENTINEL is the end-sentinel.
204 * - Sev::S_WARNING is and always shall be considered the *least severe* "abnormal" condition. Any additional
205 * "abnormal" conditions shall always have more-severe (numerically lower) values.
206 * As of this writing, within flow::log Flow module, this has only one practical meaning (but that could change):
207 * Simple_ostream_logger directs WARNING-and-more-severe messages to its configured *error* stream (`os_for_err`);
208 * while all the rest are directed to its configured other (regular) stream (`os`). (For example these might
209 * be set to `std::cerr` and `std::cout` respectively; or both to `std::cout`.)
210 *
211 * ### Informal semantics of log::Sev ###
212 * The following are not enforced by any code logic in the flow::log Flow module. However we suggest users
213 * understand and consider these, as a haphazard approach to severity selection for a given log call site can
214 * cause significant operational problems in a high-volume production environment (at least).
215 *
216 * Generally, there are 2 types of log call sites: ones inside Flow; and ones outside (user code). The former
217 * (inside Flow) are deliberately minimalistic, so as to use as few severities as possible and thus make
218 * severity selection maximally simple and, importantly, unambiguous.
219 *
220 * However, logging code outside Flow may require more sophisticated in-use severity sets. Informally we *recommend*
221 * keeping it as simple as inside-Flow's scheme... if it is sufficient. If it is not sufficient, the other
222 * severities may also be used.
223 *
224 * The log::Sev values used inside Flow (the *minimalistic* set) are as follows. (Brief meanings accompany them;
225 * see their individual doc headers for more detail.) From most to least severe:
226 * - Sev::S_FATAL: the program will abort shortly due to the condition that is being logged. Usually an
227 * `assert(false)` and/or `std::abort()` follows. If you've disabled abort-on-assertion-trip (`NDEBUG` is defined),
228 * and there is no `std::abort()` or equivalent, then the program may continue, but subsequent behavior is
229 * undefined.
230 * - Sev::S_WARNING: abnormal condition is being logged, and its aggregate volume is not high enough to be classified
231 * as TRACE instead to avoid perf impact. (Other than possibly FATAL -- used in extreme cases -- no
232 * other abnormal-condition `Sev` are in-use inside Flow nor will they be.)
233 * - Sev::S_INFO: non-abnormal condition is being logged, and it's not so frequent in practice that enabling
234 * this log level shall adversely impact performance.
235 * - Sev::S_TRACE: like INFO, but enabling this log level *can* adversely impact performance. However, entire
236 * dumps of potentially large (in aggregate) data (such as packet contents) being processed are not included.
237 * - Sev::S_DATA: like TRACE, but entire contents of potentially large (in aggregate) data (such as packet contents)
238 * being processed are included in the message, which may lead to particularly large log output (if enabled).
239 *
240 * As of this writing the following log::Sev values are available for use (by user/non-Flow code) beyond the above
241 * *minimalistic* set. Potential subjective meanings are included, but user code can use whatever conventions that
242 * suit them best.
243 * - Sev::S_ERROR: A non-FATAL abnormal condition subjectively more severe than WARNING.
244 * - Sev::S_DEBUG: A non-abnormal condition with, perhaps, non-perf-affecting verbosity (a-la INFO) but subjectively
245 * of less interest to a human glancing over a large-ish log snippet (a-la TRACE).
246 */
247enum class Sev : size_t
248{
249 /**
250 * Sentinel log level that must not be specified for any actual message (at risk of undefined behavior such
251 * as assert failure); but can be used for sentinel purposes such as specifying a log filter wherein
252 * no messages (not even Sev::S_WARNING ones) are shown.
253 */
254 S_NONE = 0,
255
256 /**
257 * Message indicates a "fatally bad" condition, such that the program shall imminently abort, typically due
258 * to immediately-following `assert(false)` and possibly `std::abort()`; or if aborts are disabled (such as
259 * via defining `NDEBUG`), and there is no explicit `std::abort()` or equivalent, then further
260 * program behavior is undefined.
261 *
262 * @note This severity *is* part of the *minimalistic* (in-use within Flow) severity set as discussed
263 * in log::Sev doc header, "Informal semantics" section.
264 */
265 S_FATAL,
266
267 /**
268 * Message indicates a "bad" condition with "worse" impact than that of Sev::S_WARNING.
269 *
270 * @note If it's "bad" but frequent enough for TRACE, we strongly recommend to make it TRACE -- not ERROR.
271 *
272 * @note This severity *is NOT* part of the *minimalistic* (in-use within Flow) severity set as discussed
273 * in log::Sev doc header, "Informal semantics" section. Informally we recommend projects
274 * do not use it unless it is holistically necessary.
275 */
276 S_ERROR,
277
278 /**
279 * Message indicates a "bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
280 * These typically should occur with less frequency than INFO messages; however, it's not a hard rule.
281 *
282 * @note If it's "bad" but frequent enough for TRACE, it's TRACE by definition -- not WARNING.
283 *
284 * @note This severity *is* part of the *minimalistic* (in-use within Flow) severity set as discussed
285 * in log::Sev doc header, "Informal semantics" section.
286 */
287 S_WARNING,
288
289 /**
290 * Message indicates a not-"bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
291 *
292 * @note That is, it's identical to WARNING *except* doesn't represent a "bad" situation.
293 *
294 * @note This severity *is* part of the *minimalistic* (in-use within Flow) severity set as discussed
295 * in log::Sev doc header, "Informal semantics" section.
296 */
297 S_INFO,
298
299 /**
300 * Message indicates a condition with, perhaps, no significant perf impact if enabled (like Sev::S_INFO)
301 * but of subjectively less interest to a human reader than INFO (hence, like Sev::S_TRACE in that respect).
302 *
303 * @note This severity *is NOT* part of the *minimalistic* (in-use within Flow) severity set as discussed
304 * in log::Sev doc header, "Informal semantics" section. Informally we recommend projects
305 * do not use it unless it is holistically necessary.
306 */
307 S_DEBUG,
308
309 /**
310 * Message indicates any condition that may occur with great frequency (thus verbose if logged).
311 * The line between Sev::S_INFO or Sev::S_WARNING and TRACE is as follows: The former is *not* allowed to classify
312 * messages such that in realistic scenarios they would degrade performance, from processor cycles or log file I/O.
313 *
314 * @see Sev::S_DATA for an even-more-verbose severity, when it's frequent and potentially large in size in aggregate.
315 *
316 * @warning One MUST be able to set max severity level to INFO and confidently count that logging will not
317 * affect performance.
318 *
319 * @note This severity *is* part of the *minimalistic* (in-use within Flow) severity set as discussed
320 * in log::Sev doc header, "Informal semantics" section.
321 */
322 S_TRACE,
323
324 /**
325 * Message satisfies Sev::S_TRACE description AND contains variable-length structure (like packet, file) dumps.
326 * If these are allowed to be logged, resulting log file might be roughly similar to (or even larger than)
327 * the data being transmitted by the logging code. Packet dumps are obvious examples.
328 *
329 * Note that just because it's a variable-length structure dump doesn't mean it's for Sev::S_DATA severity.
330 * If it's not frequent, it's fine for it to even be INFO. E.g., if I decide to dump every
331 * RST packet, it's probably okay as Sev::S_INFO, since RSTs are practically rare;
332 * that need not be Sev::S_DATA. On the other hand, if I dump every DATA packet
333 * as anything except Sev::S_DATA severity, then I should be arrested and convicted.
334 *
335 * When this level is disabled, consider logging a TRACE message instead but summarize the contents you'd
336 * dump; e.g., log a hash and/or a size and/or the few first bytes instead of the entire thing. Something is
337 * often better than nothing (but nothing is still safer than the whole thing).
338 *
339 * @note This severity *is* part of the *minimalistic* (in-use within Flow) severity set as discussed
340 * in log::Sev doc header, "Informal semantics" section.
341 */
342 S_DATA,
343
344 /// Not an actual value but rather stores the highest numerical payload, useful for validity checks.
346}; // enum class Sev
347
348// Free functions.
349
350/**
351 * Deserializes a log::Sev from a standard input stream. Reads up to but not including the next
352 * non-alphanumeric-or-underscore character; the resulting string is then mapped to a log::Sev. If none is
353 * recognized, Sev::S_NONE is the result. The recognized values are:
354 * - "0", "1", ...: Corresponds to the `int` conversion of that log::Sev (e.g., 0 being NONE).
355 * - Case-insensitive encoding of the non-S_-prefix part of the actual log::Sev member; e.g.,
356 * "warning" (or "Warning" or "WARNING" or...) for `S_WARNING`.
357 * This enables a few key things to work, including parsing from config file/command line via and conversion from
358 * `string` via `boost::lexical_cast`.
359 *
360 * @param is
361 * Stream from which to deserialize.
362 * @param val
363 * Value to set.
364 * @return `is`.
365 */
366std::istream& operator>>(std::istream& is, Sev& val);
367// @todo `@relatesalso Sev` makes Doxygen complain; maybe it doesn't work with `enum class`es like Sev.
368
369/**
370 * Serializes a log::Sev to a standard output stream. The output string is compatible with the reverse
371 * `istream>>` operator.
372 *
373 * @param os
374 * Stream to which to serialize.
375 * @param val
376 * Value to serialize.
377 * @return `os`.
378 */
379std::ostream& operator<<(std::ostream& os, Sev val);
380// @todo `@relatesalso Sev` makes Doxygen complain; maybe it doesn't work with `enum class`es like Sev.
381
382/**
383 * Log_context ADL-friendly swap: Equivalent to `val1.swap(val2)`.
384 * @param val1
385 * Object.
386 * @param val2
387 * Object.
388 */
389void swap(Log_context& val1, Log_context& val2);
390
391/**
392 * Log_context_mt ADL-friendly swap: Equivalent to `val1.swap(val2)`.
393 * @param val1
394 * Object.
395 * @param val2
396 * Object.
397 */
398void swap(Log_context_mt& val1, Log_context_mt& val2);
399
400/**
401 * Sets certain `chrono`-related formatting on the given Logger in the current thread that results in a consistent,
402 * desirable output of `duration`s and certain types of `time_point`s. The effect is that of
403 * util::beautify_chrono_ostream().
404 *
405 * @see flow::async in which new threads are set to use this formatting automatically. However you'll want to
406 * do this explicitly for the startup thread.
407 *
408 * @param logger_ptr
409 * The Logger to affect in this thread. Null is allowed; results in no-op.
410 */
411void beautify_chrono_logger_this_thread(Logger* logger_ptr);
412
413/**
414 * Estimate of memory footprint of the given value, including memory allocated on its behalf -- but
415 * excluding its shallow `sizeof`! -- in bytes.
416 *
417 * @param val
418 * Value.
419 * @return See above.
420 */
421size_t deep_size(const Msg_metadata& val);
422
423} // namespace flow::log
We may add some ADL-based overloads into this namespace outside flow.
Definition: cfg_fwd.hpp:272
Flow module providing logging functionality.
std::ostream & operator<<(std::ostream &os, Sev val)
Serializes a log::Sev to a standard output stream.
Definition: log.cpp:418
void swap(Log_context &val1, Log_context &val2)
Log_context ADL-friendly swap: Equivalent to val1.swap(val2).
Definition: log.cpp:285
size_t deep_size(const Msg_metadata &)
Estimate of memory footprint of the given value, including memory allocated on its behalf – but exclu...
Definition: log.cpp:462
std::istream & operator>>(std::istream &is, Sev &val)
Deserializes a log::Sev from a standard input stream.
Definition: log.cpp:438
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:248
@ S_DATA
Message satisfies Sev::S_TRACE description AND contains variable-length structure (like packet,...
@ S_TRACE
Message indicates any condition that may occur with great frequency (thus verbose if logged).
@ S_WARNING
Message indicates a "bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
@ S_NONE
Sentinel log level that must not be specified for any actual message (at risk of undefined behavior s...
@ S_DEBUG
Message indicates a condition with, perhaps, no significant perf impact if enabled (like Sev::S_INFO)...
@ S_ERROR
Message indicates a "bad" condition with "worse" impact than that of Sev::S_WARNING.
@ S_INFO
Message indicates a not-"bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
@ S_FATAL
Message indicates a "fatally bad" condition, such that the program shall imminently abort,...
boost::static_string< 128 > Fixed_string
Alias for a heap-free, array-backed, trivial-destructor-having string used for certain simple strings...
Definition: log_fwd.hpp:182
void beautify_chrono_logger_this_thread(Logger *logger_ptr)
Sets certain chrono-related formatting on the given Logger in the current thread that results in a co...
Definition: log.cpp:447
@ S_END_SENTINEL
CAUTION – see flow::Flow_log_component doc header for directions to find actual members of this enum ...