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

Sends logs over network (TCP/UDP) More...

#include <network_writer.h>

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

Classes

struct  connection_stats
 Get connection statistics. More...
 

Public Types

enum class  protocol_type { tcp , udp }
 

Public Member Functions

 network_writer (const std::string &host, uint16_t port, protocol_type protocol=protocol_type::tcp, size_t buffer_size=8192, std::chrono::seconds reconnect_interval=std::chrono::seconds(5))
 Constructor.
 
 ~network_writer () override
 Destructor.
 
common::VoidResult write (const log_entry &entry) override
 Write log entry.
 
common::VoidResult flush () override
 Flush pending logs.
 
std::string get_name () const override
 Get writer name.
 
bool is_connected () const
 Check if connected.
 
connection_stats get_stats () const
 
void set_integrity_policy (std::shared_ptr< security::integrity_policy > policy)
 Enable tamper-evident integrity signing for outbound frames.
 
- 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.
 
virtual bool is_healthy () const override
 Check if the writer is healthy and operational.
 
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

bool connect ()
 
void disconnect ()
 
bool send_data (const std::string &data)
 
void process_buffer ()
 
void attempt_reconnect ()
 
std::string format_for_network (const log_entry &entry)
 
std::string escape_json (const std::string &str) const
 

Private Attributes

std::string host_
 
uint16_t port_
 
protocol_type protocol_
 
size_t buffer_size_
 
std::chrono::seconds reconnect_interval_
 
int socket_fd_
 
std::atomic< bool > connected_ {false}
 
std::atomic< bool > running_ {false}
 
std::queue< log_entrybuffer_
 
std::mutex buffer_mutex_
 
std::condition_variable buffer_cv_
 
std::unique_ptr< network_send_jthread_workersend_worker_
 
std::unique_ptr< network_reconnect_jthread_workerreconnect_worker_
 
std::mutex stats_mutex_
 
connection_stats stats_ {}
 
std::shared_ptr< security::integrity_policyintegrity_policy_
 

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

Sends logs over network (TCP/UDP)

Category: Asynchronous (non-blocking network I/O with background threads)

Since
1.4.0 Added async_writer_tag for category classification

Definition at line 43 of file network_writer.h.

Member Enumeration Documentation

◆ protocol_type

Constructor & Destructor Documentation

◆ network_writer()

kcenon::logger::network_writer::network_writer ( const std::string & host,
uint16_t port,
protocol_type protocol = protocol_type::tcp,
size_t buffer_size = 8192,
std::chrono::seconds reconnect_interval = std::chrono::seconds(5) )

Constructor.

Parameters
hostRemote host address
portRemote port
protocolNetwork protocol (TCP/UDP)
buffer_sizeInternal buffer size
reconnect_intervalReconnection interval in seconds

Definition at line 199 of file network_writer.cpp.

204 : host_(host)
205 , port_(port)
206 , protocol_(protocol)
207 , buffer_size_(buffer_size)
208 , reconnect_interval_(reconnect_interval)
209 , socket_fd_(-1) {
210
211#ifdef _WIN32
212 // Initialize Winsock
213 WSADATA wsaData;
214 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
215 throw std::runtime_error("Failed to initialize Winsock");
216 }
217#endif
218
219 running_ = true;
220
221 // Create and start send worker
222 send_worker_ = std::make_unique<network_send_jthread_worker>(
223 [this] { process_buffer(); });
224 send_worker_->start();
225
226 // Create and start reconnect worker for TCP
228 reconnect_worker_ = std::make_unique<network_reconnect_jthread_worker>(
230 reconnect_worker_->start();
231 }
232
233 // Initial connection attempt
234 connect();
235}
std::chrono::seconds reconnect_interval_
std::unique_ptr< network_reconnect_jthread_worker > reconnect_worker_
std::unique_ptr< network_send_jthread_worker > send_worker_

References attempt_reconnect(), connect(), process_buffer(), protocol_, reconnect_interval_, reconnect_worker_, running_, send_worker_, and tcp.

Here is the call graph for this function:

◆ ~network_writer()

kcenon::logger::network_writer::~network_writer ( )
override

Destructor.

Definition at line 237 of file network_writer.cpp.

237 {
238 running_ = false;
239
240 // Stop workers with error handling
241 // IMPORTANT: Reset workers after stop to join their threads before accessing any members
242 utils::safe_destructor_operation("send_worker_stop", [this]() {
243 if (send_worker_) {
244 send_worker_->stop();
245 send_worker_.reset();
246 }
247 });
248
249 utils::safe_destructor_operation("reconnect_worker_stop", [this]() {
250 if (reconnect_worker_) {
251 reconnect_worker_->stop();
252 reconnect_worker_.reset();
253 }
254 });
255
256 // Disconnect with error handling
257 utils::safe_destructor_operation("network_disconnect", [this]() {
258 disconnect();
259 });
260
261#ifdef _WIN32
262 // Cleanup Winsock with error handling
263 utils::safe_destructor_operation("winsock_cleanup", []() {
264 WSACleanup();
265 });
266#endif
267}
void safe_destructor_operation(const std::string &operation_name, F &&operation) noexcept
Safe operation execution for destructors.

References disconnect(), reconnect_worker_, running_, kcenon::logger::utils::safe_destructor_operation(), and send_worker_.

Here is the call graph for this function:

Member Function Documentation

◆ attempt_reconnect()

void kcenon::logger::network_writer::attempt_reconnect ( )
private

Definition at line 436 of file network_writer.cpp.

436 {
437 if (!connected_ && running_) {
438 connect();
439 }
440}

References connect(), connected_, and running_.

Referenced by network_writer().

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

◆ connect()

bool kcenon::logger::network_writer::connect ( )
private

Definition at line 333 of file network_writer.cpp.

333 {
334 if (connected_) {
335 return true;
336 }
337
338 // Resolve hostname using getaddrinfo (thread-safe, IPv4/IPv6)
339 int sock_type = (protocol_ == protocol_type::tcp) ? SOCK_STREAM : SOCK_DGRAM;
340
341 struct addrinfo hints{}, *result = nullptr;
342 hints.ai_family = AF_UNSPEC;
343 hints.ai_socktype = sock_type;
344
345 auto port_str = std::to_string(port_);
346 int rv = getaddrinfo(host_.c_str(), port_str.c_str(), &hints, &result);
347 if (rv != 0) {
348 std::lock_guard<std::mutex> lock(stats_mutex_);
350 stats_.last_error = std::chrono::system_clock::now();
351 return false;
352 }
353
354 // Try each resolved address until one succeeds
355 for (auto* rp = result; rp != nullptr; rp = rp->ai_next) {
356 socket_fd_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
357 if (socket_fd_ < 0) {
358 continue;
359 }
360
361 if (::connect(socket_fd_, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen)) == 0) {
362 freeaddrinfo(result);
363 connected_ = true;
364
365 std::lock_guard<std::mutex> lock(stats_mutex_);
366 stats_.last_connected = std::chrono::system_clock::now();
367 return true;
368 }
369
371 socket_fd_ = -1;
372 }
373
374 freeaddrinfo(result);
375
376 std::lock_guard<std::mutex> lock(stats_mutex_);
378 stats_.last_error = std::chrono::system_clock::now();
379 return false;
380}
virtual common::VoidResult close()
Close the writer and release resources.
std::chrono::system_clock::time_point last_connected
std::chrono::system_clock::time_point last_error

References kcenon::logger::log_writer_interface::close(), connect(), connected_, kcenon::logger::network_writer::connection_stats::connection_failures, host_, kcenon::logger::network_writer::connection_stats::last_connected, kcenon::logger::network_writer::connection_stats::last_error, port_, protocol_, socket_fd_, stats_, stats_mutex_, and tcp.

Referenced by attempt_reconnect(), connect(), and network_writer().

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

◆ disconnect()

void kcenon::logger::network_writer::disconnect ( )
private

Definition at line 382 of file network_writer.cpp.

382 {
383 if (socket_fd_ >= 0) {
385 socket_fd_ = -1;
386 }
387 connected_ = false;
388}

References kcenon::logger::log_writer_interface::close(), connected_, and socket_fd_.

Referenced by send_data(), and ~network_writer().

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

◆ escape_json()

std::string kcenon::logger::network_writer::escape_json ( const std::string & str) const
private

