Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
template_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
54#include "../utils/time_utils.h"
56#include <sstream>
57#include <iomanip>
58#include <vector>
59#include <type_traits>
60#include <variant>
61
62// Use common_system's standard interface
63#include <kcenon/common/interfaces/logger_interface.h>
64
65namespace kcenon::logger {
66
88public:
92 static constexpr const char* DEFAULT_TEMPLATE =
93 "[{timestamp}] [{level}] [{thread_id}] {message}";
94
111 const std::string& template_pattern = DEFAULT_TEMPLATE,
112 const format_options& opts = format_options{}
113 ) : template_(template_pattern) {
114 options_ = opts;
116 }
117
131 [[nodiscard]] std::string format(const log_entry& entry) const override {
132 std::ostringstream oss;
133
134 for (const auto& segment : segments_) {
135 if (segment.is_placeholder) {
136 oss << resolve_placeholder(segment.content, segment.width, entry);
137 } else {
138 oss << segment.content;
139 }
140 }
141
142 return oss.str();
143 }
144
151 [[nodiscard]] std::string get_name() const override {
152 return "template_formatter";
153 }
154
161 [[nodiscard]] const std::string& get_template() const {
162 return template_;
163 }
164
173 void set_template(const std::string& template_pattern) {
174 template_ = template_pattern;
176 }
177
178private:
183 std::string content;
185 int width;
186
187 template_segment(const std::string& c, bool p, int w = 0)
188 : content(c), is_placeholder(p), width(w) {}
189 };
190
191 std::string template_;
192 std::vector<template_segment> segments_;
193
201 segments_.clear();
202
203 size_t pos = 0;
204 size_t len = template_.length();
205
206 while (pos < len) {
207 size_t start = template_.find('{', pos);
208
209 if (start == std::string::npos) {
210 // No more placeholders, add remaining text
211 if (pos < len) {
212 segments_.emplace_back(template_.substr(pos), false);
213 }
214 break;
215 }
216
217 // Add text before placeholder
218 if (start > pos) {
219 segments_.emplace_back(template_.substr(pos, start - pos), false);
220 }
221
222 // Find closing brace
223 size_t end = template_.find('}', start);
224 if (end == std::string::npos) {
225 // Unclosed brace, treat as literal
226 segments_.emplace_back(template_.substr(start), false);
227 break;
228 }
229
230 // Extract placeholder name and optional width
231 std::string placeholder = template_.substr(start + 1, end - start - 1);
232 int width = 0;
233
234 size_t colon_pos = placeholder.find(':');
235 if (colon_pos != std::string::npos) {
236 std::string width_str = placeholder.substr(colon_pos + 1);
237 placeholder = placeholder.substr(0, colon_pos);
238 try {
239 width = std::stoi(width_str);
240 } catch (...) {
241 width = 0;
242 }
243 }
244
245 segments_.emplace_back(placeholder, true, width);
246 pos = end + 1;
247 }
248 }
249
257 [[nodiscard]] std::string resolve_placeholder(
258 const std::string& name,
259 int width,
260 const log_entry& entry
261 ) const {
262 std::string value;
263
264 if (name == "timestamp") {
266 } else if (name == "timestamp_local") {
268 } else if (name == "level") {
269 std::string level_str = utils::string_utils::level_to_string(entry.level);
270 if (options_.use_colors) {
272 level_str +
274 } else {
275 value = level_str;
276 }
277 } else if (name == "level_lower") {
280 );
281 } else if (name == "message") {
282 value = entry.message.to_string();
283 } else if (name == "thread_id") {
284 if (entry.thread_id) {
285 value = entry.thread_id->to_string();
286 }
287 } else if (name == "file") {
288 if (entry.location) {
289 value = entry.location->file.to_string();
290 }
291 } else if (name == "filename") {
292 if (entry.location) {
294 entry.location->file.to_string()
295 );
296 }
297 } else if (name == "line") {
298 if (entry.location && entry.location->line > 0) {
299 value = std::to_string(entry.location->line);
300 }
301 } else if (name == "function") {
302 if (entry.location) {
303 value = entry.location->function.to_string();
304 }
305 } else if (name == "category") {
306 if (entry.category) {
307 value = entry.category->to_string();
308 }
309 } else if (name == "trace_id") {
310 if (entry.otel_ctx && !entry.otel_ctx->trace_id.empty()) {
311 value = entry.otel_ctx->trace_id;
312 }
313 } else if (name == "span_id") {
314 if (entry.otel_ctx && !entry.otel_ctx->span_id.empty()) {
315 value = entry.otel_ctx->span_id;
316 }
317 } else {
318 // Check structured fields
319 if (entry.fields) {
320 auto it = entry.fields->find(name);
321 if (it != entry.fields->end()) {
322 value = format_field_value(it->second);
323 }
324 }
325 }
326
327 // Apply width formatting if specified
328 if (width > 0 && !value.empty()) {
329 // Calculate actual display width (excluding ANSI codes)
330 size_t display_len = calculate_display_width(value);
331 if (display_len < static_cast<size_t>(width)) {
332 value += std::string(width - display_len, ' ');
333 }
334 }
335
336 return value;
337 }
338
344 [[nodiscard]] static std::string format_field_value(const log_value& value) {
345 return std::visit([](const auto& v) -> std::string {
346 using T = std::decay_t<decltype(v)>;
347 if constexpr (std::is_same_v<T, std::string>) {
348 return v;
349 } else if constexpr (std::is_same_v<T, bool>) {
350 return v ? "true" : "false";
351 } else if constexpr (std::is_same_v<T, int64_t>) {
352 return std::to_string(v);
353 } else if constexpr (std::is_same_v<T, double>) {
354 std::ostringstream oss;
355 oss << std::fixed << std::setprecision(6) << v;
356 return oss.str();
357 } else {
358 return std::to_string(v);
359 }
360 }, value);
361 }
362
368 [[nodiscard]] static size_t calculate_display_width(const std::string& str) {
369 size_t width = 0;
370 bool in_escape = false;
371
372 for (size_t i = 0; i < str.length(); ++i) {
373 if (str[i] == '\033') {
374 in_escape = true;
375 } else if (in_escape) {
376 if (str[i] == 'm') {
377 in_escape = false;
378 }
379 } else {
380 ++width;
381 }
382 }
383
384 return width;
385 }
386};
387
388} // namespace kcenon::logger
Abstract interface for log message formatters.
std::string to_string() const
Convert to std::string.
Customizable formatter using template strings with placeholders.
static constexpr const char * DEFAULT_TEMPLATE
Default template pattern.
static std::string format_field_value(const log_value &value)
Format a structured field value to string.
std::string format(const log_entry &entry) const override
Format a log entry using the template.
std::string resolve_placeholder(const std::string &name, int width, const log_entry &entry) const
Resolve a placeholder to its actual value.
std::string get_name() const override
Get formatter name.
static size_t calculate_display_width(const std::string &str)
Calculate display width excluding ANSI escape codes.
template_formatter(const std::string &template_pattern=DEFAULT_TEMPLATE, const format_options &opts=format_options{})
const std::string & get_template() const
Get current template pattern.
void parse_template()
Parse template string into segments.
void set_template(const std::string &template_pattern)
Set a new template pattern.
std::vector< template_segment > segments_
Parsed template segments.
std::string template_
Original template string.
static std::string level_to_color(log_level level, bool use_colors=true)
Convert log level to ANSI color code.
static std::string level_to_string(log_level level)
Convert log level to human-readable string.
static const char * color_reset()
ANSI color reset sequence.
static std::string to_lower(const std::string &str)
Convert string to lowercase.
static std::string extract_filename(const std::string &file_path)
Extract filename from full file path.
static std::string format_timestamp(const std::chrono::system_clock::time_point &tp)
Format timestamp to human-readable format (YYYY-MM-DD HH:MM:SS.mmm)
Definition time_utils.h:38
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.
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
std::string content
Text content or placeholder name.
template_segment(const std::string &c, bool p, int w=0)
int width
Optional field width (0 = no padding)
Time utility functions for timestamp formatting.