diff --git a/example/example.cpp b/example/example.cpp index dea76f2f..7d7f9bf9 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -20,6 +20,7 @@ void user_defined_example(); void err_handler_example(); void syslog_example(); void custom_flags_example(); +void udp_example(); #include "spdlog/spdlog.h" #include "spdlog/cfg/env.h" // support for loading levels from the environment variable @@ -75,6 +76,7 @@ int main(int, char *[]) trace_example(); stopwatch_example(); custom_flags_example(); + udp_example(); // Flush all *registered* loggers using a worker thread every 3 seconds. // note: registered loggers *must* be thread safe for this to work correctly! @@ -198,6 +200,7 @@ void trace_example() // stopwatch example #include "spdlog/stopwatch.h" #include +#include "spdlog/sinks/udp_sink.h" void stopwatch_example() { spdlog::stopwatch sw; @@ -205,6 +208,17 @@ void stopwatch_example() spdlog::info("Stopwatch: {} seconds", sw); } +void udp_example() +{ + // using spdlog::details::make_unique; + //auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); + 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"); + my_logger->info("are you ok"); +} + // A logger with multiple sinks (stdout and file) - each with a different format and log level. void multi_sink_example() { diff --git a/include/spdlog/details/udp_client.h b/include/spdlog/details/udp_client.h new file mode 100644 index 00000000..13b608c8 --- /dev/null +++ b/include/spdlog/details/udp_client.h @@ -0,0 +1,85 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifdef _WIN32 +#error include udp_client-windows.h instead +#endif + +// udp client helper +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace spdlog { +namespace details { +class udp_client +{ + int socket_ = -1; + struct sockaddr_in sockAddr_; +public: + + bool init(const std::string &host, uint16_t port) + { + socket_ = socket(PF_INET, SOCK_DGRAM, 0); + if (socket_ < 0) + { + throw_spdlog_ex("error: Create Socket Failed!"); + return false; + } + + sockAddr_.sin_family = AF_INET; + sockAddr_.sin_port = htons(port); + inet_aton(host.c_str(), &sockAddr_.sin_addr); + + memset(sockAddr_.sin_zero, 0x00, sizeof(sockAddr_.sin_zero)); + return true; + } + + bool is_init() const + { + return socket_ != -1; + } + + void close() + { + if (is_init()) + { + ::close(socket_); + socket_ = -1; + } + } + + int fd() const + { + return socket_; + } + + ~udp_client() + { + close(); + } + + // Send exactly n_bytes of the given data. + // On error close the connection and throw. + void send(const char *data, size_t n_bytes) + { + ssize_t toslen = 0; + socklen_t tolen = sizeof(struct sockaddr); + if (( toslen = sendto(socket_, data, n_bytes, 0, (struct sockaddr *)&sockAddr_, tolen)) == -1) + { + throw_spdlog_ex("sendto(2) failed", errno); + close(); + } + } +}; +} // namespace details +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/details/udp_client_windows.h b/include/spdlog/details/udp_client_windows.h new file mode 100644 index 00000000..82d800a6 --- /dev/null +++ b/include/spdlog/details/udp_client_windows.h @@ -0,0 +1,117 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#define WIN32_LEAN_AND_MEAN +// tcp client helper +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "Mswsock.lib") +#pragma comment(lib, "AdvApi32.lib") + +namespace spdlog { +namespace details { +class udp_client +{ + SOCKET socket_ = INVALID_SOCKET; + sockaddr_in addr_ = { 0 }; + + static bool winsock_initialized_() + { + SOCKET s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s == INVALID_SOCKET) + { + return false; + } + else + { + closesocket(s); + return true; + } + } + + static void init_winsock_() + { + WSADATA wsaData; + auto rv = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rv != 0) + { + throw_winsock_error_("WSAStartup failed", ::WSAGetLastError()); + } + } + + static void throw_winsock_error_(const std::string &msg, int last_error) + { + char buf[512]; + ::FormatMessage(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::format("udp_sink - {}: {}", msg, buf)); + } + +public: + bool is_init() const + { + return socket_ != INVALID_SOCKET; + } + + bool init(const std::string &host, uint16_t port) + { + // initialize winsock if needed + if (!winsock_initialized_()) + { + init_winsock_(); + } + + if (is_init()) + { + close(); + } + + addr_.sin_family = AF_INET; + addr_.sin_port = htons(port); + InetPton(AF_INET, TEXT(host.c_str()), &addr_.sin_addr.s_addr); + return true; + } + + void close() + { + ::closesocket(socket_); + socket_ = INVALID_SOCKET; + WSACleanup(); + } + + SOCKET fd() const + { + return socket_; + } + + ~udp_client() + { + close(); + } + + void send(const char *data, size_t n_bytes) + { + if ((sendto(socket_, data, n_bytes, 0, (struct sockaddr *)&addr_, sizeof(struct sockaddr))) == -1) + { + throw_spdlog_ex("sendto(2) failed", errno); + close(); + } + } +}; +} // namespace details +} // namespace spdlog + +#endif \ No newline at end of file diff --git a/include/spdlog/sinks/udp_sink.h b/include/spdlog/sinks/udp_sink.h new file mode 100644 index 00000000..961d754d --- /dev/null +++ b/include/spdlog/sinks/udp_sink.h @@ -0,0 +1,87 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include +#include +#include + +#pragma once + +// Simple udp client sink +// Connects to remote address and send the formatted log. +// Will attempt to reconnect if connection drops. +// If more complicated behaviour is needed (i.e get responses), you can inherit it and override the sink_it_ method. + +namespace spdlog { +namespace sinks { + +struct udp_sink_config +{ + std::string server_host; + uint16_t server_port; + + udp_sink_config(std::string host, uint16_t port) + : server_host{std::move(host)} + , server_port{port} + {} +}; + +template +class udp_sink : public spdlog::sinks::base_sink +{ +public: + // connect to tcp host/port or throw if failed + // host can be hostname or ip address + + explicit udp_sink(udp_sink_config sink_config) + : config_{std::move(sink_config)} + { + + } + + ~udp_sink() override = default; + +protected: + 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_init()) + { + client_.init(config_.server_host, config_.server_port); + } + client_.send(formatted.data(), formatted.size()); + } + + void flush_() override {} + udp_sink_config config_; + details::udp_client client_; +}; + +using udp_sink_mt = udp_sink; +using udp_sink_st = udp_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr udp_logger_mt(const std::string &logger_name, sinks::udp_sink_config skin_config) +{ + return Factory::template create(logger_name, skin_config); +} + +} // namespace spdlog \ No newline at end of file