Logger System 0.1.3
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
 
- 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_ {}
 

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 41 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 198 of file network_writer.cpp.

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

236 {
237 running_ = false;
238
239 // Stop workers with error handling
240 // IMPORTANT: Reset workers after stop to join their threads before accessing any members
241 utils::safe_destructor_operation("send_worker_stop", [this]() {
242 if (send_worker_) {
243 send_worker_->stop();
244 send_worker_.reset();
245 }
246 });
247
248 utils::safe_destructor_operation("reconnect_worker_stop", [this]() {
249 if (reconnect_worker_) {
250 reconnect_worker_->stop();
251 reconnect_worker_.reset();
252 }
253 });
254
255 // Disconnect with error handling
256 utils::safe_destructor_operation("network_disconnect", [this]() {
257 disconnect();
258 });
259
260#ifdef _WIN32
261 // Cleanup Winsock with error handling
262 utils::safe_destructor_operation("winsock_cleanup", []() {
263 WSACleanup();
264 });
265#endif
266}
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 435 of file network_writer.cpp.

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

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 332 of file network_writer.cpp.

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

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

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 483 of file network_writer.cpp.

483 {
484 std::string escaped;
485 for (char c : str) {
486 if (c == '"') escaped += "\\\"";
487 else if (c == '\\') escaped += "\\\\";
488 else if (c == '\n') escaped += "\\n";
489 else if (c == '\r') escaped += "\\r";
490 else if (c == '\t') escaped += "\\t";
491 else escaped += c;
492 }
493 return escaped;
494}

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 303 of file network_writer.cpp.

303 {
304 std::unique_lock<std::mutex> lock(buffer_mutex_);
305 auto start = std::chrono::steady_clock::now();
306 auto timeout = std::chrono::seconds(5); // 5 second timeout
307
308 while (!buffer_.empty()) {
309 if (buffer_cv_.wait_for(lock, timeout, [this] { return buffer_.empty() || !running_; })) {
310 if (!buffer_.empty() && !running_) {
312 "Network writer stopped before flush completed");
313 }
314 } else {
316 "Network flush timeout");
317 }
318
319 if (std::chrono::steady_clock::now() - start > timeout) {
321 "Network flush exceeded timeout");
322 }
323 }
324 return common::ok();
325}
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 441 of file network_writer.cpp.

441 {
442 // Format as JSON for network transmission
443 std::ostringstream oss;
444 oss << "{";
445
446 // Timestamp
447 auto time_t = std::chrono::system_clock::to_time_t(entry.timestamp);
448 oss << "\"@timestamp\":\"";
449 oss << std::put_time(std::gmtime(&time_t), "%Y-%m-%dT%H:%M:%SZ") << "\",";
450
451 // Level - convert logger_system::log_level to common::interfaces::log_level
452 auto level = static_cast<common::interfaces::log_level>(static_cast<int>(entry.level));
453 oss << "\"level\":\"" << utils::string_utils::level_to_string(level) << "\",";
454
455 // Message
456 oss << "\"message\":\"" << escape_json(entry.message.to_string()) << "\"";
457
458 // Optional fields from source_location
459 if (entry.location) {
460 std::string file = entry.location->file.to_string();
461 std::string function = entry.location->function.to_string();
462
463 if (!file.empty()) {
464 oss << ",\"file\":\"" << escape_json(file) << "\"";
465 oss << ",\"line\":" << entry.location->line;
466 }
467
468 if (!function.empty()) {
469 oss << ",\"function\":\"" << escape_json(function) << "\"";
470 }
471 }
472
473 // Add hostname
474 char hostname[256];
475 if (gethostname(hostname, sizeof(hostname)) == 0) {
476 oss << ",\"host\":\"" << hostname << "\"";
477 }
478
479 oss << "}\n";
480 return oss.str();
481}
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(), 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 83 of file network_writer.h.

83{ return "network"; }

◆ get_stats()

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

Definition at line 327 of file network_writer.cpp.

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

References stats_, and stats_mutex_.

◆ is_connected()

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

Check if connected.

Definition at line 88 of file network_writer.h.

88{ return connected_.load(); }

◆ process_buffer()

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

Definition at line 418 of file network_writer.cpp.

418 {
419 std::unique_lock<std::mutex> lock(buffer_mutex_);
420
421 // Process buffered logs
422 while (!buffer_.empty() && running_) {
423 auto entry = std::move(buffer_.front());
424 buffer_.pop();
425 lock.unlock();
426
427 // Format and send
428 std::string formatted = format_for_network(entry);
429 send_data(formatted);
430
431 lock.lock();
432 }
433}
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 389 of file network_writer.cpp.

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

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:

◆ 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 268 of file network_writer.cpp.

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

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 129 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 131 of file network_writer.h.

Referenced by flush().

◆ buffer_mutex_

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

Definition at line 130 of file network_writer.h.

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

◆ buffer_size_

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

Definition at line 120 of file network_writer.h.

Referenced by write().

◆ connected_

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

Definition at line 125 of file network_writer.h.

125{false};

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

◆ host_

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

Definition at line 117 of file network_writer.h.

Referenced by connect().

◆ port_

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

Definition at line 118 of file network_writer.h.

Referenced by connect().

◆ protocol_

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

Definition at line 119 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 121 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 135 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 126 of file network_writer.h.

126{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 134 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 124 of file network_writer.h.

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

◆ stats_

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

Definition at line 139 of file network_writer.h.

139{};

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

◆ stats_mutex_

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

Definition at line 138 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: