Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
kcenon::logger::otlp_writer Class Reference

OTLP log exporter for OpenTelemetry integration. More...

#include <otlp_writer.h>

Inheritance diagram for kcenon::logger::otlp_writer:
Inheritance graph
Collaboration diagram for kcenon::logger::otlp_writer:
Collaboration graph

Classes

struct  config
 Configuration for OTLP writer. More...
 
struct  export_stats
 Statistics snapshot for OTLP export (copyable) More...
 
struct  internal_stats
 Internal atomic statistics (non-copyable) More...
 

Public Types

enum class  protocol_type { http , grpc }
 Transport protocol for OTLP export. More...
 

Public Member Functions

 otlp_writer (const config &cfg)
 Constructor with configuration.
 
 ~otlp_writer () override
 Destructor - flushes remaining logs.
 
 otlp_writer (const otlp_writer &)=delete
 
otlp_writeroperator= (const otlp_writer &)=delete
 
 otlp_writer (otlp_writer &&)=delete
 
otlp_writeroperator= (otlp_writer &&)=delete
 
common::VoidResult write (const log_entry &entry) override
 Write a log entry with OTEL context.
 
common::VoidResult flush () override
 Flush pending logs.
 
std::string get_name () const override
 Get writer name.
 
bool is_healthy () const override
 Check if writer is healthy.
 
export_stats get_stats () const
 Get export statistics.
 
void force_export ()
 Force immediate export of current batch.
 
- Public Member Functions inherited from kcenon::logger::base_writer
 base_writer (std::unique_ptr< log_formatter_interface > formatter=nullptr)
 Constructor with optional formatter.
 
virtual ~base_writer ()=default
 
virtual void set_use_color (bool use_color)
 Set whether to use color output (if supported)
 
bool use_color () const
 Get current color output setting.
 
log_formatter_interfaceget_formatter () const
 Get the current formatter.
 
- Public Member Functions inherited from kcenon::logger::log_writer_interface
virtual ~log_writer_interface ()=default
 
virtual common::VoidResult close ()
 Close the writer and release resources.
 
virtual auto is_open () const -> bool
 Check if writer is open and ready.
 

Private Member Functions

void export_thread_func ()
 
bool export_batch (const std::vector< log_entry > &batch)
 
bool export_with_http (const std::vector< log_entry > &batch)
 

Static Private Member Functions

static int to_otlp_severity (common::interfaces::log_level level)
 
static std::string escape_json (const std::string &str)
 

Private Attributes

config config_
 
internal_stats stats_
 
std::queue< log_entryqueue_
 
std::mutex queue_mutex_
 
std::condition_variable queue_cv_
 
std::unique_ptr< std::thread > export_thread_
 
std::atomic< bool > running_ {false}
 
std::atomic< bool > healthy_ {true}
 

Additional Inherited Members

- Static Public Attributes inherited from kcenon::logger::async_writer_tag
static constexpr writer_category category = writer_category::asynchronous
 
- Protected Member Functions inherited from kcenon::logger::base_writer
std::string format_log_entry (const log_entry &entry) const
 Format a log entry using the current formatter.
 

Detailed Description

OTLP log exporter for OpenTelemetry integration.

Exports log records to OpenTelemetry collectors using the OTLP protocol. Supports:

  • HTTP and gRPC transport
  • Batch export for efficiency
  • Automatic retry with exponential backoff
  • Graceful degradation when collector unavailable
Note
This writer batches logs for network efficiency. Logs may be delayed by up to flush_interval before being sent.
Warning
Requires OpenTelemetry collector endpoint to be available. Logs are dropped if collector is unavailable and queue is full.

Category: Asynchronous (batched network export with background thread)

Since
3.0.0
1.4.0 Added async_writer_tag for category classification

Definition at line 74 of file otlp_writer.h.

Member Enumeration Documentation

◆ protocol_type

Transport protocol for OTLP export.

Enumerator
http 

OTLP/HTTP (port 4318 by default)

grpc 

OTLP/gRPC (port 4317 by default)

Definition at line 80 of file otlp_writer.h.

80 {
81 http,
82 grpc
83 };
@ grpc
OTLP/gRPC (port 4317 by default)
@ http
OTLP/HTTP (port 4318 by default)

Constructor & Destructor Documentation

◆ otlp_writer() [1/3]

kcenon::logger::otlp_writer::otlp_writer ( const config & cfg)
explicit

