CCF
Loading...
Searching...
No Matches
logger.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
8
9#define FMT_HEADER_ONLY
10#include <fmt/chrono.h>
11#include <fmt/format.h>
12#include <fmt/ranges.h>
13#include <iostream>
14#include <nlohmann/json.hpp>
15#include <optional>
16#include <sstream>
17#include <type_traits>
18
19namespace ccf::logger
20{
21 static constexpr LoggerLevel MOST_VERBOSE = LoggerLevel::TRACE;
22
23 static constexpr const char* LevelNames[] = {
24 "trace", "debug", "info", "fail", "fatal"};
25
26 static constexpr const char* to_string(LoggerLevel l)
27 {
28 return LevelNames[static_cast<int>(l)];
29 }
30
31 static constexpr long int ns_per_s = 1'000'000'000;
32
33 static constexpr auto preamble_length = 45u;
34
35#ifdef CCF_RAFT_TRACING
36 static size_t logical_clock = 0;
37#endif
38
39 struct LogLine
40 {
41 public:
42 friend struct Out;
44 std::string tag;
45 std::string file_name;
47 uint16_t thread_id;
48
49 std::ostringstream ss;
50 std::string msg;
51
53 LoggerLevel level_,
54 std::string_view tag_,
55 std::string_view file_name_,
56 size_t line_number_) :
57 log_level(level_),
58 tag(tag_),
59 file_name(file_name_),
60 line_number(line_number_),
61 thread_id(ccf::threading::get_current_thread_id())
62 {}
63
64 template <typename T>
65 LogLine& operator<<(const T& item)
66 {
67 ss << item;
68 return *this;
69 }
70
71 LogLine& operator<<(std::ostream& (*f)(std::ostream&))
72 {
73 ss << f;
74 return *this;
75 }
76
77 void finalize()
78 {
79 msg = ss.str();
80 }
81 };
82
83 static std::string get_timestamp(const std::tm& tm, const ::timespec& ts)
84 {
85#ifdef CCF_RAFT_TRACING
86 return std::to_string(logical_clock++);
87#else
88 // Sample: "2019-07-19 18:53:25.690267"
89 constexpr size_t nano_per_micro = 1000;
90 return fmt::format(
91 "{:%Y-%m-%dT%H:%M:%S}.{:0>6}Z", tm, ts.tv_nsec / nano_per_micro);
92#endif
93 }
94
96 {
97 public:
98 AbstractLogger() = default;
99 virtual ~AbstractLogger() = default;
100
101 virtual void emit(const std::string& s)
102 {
103 std::cout.write(s.c_str(), s.size());
104 std::cout.flush();
105 }
106
107 virtual void write(const LogLine& ll) = 0;
108 };
109
111 {
112 public:
113 void write(const LogLine& ll) override
114 {
115 // Fetch time
116 ::timespec host_ts{};
117 if (::timespec_get(&host_ts, TIME_UTC) == 0)
118 {
119 throw std::runtime_error("timespec_get failed");
120 }
121 std::tm host_tm{};
122 ::gmtime_r(&host_ts.tv_sec, &host_tm);
123
124#ifdef CCF_RAFT_TRACING
125 auto escaped_msg = ll.msg;
126 if (!nlohmann::json::accept(escaped_msg))
127 {
128 // Only dump to json if not already json, to avoid double-escaping when
129 // logging json
130 // https://json.nlohmann.me/features/parsing/parse_exceptions/#use-accept-function
131 escaped_msg = nlohmann::json(ll.msg).dump();
132 }
133#else
134 const auto escaped_msg = nlohmann::json(ll.msg).dump();
135#endif
136
137 std::string s;
138 s = fmt::format(
139 "{{\"h_ts\":\"{}\",\"thread_id\":\"{}\",\"level\":\"{}\",\"tag\":\"{}"
140 "\",\"file\":\"{}\",\"number\":\"{}\",\"msg\":{}}}\n",
141 get_timestamp(host_tm, host_ts),
142 ll.thread_id,
143 to_string(ll.log_level),
144 ll.tag,
145 ll.file_name,
146 ll.line_number,
147 escaped_msg);
148
149 emit(s);
150 }
151 };
152
153 static std::string format_to_text(const LogLine& ll)
154 {
155 // Fetch time
156 ::timespec host_ts{};
157 if (::timespec_get(&host_ts, TIME_UTC) == 0)
158 {
159 throw std::runtime_error("timespec_get failed");
160 }
161 std::tm host_tm{};
162 ::gmtime_r(&host_ts.tv_sec, &host_tm);
163
164 auto file_line = fmt::format("{}:{} ", ll.file_name, ll.line_number);
165 auto* file_line_data = file_line.data();
166
167 // The preamble is the level, then tag, then file line. If the file line is
168 // too long, the final characters are retained.
169 auto preamble = fmt::format(
170 "[{:<5}]{} ",
171 to_string(ll.log_level),
172 (ll.tag.empty() ? "" : fmt::format("[{}]", ll.tag)))
173 .substr(0, preamble_length);
174 const auto max_file_line_len = preamble_length - preamble.size();
175
176 const auto len = file_line.size();
177 if (len > max_file_line_len)
178 {
179 file_line_data += len - max_file_line_len;
180 }
181
182 preamble += file_line_data;
183
184 return fmt::format(
185 "{} {:<3} {:<45}| {}\n",
186 get_timestamp(host_tm, host_ts),
187 ll.thread_id,
188 preamble,
189 ll.msg);
190 }
191
193 {
194 public:
195 void write(const LogLine& ll) override
196 {
197 emit(format_to_text(ll));
198 }
199 };
200
201 class config
202 {
203 public:
204 static std::vector<std::unique_ptr<AbstractLogger>>& loggers()
205 {
206 return get_loggers();
207 }
208
210 {
211 get_loggers().emplace_back(std::make_unique<TextConsoleLogger>());
212 }
213
215 {
216 get_loggers().emplace_back(std::make_unique<JsonConsoleLogger>());
217 }
218
219 static void default_init()
220 {
221 get_loggers().clear();
223 }
224
226 {
227 static LoggerLevel the_level = MOST_VERBOSE;
228
229 return the_level;
230 }
231
232 static bool ok(LoggerLevel l)
233 {
234 return l >= level();
235 }
236
237 private:
238 static std::vector<std::unique_ptr<AbstractLogger>>& get_loggers()
239 {
240 static std::vector<std::unique_ptr<AbstractLogger>> the_loggers;
241 return the_loggers;
242 }
243 };
244
245 struct Out
246 {
247 bool operator==(LogLine& line)
248 {
249 line.finalize();
250
251 for (auto const& logger : config::loggers())
252 {
253 logger->write(line);
254 }
255
256 return true;
257 }
258 };
259
260#pragma clang diagnostic push
261#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
262
263// Clang 12.0 and 13.0 fails to compile the FMT_STRING macro in certain
264// contexts. Error is: non-literal type '<dependent type>' cannot be used in a
265// constant expression. Since consteval is available in these compilers, format
266// should already use compile-time checks.
267#if defined(__clang__) && __clang_major__ >= 12
268# define CCF_FMT_STRING(s) (s)
269#else
270# define CCF_FMT_STRING(s) FMT_STRING(s)
271#endif
272
273// The == operator is being used to:
274// 1. Be a lower precedence than <<, such that using << on the LogLine will
275// happen before the LogLine is "equalitied" with the Out.
276// 2. Be a higher precedence than &&, such that the log statement is bound
277// more tightly than the short-circuiting.
278// This allows:
279// CCF_LOG_OUT(DEBUG, "foo") << "this " << "msg";
280#define CCF_LOG_OUT(LVL, TAG) \
281 ccf::logger::config::ok(ccf::LoggerLevel::LVL) && \
282 ccf::logger::Out() == \
283 ccf::logger::LogLine(ccf::LoggerLevel::LVL, TAG, __FILE__, __LINE__)
284
285// To avoid repeating the (s, ...) args for every macro, we cheat with a curried
286// macro here by ending the macro with another macro name, which then accepts
287// the trailing arguments
288#define CCF_LOG_FMT_2(s, ...) fmt::format(CCF_FMT_STRING(s), ##__VA_ARGS__)
289#define CCF_LOG_FMT(LVL, TAG) CCF_LOG_OUT(LVL, TAG) << CCF_LOG_FMT_2
290
291#define CCF_APP_TRACE CCF_LOG_FMT(TRACE, "app")
292#define CCF_APP_DEBUG CCF_LOG_FMT(DEBUG, "app")
293#define CCF_APP_INFO CCF_LOG_FMT(INFO, "app")
294#define CCF_APP_FAIL CCF_LOG_FMT(FAIL, "app")
295#define CCF_APP_FATAL CCF_LOG_FMT(FATAL, "app")
296
297#pragma clang diagnostic pop
298}
Definition logger.h:96
virtual ~AbstractLogger()=default
virtual void emit(const std::string &s)
Definition logger.h:101
virtual void write(const LogLine &ll)=0
Definition logger.h:111
void write(const LogLine &ll) override
Definition logger.h:113
Definition logger.h:193
void write(const LogLine &ll) override
Definition logger.h:195
Definition logger.h:202
static LoggerLevel & level()
Definition logger.h:225
static bool ok(LoggerLevel l)
Definition logger.h:232
static void default_init()
Definition logger.h:219
static std::vector< std::unique_ptr< AbstractLogger > > & loggers()
Definition logger.h:204
static void add_text_console_logger()
Definition logger.h:209
static void add_json_console_logger()
Definition logger.h:214
Definition logger.h:20
Definition app_interface.h:14
LoggerLevel
Definition logger_level.h:9
Definition logger.h:40
size_t line_number
Definition logger.h:46
uint16_t thread_id
Definition logger.h:47
LogLine & operator<<(const T &item)
Definition logger.h:65
std::ostringstream ss
Definition logger.h:49
LogLine(LoggerLevel level_, std::string_view tag_, std::string_view file_name_, size_t line_number_)
Definition logger.h:52
std::string file_name
Definition logger.h:45
std::string tag
Definition logger.h:44
std::string msg
Definition logger.h:50
LoggerLevel log_level
Definition logger.h:43
LogLine & operator<<(std::ostream &(*f)(std::ostream &))
Definition logger.h:71
void finalize()
Definition logger.h:77
Definition logger.h:246
bool operator==(LogLine &line)
Definition logger.h:247