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