Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
logfmt_formatter.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#pragma once
6
34#include "../utils/time_utils.h"
36#include <sstream>
37#include <iomanip>
38#include <type_traits>
39#include <variant>
40
41// Use common_system's standard interface
42#include <kcenon/common/interfaces/logger_interface.h>
43
44namespace kcenon::logger {
45
46// Type alias for log_level
47using log_level = common::interfaces::log_level;
48
69public:
79 options_ = opts;
80 // Logfmt doesn't support colors or pretty print
81 options_.use_colors = false;
82 options_.pretty_print = false;
83 }
84
103 [[nodiscard]] std::string format(const log_entry& entry) const override {
104 std::ostringstream oss;
105 bool first = true;
106
107 // Level
109 oss << "level=" << level_to_lowercase(entry.level);
110 first = false;
111 }
112
113 // Timestamp (ISO 8601)
115 if (!first) {
116 oss << " ";
117 }
118 oss << "ts=" << utils::time_utils::format_iso8601(entry.timestamp);
119 first = false;
120 }
121
122 // Message (always include)
123 if (!first) {
124 oss << " ";
125 }
126 oss << "msg=" << escape_logfmt_value(entry.message.to_string());
127 first = false;
128
129 // Thread ID
130 if (options_.include_thread_id && entry.thread_id) {
131 oss << " thread_id=" << escape_logfmt_value(entry.thread_id->to_string());
132 }
133
134 // Source location
136 std::string file_path = entry.location->file.to_string();
137 if (!file_path.empty()) {
138 oss << " file=" << escape_logfmt_value(file_path);
139 }
140
141 if (entry.location->line > 0) {
142 oss << " line=" << entry.location->line;
143 }
144
145 std::string func = entry.location->function.to_string();
146 if (!func.empty()) {
147 oss << " function=" << escape_logfmt_value(func);
148 }
149 }
150
151 // Category (if present)
152 if (entry.category) {
153 std::string cat = entry.category->to_string();
154 if (!cat.empty()) {
155 oss << " category=" << escape_logfmt_value(cat);
156 }
157 }
158
159 // OpenTelemetry context (if present)
160 if (entry.otel_ctx && entry.otel_ctx->is_valid()) {
161 if (!entry.otel_ctx->trace_id.empty()) {
162 oss << " trace_id=" << entry.otel_ctx->trace_id;
163 }
164 if (!entry.otel_ctx->span_id.empty()) {
165 oss << " span_id=" << entry.otel_ctx->span_id;
166 }
167 if (!entry.otel_ctx->trace_flags.empty()) {
168 oss << " trace_flags=" << entry.otel_ctx->trace_flags;
169 }
170 }
171
172 // Structured fields (if present)
173 if (entry.fields && !entry.fields->empty()) {
174 for (const auto& [key, value] : *entry.fields) {
175 oss << " " << escape_logfmt_key(key) << "=";
176 format_value(oss, value);
177 }
178 }
179
180 return oss.str();
181 }
182
189 [[nodiscard]] std::string get_name() const override {
190 return "logfmt_formatter";
191 }
192
193private:
199 static std::string level_to_lowercase(log_level level) {
200 switch (level) {
201 case log_level::trace: return "trace";
202 case log_level::debug: return "debug";
203 case log_level::info: return "info";
204 case log_level::warning: return "warn";
205 case log_level::error: return "error";
206 case log_level::critical: return "critical";
207 case log_level::off: return "off";
208 default: return "unknown";
209 }
210 }
211
217 static std::string escape_logfmt_key(const std::string& key) {
218 std::string result;
219 result.reserve(key.size());
220 for (char c : key) {
221 if (c == ' ' || c == '=' || c == '"' || c == '\n' || c == '\t') {
222 result += '_';
223 } else {
224 result += c;
225 }
226 }
227 return result;
228 }
229
238 static std::string escape_logfmt_value(const std::string& value) {
239 // Check if quoting is needed
240 bool needs_quoting = false;
241 for (char c : value) {
242 if (c == ' ' || c == '"' || c == '=' || c == '\n' || c == '\t' || c == '\r') {
243 needs_quoting = true;
244 break;
245 }
246 }
247
248 if (!needs_quoting && !value.empty()) {
249 return value;
250 }
251
252 // Quote and escape
253 std::ostringstream oss;
254 oss << '"';
255 for (char c : value) {
256 switch (c) {
257 case '"': oss << "\\\""; break;
258 case '\\': oss << "\\\\"; break;
259 case '\n': oss << "\\n"; break;
260 case '\r': oss << "\\r"; break;
261 case '\t': oss << "\\t"; break;
262 default: oss << c;
263 }
264 }
265 oss << '"';
266 return oss.str();
267 }
268
274 static void format_value(std::ostringstream& oss, const log_value& value) {
275 std::visit([&oss](const auto& v) {
276 using T = std::decay_t<decltype(v)>;
277 if constexpr (std::is_same_v<T, std::string>) {
278 oss << escape_logfmt_value(v);
279 } else if constexpr (std::is_same_v<T, bool>) {
280 oss << (v ? "true" : "false");
281 } else if constexpr (std::is_same_v<T, int64_t>) {
282 oss << v;
283 } else if constexpr (std::is_same_v<T, double>) {
284 oss << std::fixed << std::setprecision(6) << v;
285 } else {
286 oss << v;
287 }
288 }, value);
289 }
290};
291
292} // namespace kcenon::logger
Abstract interface for log message formatters.
Formatter that outputs logfmt-structured log messages.
logfmt_formatter(const format_options &opts=format_options{})
Constructor with optional format options.
std::string format(const log_entry &entry) const override
Format a log entry to logfmt string.
static void format_value(std::ostringstream &oss, const log_value &value)
Format a log_value to logfmt.
static std::string escape_logfmt_value(const std::string &value)
Escape a logfmt value.
static std::string escape_logfmt_key(const std::string &key)
Escape a logfmt key (remove spaces and special characters)
static std::string level_to_lowercase(log_level level)
Convert log level to lowercase string.
std::string get_name() const override
Get formatter name.
std::string to_string() const
Convert to std::string.
static std::string format_iso8601(const std::chrono::system_clock::time_point &tp)
Format timestamp to ISO 8601 / RFC 3339 format with UTC timezone.
Definition time_utils.h:76
Data structures for representing log entries and source locations kcenon.
Interface for log message formatters (Strategy Pattern) kcenon.
common::interfaces::log_level log_level
std::variant< std::string, int64_t, double, bool > log_value
Value type for structured logging fields.
Definition log_entry.h:69
String utility functions for log formatting and conversion.
Configuration options for log formatting.
Represents a single log entry with all associated metadata.
Definition log_entry.h:155
std::optional< source_location > location
Optional source code location information.
Definition log_entry.h:183
std::optional< log_fields > fields
Optional structured fields for key-value logging.
Definition log_entry.h:213
std::optional< small_string_64 > thread_id
Optional thread identifier.
Definition log_entry.h:190
std::optional< otlp::otel_context > otel_ctx
Optional OpenTelemetry context for trace correlation.
Definition log_entry.h:204
log_level level
Severity level of the log message.
Definition log_entry.h:162
std::optional< small_string_128 > category
Optional category for log filtering and routing.
Definition log_entry.h:197
small_string_256 message
The actual log message.
Definition log_entry.h:169
std::chrono::system_clock::time_point timestamp
Timestamp when the log entry was created.
Definition log_entry.h:175
Time utility functions for timestamp formatting.