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 {
63 }
64
65 template <typename T>
66 LogLine& operator<<(const T& item)
67 {
68 ss << item;
69 return *this;
70 }
71
72 LogLine& operator<<(std::ostream& (*f)(std::ostream&))
73 {
74 ss << f;
75 return *this;
76 }
77
78 void finalize()
79 {
80 msg = ss.str();
81 }
82 };
83
84 static std::string get_timestamp(const std::tm& tm, const ::timespec& ts)
85 {
86#ifdef CCF_RAFT_TRACING
87 return std::to_string(logical_clock++);
88#else
89 // Sample: "2019-07-19 18:53:25.690267"
90 return fmt::format("{:%Y-%m-%dT%H:%M:%S}.{:0>6}Z", tm, ts.tv_nsec / 1000);
91#endif
92 }
93
95 {
96 public:
97 AbstractLogger() = default;
98 virtual ~AbstractLogger() = default;
99
100 virtual void emit(const std::string& s)
101 {
102 std::cout.write(s.c_str(), s.size());
103 std::cout.flush();
104 }
105
106 virtual void write(
107 const LogLine& ll,
108 const std::optional<double>& enclave_offset = std::nullopt) = 0;
109 };
110
112 {
113 public:
114 void write(
115 const LogLine& ll,
116 const std::optional<double>& enclave_offset = std::nullopt) override
117 {
118 // Fetch time
119 ::timespec host_ts;
120 ::timespec_get(&host_ts, TIME_UTC);
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 if (enclave_offset.has_value())
139 {
140 ::timespec enc_ts = host_ts;
141 enc_ts.tv_sec += (size_t)enclave_offset.value();
142 enc_ts.tv_nsec +=
143 (long long)(enclave_offset.value() * ns_per_s) % ns_per_s;
144
145 if (enc_ts.tv_nsec > ns_per_s)
146 {
147 enc_ts.tv_sec += 1;
148 enc_ts.tv_nsec -= ns_per_s;
149 }
150
151 std::tm enclave_tm;
152 gmtime_r(&enc_ts.tv_sec, &enclave_tm);
153
154 s = fmt::format(
155 "{{\"h_ts\":\"{}\",\"e_ts\":\"{}\",\"thread_id\":\"{}\",\"level\":\"{"
156 "}\",\"tag\":\"{}\",\"file\":\"{}\",\"number\":\"{}\",\"msg\":{}}}\n",
157 get_timestamp(host_tm, host_ts),
158 get_timestamp(enclave_tm, enc_ts),
159 ll.thread_id,
160 to_string(ll.log_level),
161 ll.tag,
162 ll.file_name,
163 ll.line_number,
164 escaped_msg);
165 }
166 else
167 {
168 s = fmt::format(
169 "{{\"h_ts\":\"{}\",\"thread_id\":\"{}\",\"level\":\"{}\",\"tag\":\"{}"
170 "\",\"file\":\"{}\",\"number\":\"{}\",\"msg\":{}}}\n",
171 get_timestamp(host_tm, host_ts),
172 ll.thread_id,
173 to_string(ll.log_level),
174 ll.tag,
175 ll.file_name,
176 ll.line_number,
177 escaped_msg);
178 }
179
180 emit(s);
181 }
182 };
183
184 static std::string format_to_text(
185 const LogLine& ll,
186 const std::optional<double>& enclave_offset = std::nullopt)
187 {
188 // Fetch time
189 ::timespec host_ts;
190 ::timespec_get(&host_ts, TIME_UTC);
191 std::tm host_tm;
192 ::gmtime_r(&host_ts.tv_sec, &host_tm);
193
194 auto file_line = fmt::format("{}:{} ", ll.file_name, ll.line_number);
195 auto file_line_data = file_line.data();
196
197 // The preamble is the level, then tag, then file line. If the file line is
198 // too long, the final characters are retained.
199 auto preamble = fmt::format(
200 "[{:<5}]{} ",
201 to_string(ll.log_level),
202 (ll.tag.empty() ? "" : fmt::format("[{}]", ll.tag)))
203 .substr(0, preamble_length);
204 const auto max_file_line_len = preamble_length - preamble.size();
205
206 const auto len = file_line.size();
207 if (len > max_file_line_len)
208 {
209 file_line_data += len - max_file_line_len;
210 }
211
212 preamble += file_line_data;
213
214 if (enclave_offset.has_value())
215 {
216 // Sample: "2019-07-19 18:53:25.690183 -0.130 " where -0.130 indicates
217 // that the time inside the enclave was 130 milliseconds earlier than
218 // the host timestamp printed on the line
219 return fmt::format(
220 "{} {:+01.3f} {:<3} {:<45}| {}\n",
221 get_timestamp(host_tm, host_ts),
222 enclave_offset.value(),
223 ll.thread_id,
224 preamble,
225 ll.msg);
226 }
227 else
228 {
229 // Padding on the right to align the rest of the message
230 // with lines that contain enclave time offsets
231 return fmt::format(
232 "{} {:<3} {:<45}| {}\n",
233 get_timestamp(host_tm, host_ts),
234 ll.thread_id,
235 preamble,
236 ll.msg);
237 }
238 }
239
241 {
242 public:
243 void write(
244 const LogLine& ll,
245 const std::optional<double>& enclave_offset = std::nullopt) override
246 {
247 emit(format_to_text(ll, enclave_offset));
248 }
249 };
250
251 class config
252 {
253 public:
254 static inline std::vector<std::unique_ptr<AbstractLogger>>& loggers()
255 {
256 return get_loggers();
257 }
258
259 static inline void add_text_console_logger()
260 {
261 get_loggers().emplace_back(std::make_unique<TextConsoleLogger>());
262 }
263
264 static inline void add_json_console_logger()
265 {
266 get_loggers().emplace_back(std::make_unique<JsonConsoleLogger>());
267 }
268
269 static inline void default_init()
270 {
271 get_loggers().clear();
273 }
274
275 static inline LoggerLevel& level()
276 {
277 static LoggerLevel the_level = MOST_VERBOSE;
278
279 return the_level;
280 }
281
282 static inline bool ok(LoggerLevel l)
283 {
284 return l >= level();
285 }
286
287 private:
288 static inline std::vector<std::unique_ptr<AbstractLogger>>& get_loggers()
289 {
290 static std::vector<std::unique_ptr<AbstractLogger>> the_loggers;
291 return the_loggers;
292 }
293 };
294
295 struct Out
296 {
297 bool operator==(LogLine& line)
298 {
299 line.finalize();
300
301 for (auto const& logger : config::loggers())
302 {
303 logger->write(line);
304 }
305
306 return true;
307 }
308 };
309
310#pragma clang diagnostic push
311#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
312
313// Clang 12.0 and 13.0 fails to compile the FMT_STRING macro in certain
314// contexts. Error is: non-literal type '<dependent type>' cannot be used in a
315// constant expression. Since consteval is available in these compilers, format
316// should already use compile-time checks.
317#if defined(__clang__) && __clang_major__ >= 12
318# define CCF_FMT_STRING(s) (s)
319#else
320# define CCF_FMT_STRING(s) FMT_STRING(s)
321#endif
322
323// The == operator is being used to:
324// 1. Be a lower precedence than <<, such that using << on the LogLine will
325// happen before the LogLine is "equalitied" with the Out.
326// 2. Be a higher precedence than &&, such that the log statement is bound
327// more tightly than the short-circuiting.
328// This allows:
329// CCF_LOG_OUT(DEBUG, "foo") << "this " << "msg";
330#define CCF_LOG_OUT(LVL, TAG) \
331 ccf::logger::config::ok(ccf::LoggerLevel::LVL) && \
332 ccf::logger::Out() == \
333 ccf::logger::LogLine(ccf::LoggerLevel::LVL, TAG, __FILE__, __LINE__)
334
335// To avoid repeating the (s, ...) args for every macro, we cheat with a curried
336// macro here by ending the macro with another macro name, which then accepts
337// the trailing arguments
338#define CCF_LOG_FMT_2(s, ...) fmt::format(CCF_FMT_STRING(s), ##__VA_ARGS__)
339#define CCF_LOG_FMT(LVL, TAG) CCF_LOG_OUT(LVL, TAG) << CCF_LOG_FMT_2
340
341 enum class macro
342 {
343 LOG_TRACE_FMT [[deprecated("Use CCF_APP_TRACE instead")]],
344 LOG_DEBUG_FMT [[deprecated("Use CCF_APP_DEBUG instead")]],
345 LOG_INFO_FMT [[deprecated("Use CCF_APP_INFO instead")]],
346 LOG_FAIL_FMT [[deprecated("Use CCF_APP_FAIL instead")]],
347 LOG_FATAL_FMT [[deprecated("Use CCF_APP_FATAL instead")]],
348 };
349
350#ifndef CCF_LOGGER_NO_DEPRECATE
351# define CCF_LOGGER_DEPRECATE(MACRO) ccf::logger::macro::MACRO;
352#else
353# define CCF_LOGGER_DEPRECATE(MACRO)
354#endif
355
356#define LOG_TRACE_FMT CCF_LOGGER_DEPRECATE(LOG_TRACE_FMT) CCF_LOG_FMT(TRACE, "")
357#define LOG_DEBUG_FMT CCF_LOGGER_DEPRECATE(LOG_DEBUG_FMT) CCF_LOG_FMT(DEBUG, "")
358
359#define CCF_APP_TRACE CCF_LOG_FMT(TRACE, "app")
360#define CCF_APP_DEBUG CCF_LOG_FMT(DEBUG, "app")
361
362#define LOG_INFO_FMT CCF_LOGGER_DEPRECATE(LOG_INFO_FMT) CCF_LOG_FMT(INFO, "")
363#define LOG_FAIL_FMT CCF_LOGGER_DEPRECATE(LOG_FAIL_FMT) CCF_LOG_FMT(FAIL, "")
364#define LOG_FATAL_FMT CCF_LOGGER_DEPRECATE(LOG_FATAL_FMT) CCF_LOG_FMT(FATAL, "")
365
366#define CCF_APP_INFO CCF_LOG_FMT(INFO, "app")
367#define CCF_APP_FAIL CCF_LOG_FMT(FAIL, "app")
368#define CCF_APP_FATAL CCF_LOG_FMT(FATAL, "app")
369
370#pragma clang diagnostic pop
371}
Definition logger.h:95
virtual ~AbstractLogger()=default
virtual void emit(const std::string &s)
Definition logger.h:100
virtual void write(const LogLine &ll, const std::optional< double > &enclave_offset=std::nullopt)=0
Definition logger.h:112
void write(const LogLine &ll, const std::optional< double > &enclave_offset=std::nullopt) override
Definition logger.h:114
Definition logger.h:241
void write(const LogLine &ll, const std::optional< double > &enclave_offset=std::nullopt) override
Definition logger.h:243
Definition logger.h:252
static LoggerLevel & level()
Definition logger.h:275
static bool ok(LoggerLevel l)
Definition logger.h:282
static void default_init()
Definition logger.h:269
static std::vector< std::unique_ptr< AbstractLogger > > & loggers()
Definition logger.h:254
static void add_text_console_logger()
Definition logger.h:259
static void add_json_console_logger()
Definition logger.h:264
Definition logger.h:20
macro
Definition logger.h:342
uint16_t get_current_thread_id()
Definition thread_local.cpp:15
LoggerLevel
Definition logger_level.h:8
@ TRACE
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:66
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:72
void finalize()
Definition logger.h:78
Definition logger.h:296
bool operator==(LogLine &line)
Definition logger.h:297