Definition at line 503 of file network_writer.cpp.

503 {
504 std::string escaped;
505 for (char c : str) {
506 if (c == '"') escaped += "\\\"";
507 else if (c == '\\') escaped += "\\\\";
508 else if (c == '\n') escaped += "\\n";
509 else if (c == '\r') escaped += "\\r";
510 else if (c == '\t') escaped += "\\t";
511 else escaped += c;
512 }
513 return escaped;
514}

Referenced by format_for_network().

Here is the caller graph for this function:

◆ flush()

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

Flush pending logs.

Implements kcenon::logger::base_writer.

Definition at line 304 of file network_writer.cpp.

304 {
305 std::unique_lock<std::mutex> lock(buffer_mutex_);
306 auto start = std::chrono::steady_clock::now();
307 auto timeout = std::chrono::seconds(5); // 5 second timeout
308
309 while (!buffer_.empty()) {
310 if (buffer_cv_.wait_for(lock, timeout, [this] { return buffer_.empty() || !running_; })) {
311 if (!buffer_.empty() && !running_) {
313 "Network writer stopped before flush completed");
314 }
315 } else {
317 "Network flush timeout");
318 }
319
320 if (std::chrono::steady_clock::now() - start > timeout) {
322 "Network flush exceeded timeout");
323 }
324 }
325 return common::ok();
326}
std::condition_variable buffer_cv_
std::queue< log_entry > buffer_
VoidResult ok()
common::VoidResult make_logger_void_result(logger_error_code code, const std::string &message="")

References buffer_, buffer_cv_, buffer_mutex_, kcenon::logger::flush_timeout, kcenon::logger::make_logger_void_result(), kcenon::common::ok(), and running_.

Here is the call graph for this function:

◆ format_for_network()

std::string kcenon::logger::network_writer::format_for_network ( const log_entry & entry)
private

Definition at line 442 of file network_writer.cpp.

442 {
443 // Format as JSON for network transmission
444 std::ostringstream oss;
445 oss << "{";
446
447 // Timestamp
448 auto time_t = std::chrono::system_clock::to_time_t(entry.timestamp);
449 oss << "\"@timestamp\":\"";
450 oss << std::put_time(std::gmtime(&time_t), "%Y-%m-%dT%H:%M:%SZ") << "\",";
451
452 // Level - convert logger_system::log_level to common::interfaces::log_level
453 auto level = static_cast<common::interfaces::log_level>(static_cast<int>(entry.level));
454 oss << "\"level\":\"" << utils::string_utils::level_to_string(level) << "\",";
455
456 // Message
457 oss << "\"message\":\"" << escape_json(entry.message.to_string()) << "\"";
458
459 // Optional fields from source_location
460 if (entry.location) {
461 std::string file = entry.location->file.to_string();
462 std::string function = entry.location->function.to_string();
463
464 if (!file.empty()) {
465 oss << ",\"file\":\"" << escape_json(file) << "\"";
466 oss << ",\"line\":" << entry.location->line;
467 }
468
469 if (!function.empty()) {
470 oss << ",\"function\":\"" << escape_json(function) << "\"";
471 }
472 }
473
474 // Add hostname
475 char hostname[256];
476 if (gethostname(hostname, sizeof(hostname)) == 0) {
477 oss << ",\"host\":\"" << hostname << "\"";
478 }
479
480 // Tamper-evident signature (Issue #612). The signature covers the
481 // unsigned JSON body (closed with '}'); verifiers recompute by
482 // stripping the trailing ,"sig_alg":"..","signature":".." suffix,
483 // which is a stable canonical form on the wire.
484 if (integrity_policy_) {
485 std::string unsigned_body = oss.str() + "}";
486 std::string sig = integrity_policy_->sign(unsigned_body);
487 if (!sig.empty()) {
488 oss << ",\"sig_alg\":\"" << integrity_policy_->name() << "\"";
489 oss << ",\"signature\":\"" << sig << "\"";
490 }
491 }
492
493 oss << "}\n";
494 return oss.str();
495}
std::shared_ptr< security::integrity_policy > integrity_policy_
std::string escape_json(const std::string &str) const
static std::string level_to_string(log_level level)
Convert log level to human-readable string.

