Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
structured_logger.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#pragma once
11
12#include <kcenon/common/interfaces/logger_interface.h>
13#include <string>
14#include <unordered_map>
15#include <variant>
16#include <memory>
17#include <functional>
18#include <iostream>
19#include <iomanip>
20#include <sstream>
21#include <ctime>
22
24
25// Type alias for log_level
26using log_level = common::interfaces::log_level;
27
31using log_value = std::variant<std::string, int, double, bool>;
32
40 log_level level;
41 std::string message;
42 std::unordered_map<std::string, log_value> fields;
43 std::chrono::system_clock::time_point timestamp;
44
45 structured_log_entry() : timestamp(std::chrono::system_clock::now()) {}
46};
47
56public:
57 virtual ~structured_logger_interface() = default;
58
62 virtual void log_structured(const structured_log_entry& entry) = 0;
63
67 virtual class log_builder start_log(log_level level) = 0;
68};
69
77private:
80
81public:
83 : logger_(logger) {
84 entry_.level = level;
85 }
86
87 log_builder& message(const std::string& msg) {
88 entry_.message = msg;
89 return *this;
90 }
91
92 log_builder& field(const std::string& key, const log_value& value) {
93 entry_.fields[key] = value;
94 return *this;
95 }
96
97 log_builder& field(const std::string& key, const std::string& value) {
98 entry_.fields[key] = value;
99 return *this;
100 }
101
102 log_builder& field(const std::string& key, int value) {
103 entry_.fields[key] = value;
104 return *this;
105 }
106
107 log_builder& field(const std::string& key, double value) {
108 entry_.fields[key] = value;
109 return *this;
110 }
111
112 log_builder& field(const std::string& key, bool value) {
113 entry_.fields[key] = value;
114 return *this;
115 }
116
117 void log() {
118 if (logger_) {
120 }
121 }
122};
123
128 json, // JSON format
129 logfmt // key=value format (Heroku/logfmt style)
130};
131
135using structured_output_callback = std::function<void(log_level, const std::string&)>;
136
159private:
162 bool output_to_stderr_ = false;
163
164public:
170 format_ = format;
171 }
172
178 output_callback_ = std::move(callback);
179 }
180
185 void set_output_to_stderr(bool use_stderr) {
186 output_to_stderr_ = use_stderr;
187 }
188
189 void log_structured(const structured_log_entry& entry) override {
190 std::string formatted;
191
192 switch (format_) {
194 formatted = json_formatter::format(entry);
195 break;
197 formatted = format_logfmt(entry);
198 break;
199 }
200
201 // Output the formatted log
202 if (output_callback_) {
203 output_callback_(entry.level, formatted);
204 } else {
205 auto& out = output_to_stderr_ ? std::cerr : std::cout;
206 out << formatted << std::endl;
207 }
208 }
209
210 log_builder start_log(log_level level) override {
211 return log_builder(level, this);
212 }
213
214private:
218 static std::string format_logfmt(const structured_log_entry& entry) {
219 std::ostringstream oss;
220
221 // Standard fields first
222 oss << "level=" << level_to_string(entry.level);
223 oss << " ts=" << format_timestamp_logfmt(entry.timestamp);
224
225 if (!entry.message.empty()) {
226 oss << " msg=" << escape_logfmt_value(entry.message);
227 }
228
229 // Custom fields
230 for (const auto& [key, value] : entry.fields) {
231 oss << " " << key << "=";
232 std::visit([&oss](const auto& v) {
233 using T = std::decay_t<decltype(v)>;
234 if constexpr (std::is_same_v<T, std::string>) {
235 oss << escape_logfmt_value(v);
236 } else if constexpr (std::is_same_v<T, bool>) {
237 oss << (v ? "true" : "false");
238 } else {
239 oss << v;
240 }
241 }, value);
242 }
243
244 return oss.str();
245 }
246
247 static std::string level_to_string(log_level level) {
248 switch (level) {
249 case log_level::trace: return "trace";
250 case log_level::debug: return "debug";
251 case log_level::info: return "info";
252 case log_level::warning: return "warn";
253 case log_level::error: return "error";
254 case log_level::critical: return "fatal";
255 case log_level::off: return "off";
256 default: return "unknown";
257 }
258 }
259
260 static std::string format_timestamp_logfmt(std::chrono::system_clock::time_point tp) {
261 auto time_t_val = std::chrono::system_clock::to_time_t(tp);
262 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
263 tp.time_since_epoch()) % 1000;
264
265 std::ostringstream oss;
266 oss << std::put_time(std::gmtime(&time_t_val), "%Y-%m-%dT%H:%M:%S");
267 oss << "." << std::setfill('0') << std::setw(3) << ms.count() << "Z";
268 return oss.str();
269 }
270
271 static std::string escape_logfmt_value(const std::string& value) {
272 // If value contains spaces, quotes, or special chars, quote it
273 bool needs_quoting = false;
274 for (char c : value) {
275 if (c == ' ' || c == '"' || c == '=' || c == '\n' || c == '\t') {
276 needs_quoting = true;
277 break;
278 }
279 }
280
281 if (!needs_quoting && !value.empty()) {
282 return value;
283 }
284
285 // Quote and escape
286 std::ostringstream oss;
287 oss << '"';
288 for (char c : value) {
289 switch (c) {
290 case '"': oss << "\\\""; break;
291 case '\\': oss << "\\\\"; break;
292 case '\n': oss << "\\n"; break;
293 case '\t': oss << "\\t"; break;
294 default: oss << c;
295 }
296 }
297 oss << '"';
298 return oss.str();
299 }
300};
301
311public:
312 static std::string format(const structured_log_entry& entry) {
313 std::ostringstream json;
314 json << "{";
315 json << "\"timestamp\":\"" << format_timestamp_iso8601(entry.timestamp) << "\",";
316 json << "\"level\":\"" << level_to_string(entry.level) << "\",";
317 json << "\"message\":" << escape_json_string(entry.message);
318
319 // Add custom fields
320 for (const auto& [key, value] : entry.fields) {
321 json << ",\"" << escape_json_key(key) << "\":";
322
323 std::visit([&json](const auto& v) {
324 using T = std::decay_t<decltype(v)>;
325 if constexpr (std::is_same_v<T, std::string>) {
327 } else if constexpr (std::is_same_v<T, bool>) {
328 json << (v ? "true" : "false");
329 } else if constexpr (std::is_same_v<T, int>) {
330 json << v;
331 } else if constexpr (std::is_same_v<T, double>) {
332 json << std::fixed << std::setprecision(6) << v;
333 } else {
334 json << v;
335 }
336 }, value);
337 }
338
339 json << "}";
340 return json.str();
341 }
342
343private:
344 static std::string level_to_string(log_level level) {
345 switch (level) {
346 case log_level::trace: return "TRACE";
347 case log_level::debug: return "DEBUG";
348 case log_level::info: return "INFO";
349 case log_level::warning: return "WARN";
350 case log_level::error: return "ERROR";
351 case log_level::critical: return "FATAL";
352 case log_level::off: return "OFF";
353 default: return "UNKNOWN";
354 }
355 }
356
357 static std::string format_timestamp_iso8601(std::chrono::system_clock::time_point tp) {
358 auto time_t_val = std::chrono::system_clock::to_time_t(tp);
359 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
360 tp.time_since_epoch()) % 1000;
361
362 std::ostringstream oss;
363 oss << std::put_time(std::gmtime(&time_t_val), "%Y-%m-%dT%H:%M:%S");
364 oss << "." << std::setfill('0') << std::setw(3) << ms.count() << "Z";
365 return oss.str();
366 }
367
368 static std::string escape_json_string(const std::string& s) {
369 std::ostringstream oss;
370 oss << '"';
371 for (char c : s) {
372 switch (c) {
373 case '"': oss << "\\\""; break;
374 case '\\': oss << "\\\\"; break;
375 case '\b': oss << "\\b"; break;
376 case '\f': oss << "\\f"; break;
377 case '\n': oss << "\\n"; break;
378 case '\r': oss << "\\r"; break;
379 case '\t': oss << "\\t"; break;
380 default:
381 if (static_cast<unsigned char>(c) < 0x20) {
382 oss << "\\u" << std::hex << std::setfill('0') << std::setw(4) << static_cast<int>(c);
383 } else {
384 oss << c;
385 }
386 }
387 }
388 oss << '"';
389 return oss.str();
390 }
391
392 static std::string escape_json_key(const std::string& key) {
393 // Keys should be simple identifiers, but escape just in case
394 std::string result;
395 for (char c : key) {
396 if (c == '"' || c == '\\') {
397 result += '\\';
398 }
399 result += c;
400 }
401 return result;
402 }
403};
404
405} // namespace kcenon::logger::structured
static std::string level_to_string(log_level level)
static std::string escape_logfmt_value(const std::string &value)
void set_output_callback(structured_output_callback callback)
Set a custom output callback.
log_builder start_log(log_level level) override
Start building a structured log entry.
void set_output_to_stderr(bool use_stderr)
Set whether to output to stderr (default: stdout)
void set_format(structured_format format)
Set the output format.
void log_structured(const structured_log_entry &entry) override
Log a structured message.
static std::string format_timestamp_logfmt(std::chrono::system_clock::time_point tp)
static std::string format_logfmt(const structured_log_entry &entry)
Format entry in logfmt style (key=value pairs)
JSON formatter for structured logs.
static std::string escape_json_key(const std::string &key)
static std::string escape_json_string(const std::string &s)
static std::string format(const structured_log_entry &entry)
static std::string level_to_string(log_level level)
static std::string format_timestamp_iso8601(std::chrono::system_clock::time_point tp)
Builder for structured log entries.
log_builder & field(const std::string &key, double value)
log_builder(log_level level, structured_logger_interface *logger)
structured_logger_interface * logger_
log_builder & field(const std::string &key, const log_value &value)
log_builder & field(const std::string &key, bool value)
log_builder & field(const std::string &key, int value)
log_builder & field(const std::string &key, const std::string &value)
log_builder & message(const std::string &msg)
virtual void log_structured(const structured_log_entry &entry)=0
Log a structured message.
virtual class log_builder start_log(log_level level)=0
Start building a structured log entry.
std::function< void(log_level, const std::string &)> structured_output_callback
Output callback type for structured logger.
std::variant< std::string, int, double, bool > log_value
Value type for structured logging.
structured_format
Output format for structured logs.
Structured log entry.
structured_log_entry()
std::string message
std::unordered_map< std::string, log_value > fields
std::chrono::system_clock::time_point timestamp
log_level level