From 9e1a7e048a15eab6489d6901e879b55cd540d76e Mon Sep 17 00:00:00 2001 From: LowLevelLore Date: Sat, 8 Mar 2025 17:12:58 +0530 Subject: [PATCH] Added timeout to send for tcp_client and tcp_client-windows --- example/example.cpp | 456 ++++---------------- include/spdlog/details/tcp_client-windows.h | 79 ++-- include/spdlog/details/tcp_client.h | 18 +- include/spdlog/sinks/tcp_sink.h | 13 +- logs/async_log.txt | 101 +++++ logs/basic-log.txt | 1 + logs/daily_2025-03-07.txt | 1 + logs/events-sample.txt | 3 + logs/multisink.txt | 2 + logs/new-default-log.txt | 2 + logs/rotating.txt | 1 + 11 files changed, 255 insertions(+), 422 deletions(-) create mode 100644 logs/async_log.txt create mode 100644 logs/basic-log.txt create mode 100644 logs/daily_2025-03-07.txt create mode 100644 logs/events-sample.txt create mode 100644 logs/multisink.txt create mode 100644 logs/new-default-log.txt create mode 100644 logs/rotating.txt diff --git a/example/example.cpp b/example/example.cpp index 38d644ad..a2c31e0d 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -1,403 +1,95 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) - -// spdlog usage example - -#include +#include +#include #include - -void load_levels_example(); -void stdout_logger_example(); -void basic_example(); -void rotating_example(); -void daily_example(); -void callback_example(); -void async_example(); -void binary_example(); -void vector_example(); -void stopwatch_example(); -void trace_example(); -void multi_sink_example(); -void user_defined_example(); -void err_handler_example(); -void syslog_example(); -void udp_example(); -void custom_flags_example(); -void file_events_example(); -void replace_default_logger_example(); -void mdc_example(); - +#include +#include +#include +#include #include "spdlog/spdlog.h" -#include "spdlog/cfg/env.h" // support for loading levels from the environment variable -#include "spdlog/fmt/ostr.h" // support for user defined types - -int main(int, char *[]) { - // Log levels can be loaded from argv/env using "SPDLOG_LEVEL" - load_levels_example(); - - spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, - SPDLOG_VER_PATCH); - - spdlog::warn("Easy padding in numbers like {:08d}", 12); - spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); - spdlog::info("Support for floats {:03.2f}", 1.23456); - spdlog::info("Positional args are {1} {0}..", "too", "supported"); - spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left"); - - // Runtime log levels - spdlog::set_level(spdlog::level::info); // Set global log level to info - spdlog::debug("This message should not be displayed!"); - spdlog::set_level(spdlog::level::trace); // Set specific logger's log level - spdlog::debug("This message should be displayed.."); - - // Customize msg format for all loggers - spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v"); - spdlog::info("This an info message with custom format"); - spdlog::set_pattern("%+"); // back to default format - spdlog::set_level(spdlog::level::info); - - // Backtrace support - // Loggers can store in a ring buffer all messages (including debug/trace) for later inspection. - // When needed, call dump_backtrace() to see what happened: - spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages - for (int i = 0; i < 100; i++) { - spdlog::debug("Backtrace message {}", i); // not logged.. +#include "spdlog/sinks/tcp_sink.h" + +// Server that listens for a single connection, processes one message, then exits. +void tcp_server_single(int port) { + int server_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("socket"); + return; } - // e.g. if some error happened: - spdlog::dump_backtrace(); // log them now! - - try { - stdout_logger_example(); - basic_example(); - rotating_example(); - daily_example(); - callback_example(); - async_example(); - binary_example(); - vector_example(); - multi_sink_example(); - user_defined_example(); - err_handler_example(); - trace_example(); - stopwatch_example(); - udp_example(); - custom_flags_example(); - file_events_example(); - replace_default_logger_example(); - mdc_example(); - - // Flush all *registered* loggers using a worker thread every 3 seconds. - // note: registered loggers *must* be thread safe for this to work correctly! - spdlog::flush_every(std::chrono::seconds(3)); - - // Apply some function on all registered loggers - spdlog::apply_all([&](std::shared_ptr l) { l->info("End of example."); }); - - // Release all spdlog resources, and drop all loggers in the registry. - // This is optional (only mandatory if using windows + async log). - spdlog::shutdown(); + int opt = 1; + if (::setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + perror("setsockopt"); + ::close(server_fd); + return; } - - // Exceptions will only be thrown upon failed logger or sink construction (not during logging). - catch (const spdlog::spdlog_ex &ex) { - std::printf("Log initialization failed: %s\n", ex.what()); - return 1; + sockaddr_in address; + std::memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(port); + if (::bind(server_fd, reinterpret_cast(&address), sizeof(address)) < 0) { + perror("bind"); + ::close(server_fd); + return; } -} - -#include "spdlog/sinks/stdout_color_sinks.h" -// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed. -void stdout_logger_example() { - // Create color multi threaded logger. - auto console = spdlog::stdout_color_mt("console"); - // or for stderr: - // auto console = spdlog::stderr_color_mt("error-logger"); -} - -#include "spdlog/sinks/basic_file_sink.h" -void basic_example() { - // Create basic file logger (not rotated). - auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true); -} - -#include "spdlog/sinks/rotating_file_sink.h" -void rotating_example() { - // Create a file rotating logger with 5mb size max and 3 rotated files. - auto rotating_logger = - spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); -} - -#include "spdlog/sinks/daily_file_sink.h" -void daily_example() { - // Create a daily logger - a new file is created every day on 2:30am. - auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); -} - -#include "spdlog/sinks/callback_sink.h" -void callback_example() { - // Create the logger - auto logger = spdlog::callback_logger_mt("custom_callback_logger", - [](const spdlog::details::log_msg & /*msg*/) { - // do what you need to do with msg - }); -} - -#include "spdlog/cfg/env.h" -void load_levels_example() { - // Set the log level to "info" and mylogger to "trace": - // SPDLOG_LEVEL=info,mylogger=trace && ./example - spdlog::cfg::load_env_levels(); - // or specify the env variable name: - // MYAPP_LEVEL=info,mylogger=trace && ./example - // spdlog::cfg::load_env_levels("MYAPP_LEVEL"); - // or from command line: - // ./example SPDLOG_LEVEL=info,mylogger=trace - // #include "spdlog/cfg/argv.h" // for loading levels from argv - // spdlog::cfg::load_argv_levels(args, argv); -} - -#include "spdlog/async.h" -void async_example() { - // Default thread pool settings can be modified *before* creating the async logger: - // spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread. - auto async_file = - spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); - // alternatively: - // auto async_file = - // spdlog::create_async("async_file_logger", - // "logs/async_log.txt"); - - for (int i = 1; i < 101; ++i) { - async_file->info("Async message #{}", i); - } -} - -// Log binary data as hex. -// Many types of std::container types can be used. -// Iterator ranges are supported too. -// Format flags: -// {:X} - print in uppercase. -// {:s} - don't separate each byte with space. -// {:p} - don't print the position on each line start. -// {:n} - don't split the output to lines. - -#if !defined SPDLOG_USE_STD_FORMAT || defined(_MSC_VER) - #include "spdlog/fmt/bin_to_hex.h" -void binary_example() { - std::vector buf; - for (int i = 0; i < 80; i++) { - buf.push_back(static_cast(i & 0xff)); + if (::listen(server_fd, 1) < 0) { + perror("listen"); + ::close(server_fd); + return; } - spdlog::info("Binary example: {}", spdlog::to_hex(buf)); - spdlog::info("Another binary example:{:n}", - spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); - // more examples: - // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); - // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); - // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); - // logger->info("hexdump style: {:a}", spdlog::to_hex(buf)); - // logger->info("hexdump style, 20 chars per line {:a}", spdlog::to_hex(buf, 20)); -} -#else -void binary_example() { - // not supported with std::format yet -} -#endif - -// Log a vector of numbers -#ifndef SPDLOG_USE_STD_FORMAT - #include "spdlog/fmt/ranges.h" -void vector_example() { - std::vector vec = {1, 2, 3}; - spdlog::info("Vector example: {}", vec); -} - -#else -void vector_example() {} -#endif - -// ! DSPDLOG_USE_STD_FORMAT - -// Compile time log levels. -// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE) -void trace_example() { - // trace from default logger - SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23); - // debug from default logger - SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23); - - // trace from logger object - auto logger = spdlog::get("file_logger"); - SPDLOG_LOGGER_TRACE(logger, "another trace message"); -} + std::cout << "Server listening on port " << port << std::endl; -// stopwatch example -#include "spdlog/stopwatch.h" -#include -void stopwatch_example() { - spdlog::stopwatch sw; - std::this_thread::sleep_for(std::chrono::milliseconds(123)); - spdlog::info("Stopwatch: {} seconds", sw); -} - -#include "spdlog/sinks/udp_sink.h" -void udp_example() { - spdlog::sinks::udp_sink_config cfg("127.0.0.1", 11091); - auto my_logger = spdlog::udp_logger_mt("udplog", cfg); - my_logger->set_level(spdlog::level::debug); - my_logger->info("hello world"); -} - -// A logger with multiple sinks (stdout and file) - each with a different format and log level. -void multi_sink_example() { - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::warn); - console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); - - auto file_sink = - std::make_shared("logs/multisink.txt", true); - file_sink->set_level(spdlog::level::trace); - - spdlog::logger logger("multi_sink", {console_sink, file_sink}); - logger.set_level(spdlog::level::debug); - logger.warn("this should appear in both console and file"); - logger.info("this message should not appear in the console, only in the file"); -} - -// User defined types logging -struct my_type { - int i = 0; - explicit my_type(int i) - : i(i){} -}; - -#ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib -template <> -struct fmt::formatter : fmt::formatter { - auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) { - return fmt::format_to(ctx.out(), "[my_type i={}]", my.i); + int client_fd = ::accept(server_fd, nullptr, nullptr); + if (client_fd < 0) { + perror("accept"); + ::close(server_fd); + return; } -}; - -#else // when using std::format -template <> -struct std::formatter : std::formatter { - auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) { - return std::format_to(ctx.out(), "[my_type i={}]", my.i); + std::cout << "Server: Client connected" << std::endl; + + char buffer[1024] = {0}; + ssize_t n = ::read(client_fd, buffer, sizeof(buffer)); + if (n > 0) { + std::cout << "Server received: " << std::string(buffer, n) << std::endl; + } else if (n < 0) { + perror("read"); } -}; -#endif -void user_defined_example() { spdlog::info("user defined type: {}", my_type(14)); } - -// Custom error handler. Will be triggered on log failure. -void err_handler_example() { - // can be set globally or per logger(logger->set_error_handler(..)) - spdlog::set_error_handler([](const std::string &msg) { - printf("*** Custom log error handler: %s ***\n", msg.c_str()); - }); + ::close(client_fd); + ::close(server_fd); + std::cout << "Server: Connection closed, server exiting" << std::endl; } -// syslog example (linux/osx/freebsd) -#ifndef _WIN32 - #include "spdlog/sinks/syslog_sink.h" -void syslog_example() { - std::string ident = "spdlog-example"; - auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); - syslog_logger->warn("This is warning that will end up in syslog."); -} -#endif +int main() { + const int port = 12345; -// Android example. -#if defined(__ANDROID__) - #include "spdlog/sinks/android_sink.h" -void android_example() { - std::string tag = "spdlog-android"; - auto android_logger = spdlog::android_logger_mt("android", tag); - android_logger->critical("Use \"adb shell logcat\" to view this message."); -} -#endif + // Start a server that will accept one connection and then exit. + std::thread server_thread(tcp_server_single, port); + std::this_thread::sleep_for(std::chrono::seconds(1)); // give server time to start -// Log patterns can contain custom flags. -// this will add custom flag '%*' which will be bound to a instance -#include "spdlog/pattern_formatter.h" -class my_formatter_flag : public spdlog::custom_flag_formatter { -public: - void format(const spdlog::details::log_msg &, - const std::tm &, - spdlog::memory_buf_t &dest) override { - std::string some_txt = "custom-flag"; - dest.append(some_txt.data(), some_txt.data() + some_txt.size()); - } + // Create a logger that connects to the server with a 5-second timeout. + spdlog::sinks::tcp_sink_config config("10.255.255.1", port, 5); + auto tcpSink = std::make_shared(config); + auto logger = std::make_shared("TCP Logger", tcpSink); + logger->set_level(spdlog::level::err); - std::unique_ptr clone() const override { - return spdlog::details::make_unique(); + // First log: should connect successfully and send the message. + try { + logger->error("First message: connection established"); + } catch (const spdlog::spdlog_ex &ex) { + std::cerr << "Exception during first log: " << ex.what() << std::endl; } -}; -void custom_flags_example() { - using spdlog::details::make_unique; // for pre c++14 - auto formatter = make_unique(); - formatter->add_flag('*').set_pattern("[%n] [%*] [%^%l%$] %v"); - // set the new formatter using spdlog::set_formatter(formatter) or - // logger->set_formatter(formatter) spdlog::set_formatter(std::move(formatter)); -} + server_thread.join(); // server is now down -void file_events_example() { - // pass the spdlog::file_event_handlers to file sinks for open/close log file notifications - spdlog::file_event_handlers handlers; - handlers.before_open = [](spdlog::filename_t filename) { - spdlog::info("Before opening {}", filename); - }; - handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { - spdlog::info("After opening {}", filename); - fputs("After opening\n", fstream); - }; - handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { - spdlog::info("Before closing {}", filename); - fputs("Before closing\n", fstream); - }; - handlers.after_close = [](spdlog::filename_t filename) { - spdlog::info("After closing {}", filename); - }; - auto file_sink = std::make_shared("logs/events-sample.txt", - true, handlers); - spdlog::logger my_logger("some_logger", file_sink); - my_logger.info("Some log line"); -} - -void replace_default_logger_example() { - // store the old logger so we don't break other examples. - auto old_logger = spdlog::default_logger(); - - auto new_logger = - spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true); - spdlog::set_default_logger(new_logger); - spdlog::set_level(spdlog::level::info); - spdlog::debug("This message should not be displayed!"); - spdlog::set_level(spdlog::level::trace); - spdlog::debug("This message should be displayed.."); - - spdlog::set_default_logger(old_logger); -} + std::cout << "Server is down. Now attempting to reconnect..." << std::endl; -// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread local storage. -// Each thread maintains its own MDC, which loggers use to append diagnostic information to log outputs. -// Note: it is not supported in asynchronous mode due to its reliance on thread-local storage. + // Second log: since the connection is no longer valid, a reconnect is attempted. + // Because there is no server, the connect() will eventually time out and throw. + try { + logger->error("Second message: expecting timeout error"); + } catch (const spdlog::spdlog_ex &ex) { + std::cerr << "Timeout error caught: " << ex.what() << std::endl; + } -#ifndef SPDLOG_NO_TLS - #include "spdlog/mdc.h" -void mdc_example() -{ - spdlog::mdc::put("key1", "value1"); - spdlog::mdc::put("key2", "value2"); - // if not using the default format, you can use the %& formatter to print mdc data as well - spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [%&] %v"); - spdlog::info("Some log message with context"); -} -#else -void mdc_example() { - // if TLS feature is disabled + return 0; } -#endif diff --git a/include/spdlog/details/tcp_client-windows.h b/include/spdlog/details/tcp_client-windows.h index bf8f7b87..53a9d7a2 100644 --- a/include/spdlog/details/tcp_client-windows.h +++ b/include/spdlog/details/tcp_client-windows.h @@ -21,6 +21,7 @@ namespace spdlog { namespace details { + class tcp_client { SOCKET socket_ = INVALID_SOCKET; @@ -37,42 +38,25 @@ class tcp_client { ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL); - throw_spdlog_ex(fmt_lib::format("tcp_sink - {}: {}", msg, buf)); } public: - tcp_client() { init_winsock_(); } - - ~tcp_client() { - close(); - ::WSACleanup(); - } - - bool is_connected() const { return socket_ != INVALID_SOCKET; } - - void close() { - ::closesocket(socket_); - socket_ = INVALID_SOCKET; - } - - SOCKET fd() const { return socket_; } - - // try to connect or throw on failure - void connect(const std::string &host, int port) { + // Added timeout_ms parameter (in milliseconds) + void connect(const std::string &host, int port, int timeout_sec) { if (is_connected()) { close(); } - struct addrinfo hints {}; + struct addrinfo hints{}; ZeroMemory(&hints, sizeof(hints)); - - hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on + hints.ai_family = AF_UNSPEC; // IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // TCP - hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value + hints.ai_flags = AI_NUMERICSERV; // port as numeric value hints.ai_protocol = 0; + int timeout_ms = timeout_sec * 1000; auto port_str = std::to_string(port); - struct addrinfo *addrinfo_result; + struct addrinfo *addrinfo_result = nullptr; auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); int last_error = 0; if (rv != 0) { @@ -81,13 +65,11 @@ public: throw_winsock_error_("getaddrinfo failed", last_error); } - // Try each address until we successfully connect(2). - + // Try each address until we successfully connect. for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (socket_ == INVALID_SOCKET) { last_error = ::WSAGetLastError(); - WSACleanup(); continue; } if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) { @@ -103,33 +85,66 @@ public: throw_winsock_error_("connect failed", last_error); } + if (::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout_ms), + sizeof(timeout_ms)) == SOCKET_ERROR) { + last_error = ::WSAGetLastError(); + WSACleanup(); + throw_winsock_error_("setsockopt(SO_RCVTIMEO) failed", last_error); + } + if (::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout_ms), + sizeof(timeout_ms)) == SOCKET_ERROR) { + last_error = ::WSAGetLastError(); + WSACleanup(); + throw_winsock_error_("setsockopt(SO_SNDTIMEO) failed", last_error); + } + // set TCP_NODELAY int enable_flag = 1; ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&enable_flag), sizeof(enable_flag)); } + bool is_connected() const { return socket_ != INVALID_SOCKET; } + + void close() { + if (is_connected()) { + ::closesocket(socket_); + socket_ = INVALID_SOCKET; + } + } + + SOCKET fd() const { return socket_; } + + ~tcp_client() { + close(); + ::WSACleanup(); + } + // Send exactly n_bytes of the given data. // On error close the connection and throw. void send(const char *data, size_t n_bytes) { size_t bytes_sent = 0; while (bytes_sent < n_bytes) { const int send_flags = 0; - auto write_result = - ::send(socket_, data + bytes_sent, (int)(n_bytes - bytes_sent), send_flags); + auto write_result = ::send(socket_, data + bytes_sent, + static_cast(n_bytes - bytes_sent), send_flags); if (write_result == SOCKET_ERROR) { int last_error = ::WSAGetLastError(); close(); + if (last_error == WSAETIMEDOUT) { + throw_winsock_error_("Connection timed out", last_error); + } throw_winsock_error_("send failed", last_error); } - - if (write_result == 0) // (probably should not happen but in any case..) - { + if (write_result == 0) { // (probably should not happen but in any case..) break; } bytes_sent += static_cast(write_result); } } }; + } // namespace details } // namespace spdlog diff --git a/include/spdlog/details/tcp_client.h b/include/spdlog/details/tcp_client.h index 9d3c40d5..1438191a 100644 --- a/include/spdlog/details/tcp_client.h +++ b/include/spdlog/details/tcp_client.h @@ -40,9 +40,9 @@ public: ~tcp_client() { close(); } // try to connect or throw on failure - void connect(const std::string &host, int port) { + void connect(const std::string &host, int port, int timeout_sec) { close(); - struct addrinfo hints {}; + struct addrinfo hints{}; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on hints.ai_socktype = SOCK_STREAM; // TCP @@ -56,7 +56,6 @@ public: throw_spdlog_ex(fmt_lib::format("::getaddrinfo failed: {}", gai_strerror(rv))); } - // Try each address until we successfully connect(2). int last_errno = 0; for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { #if defined(SOCK_CLOEXEC) @@ -69,10 +68,19 @@ public: last_errno = errno; continue; } + struct timeval timeout; + timeout.tv_sec = timeout_sec; + timeout.tv_usec = 0; + ::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), + sizeof(timeout)); + ::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeout), + sizeof(timeout)); + rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen); if (rv == 0) { break; } + std::cout << "HERE\n"; last_errno = errno; ::close(socket_); socket_ = -1; @@ -111,7 +119,11 @@ public: auto write_result = ::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags); if (write_result < 0) { + int err = errno; close(); + if (err == ETIMEDOUT) { + throw_spdlog_ex("Connection timed out", ETIMEDOUT); + } throw_spdlog_ex("write(2) failed", errno); } diff --git a/include/spdlog/sinks/tcp_sink.h b/include/spdlog/sinks/tcp_sink.h index 25349645..e92fbc65 100644 --- a/include/spdlog/sinks/tcp_sink.h +++ b/include/spdlog/sinks/tcp_sink.h @@ -31,11 +31,13 @@ namespace sinks { struct tcp_sink_config { std::string server_host; int server_port; + int timeout_sec; bool lazy_connect = false; // if true connect on first log call instead of on construction - tcp_sink_config(std::string host, int port) + tcp_sink_config(std::string host, int port, int timeout_sec) : server_host{std::move(host)}, - server_port{port} {} + server_port{port}, + timeout_sec(timeout_sec) {} }; template @@ -47,19 +49,20 @@ public: explicit tcp_sink(tcp_sink_config sink_config) : config_{std::move(sink_config)} { if (!config_.lazy_connect) { - this->client_.connect(config_.server_host, config_.server_port); + this->client_.connect(config_.server_host, config_.server_port, config_.timeout_sec); } } ~tcp_sink() override = default; protected: - void sink_it_(const spdlog::details::log_msg &msg) override { + void sink_it_(const spdlog::details::log_msg& msg) override { spdlog::memory_buf_t formatted; spdlog::sinks::base_sink::formatter_->format(msg, formatted); if (!client_.is_connected()) { - client_.connect(config_.server_host, config_.server_port); + client_.connect(config_.server_host, config_.server_port, config_.timeout_sec); } + client_.send(formatted.data(), formatted.size()); } diff --git a/logs/async_log.txt b/logs/async_log.txt new file mode 100644 index 00000000..45bd1c40 --- /dev/null +++ b/logs/async_log.txt @@ -0,0 +1,101 @@ +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #1 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #2 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #3 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #4 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #5 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #6 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #7 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #8 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #9 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #10 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #11 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #12 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #13 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #14 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #15 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #16 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #17 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #18 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #19 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #20 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #21 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #22 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #23 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #24 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #25 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #26 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #27 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #28 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #29 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #30 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #31 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #32 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #33 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #34 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #35 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #36 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #37 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #38 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #39 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #40 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #41 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #42 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #43 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #44 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #45 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #46 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #47 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #48 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #49 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #50 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #51 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #52 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #53 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #54 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #55 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #56 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #57 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #58 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #59 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #60 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #61 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #62 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #63 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #64 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #65 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #66 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #67 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #68 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #69 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #70 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #71 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #72 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #73 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #74 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #75 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #76 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #77 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #78 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #79 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #80 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #81 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #82 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #83 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #84 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #85 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #86 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #87 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #88 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #89 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #90 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #91 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #92 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #93 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #94 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #95 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #96 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #97 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #98 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #99 +[2025-03-07 21:47:05.904] [async_file_logger] [info] Async message #100 +[21:47:06 +05:30] [I] [] End of example. diff --git a/logs/basic-log.txt b/logs/basic-log.txt new file mode 100644 index 00000000..b7f89a1e --- /dev/null +++ b/logs/basic-log.txt @@ -0,0 +1 @@ +[21:47:06 +05:30] [I] [key1:value1 key2:value2] End of example. diff --git a/logs/daily_2025-03-07.txt b/logs/daily_2025-03-07.txt new file mode 100644 index 00000000..b7f89a1e --- /dev/null +++ b/logs/daily_2025-03-07.txt @@ -0,0 +1 @@ +[21:47:06 +05:30] [I] [key1:value1 key2:value2] End of example. diff --git a/logs/events-sample.txt b/logs/events-sample.txt new file mode 100644 index 00000000..5e022e24 --- /dev/null +++ b/logs/events-sample.txt @@ -0,0 +1,3 @@ +After opening +[2025-03-07 21:47:06.027] [some_logger] [info] Some log line +Before closing diff --git a/logs/multisink.txt b/logs/multisink.txt new file mode 100644 index 00000000..9701fbfd --- /dev/null +++ b/logs/multisink.txt @@ -0,0 +1,2 @@ +[2025-03-07 21:47:05.904] [multi_sink] [warning] this should appear in both console and file +[2025-03-07 21:47:05.904] [multi_sink] [info] this message should not appear in the console, only in the file diff --git a/logs/new-default-log.txt b/logs/new-default-log.txt new file mode 100644 index 00000000..c25ab09b --- /dev/null +++ b/logs/new-default-log.txt @@ -0,0 +1,2 @@ +[2025-03-07 21:47:06.027] [new_default_logger] [debug] This message should be displayed.. +[21:47:06 +05:30] [I] [key1:value1 key2:value2] End of example. diff --git a/logs/rotating.txt b/logs/rotating.txt new file mode 100644 index 00000000..b7f89a1e --- /dev/null +++ b/logs/rotating.txt @@ -0,0 +1 @@ +[21:47:06 +05:30] [I] [key1:value1 key2:value2] End of example.