References escape_json(), integrity_policy_, kcenon::logger::log_entry::level, kcenon::logger::utils::string_utils::level_to_string(), kcenon::logger::log_entry::location, kcenon::logger::log_entry::message, kcenon::logger::log_entry::timestamp, and kcenon::logger::small_string< SSO_SIZE >::to_string().

Referenced by process_buffer().

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

◆ get_name()

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

Get writer name.

Implements kcenon::logger::base_writer.

Definition at line 85 of file network_writer.h.

85{ return "network"; }

◆ get_stats()

network_writer::connection_stats kcenon::logger::network_writer::get_stats ( ) const

Definition at line 328 of file network_writer.cpp.

328 {
329 std::lock_guard<std::mutex> lock(stats_mutex_);
330 return stats_;
331}

References stats_, and stats_mutex_.

◆ is_connected()

bool kcenon::logger::network_writer::is_connected ( ) const
inline

Check if connected.

Definition at line 90 of file network_writer.h.

90{ return connected_.load(); }

◆ process_buffer()

void kcenon::logger::network_writer::process_buffer ( )
private

Definition at line 419 of file network_writer.cpp.

419 {
420 std::unique_lock<std::mutex> lock(buffer_mutex_);
421
422 // Process buffered logs
423 while (!buffer_.empty() && running_) {
424 auto entry = std::move(buffer_.front());
425 buffer_.pop();
426 lock.unlock();
427
428 // Format and send
429 std::string formatted = format_for_network(entry);
430 send_data(formatted);
431
432 lock.lock();
433 }
434}
std::string format_for_network(const log_entry &entry)
bool send_data(const std::string &data)

References buffer_, buffer_mutex_, format_for_network(), running_, and send_data().

Referenced by network_writer().

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

◆ send_data()

bool kcenon::logger::network_writer::send_data ( const std::string & data)
private

Definition at line 390 of file network_writer.cpp.

390 {
391 if (!connected_ || socket_fd_ < 0) {
392 return false;
393 }
394
395#ifdef _WIN32
396 int sent = ::send(socket_fd_, data.c_str(), static_cast<int>(data.length()), 0);
397#else
398 ssize_t sent = ::send(socket_fd_, data.c_str(), data.length(), 0);
399#endif
400 if (sent < 0) {
402 // TCP connection lost
403 disconnect();
404
405 std::lock_guard<std::mutex> lock(stats_mutex_);
407 stats_.last_error = std::chrono::system_clock::now();
408 }
409 return false;
410 }
411
412 std::lock_guard<std::mutex> lock(stats_mutex_);
414 stats_.bytes_sent += sent;
415
416 return true;
417}

References kcenon::logger::network_writer::connection_stats::bytes_sent, connected_, disconnect(), kcenon::logger::network_writer::connection_stats::last_error, kcenon::logger::network_writer::connection_stats::messages_sent, protocol_, kcenon::logger::network_writer::connection_stats::send_failures, socket_fd_, stats_, stats_mutex_, and tcp.

Referenced by process_buffer().

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

◆ set_integrity_policy()

void kcenon::logger::network_writer::set_integrity_policy ( std::shared_ptr< security::integrity_policy > policy)

Enable tamper-evident integrity signing for outbound frames.

Parameters
policySigning policy (shared ownership); pass nullptr to disable.

