Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Logger.cpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2013-2019 Swirly Cloud Limited
3// Copyright (C) 2022 Reactive Markets Limited
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17#include "Logger.hpp"
18
20
21#include <atomic>
22#include <mutex>
23
24#include <syslog.h>
25
26#include <sys/uio.h> // writev()
27
28#if defined(__linux__)
29#include <sys/syscall.h>
30#endif
31
32namespace toolbox {
33inline namespace sys {
34using namespace std;
35namespace {
36
37const char* Labels[] = {"NONE", "CRIT", "ERROR", "WARN", "METRIC", "NOTICE", "INFO", "DEBUG"};
38
39// The gettid() function is a Linux-specific function call.
40#if defined(__linux__)
41inline pid_t gettid()
42{
43 struct S {
44 pid_t tid = {};
45 bool init_done = false;
46 };
47
48 thread_local S s{};
49
50 // 'tid' cannot be set during initialisation of struct S -- because
51 // the C++ standard doesn't guarantee which thread initialises it.
52 if (!s.init_done) [[unlikely]] {
53 s.tid = syscall(SYS_gettid);
54 s.init_done = true;
55 }
56
57 return s.tid;
58}
59#else
60inline pid_t gettid()
61{
62 return getpid();
63}
64#endif
65
66struct LogBufPoolWrapper {
67 static constexpr std::size_t InitialPoolSize = 8;
68 LogBufPoolWrapper()
69 {
70 for ([[maybe_unused]] std::size_t i = 0; i < InitialPoolSize; i++) {
72 }
73 }
75};
76static LogBufPoolWrapper log_buf_pool_{};
77
78class NullLogger final : public Logger {
79 void do_write_log(WallTime /*ts*/, LogLevel /*level*/, int /*tid*/, LogMsgPtr&& msg,
80 size_t /*size*/, bool /*warming_fake*/) noexcept override
81 {
82 log_buf_pool().bounded_push(std::move(msg));
83 }
85
86class StdLogger final : public Logger {
87 void do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg, size_t size,
88 bool warming_fake) noexcept override
89 {
90 const auto finally
91 = make_finally([&]() noexcept { log_buf_pool().bounded_push(std::move(msg)); });
92
93 if (warming_fake) [[unlikely]] {
94 return;
95 }
96
97 const auto t{WallClock::to_time_t(ts)};
98 tm tm;
99 localtime_r(&t, &tm);
100
101 // The following format has an upper-bound of 49 characters:
102 // "%Y/%m/%d %H:%M:%S.%06d %-6s [%d]: "
103 //
104 // Example:
105 // 2022/03/14 00:00:00.000000 NOTICE [0123456789]: msg...
106 // <---------------------------------------------->
107 static constexpr size_t upper_bound{48 + 1};
108 char head[upper_bound];
109 size_t hlen{strftime(head, sizeof(head), "%Y/%m/%d %H:%M:%S", &tm)};
110 const auto us{static_cast<int>(us_since_epoch(ts) % 1000000)};
111 hlen += snprintf(head + hlen, upper_bound - hlen, ".%06d %-6s [%d]: ", us, log_label(level),
112 tid);
113 char tail{'\n'};
114 iovec iov[] = {
115 {head, hlen}, //
116 {msg.get(), size}, //
117 {&tail, 1} //
118 };
119
120 int fd{level > LogLevel::Error ? STDOUT_FILENO : STDERR_FILENO};
121 // The following lock was required to avoid interleaving.
122 lock_guard<mutex> lock{mutex_};
123 // Best effort given that this is the logger.
124 ignore = writev(fd, iov, sizeof(iov) / sizeof(iov[0]));
125 }
126 mutex mutex_;
128
129class SysLogger final : public Logger {
130 void do_write_log(WallTime /*ts*/, LogLevel level, int /*tid*/, LogMsgPtr&& msg, size_t size,
131 bool warming_fake) noexcept override
132 {
133 const auto finally
134 = make_finally([&]() noexcept { log_buf_pool().bounded_push(std::move(msg)); });
135
136 if (warming_fake) [[unlikely]] {
137 return;
138 }
139
140 int prio;
141 switch (level) {
142 case LogLevel::None:
143 return;
144 case LogLevel::Crit:
145 prio = LOG_CRIT;
146 break;
147 case LogLevel::Error:
148 prio = LOG_ERR;
149 break;
150 case LogLevel::Warn:
152 break;
153 case LogLevel::Metric:
155 break;
156 case LogLevel::Notice:
158 break;
159 case LogLevel::Info:
160 prio = LOG_INFO;
161 break;
162 default:
163 prio = LOG_DEBUG;
164 }
165 syslog(prio, "%.*s", static_cast<int>(size), msg.get());
166 }
168
169// Global log level and logger function.
172thread_local bool warming_mode_{false};
173
175{
176 return level_.load(memory_order_acquire);
177}
178
179inline Logger& acquire_logger() noexcept
180{
181 return *logger_.load(memory_order_acquire);
182}
183
184} // namespace
185
187{
188 return log_buf_pool_.pool;
189}
190
191void set_log_warming_mode(bool enabled) noexcept
192{
194}
195
200
202{
203 return null_logger_;
204}
205
207{
208 return std_logger_;
209}
210
212{
213 return sys_logger_;
214}
215
216const char* log_label(LogLevel level) noexcept
217{
218 return Labels[static_cast<int>(min(max(level, LogLevel::None), LogLevel::Debug))];
219}
220
225
227{
228 return level_.exchange(max(level, LogLevel{}), memory_order_acq_rel);
229}
230
232{
233 return acquire_logger();
234}
235
237{
238 return *logger_.exchange(&logger, memory_order_acq_rel);
239}
240
241void write_log(WallTime ts, LogLevel level, LogMsgPtr&& msg, std::size_t size) noexcept
242{
243 acquire_logger().write_log(ts, level, static_cast<int>(gettid()), std::move(msg), size,
245}
246
247Logger::~Logger() = default;
248
253
255{
256 write_all_messages();
257}
258
259void AsyncLogger::write_all_messages()
260{
261 Task t;
262 int fake_count = 0;
263
264 while (tq_.pop(t)) {
265 if (t.msg != nullptr) {
266 logger_.write_log(t.ts, t.level, t.tid, LogMsgPtr{t.msg}, t.size, false);
267 } else {
268 fake_count++;
269 }
270 }
271 fake_pushed_count_.fetch_sub(fake_count, std::memory_order_relaxed);
272}
273
275{
276 write_all_messages();
277 std::this_thread::sleep_for(50ms);
278
279 return (!tq_.empty() || !stop_);
280}
281
283{
284 stop_ = true;
285}
286
287void AsyncLogger::do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg, size_t size,
288 bool warming_fake) noexcept
289{
290 char* const msg_ptr = msg.release();
291 auto push_to_queue = [&](char* ptr) -> bool {
292 return tq_.push(Task{.ts = ts, .level = level, .tid = tid, .msg = ptr, .size = size});
293 };
294 try {
295 if (warming_fake) [[unlikely]] {
296 const auto cnt = fake_pushed_count_.load(std::memory_order_relaxed);
297 const auto d = ts - last_time_fake_pushed_.load(std::memory_order_relaxed);
298
299 constexpr Millis FakePushInterval = 10ms;
300 constexpr int MaxPushedFakeCount = 25;
301
302 // It's possible that `last_time_fake_pushed_` OR `fake_pushed_count_` have since
303 // been modified by another thread (since the load). In this extremely rare case, a back
304 // to back push to the queue will occur. But that's fine -- not a big deal, these limits
305 // are not supposed to absolute hard limits, they are just to ensure that the queue is
306 // not flooded with "fake" entries. This compromise allows keeping the implementation
307 // simple, otherwise more complicated code is required to deal with this case.
309 if (push_to_queue(nullptr)) {
310 last_time_fake_pushed_.store(ts, std::memory_order_relaxed);
311 fake_pushed_count_.fetch_add(1, std::memory_order_relaxed);
312 }
313 }
314 } else if (push_to_queue(msg_ptr)) [[likely]] {
315 // Successfully pushed the task to the queue, release ownership of msg_ptr.
316 return;
317 }
318 } catch (const std::bad_alloc&) {
319 // Catching `std::bad_alloc` here is *defensive plumbing* that keeps the logger non-throwing
320 // and prevents crashes caused by an out-of-memory situation during rare log-burst spikes.
321 }
322 // Failed to push the task OR warming fake, restore ownership of msg_ptr.
323 log_buf_pool().bounded_push(LogMsgPtr{msg_ptr});
324}
325
326} // namespace sys
327} // namespace toolbox
LogBufPool pool
Definition Logger.cpp:74
AsyncLogger(Logger &logger)
Definition Logger.cpp:249
void stop()
Interrupt and exit any inprogress call to run().
Definition Logger.cpp:282
The Logger is implemented by types that write log messages to a sink.
Definition Logger.hpp:110
void write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr &&msg, std::size_t size, bool warming_fake) noexcept
Definition Logger.hpp:123
STL namespace.
int64_t min(const Histogram &h) noexcept
Definition Utility.cpp:37
int64_t max(const Histogram &h) noexcept
Definition Utility.cpp:42
boost::lockfree::stack< LogMsgPtr, boost::lockfree::capacity< LogBufPoolCapacity > > LogBufPool
Definition Logger.hpp:56
const char * log_label(LogLevel level) noexcept
Return log label for given log level.
Definition Logger.cpp:216
constexpr std::int64_t us_since_epoch(std::chrono::time_point< ClockT, Duration > t) noexcept
Definition Time.hpp:182
Logger & set_logger(Logger &logger) noexcept
Set logger globally for all threads.
Definition Logger.cpp:236
Logger & get_logger() noexcept
Return current logger.
Definition Logger.cpp:231
std::chrono::milliseconds Millis
Definition Time.hpp:37
void write_log(WallTime ts, LogLevel level, LogMsgPtr &&msg, std::size_t size) noexcept
Definition Logger.cpp:241
Logger & null_logger() noexcept
Null logger. This logger does nothing and is effectively /dev/null.
Definition Logger.cpp:201
WallClock::time_point WallTime
Definition Time.hpp:113
StoragePtr< MaxLogLine > LogMsgPtr
Definition Logger.hpp:54
bool log_warming_mode_enabled() noexcept
Returns true if log warming mode is enabled.
Definition Logger.cpp:196
LogLevel get_log_level() noexcept
Return current log level.
Definition Logger.cpp:221
LogBufPool & log_buf_pool() noexcept
A pool of log buffers, eliminating the need for dynamic memory allocations when logging.
Definition Logger.cpp:186
Logger & std_logger() noexcept
Definition Logger.cpp:206
LogLevel set_log_level(LogLevel level) noexcept
Set log level globally for all threads.
Definition Logger.cpp:226
void set_log_warming_mode(bool enabled) noexcept
Definition Logger.cpp:191
Logger & sys_logger() noexcept
System logger. This logger calls syslog().
Definition Logger.cpp:211
auto make_finally(FnT fn) noexcept
Definition Finally.hpp:48
constexpr std::size_t size(const detail::Struct< detail::Member< TagsT, ValuesT >... > &s)
Definition Struct.hpp:98
constexpr auto bind() noexcept
Definition Slot.hpp:97
static constexpr std::time_t to_time_t(const time_point &tp) noexcept
Definition Time.hpp:98