From a97eaef161a56f4e161bc0f31ec14f3708d36352 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 5 Aug 2024 17:08:23 +0100 Subject: [PATCH 1/5] Fix esp-idf and IPv6 support by using plain UDP socket, drop Syslog library It turns out to be fairly simple to just send the UDP packets directly, and the UDP socket handling can then be a whole lot more generic. --- README.md | 2 -- components/syslog/__init__.py | 23 +++++++---------- components/syslog/syslog_component.cpp | 35 +++++++++++++++----------- components/syslog/syslog_component.h | 15 +++-------- 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 5ac68d0..1115b8d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ A simple syslog component for esphome. The component is designed to auto attach itself to the logger core module (like the MQTT component does with the `log_topic`) -This component uses the https://github.com/arcao/Syslog library version 2.0 at its core - ## How to ### Manually diff --git a/components/syslog/__init__.py b/components/syslog/__init__.py index af8d58b..637d0ed 100644 --- a/components/syslog/__init__.py +++ b/components/syslog/__init__.py @@ -8,7 +8,7 @@ CONF_ENABLE_LOGGER_MESSAGES = "enable_logger" CONF_MIN_LEVEL = "min_level" -DEPENDENCIES = ['logger','network'] +DEPENDENCIES = ['logger','network','socket'] debug_ns = cg.esphome_ns.namespace('debug') syslog_ns = cg.esphome_ns.namespace('syslog') @@ -16,17 +16,14 @@ SyslogComponent = syslog_ns.class_('SyslogComponent', cg.Component) SyslogLogAction = syslog_ns.class_('SyslogLogAction', automation.Action) -CONFIG_SCHEMA = cv.All( - cv.Schema({ - cv.GenerateID(): cv.declare_id(SyslogComponent), - cv.Optional(CONF_IP_ADDRESS, default="255.255.255.255"): cv.string_strict, - cv.Optional(CONF_PORT, default=514): cv.port, - cv.Optional(CONF_ENABLE_LOGGER_MESSAGES, default=True): cv.boolean, - cv.Optional(CONF_STRIP_COLORS, default=True): cv.boolean, - cv.Optional(CONF_MIN_LEVEL, default="DEBUG"): is_log_level, - }), - cv.only_with_arduino, -) +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(SyslogComponent), + cv.Optional(CONF_IP_ADDRESS, default="255.255.255.255"): cv.string_strict, + cv.Optional(CONF_PORT, default=514): cv.port, + cv.Optional(CONF_ENABLE_LOGGER_MESSAGES, default=True): cv.boolean, + cv.Optional(CONF_STRIP_COLORS, default=True): cv.boolean, + cv.Optional(CONF_MIN_LEVEL, default="DEBUG"): is_log_level, +}) SYSLOG_LOG_ACTION_SCHEMA = cv.Schema({ cv.GenerateID(): cv.use_id(SyslogComponent), @@ -36,8 +33,6 @@ }) def to_code(config): - cg.add_library('Syslog', '2.0.0') - var = cg.new_Pvariable(config[CONF_ID]) yield cg.register_component(var, config) diff --git a/components/syslog/syslog_component.cpp b/components/syslog/syslog_component.cpp index ea80947..ff2bb12 100644 --- a/components/syslog/syslog_component.cpp +++ b/components/syslog/syslog_component.cpp @@ -23,12 +23,22 @@ static const uint8_t esphome_to_syslog_log_levels[] = {0, 3, 4, 6, 5, 7, 7, 7}; SyslogComponent::SyslogComponent() { this->settings_.client_id = App.get_name(); - // Get the WifiUDP client here instead of getting it in setup() to make sure we always have a client when calling log() - // Calling log() without the device connected should not be an issue since there is a wifi connected check and WifiUDP fails "silently" and doesn't generate an exception anyways - this->udp_ = new WiFiUDP(); } void SyslogComponent::setup() { + + this->server_socklen = socket::set_sockaddr((struct sockaddr *)&this->server, sizeof(this->server), + this->settings_.address, this->settings_.port); + if (!this->server_socklen) { + ESP_LOGW(TAG, "Failed to parse server IP address '%s'", this->settings_.address.c_str()); + return; + } + this->socket_ = socket::socket(this->server.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (!this->socket_) { + ESP_LOGW(TAG, "Failed to create UDP socket"); + return; + } + this->log(ESPHOME_LOG_LEVEL_INFO , "syslog", "Syslog started"); ESP_LOGI(TAG, "Started"); @@ -53,21 +63,16 @@ void SyslogComponent::loop() { void SyslogComponent::log(uint8_t level, const std::string &tag, const std::string &payload) { level = level > 7 ? 7 : level; - // Simple check to make sure that there is connectivity, if not, log the issue and return - if(WiFi.status() != WL_CONNECTED) { - ESP_LOGW(TAG, "Tried to send \"%s\"@\"%s\" with level %d but Wifi isn't connected yet", tag.c_str(), payload.c_str(), level); + if (!this->socket_) { + ESP_LOGW(TAG, "Tried to send \"%s\"@\"%s\" with level %d but socket isn't connected", tag.c_str(), payload.c_str(), level); return; } - Syslog syslog( - *this->udp_, - this->settings_.address.c_str(), - this->settings_.port, - this->settings_.client_id.c_str(), - tag.c_str(), - LOG_KERN - ); - if(!syslog.log(esphome_to_syslog_log_levels[level], payload.c_str())) { + int pri = esphome_to_syslog_log_levels[level]; + std::string buf = str_sprintf("<%d>1 - %s %s - - - \xEF\xBB\xBF%s", + pri, this->settings_.client_id.c_str(), + tag.c_str(), payload.c_str()); + if (this->socket_->sendto(buf.c_str(), buf.length(), 0, (struct sockaddr *)&this->server, this->server_socklen) < 0) { ESP_LOGW(TAG, "Tried to send \"%s\"@\"%s\" with level %d but but failed for an unknown reason", tag.c_str(), payload.c_str(), level); } } diff --git a/components/syslog/syslog_component.h b/components/syslog/syslog_component.h index b1cd173..1cb9465 100644 --- a/components/syslog/syslog_component.h +++ b/components/syslog/syslog_component.h @@ -6,16 +6,7 @@ #include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "esphome/core/log.h" -#include -#include - -#if defined ESP8266 || defined ARDUINO_ESP8266_ESP01 - #include -#else - #include -#endif - -#include +#include "esphome/components/socket/socket.h" namespace esphome { namespace syslog { @@ -51,7 +42,9 @@ class SyslogComponent : public Component { bool strip_colors; bool enable_logger; SYSLOGSettings settings_; - UDP *udp_ = NULL; + std::unique_ptr socket_ = nullptr; + struct sockaddr_storage server; + socklen_t server_socklen; }; template class SyslogLogAction : public Action { From 4f7ca629c8fbaaef9ee7c31f59d2a121f45bece5 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 5 Aug 2024 17:08:28 +0100 Subject: [PATCH 2/5] Work around socket::set_sockaddr() bug with Legacy IP syslog Using a Legacy IP syslog server in an IPv6-enabled build fails due to a socket::set_sockaddr() bug. Work around it in older versions of esphome. --- components/syslog/syslog_component.cpp | 35 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/components/syslog/syslog_component.cpp b/components/syslog/syslog_component.cpp index ff2bb12..7ad52aa 100644 --- a/components/syslog/syslog_component.cpp +++ b/components/syslog/syslog_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/version.h" #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -9,7 +10,6 @@ /* #include "esphome/core/helpers.h" #include "esphome/core/defines.h" -#include "esphome/core/version.h" */ namespace esphome { @@ -27,8 +27,37 @@ SyslogComponent::SyslogComponent() { void SyslogComponent::setup() { - this->server_socklen = socket::set_sockaddr((struct sockaddr *)&this->server, sizeof(this->server), - this->settings_.address, this->settings_.port); + /* + * Older versions of socket::set_sockaddr() return bogus results + * if trying to log to a Legacy IP address when IPv6 is enabled. + * Fixed by https://github.com/esphome/esphome/pull/7196 + */ + this->server_socklen = 0; + if (ESPHOME_VERSION_CODE >= VERSION_CODE(2024, 8, 0)) { + this->server_socklen = socket::set_sockaddr((struct sockaddr *)&this->server, sizeof(this->server), + this->settings_.address, this->settings_.port); + } +#if USE_NETWORK_IPV6 + else if (this->settings_.address.find(':') != std::string::npos) { + auto *server6 = reinterpret_cast(&this->server); + memset(server6, 0, sizeof(*server6)); + server6->sin6_family = AF_INET6; + server6->sin6_port = htons(this->settings_.port); + + ip6_addr_t ip6; + inet6_aton(this->settings_.address.c_str(), &ip6); + memcpy(server6->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr)); + this->server_socklen = sizeof(*server6); + } +#endif /* USE_NETWORK_IPV6 */ + else { + auto *server4 = reinterpret_cast(&this->server); + memset(server4, 0, sizeof(*server4)); + server4->sin_family = AF_INET; + server4->sin_addr.s_addr = inet_addr(this->settings_.address.c_str()); + server4->sin_port = htons(this->settings_.port); + this->server_socklen = sizeof(*server4); + } if (!this->server_socklen) { ESP_LOGW(TAG, "Failed to parse server IP address '%s'", this->settings_.address.c_str()); return; From e09b2bdfd45936dea5209ee4025059d4ec2ba65f Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 24 Feb 2025 20:06:28 +0000 Subject: [PATCH 3/5] Mark failed on error, attempt to build for esp8266 --- components/syslog/syslog_component.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/syslog/syslog_component.cpp b/components/syslog/syslog_component.cpp index 7ad52aa..f716d08 100644 --- a/components/syslog/syslog_component.cpp +++ b/components/syslog/syslog_component.cpp @@ -12,6 +12,11 @@ #include "esphome/core/defines.h" */ +#ifdef USE_SOCKET_IMPL_LWIP_TCP +#include +#define IPPROTO_UDP IP_PROTO_UDP +#endif + namespace esphome { namespace syslog { @@ -59,12 +64,14 @@ void SyslogComponent::setup() { this->server_socklen = sizeof(*server4); } if (!this->server_socklen) { - ESP_LOGW(TAG, "Failed to parse server IP address '%s'", this->settings_.address.c_str()); + ESP_LOGE(TAG, "Failed to parse server IP address '%s'", this->settings_.address.c_str()); + this->mark_failed(); return; } this->socket_ = socket::socket(this->server.ss_family, SOCK_DGRAM, IPPROTO_UDP); if (!this->socket_) { - ESP_LOGW(TAG, "Failed to create UDP socket"); + ESP_LOGE(TAG, "Failed to create UDP socket"); + this->mark_failed(); return; } @@ -92,6 +99,9 @@ void SyslogComponent::loop() { void SyslogComponent::log(uint8_t level, const std::string &tag, const std::string &payload) { level = level > 7 ? 7 : level; + if (this->is_failed()) + return; + if (!this->socket_) { ESP_LOGW(TAG, "Tried to send \"%s\"@\"%s\" with level %d but socket isn't connected", tag.c_str(), payload.c_str(), level); return; From b8fffb65a7e083ce7462297c86859e00e65d105f Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 25 Feb 2025 08:22:06 +0000 Subject: [PATCH 4/5] Update components/syslog/syslog_component.cpp Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> --- components/syslog/syslog_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/syslog/syslog_component.cpp b/components/syslog/syslog_component.cpp index f716d08..061832b 100644 --- a/components/syslog/syslog_component.cpp +++ b/components/syslog/syslog_component.cpp @@ -102,6 +102,8 @@ void SyslogComponent::log(uint8_t level, const std::string &tag, const std::stri if (this->is_failed()) return; + level = level > 7 ? 7 : level; + if (!this->socket_) { ESP_LOGW(TAG, "Tried to send \"%s\"@\"%s\" with level %d but socket isn't connected", tag.c_str(), payload.c_str(), level); return; From ea16d150719bcb2743b577d840c38c4d276961e1 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 25 Feb 2025 08:22:28 +0000 Subject: [PATCH 5/5] Update components/syslog/syslog_component.cpp Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> --- components/syslog/syslog_component.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/syslog/syslog_component.cpp b/components/syslog/syslog_component.cpp index 061832b..c785e4f 100644 --- a/components/syslog/syslog_component.cpp +++ b/components/syslog/syslog_component.cpp @@ -97,7 +97,6 @@ void SyslogComponent::loop() { } void SyslogComponent::log(uint8_t level, const std::string &tag, const std::string &payload) { - level = level > 7 ? 7 : level; if (this->is_failed()) return;