When installed, each JSON frame emitted on the socket carries an extra "signature" field (hex-encoded) computed over the frame without that field (Issue #612, ISO/IEC 27001 A.14.1.3 for the transmission protection aspect). This lets the receiving log aggregator reject tampered frames.

Definition at line 497 of file network_writer.cpp.

498 {
499 std::lock_guard<std::mutex> lock(buffer_mutex_);
500 integrity_policy_ = std::move(policy);
501}

References buffer_mutex_, and integrity_policy_.

◆ write()

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

Write log entry.

Parameters
entryThe log entry to write
Returns
common::VoidResult indicating success or error
Since
3.5.0 Changed to use log_entry directly

Implements kcenon::logger::base_writer.

Definition at line 269 of file network_writer.cpp.

269 {
270
271 std::lock_guard<std::mutex> lock(buffer_mutex_);
272
273 // Check buffer size
274 if (buffer_.size() >= buffer_size_) {
275 // Drop oldest message
276 buffer_.pop();
277 std::lock_guard<std::mutex> stats_lock(stats_mutex_);
279 // Note: We still accept the new message after dropping the oldest
280 }
281
282 // Create a copy of the entry since log_entry is move-only
283 if (entry.location) {
284 buffer_.emplace(entry.level,
285 entry.message.to_string(),
286 entry.location->file.to_string(),
287 entry.location->line,
288 entry.location->function.to_string(),
289 entry.timestamp);
290 } else {
291 buffer_.emplace(entry.level,
292 entry.message.to_string(),
293 entry.timestamp);
294 }
295
296 // Notify send worker
297 if (send_worker_) {
298 send_worker_->notify_work();
299 }
300
301 return common::ok();
302}

References buffer_, buffer_mutex_, buffer_size_, kcenon::logger::log_entry::level, kcenon::logger::log_entry::location, kcenon::logger::log_entry::message, kcenon::common::ok(), kcenon::logger::network_writer::connection_stats::send_failures, send_worker_, stats_, stats_mutex_, 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

◆ buffer_

std::queue<log_entry> kcenon::logger::network_writer::buffer_
private

Definition at line 143 of file network_writer.h.

Referenced by flush(), process_buffer(), and write().

◆ buffer_cv_

std::condition_variable kcenon::logger::network_writer::buffer_cv_
private

Definition at line 145 of file network_writer.h.

Referenced by flush().

◆ buffer_mutex_

std::mutex kcenon::logger::network_writer::buffer_mutex_
mutableprivate

Definition at line 144 of file network_writer.h.

Referenced by flush(), process_buffer(), set_integrity_policy(), and write().

◆ buffer_size_

size_t kcenon::logger::network_writer::buffer_size_
private

Definition at line 134 of file network_writer.h.

Referenced by write().

◆ connected_

std::atomic<bool> kcenon::logger::network_writer::connected_ {false}
private

Definition at line 139 of file network_writer.h.

139{false};

Referenced by attempt_reconnect(), connect(), disconnect(), and send_data().

◆ host_

std::string kcenon::logger::network_writer::host_
private

Definition at line 131 of file network_writer.h.

Referenced by connect().

◆ integrity_policy_

std::shared_ptr<security::integrity_policy> kcenon::logger::network_writer::integrity_policy_
private

Definition at line 156 of file network_writer.h.

Referenced by format_for_network(), and set_integrity_policy().

◆ port_

uint16_t kcenon::logger::network_writer::port_
private

Definition at line 132 of file network_writer.h.

Referenced by connect().

◆ protocol_

protocol_type kcenon::logger::network_writer::protocol_
private

Definition at line 133 of file network_writer.h.

Referenced by connect(), network_writer(), and send_data().

◆ reconnect_interval_

std::chrono::seconds kcenon::logger::network_writer::reconnect_interval_
private

Definition at line 135 of file network_writer.h.

Referenced by network_writer().

◆ reconnect_worker_

std::unique_ptr<network_reconnect_jthread_worker> kcenon::logger::network_writer::reconnect_worker_
private

Definition at line 149 of file network_writer.h.

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

◆ running_

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

Definition at line 140 of file network_writer.h.

140{false};

Referenced by attempt_reconnect(), flush(), network_writer(), process_buffer(), and ~network_writer().

◆ send_worker_

std::unique_ptr<network_send_jthread_worker> kcenon::logger::network_writer::send_worker_
private

Definition at line 148 of file network_writer.h.

Referenced by network_writer(), write(), and ~network_writer().

◆ socket_fd_

int kcenon::logger::network_writer::socket_fd_
private

Definition at line 138 of file network_writer.h.

Referenced by connect(), disconnect(), and send_data().

◆ stats_

connection_stats kcenon::logger::network_writer::stats_ {}
private

Definition at line 153 of file network_writer.h.

153{};

Referenced by connect(), get_stats(), send_data(), and write().

◆ stats_mutex_

std::mutex kcenon::logger::network_writer::stats_mutex_
mutableprivate

Definition at line 152 of file network_writer.h.

Referenced by connect(), get_stats(), send_data(), and write().


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