diff --git a/include/spdlog/details/tcp_client.h b/include/spdlog/details/tcp_client.h new file mode 100644 index 00000000..29f34839 --- /dev/null +++ b/include/spdlog/details/tcp_client.h @@ -0,0 +1,145 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// tcp client helper +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace spdlog { +namespace details { +class tcp_client +{ + int socket_ = -1; + +public: + bool is_connected() const + { + return socket_ != -1; + } + + void close() + { + if (is_connected()) + { + ::close(socket_); + socket_ = -1; + } + } + + int fd() const + { + return socket_; + } + + ~tcp_client() + { + close(); + } + + // try to connect or throw on failure + void connect(const std::string &host, int port) + { + close(); + spdlog::info("Connecting.."); + struct addrinfo hints{}; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; // TCP + hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value + hints.ai_protocol = 0; + + auto port_str = std::to_string(port); + struct addrinfo *addrinfo_result; + auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); + if (rv != 0) + { + auto msg = fmt::format("::getaddrinfo failed: {}", gai_strerror(rv)); + SPDLOG_THROW(spdlog::spdlog_ex(msg)); + } + + // Try each address until we successfully connect(2). + int last_errno = 0; + for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) + { +#ifdef SPDLOG_PREVENT_CHILD_FD + int const flags = SOCK_CLOEXEC; +#else + int const flags = 0; +#endif + socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol); + if (socket_ == -1) + { + last_errno = errno; + continue; + } + rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen); + if (rv == 0) + { + break; + } + else + { + last_errno = errno; + ::close(socket_); + socket_ = -1; + } + } + ::freeaddrinfo(addrinfo_result); + if (socket_ == -1) + { + SPDLOG_THROW(spdlog::spdlog_ex("::connect failed", last_errno)); + } + + // set TCP_NODELAY + int enable_flag = 1; + ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag)); + + // prevent sigpipe on systems where MSG_NOSIGNAL is not available +#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) + ::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, (char *)&enable_flag, sizeof(enable_flag)); +#endif + +#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) +#error "tcp_sink would raise SIGPIPE since niether SO_NOSIGPIPE nor MSG_NOSIGNAL are available" +#endif + } + + // 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) + { +#if defined(MSG_NOSIGNAL) + const int send_flags = MSG_NOSIGNAL; +#else + const int send_flags = 0; +#endif + auto write_result = ::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags); + if (write_result < 0) + { + close(); + SPDLOG_THROW(spdlog::spdlog_ex("write(2) failed", errno)); + } + + if (write_result == 0) // (probably should not happen but in any case..) + { + break; + } + bytes_sent += static_cast(write_result); + } + } +}; +} // namespace details +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/sinks/tcp_sink.h b/include/spdlog/sinks/tcp_sink.h index e380d3bb..30e34d95 100644 --- a/include/spdlog/sinks/tcp_sink.h +++ b/include/spdlog/sinks/tcp_sink.h @@ -6,121 +6,65 @@ #include #include #include - -#include -#include -#include -#include - +#include #include #include +#include +#include #pragma once +// tcp client sink +// connect to remote address and send the formatted log. +// will attempt to reconnect if connection drops. + namespace spdlog { namespace sinks { +struct tcp_sink_config +{ + std::string server_host; + int server_port; + bool lazy_connect = false; // connect on first log call instead of in construction + + tcp_sink_config(std::string host, int port) + : server_host{std::move(host)} + , server_port{port} + {} +}; + template class tcp_sink : public spdlog::sinks::base_sink { public: // connect to tcp host/port or throw if failed // host can be hostname or ip address - tcp_sink(std::string host, int port) + tcp_sink(tcp_sink_config sink_config) + : config_{std::move(sink_config)} { - sock_ = connect_to(host, port); - } - - ~tcp_sink() override - { - if (sock_ != -1) + if (!config_.lazy_connect) { - ::close(sock_); + this->client_.connect(config_.server_host, config_.server_port); } } + ~tcp_sink() override {} + protected: void sink_it_(const spdlog::details::log_msg &msg) override { spdlog::memory_buf_t formatted; spdlog::sinks::base_sink::formatter_->format(msg, formatted); - size_t bytes_sent = 0; - while (bytes_sent < formatted.size()) + if (!client_.is_connected()) { - auto write_result = ::write(sock_, formatted.data() + bytes_sent, formatted.size() - bytes_sent); - if (write_result < 0) - { - SPDLOG_THROW(spdlog::spdlog_ex("write(2) failed", errno)); - } - - if (write_result == 0) // (probably should not happen but in any case..) - { - break; - } - bytes_sent += static_cast(write_result); + client_.connect(config_.server_host, config_.server_port); } + client_.send(formatted.data(), formatted.size()); } void flush_() override {} - -private: - // try to connect and return socket fd or throw on failure - int connect_to(const std::string &host, int port) - { - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_INET; // IPv4 - hints.ai_socktype = SOCK_STREAM; // TCP - hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value - hints.ai_protocol = 0; - - auto port_str = std::to_string(port); - struct addrinfo *addrinfo_result; - auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); - if (rv != 0) - { - auto msg = fmt::format("::getaddrinfo failed: {}", gai_strerror(rv)); - SPDLOG_THROW(spdlog::spdlog_ex(msg)); - } - - // Try each address until we successfully connect(2). - int socket_rv = -1; - int last_errno = 0; - for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) - { - #ifdef SPDLOG_PREVENT_CHILD_FD - int const flags = SOCK_CLOEXEC; - #else - int const flags = 0; - #endif - socket_rv = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol); - if (socket_rv == -1) - { - last_errno = errno; - continue; - } - rv = ::connect(socket_rv, rp->ai_addr, rp->ai_addrlen); - if (rv == 0) - { - break; - } - else - { - last_errno = errno; - ::close(socket_rv); - socket_rv = -1; - } - } - ::freeaddrinfo(addrinfo_result); - if (socket_rv == -1) - { - SPDLOG_THROW(spdlog::spdlog_ex("::connect failed", last_errno)); - } - return socket_rv; - } - -private: - int sock_ = -1; + tcp_sink_config config_; + details::tcp_client client_; }; using tcp_sink_mt = tcp_sink;