Constructor with configuration.

Parameters
cfgConfiguration for OTLP export

Definition at line 113 of file otlp_writer.cpp.

114 : config_(cfg) {
115
116#ifdef LOGGER_HAS_OTLP
117 otel_impl_ = std::make_unique<otel_impl>(cfg);
118#endif
119
120 // Start background export thread
121 running_.store(true);
122 export_thread_ = std::make_unique<std::thread>([this]() {
124 });
125}
std::unique_ptr< std::thread > export_thread_
std::atomic< bool > running_

References export_thread_, export_thread_func(), and running_.

Here is the call graph for this function:

◆ ~otlp_writer()

kcenon::logger::otlp_writer::~otlp_writer ( )
override

Destructor - flushes remaining logs.

Definition at line 127 of file otlp_writer.cpp.

127 {
128 // Stop background thread
129 running_.store(false);
130
131 // Wake up export thread
132 {
133 std::lock_guard<std::mutex> lock(queue_mutex_);
134 queue_cv_.notify_one();
135 }
136
137 // Wait for thread to finish
138 if (export_thread_ && export_thread_->joinable()) {
139 export_thread_->join();
140 }
141
142 // Export remaining logs
143 std::vector<log_entry> remaining;
144 {
145 std::lock_guard<std::mutex> lock(queue_mutex_);
146 while (!queue_.empty()) {
147 remaining.push_back(std::move(queue_.front()));
148 queue_.pop();
149 }
150 }
151
152 if (!remaining.empty()) {
153 export_batch(remaining);
154 }
155}
std::condition_variable queue_cv_
std::queue< log_entry > queue_
bool export_batch(const std::vector< log_entry > &batch)

References export_batch(), export_thread_, queue_, queue_cv_, queue_mutex_, and running_.

Here is the call graph for this function:

◆ otlp_writer() [2/3]

kcenon::logger::otlp_writer::otlp_writer ( const otlp_writer & )
delete

◆ otlp_writer() [3/3]

kcenon::logger::otlp_writer::otlp_writer ( otlp_writer && )
delete

Member Function Documentation

◆ escape_json()

std::string kcenon::logger::otlp_writer::escape_json ( const std::string & str)
staticprivate

Definition at line 457 of file otlp_writer.cpp.

457 {
458 std::ostringstream result;
459 for (char c : str) {
460 switch (c) {
461 case '"': result << "\\\""; break;
462 case '\\': result << "\\\\"; break;
463 case '\b': result << "\\b"; break;
464 case '\f': result << "\\f"; break;
465 case '\n': result << "\\n"; break;
466 case '\r': result << "\\r"; break;
467 case '\t': result << "\\t"; break;
468 default:
469 if (static_cast<unsigned char>(c) < 0x20) {
470 result << "\\u" << std::hex << std::setfill('0')
471 << std::setw(4) << static_cast<int>(c);
472 } else {
473 result << c;
474 }
475 }
476 }
477 return result.str();
478}

Referenced by export_with_http().

Here is the caller graph for this function:

◆ export_batch()

bool kcenon::logger::otlp_writer::export_batch ( const std::vector< log_entry > & batch)
private

Definition at line 262 of file otlp_writer.cpp.

262 {
263 bool success = false;
264 std::size_t retries = 0;
265 auto delay = config_.retry_delay;
266
267 while (!success && retries <= config_.max_retries) {
268 if (retries > 0) {
269 stats_.retries.fetch_add(1, std::memory_order_relaxed);
270 std::this_thread::sleep_for(delay);
271 delay *= 2; // Exponential backoff
272 }
273
274#ifdef LOGGER_HAS_OTLP
275 success = export_with_otel_sdk(batch);
276#else
277 success = export_with_http(batch);
278#endif
279
280 ++retries;
281 }
282
283 if (success) {
284 stats_.logs_exported.fetch_add(batch.size(), std::memory_order_relaxed);
285 stats_.export_success.fetch_add(1, std::memory_order_relaxed);
286 stats_.last_export = std::chrono::system_clock::now();
287 healthy_.store(true, std::memory_order_release);
288 } else {
289 stats_.logs_dropped.fetch_add(batch.size(), std::memory_order_relaxed);
290 stats_.export_failures.fetch_add(1, std::memory_order_relaxed);
291 stats_.last_error = std::chrono::system_clock::now();
292 healthy_.store(false, std::memory_order_release);
293 }
294
295 return success;
296}
bool export_with_http(const std::vector< log_entry > &batch)
std::atomic< bool > healthy_
std::size_t max_retries
Number of retry attempts on failure.
std::chrono::milliseconds retry_delay
Initial retry delay (doubled on each retry)
std::chrono::system_clock::time_point last_error
std::chrono::system_clock::time_point last_export

