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,
88 size_t size, bool warming_fake) noexcept override
89 {
90 const auto finally = make_finally([&]() noexcept {
91 log_buf_pool().bounded_push(std::move(msg));
92 });
93
94 if (warming_fake) [[unlikely]] {
95 return;
96 }
97
98 const auto t{WallClock::to_time_t(ts)};
99 tm tm;
100 localtime_r(&t, &tm);
101
102 // The following format has an upper-bound of 49 characters:
103 // "%Y/%m/%d %H:%M:%S.%06d %-6s [%d]: "
104 //
105 // Example:
106 // 2022/03/14 00:00:00.000000 NOTICE [0123456789]: msg...
107 // <---------------------------------------------->
108 static constexpr size_t upper_bound{48 + 1};
109 char head[upper_bound];
110 size_t hlen{strftime(head, sizeof(head), "%Y/%m/%d %H:%M:%S", &tm)};
111 const auto us{static_cast<int>(us_since_epoch(ts) % 1000000)};
112 hlen += snprintf(head + hlen, upper_bound - hlen, ".%06d %-6s [%d]: ", us, log_label(level), 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,
131 size_t size, bool warming_fake) noexcept override
132 {
133 const auto finally = make_finally([&]() noexcept {
134 log_buf_pool().bounded_push(std::move(msg));
135 });
136
137 if (warming_fake) [[unlikely]] {
138 return;
139 }
140
141 int prio;
142 switch (level) {
143 case LogLevel::None:
144 return;
145 case LogLevel::Crit:
146 prio = LOG_CRIT;
147 break;
148 case LogLevel::Error:
149 prio = LOG_ERR;
150 break;
151 case LogLevel::Warn:
153 break;
154 case LogLevel::Metric:
156 break;
157 case LogLevel::Notice:
159 break;
160 case LogLevel::Info:
161 prio = LOG_INFO;
162 break;
163 default:
164 prio = LOG_DEBUG;
165 }
166 syslog(prio, "%.*s", static_cast<int>(size), msg.get());
167 }
169
170// Global log level and logger function.
173thread_local bool warming_mode_{false};
174
176{
177 return level_.load(memory_order_acquire);
178}
179
180inline Logger& acquire_logger() noexcept
181{
182 return *logger_.load(memory_order_acquire);
183}
184
185} // namespace
186
188{
189 return log_buf_pool_.pool;
190}
191
192void set_log_warming_mode(bool enabled) noexcept
193{
195}
196
201
203{
204 return null_logger_;
205}
206
208{
209 return std_logger_;
210}
211
213{
214 return sys_logger_;
215}
216
217const char* log_label(LogLevel level) noexcept
218{
219 return Labels[static_cast<int>(min(max(level, LogLevel::None), LogLevel::Debug))];
220}
221
226
228{
229 return level_.exchange(max(level, LogLevel{}), memory_order_acq_rel);
230}
231
233{
234 return acquire_logger();
235}
236
238{
239 return *logger_.exchange(&logger, memory_order_acq_rel);
240}
241
242void write_log(WallTime ts, LogLevel level, LogMsgPtr&& msg, std::size_t size) noexcept
243{
244 acquire_logger().write_log(ts, level, static_cast<int>(gettid()), std::move(msg), size,
246}
247
248Logger::~Logger() = default;
249
254
256{
257 write_all_messages();
258}
259
260void AsyncLogger::write_all_messages()
261{
262 Task t;
263 int fake_count = 0;
264
265 while (tq_.pop(t)) {
266 if (t.msg != nullptr) {
267 logger_.write_log(t.ts, t.level, t.tid, LogMsgPtr{t.msg}, t.size, false);
268 } else {
269 fake_count++;
270 }
271 }
272 fake_pushed_count_.fetch_sub(fake_count, std::memory_order_relaxed);
273}
274
276{
277 write_all_messages();
278 std::this_thread::sleep_for(50ms);
279
280 return (!tq_.empty() || !stop_);
281}
282
284{
285 stop_ = true;
286}
287
288void AsyncLogger::do_write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr&& msg,
289 size_t size, bool warming_fake) noexcept
290{
291 char* const msg_ptr = msg.release();
292 auto push_to_queue = [&](char* ptr) -> bool {
293 return tq_.push(Task{.ts = ts, .level = level, .tid = tid, .msg = ptr, .size = size});
294 };
295 try {
296 if (warming_fake) [[unlikely]] {
297 const auto cnt = fake_pushed_count_.load(std::memory_order_relaxed);
298 const auto d = ts - last_time_fake_pushed_.load(std::memory_order_relaxed);
299
300 constexpr Millis FakePushInterval = 10ms;
301 constexpr int MaxPushedFakeCount = 25;
302
303 // It's possible that `last_time_fake_pushed_` OR `fake_pushed_count_` have since
304 // been modified by another thread (since the load). In this extremely rare case, a back
305 // to back push to the queue will occur. But that's fine -- not a big deal, these limits
306 // are not supposed to absolute hard limits, they are just to ensure that the queue is
307 // not flooded with "fake" entries. This compromise allows keeping the implementation
308 // simple, otherwise more complicated code is required to deal with this case.
310 if (push_to_queue(nullptr)) {
311 last_time_fake_pushed_.store(ts, std::memory_order_relaxed);
312 fake_pushed_count_.fetch_add(1, std::memory_order_relaxed);
313 }
314 }
315 } else if (push_to_queue(msg_ptr)) [[likely]] {
316 // Successfully pushed the task to the queue, release ownership of msg_ptr.
317 return;
318 }
319 } catch (const std::bad_alloc&) {
320 // Catching `std::bad_alloc` here is *defensive plumbing* that keeps the logger non-throwing
321 // and prevents crashes caused by an out-of-memory situation during rare log-burst spikes.
322 }
323 // Failed to push the task OR warming fake, restore ownership of msg_ptr.
324 log_buf_pool().bounded_push(LogMsgPtr{msg_ptr});
325}
326
327} // namespace sys
328} // namespace toolbox
LogBufPool pool
Definition Logger.cpp:74
AsyncLogger(Logger &logger)
Definition Logger.cpp:250
void stop()
Interrupt and exit any inprogress call to run().
Definition Logger.cpp:283
void write_log(WallTime ts, LogLevel level, int tid, LogMsgPtr &&msg, std::size_t size, bool warming_fake) noexcept
Definition Logger.hpp:124
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:217
constexpr std::int64_t us_since_epoch(std::chrono::time_point< ClockT, Duration > t) noexcept
Definition Time.hpp:181
Logger & set_logger(Logger &logger) noexcept
Set logger globally for all threads.
Definition Logger.cpp:237
Logger & get_logger() noexcept
Return current logger.
Definition Logger.cpp:232
std::chrono::milliseconds Millis
Definition Time.hpp:36
void write_log(WallTime ts, LogLevel level, LogMsgPtr &&msg, std::size_t size) noexcept
Definition Logger.cpp:242
Logger & null_logger() noexcept
Null logger. This logger does nothing and is effectively /dev/null.
Definition Logger.cpp:202
WallClock::time_point WallTime
Definition Time.hpp:112
StoragePtr< MaxLogLine > LogMsgPtr
Definition Logger.hpp:54
bool log_warming_mode_enabled() noexcept
Returns true if log warming mode is enabled.
Definition Logger.cpp:197
LogLevel get_log_level() noexcept
Return current log level.
Definition Logger.cpp:222
LogBufPool & log_buf_pool() noexcept
A pool of log buffers, eliminating the need for dynamic memory allocations when logging.
Definition Logger.cpp:187
Logger & std_logger() noexcept
Definition Logger.cpp:207
LogLevel set_log_level(LogLevel level) noexcept
Set log level globally for all threads.
Definition Logger.cpp:227
void set_log_warming_mode(bool enabled) noexcept
Definition Logger.cpp:192
Logger & sys_logger() noexcept
System logger. This logger calls syslog().
Definition Logger.cpp:212
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:97