Flow 3.0.0
Flow project: Full implementation reference.
log.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
19#include "flow/log/log.hpp"
20#include "flow/error/error.hpp"
22#include <type_traits>
23
24namespace flow::log
25{
26
27// Global initializations.
28
30static_assert(std::is_trivially_destructible_v<Msg_metadata>,
31 "We want this fully available even while thread_local deinit is happening, for FLOW_LOG_DO_LOG() et al.");
32
33// Static initializations.
34
36static_assert(std::is_trivially_destructible_v<Fixed_string>,
37 "We want this fully available even while thread_local deinit is happening, for FLOW_LOG_DO_LOG() et al.");
38
39// Logger implementations.
40
42 bool also_set_os_name) // Static.
43{
44 using std::string;
45 using boost::system::system_category;
46 using ::pthread_setname_np;
47 using ::pthread_self;
48
49 /* Either set or delete (empty means no nickname, which is the original state of things).
50 * Note: This value is saved in a thread-local fashion. This has no effect on the
51 * value of s_this_thread_nickname in any thread except the one in which we currently execute.
52 *
53 * For reason(s) explained in its doc header, the target is a Fixed_string; hence truncate if necessary. */
54 {
55 const auto thread_nickname_maybe_trunc = thread_nickname.substr(0, Fixed_string::static_capacity);
56 // util::String_view may or may not be supported by Fixed_string, so just explicit form, so it'll always compile.
57 s_this_thread_nickname.assign(thread_nickname_maybe_trunc.data(), thread_nickname_maybe_trunc.size());
58 }
59
60 // Log about it if given an object capable of logging about itself.
61 if (logger_ptr)
62 {
63 FLOW_LOG_SET_CONTEXT(logger_ptr, Flow_log_component::S_LOG);
64 FLOW_LOG_INFO("Set new thread nickname for current thread ID [" << util::this_thread::get_id() << "] / "
65 "thread-token [" << util::this_thread_unique_token() << "].");
66 /* (Regarding that unique-token: While we do not generally log unique-tokens per-message <=> do not keep
67 * it in Msg_metadata -- consult the latter's doc header for some discussion -- it can be a useful
68 * truly-unique, within a process, thread identifier albeit one with less universal recognition. This is
69 * a cheap place to log it once, without having to store it per-message. This can help correlate information
70 * in the field despite by default only appearing here.) */
71 }
72
73 if (also_set_os_name)
74 {
75#ifndef FLOW_OS_LINUX
76 static_assert(false, "this_thread_set_logged_nickname() also_set_os_name implementation is for Linux only "
77 "for now.");
78 /* `man pthread_setname_np` works in Darwin/Mac but is very short, and there is no `man pthread_getname_np`.
79 * It might work fine for Mac, but it's untested, and I (ygoldfel) didn't want to deal with it yet.
80 * In particular it's unclear if the MAX_PTHREAD_NAME_SZ would apply (it's not in `man` for Mac)... etc.
81 * @todo Look into it. */
82#endif
83 string os_name = thread_nickname.empty() ? util::ostream_op_string(util::this_thread::get_id())
84 : string(thread_nickname);
85
86 // See `man pthread_setname_np`. There is a hard limit on the length of the name, and it is:
87 constexpr size_t MAX_PTHREAD_NAME_SZ = 15;
88 if (os_name.size() > MAX_PTHREAD_NAME_SZ)
89 {
90 // As advertised: Truncate. `man` indicates not doing so shall lead to ERANGE error.
91 os_name.erase(MAX_PTHREAD_NAME_SZ);
92 }
93
94 const auto result_code = pthread_setname_np(pthread_self(), os_name.c_str());
95
96 // Log about it if given an object capable of logging about itself.
97 if (logger_ptr)
98 {
99 FLOW_LOG_SET_CONTEXT(logger_ptr, Flow_log_component::S_LOG);
100 if (result_code == -1)
101 {
102 const Error_code sys_err_code{errno, system_category()};
103 FLOW_LOG_WARNING("Unable to set OS thread name to [" << os_name << "], possibly truncated "
104 "to [" << MAX_PTHREAD_NAME_SZ << "] characters, via pthread_setname_np(). "
105 "This should only occur due to an overlong name, which we guard against, so this is "
106 "highly unexpected. Details follow.");
108 }
109 else
110 {
111 FLOW_LOG_INFO("OS thread name has been set to [" << os_name << "], possibly truncated "
112 "to [" << MAX_PTHREAD_NAME_SZ << "] characters.");
113 }
114 }
115
116 // We could continue, but it's indicative of a newly-broken API or misunderstanding of `man`: better to be alerted.
117 assert(result_code != -1);
118 } // if (also_set_os_name)
119} // Logger::this_thread_set_logged_nickname()
120
121std::ostream& Logger::this_thread_logged_name_os_manip(std::ostream& os) // Static.
122{
123 // Reminder: we are an ostream manipulator, invoked like flush or endl: os << flush;
124
125 /* If there's a thread nickname, output that. Otherwise default to actual thread ID.
126 * ATTN: This must be consistent with behavior in set_thread_info*().
127 * We could also just use the latter here, but the following is a bit quicker. @todo Reconsider maybe. */
128
129 if (!s_this_thread_nickname.empty())
130 {
131 return os << s_this_thread_nickname;
132 }
133 // else
134 return os << util::this_thread::get_id();
135}
136
137void Logger::set_thread_info(Fixed_string* call_thread_nickname, flow::util::Thread_id* call_thread_id) // Static.
138{
139 assert(call_thread_nickname);
140 assert(call_thread_id);
141
142 /* If there's a thread nickname, output that. Otherwise default to actual thread ID.
143 * ATTN: This must be consistent with behavior in this_thread_logged_name_os_manip() (and our overloads). */
144
145 if (s_this_thread_nickname.empty())
146 {
147 *call_thread_id = util::this_thread::get_id();
148 }
149 else
150 {
151 *call_thread_nickname = s_this_thread_nickname; // Fixed_string => Fixed_string.
152 }
153}
154
155void Logger::set_thread_info(std::string* call_thread_nickname, flow::util::Thread_id* call_thread_id) // Static.
156{
157 assert(call_thread_nickname);
158 assert(call_thread_id);
159
160 // Mirror overload. @todo Maybe code reuse? Template? Meh.
161 if (s_this_thread_nickname.empty())
162 {
163 *call_thread_id = util::this_thread::get_id();
164 }
165 else
166 {
167 *call_thread_nickname = s_this_thread_nickname; // Fixed_string => string.
168 }
169}
170
171void Logger::set_thread_info(util::String_view* call_thread_nickname, flow::util::Thread_id* call_thread_id) // Static.
172{
173 assert(call_thread_nickname);
174 assert(call_thread_id);
175
176 // Mirror overload. @todo Maybe code reuse? Template? Meh.
177 if (s_this_thread_nickname.empty())
178 {
179 *call_thread_id = util::this_thread::get_id();
180 }
181 else
182 {
183 // Fixed_string => String_view.
184 *call_thread_nickname = {s_this_thread_nickname.begin(), s_this_thread_nickname.size()};
185 }
186}
187
189{
190 assert(msg_metadata);
191 set_thread_info(&msg_metadata->m_call_thread_nickname, &msg_metadata->m_call_thread_id);
192}
193
194std::ostream* Logger::this_thread_ostream() const
195{
196 // Grab the stream used for the current thread by this particular Logger.
197 const auto appender_or_null = Thread_local_string_appender::this_thread_string_appender(*this);
198 return appender_or_null ? appender_or_null->appender_ostream() : nullptr; // Latter can happen near thread exit.
199}
200
201// Component implementations.
202
204 m_payload_type_or_null(nullptr), // <=> empty() == true.
205 m_payload_enum_raw_value() // Should not be necessary (uninit=OK), but in some contexts at least gcc-9 warns.
206{
207 // That's it.
208}
209
210Component::Component(const Component& src) = default;
211Component::Component(Component&& src_moved) = default; // Note it doesn't empty()-ify src_moved.
212Component& Component::operator=(const Component& src) = default;
213Component& Component::operator=(Component&& src_moved) = default; // Note it doesn't empty()-ify src_moved.
214
216{
218}
219
220const std::type_info& Component::payload_type() const
221{
222 assert(!empty()); // We advertised undefined behavior in this case.
224}
225
227{
228 assert(!empty()); // We advertised undefined behavior in this case.
230}
231
232// Log_context implementations.
233
235 m_logger(logger)
236{
237 // Nothing.
238}
239
240Log_context::Log_context(const Log_context& src) = default;
241
243{
244 operator=(std::move(src));
245}
246
247Log_context& Log_context::operator=(const Log_context& src) = default;
248
250{
251 if (&src != this)
252 {
253 operator=(static_cast<const Log_context&>(src));
254 src.m_logger = nullptr;
255 src.m_component = {};
256 }
257 return *this;
258}
259
261{
262 return m_logger;
263}
264
266{
267 const auto prev = m_logger;
268 m_logger = logger;
269 return prev;
270}
271
273{
274 return m_component;
275}
276
278{
279 using std::swap;
280
281 swap(m_logger, other.m_logger);
283}
284
285void swap(Log_context& val1, Log_context& val2)
286{
287 val1.swap(val2);
288}
289
290// Log_context_mt implementations.
291
293 Log_context(logger)
294{
295 // Nothing.
296}
297
299 Log_context() // Eliminate possible warning at tiny (if any) perf cost.
300{
301 // We could just do `operator=(src)`; but to avoid unnecessary locking of this->m_mutex do it manually.
302 util::Lock_guard<decltype(m_mutex)> lock{src.m_mutex};
304}
305
307 Log_context() // Clear *this in preparation for swap.
308{
309 using std::swap; // This enables proper ADL.
310
311 // We could just do `operator=(move(src))`; but to avoid unnecessary locking of this->m_mutex do it manually.
312 util::Lock_guard<decltype(m_mutex)> lock{src.m_mutex};
313 swap(static_cast<Log_context&>(*this),
314 static_cast<Log_context&>(src));
315}
316
318{
319 using Lock = util::Lock_guard<decltype(m_mutex)>;
320
321 if (&src != this)
322 {
323 /* Naively we'd do something close to:
324 * Lock lock1{m_mutex};
325 * Lock lock2{src.m_mutex};
326 * Log_context::operator=(src);
327 * However conceivably this could cause an obscure deadlock for reasons similar to those cited in swap(). As there:
328 * Seems there's no choice but to lock things piecewise and execute the copy-assignment via a temporary
329 * intermediary Log_context. */
330
331 Log_context obj_tmp;
332 {
333 Lock lock{src.m_mutex};
334 obj_tmp = src;
335 }
336 {
337 Lock lock{m_mutex};
338 Log_context::operator=(obj_tmp);
339 }
340 }
341 return *this;
342}
343
345{
346 using Lock = util::Lock_guard<decltype(m_mutex)>;
347 using std::swap; // This enables proper ADL.
348
349 if (&src != this)
350 {
351 // Same deal as in copy ctor; just have to add the clearing of `src` which we do by using swap(L_c&, L_c&).
352
353 Log_context obj_tmp;
354 {
355 Lock lock{src.m_mutex};
356 swap(obj_tmp, static_cast<Log_context&>(src));
357 }
358 {
359 Lock lock{m_mutex};
360 Log_context::operator=(obj_tmp);
361 }
362 }
363
364 return *this;
365} // Log_context_mt::operator=(&&)
366
368{
369 util::Lock_guard<decltype(m_mutex)> lock{m_mutex};
370 return Log_context::set_logger(logger);
371}
372
374{
375 using Lock = util::Lock_guard<decltype(m_mutex)>;
376 using std::swap; // This enables proper ADL.
377
378 /* Naively we'd do something close to:
379 * Lock lock1{m_mutex};
380 * Lock lock2{other.m_mutex};
381 * Log_context::swap(other);
382 * However conceivably this could cause an obscure deadlock; e.g. at least if one concurrently tries
383 * lc_mt1.swap(lc_mt2);
384 * and
385 * lc_mt2.swap(lc_mt1);
386 * Strange thing to do, but it is legal, and a classic AB-BA deadlock results.
387 * Seems there's no choice but to lock things in series and use a temporary intermediary.
388 * (We could've also let the default std::swap() just do one move-construct and two move-assignments, but
389 * perf-wise that'd do some unnecessary stuff.) */
390
391 auto& obj1_mt = *this;
392 auto& obj2_mt = other;
393 auto& obj1_rw = static_cast<Log_context&>(obj1_mt);
394 auto& obj2_rw = static_cast<Log_context&>(obj2_mt);
395
396 Log_context tmp_rw;
397 {
398 Lock lock{obj1_mt.m_mutex};
399 tmp_rw = obj1_rw;
400 }
401 {
402 Lock lock{obj2_mt.m_mutex};
403 swap(tmp_rw, obj2_rw);
404 }
405 {
406 Lock lock{obj1_mt.m_mutex};
407 obj1_rw = tmp_rw;
408 }
409} // Log_context_mt::swap()
410
412{
413 val1.swap(val2);
414}
415
416// Sev implementations.
417
418std::ostream& operator<<(std::ostream& os, Sev val)
419{
420 // Note: Must satisfy istream_to_enum() requirements.
421 switch (val)
422 {
423 case Sev::S_NONE: return os << "NONE";
424 case Sev::S_FATAL: return os << "FATAL";
425 case Sev::S_ERROR: return os << "ERROR";
426 case Sev::S_WARNING: return os << "WARNING";
427 case Sev::S_INFO: return os << "INFO";
428 case Sev::S_DEBUG: return os << "DEBUG";
429 case Sev::S_TRACE: return os << "TRACE";
430 case Sev::S_DATA: return os << "DATA";
431 case Sev::S_END_SENTINEL: assert(false && "Should not be printing sentinel.");
432 }
433
434 assert(false && "Looks like a corrupt/sentinel log::Sev value. gcc would've caught an incomplete switch().");
435 return os;
436}
437
438std::istream& operator>>(std::istream& is, Sev& val)
439{
440 // Range [NONE, END_SENTINEL); no match => NONE; allow for number instead of ostream<< string; case-insensitive.
442 return is;
443}
444
445// Free function implementations.
446
448{
450
451 if (logger_ptr)
452 {
453 const auto os = logger_ptr->this_thread_ostream();
454 if (os)
455 {
457 }
458 // else { Nothing left to beautify (near thread exit). It is fine. }
459 }
460}
461
463{
464 /* We're following the loose pattern explained at the end of Async_file_logger::mem_cost()...
465 * ...and since we (now) lack any non-shallow memory use in any members, the right answer is just zero.
466 * (Historically there was an std::string, so we had return deep_size(<that thing>); it is now Fixed_string.) */
467 return 0;
468}
469
470} // namespace flow::log
A light-weight class, each object storing a component payload encoding an enum value from enum type o...
Definition: log.hpp:935
Component & operator=(const Component &src)
Overwrites *this with a copy of src.
unsigned int enum_raw_t
The type Payload must be enum class Payload : enum_raw_t: an enum type encoded via this integer type.
Definition: log.hpp:940
std::type_info const * m_payload_type_or_null
The typeid() of the Payload passed to the 1-arg constructor; if 0-arg ctor was used (empty() is true)...
Definition: log.hpp:1112
const std::type_info & payload_type() const
Returns typeid(Payload), where Payload was the template param used when calling the originating one-a...
Definition: log.cpp:220
bool empty() const
Returns true if *this is as if default-constructed (a null Component); false if as if constructed via...
Definition: log.cpp:215
enum_raw_t m_payload_enum_raw_value
The internally stored integer representation of the enum value passed to the 1-arg constructor; meani...
Definition: log.hpp:1118
enum_raw_t payload_enum_raw_value() const
Returns the numeric value of the enum payload stored by this Component, originating in the one-arg co...
Definition: log.cpp:226
Component()
Constructs a Component that stores no payload; meaning an unspecified-component Component that return...
Definition: log.cpp:203
Similar mission to Log_context but enables one to safely log under the possibility of a concurrent se...
Definition: log.hpp:1954
void swap(Log_context_mt &other)
Identical to Log_context API; but safe against concurrent operations on a *this.
Definition: log.cpp:373
Log_context_mt & operator=(const Log_context_mt &src)
Identical to Log_context API; but safe against concurrent operations on a *this.
Definition: log.cpp:317
util::Mutex_non_recursive m_mutex
Protects access to data in static_cast<Log_context&>(*this) (especially Log_context::m_logger).
Definition: log.hpp:2125
Log_context_mt(Logger *logger=nullptr)
Identical to Log_context API.
Definition: log.cpp:292
Logger * set_logger(Logger *logger)
Identical to Log_context API; but safe against concurrent operations on a *this.
Definition: log.cpp:367
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1786
Log_context & operator=(const Log_context &src)
Assignment operator that behaves similarly to the copy constructor.
const Component & get_log_component() const
Returns reference to the stored Component object, particularly as many FLOW_LOG_*() macros expect.
Definition: log.cpp:272
Log_context(Logger *logger=nullptr)
Constructs Log_context by storing the given pointer to a Logger and a null Component.
Definition: log.cpp:234
Logger * set_logger(Logger *logger)
Sets the value to be returned by the next get_logger() call; returns get_logger() from before the cha...
Definition: log.cpp:265
void swap(Log_context &other)
Swaps Logger pointers and Component objects held by *this and other.
Definition: log.cpp:277
Component m_component
The held Component object. Making the object non-const to allow operator=() to work.
Definition: log.hpp:1901
Logger * m_logger
The held Logger pointer. Making the pointer itself non-const to allow operator=() to work.
Definition: log.hpp:1899
Logger * get_logger() const
Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect.
Definition: log.cpp:260
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1392
static void set_thread_info_in_msg_metadata(Msg_metadata *msg_metadata)
Sets msg_metadata->m_call_thread_nickname (if set) or else msg_metadata->m_call_thread_id,...
Definition: log.cpp:188
static std::ostream & this_thread_logged_name_os_manip(std::ostream &os)
ostream manipulator function that, if output via operator<< to an ostream, will cause the current thr...
Definition: log.cpp:121
static void this_thread_set_logged_nickname(util::String_view thread_nickname={}, Logger *logger_ptr=nullptr, bool also_set_os_name=true)
Sets or unsets the current thread's logging-worthy string name; optionally sets the OS thread name (s...
Definition: log.cpp:41
static thread_local Fixed_string s_this_thread_nickname
Thread-local storage for each thread's logged name (empty, which is default, means no nickname stored...
Definition: log.hpp:1702
std::ostream * this_thread_ostream() const
Returns the stream dedicated to the executing thread and this Logger, so that the caller can apply st...
Definition: log.cpp:194
static void set_thread_info(Fixed_string *call_thread_nickname, util::Thread_id *call_thread_id)
Same as set_thread_info_in_msg_metadata() but targets the given two variables as opposed to a Msg_met...
Definition: log.cpp:137
static Thread_local_string_appender * this_thread_string_appender(const util::Unique_id_holder &source_obj_id)
Returns a pointer to the exactly one Thread_local_string_appender object that is accessible from the ...
#define FLOW_ERROR_SYS_ERROR_LOG_WARNING()
Logs a warning about the (often errno-based or from a library) error code in sys_err_code.
Definition: error.hpp:269
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:200
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:155
#define FLOW_LOG_SET_CONTEXT(ARG_logger_ptr, ARG_component_payload)
For the rest of the block within which this macro is instantiated, causes all FLOW_LOG_....
Definition: log.hpp:473
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
void swap(Log_context_mt &val1, Log_context_mt &val2)
Log_context_mt ADL-friendly swap: Equivalent to val1.swap(val2).
Definition: log.cpp:411
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_END_SENTINEL
Not an actual value but rather stores the highest numerical payload, useful for validity checks.
@ 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,...
thread_local Msg_metadata this_thread_sync_msg_metadata
Thread-local Msg_metadata object used by FLOW_LOG_WITHOUT_CHECKING() for an alleged perf bonus in the...
Definition: log.cpp:29
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
void beautify_chrono_ostream(std::ostream *os_ptr)
Sets certain chrono-related formatting on the given ostream that results in a consistent,...
Definition: util.cpp:174
Thread::id Thread_id
Short-hand for an OS-provided ID of a util::Thread.
Definition: util_fwd.hpp:123
Enum istream_to_enum(std::istream *is_ptr, Enum enum_default, Enum enum_sentinel, bool accept_num_encoding, bool case_sensitive, Enum enum_lowest)
Deserializes an enum class value from a standard input stream.
Definition: util.hpp:716
boost::unique_lock< Mutex > Lock_guard
Short-hand for advanced-capability RAII lock guard for any mutex, ensuring exclusive ownership of tha...
Definition: util_fwd.hpp:298
Thread_token this_thread_unique_token()
Returns a value conceptually similar to this_thread::get_id() – a thread ID – but that is never recyc...
std::string ostream_op_string(T &&... ostream_args)
Equivalent to ostream_op_to_string() but returns a new string by value instead of writing to the call...
Definition: util.hpp:575
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
boost::system::error_code Error_code
Short-hand for a boost.system error code (which basically encapsulates an integer/enum error code and...
Definition: common.hpp:497
Simple data store containing all of the information generated at every logging call site by flow::log...
Definition: log.hpp:1151
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:1243
Fixed_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:1233