References config_, kcenon::logger::otlp_writer::internal_stats::export_failures, kcenon::logger::otlp_writer::internal_stats::export_success, export_with_http(), healthy_, kcenon::logger::otlp_writer::internal_stats::last_error, kcenon::logger::otlp_writer::internal_stats::last_export, kcenon::logger::otlp_writer::internal_stats::logs_dropped, kcenon::logger::otlp_writer::internal_stats::logs_exported, kcenon::logger::otlp_writer::config::max_retries, kcenon::logger::otlp_writer::internal_stats::retries, kcenon::logger::otlp_writer::config::retry_delay, stats_, and kcenon::logger::success.

Referenced by export_thread_func(), force_export(), and ~otlp_writer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ export_thread_func()

void kcenon::logger::otlp_writer::export_thread_func ( )
private

Definition at line 231 of file otlp_writer.cpp.

231 {
232 while (running_.load(std::memory_order_acquire)) {
233 std::vector<log_entry> batch;
234
235 {
236 std::unique_lock<std::mutex> lock(queue_mutex_);
237
238 // Wait for batch size or timeout
239 queue_cv_.wait_for(lock, config_.flush_interval, [this] {
240 return !running_.load(std::memory_order_acquire) ||
241 queue_.size() >= config_.max_batch_size;
242 });
243
244 if (!running_.load(std::memory_order_acquire) && queue_.empty()) {
245 break;
246 }
247
248 // Collect batch
249 while (!queue_.empty() && batch.size() < config_.max_batch_size) {
250 batch.push_back(std::move(queue_.front()));
251 queue_.pop();
252 }
253 }
254
255 // Export batch
256 if (!batch.empty()) {
257 export_batch(batch);
258 }
259 }
260}
std::chrono::milliseconds flush_interval
Maximum time to wait before flushing batch.
std::size_t max_batch_size
Maximum batch size before forced flush.

References config_, export_batch(), kcenon::logger::otlp_writer::config::flush_interval, kcenon::logger::otlp_writer::config::max_batch_size, queue_, queue_cv_, queue_mutex_, and running_.

Referenced by otlp_writer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ export_with_http()

bool kcenon::logger::otlp_writer::export_with_http ( const std::vector< log_entry > & batch)
private

Definition at line 376 of file otlp_writer.cpp.

376 {
377 // Fallback implementation without OpenTelemetry SDK
378 // This is a minimal HTTP export implementation for demonstration
379 // In production, you would use a proper HTTP client library
380
381 // Build JSON payload following OTLP/HTTP JSON format
382 std::ostringstream json;
383 json << R"({"resourceLogs":[{"resource":{"attributes":[)";
384
385 // Add service name attribute
386 json << R"({"key":"service.name","value":{"stringValue":")"
387 << config_.service_name << R"("}}]},)";
388
389 json << R"("scopeLogs":[{"logRecords":[)";
390
391 bool first = true;
392 for (const auto& entry : batch) {
393 if (!first) json << ",";
394 first = false;
395
396 // Convert log_level from logger_system to common::interfaces
397 auto level = static_cast<common::interfaces::log_level>(static_cast<int>(entry.level));
398
399 // Convert timestamp to nanoseconds
400 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
401 entry.timestamp.time_since_epoch()).count();
402
403 json << R"({"timeUnixNano":")" << ns << R"(",)";
404 json << R"("severityNumber":)" << to_otlp_severity(level) << ",";
405 json << R"("body":{"stringValue":")" << escape_json(entry.message.to_string()) << R"("},)";
406
407 // Add attributes
408 json << R"("attributes":[)";
409 bool first_attr = true;
410
411 if (entry.location) {
412 std::string file = entry.location->file.to_string();
413
414 if (!file.empty()) {
415 if (!first_attr) json << ",";
416 first_attr = false;
417 json << R"({"key":"code.filepath","value":{"stringValue":")"
418 << escape_json(file) << R"("}})";
419 }
420
421 if (entry.location->line > 0) {
422 if (!first_attr) json << ",";
423 first_attr = false;
424 json << R"({"key":"code.lineno","value":{"intValue":")"
425 << entry.location->line << R"("}})";
426 }
427 }
428
429 if (entry.otel_ctx) {
430 if (!first_attr) json << ",";
431 first_attr = false;
432 json << R"({"key":"trace_id","value":{"stringValue":")"
433 << entry.otel_ctx->trace_id << R"("}},)";
434 json << R"({"key":"span_id","value":{"stringValue":")"
435 << entry.otel_ctx->span_id << R"("}})";
436 }
437
438 json << "]}";
439 }
440
441 json << "]}]}]}";
442
443 // OTLP HTTP transport is not available (LOGGER_HAS_OTLP not defined).
444 // The JSON payload was built but cannot be sent without an HTTP client.
445 // Return false so callers know export did not succeed.
446 static bool warned = false;
447 if (!warned)
448 {
449 std::cerr << "[logger_system] WARNING: OTLP export configured but no HTTP transport "
450 << "available. Build with LOGGER_ENABLE_OTLP=ON and OpenTelemetry SDK, "
451 << "or provide an HTTP client library. Log data is NOT being exported.\n";
452 warned = true;
453 }
454 return false;
455}
static int to_otlp_severity(common::interfaces::log_level level)
static std::string escape_json(const std::string &str)
@ json
JSON structured format.
std::string service_name
Service name (required for resource attributes)

References config_, escape_json(), kcenon::logger::json, kcenon::logger::otlp_writer::config::service_name, and to_otlp_severity().

Referenced by export_batch().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ flush()

common::VoidResult kcenon::logger::otlp_writer::flush ( )
overridevirtual

Flush pending logs.

Implements kcenon::logger::base_writer.

Definition at line 194 of file otlp_writer.cpp.

194 {
195 force_export();
196 return common::ok();
197}
void force_export()
Force immediate export of current batch.
VoidResult ok()

References force_export(), and kcenon::common::ok().

Here is the call graph for this function:

◆ force_export()

void kcenon::logger::otlp_writer::force_export ( )

Force immediate export of current batch.

Definition at line 215 of file otlp_writer.cpp.

215 {
216 std::vector<log_entry> batch;
217
218 {
219 std::lock_guard<std::mutex> lock(queue_mutex_);
220 while (!queue_.empty()) {
221 batch.push_back(std::move(queue_.front()));
222 queue_.pop();
223 }
224 }
225
226 if (!batch.empty()) {
227 export_batch(batch);
228 }
229}

References export_batch(), queue_, and queue_mutex_.

Referenced by flush().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_name()

std::string kcenon::logger::otlp_writer::get_name ( ) const
inlineoverridevirtual

Get writer name.

Implements kcenon::logger::base_writer.

Definition at line 230 of file otlp_writer.h.

230{ return "otlp"; }

◆ get_stats()

otlp_writer::export_stats kcenon::logger::otlp_writer::get_stats ( ) const

◆ is_healthy()

bool kcenon::logger::otlp_writer::is_healthy ( ) const
overridevirtual

Check if writer is healthy.

Reimplemented from kcenon::logger::base_writer.

Definition at line 199 of file otlp_writer.cpp.

199 {
200 return healthy_.load(std::memory_order_acquire);
201}

References healthy_.

◆ operator=() [1/2]

otlp_writer & kcenon::logger::otlp_writer::operator= ( const otlp_writer & )
delete

◆ operator=() [2/2]

otlp_writer & kcenon::logger::otlp_writer::operator= ( otlp_writer && )
delete

◆ to_otlp_severity()

int kcenon::logger::otlp_writer::to_otlp_severity ( common::interfaces::log_level level)
staticprivate

Definition at line 298 of file otlp_writer.cpp.

298 {
299 // OTLP Severity Numbers (per OpenTelemetry specification)
300 // https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
301 switch (level) {
302 case common::interfaces::log_level::trace:
303 return 1; // TRACE
304 case common::interfaces::log_level::debug:
305 return 5; // DEBUG
306 case common::interfaces::log_level::info:
307 return 9; // INFO
308 case common::interfaces::log_level::warn:
309 return 13; // WARN
310 case common::interfaces::log_level::error:
311 return 17; // ERROR
312 case common::interfaces::log_level::fatal:
313 return 21; // FATAL
314 default:
315 return 9; // INFO as default
316 }
317}

Referenced by export_with_http().

Here is the caller graph for this function:

◆ write()

common::VoidResult kcenon::logger::otlp_writer::write ( const log_entry & entry)
overridevirtual

Write a log entry with OTEL context.

Parameters
entryThe log entry to write
Returns
common::VoidResult indicating success or error
Since
3.5.0 This is now the only write method (legacy API removed)

Implements kcenon::logger::base_writer.

Definition at line 157 of file otlp_writer.cpp.

157 {
158 {
159 std::lock_guard<std::mutex> lock(queue_mutex_);
160
161 // Check queue size limit
162 if (queue_.size() >= config_.max_queue_size) {
163 stats_.logs_dropped.fetch_add(1, std::memory_order_relaxed);
164 return common::ok(); // Drop silently to avoid backpressure
165 }
166
167 // Create a copy of the entry since log_entry is move-only
168 if (entry.location) {
169 queue_.emplace(entry.level,
170 entry.message.to_string(),
171 entry.location->file.to_string(),
172 entry.location->line,
173 entry.location->function.to_string(),
174 entry.timestamp);
175 } else {
176 queue_.emplace(entry.level,
177 entry.message.to_string(),
178 entry.timestamp);
179 }
180
181 // Copy OTEL context to the queued entry
182 auto& queued_entry = queue_.back();
183 queued_entry.otel_ctx = entry.otel_ctx.has_value() ? entry.otel_ctx : otlp::otel_context_storage::get();
184
185 // Wake up export thread if batch size reached
186 if (queue_.size() >= config_.max_batch_size) {
187 queue_cv_.notify_one();
188 }
189 }
190
191 return common::ok();
192}
static std::optional< otel_context > get()
Get the OpenTelemetry context for the current thread.
std::size_t max_queue_size
Maximum queue size (logs dropped if exceeded)

References config_, kcenon::logger::otlp::otel_context_storage::get(), kcenon::logger::log_entry::level, kcenon::logger::log_entry::location, kcenon::logger::otlp_writer::internal_stats::logs_dropped, kcenon::logger::otlp_writer::config::max_batch_size, kcenon::logger::otlp_writer::config::max_queue_size, kcenon::logger::log_entry::message, kcenon::common::ok(), kcenon::logger::log_entry::otel_ctx, queue_, queue_cv_, queue_mutex_, stats_, kcenon::logger::log_entry::timestamp, and kcenon::logger::small_string< SSO_SIZE >::to_string().

Here is the call graph for this function:

Member Data Documentation

◆ config_

config kcenon::logger::otlp_writer::config_
private

Definition at line 269 of file otlp_writer.h.

Referenced by export_batch(), export_thread_func(), export_with_http(), and write().

◆ export_thread_

std::unique_ptr<std::thread> kcenon::logger::otlp_writer::export_thread_
private

Definition at line 278 of file otlp_writer.h.

Referenced by otlp_writer(), and ~otlp_writer().

◆ healthy_

std::atomic<bool> kcenon::logger::otlp_writer::healthy_ {true}
private

Definition at line 280 of file otlp_writer.h.

280{true};

Referenced by export_batch(), and is_healthy().

◆ queue_

std::queue<log_entry> kcenon::logger::otlp_writer::queue_
private

Definition at line 273 of file otlp_writer.h.

Referenced by export_thread_func(), force_export(), write(), and ~otlp_writer().

◆ queue_cv_

std::condition_variable kcenon::logger::otlp_writer::queue_cv_
private

Definition at line 275 of file otlp_writer.h.

Referenced by export_thread_func(), write(), and ~otlp_writer().

◆ queue_mutex_

std::mutex kcenon::logger::otlp_writer::queue_mutex_
mutableprivate

Definition at line 274 of file otlp_writer.h.

Referenced by export_thread_func(), force_export(), write(), and ~otlp_writer().

◆ running_

std::atomic<bool> kcenon::logger::otlp_writer::running_ {false}
private

Definition at line 279 of file otlp_writer.h.

279{false};

Referenced by export_thread_func(), otlp_writer(), and ~otlp_writer().

◆ stats_

internal_stats kcenon::logger::otlp_writer::stats_
private

Definition at line 270 of file otlp_writer.h.

Referenced by export_batch(), get_stats(), and write().


The documentation for this class was generated from the following files: