From 3b8a5db97c67abad90c6028b05c7d1a45b3b2a9b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 5 May 2025 14:48:13 +1000 Subject: [PATCH] [syslog] Implement logging via syslog (#8637) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/syslog/__init__.py | 41 ++++++++++++++++ esphome/components/syslog/esphome_syslog.cpp | 49 +++++++++++++++++++ esphome/components/syslog/esphome_syslog.h | 25 ++++++++++ tests/components/syslog/common.yaml | 15 ++++++ tests/components/syslog/test.bk72xx-ard.yaml | 1 + tests/components/syslog/test.esp32-ard.yaml | 1 + .../components/syslog/test.esp32-c3-ard.yaml | 1 + .../components/syslog/test.esp32-c3-idf.yaml | 1 + tests/components/syslog/test.esp32-idf.yaml | 1 + tests/components/syslog/test.esp8266-ard.yaml | 1 + tests/components/syslog/test.host.yaml | 4 ++ tests/components/syslog/test.rp2040-ard.yaml | 1 + 13 files changed, 142 insertions(+) create mode 100644 esphome/components/syslog/__init__.py create mode 100644 esphome/components/syslog/esphome_syslog.cpp create mode 100644 esphome/components/syslog/esphome_syslog.h create mode 100644 tests/components/syslog/common.yaml create mode 100644 tests/components/syslog/test.bk72xx-ard.yaml create mode 100644 tests/components/syslog/test.esp32-ard.yaml create mode 100644 tests/components/syslog/test.esp32-c3-ard.yaml create mode 100644 tests/components/syslog/test.esp32-c3-idf.yaml create mode 100644 tests/components/syslog/test.esp32-idf.yaml create mode 100644 tests/components/syslog/test.esp8266-ard.yaml create mode 100644 tests/components/syslog/test.host.yaml create mode 100644 tests/components/syslog/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index d6381f9799..29919b6d70 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -429,6 +429,7 @@ esphome/components/sun/* @OttoWinter esphome/components/sun_gtil2/* @Mat931 esphome/components/switch/* @esphome/core esphome/components/switch/binary_sensor/* @ssieb +esphome/components/syslog/* @clydebarrow esphome/components/t6615/* @tylermenezes esphome/components/tc74/* @sethgirvan esphome/components/tca9548a/* @andreashergert1984 diff --git a/esphome/components/syslog/__init__.py b/esphome/components/syslog/__init__.py new file mode 100644 index 0000000000..80b79d2040 --- /dev/null +++ b/esphome/components/syslog/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import udp +from esphome.components.logger import LOG_LEVELS, is_log_level +from esphome.components.time import RealTimeClock +from esphome.components.udp import CONF_UDP_ID +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_LEVEL, CONF_PORT, CONF_TIME_ID +from esphome.cpp_types import Component, Parented + +CODEOWNERS = ["@clydebarrow"] + +DEPENDENCIES = ["udp", "logger", "time"] + +syslog_ns = cg.esphome_ns.namespace("syslog") +Syslog = syslog_ns.class_("Syslog", Component, Parented.template(udp.UDPComponent)) + +CONF_STRIP = "strip" +CONF_FACILITY = "facility" +CONFIG_SCHEMA = udp.UDP_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Syslog), + cv.GenerateID(CONF_TIME_ID): cv.use_id(RealTimeClock), + cv.Optional(CONF_PORT, default=514): cv.port, + cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, + cv.Optional(CONF_STRIP, default=True): cv.boolean, + cv.Optional(CONF_FACILITY, default=16): cv.int_range(0, 23), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_UDP_ID]) + time = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(parent.set_broadcast_port(config[CONF_PORT])) + cg.add(parent.set_should_broadcast()) + level = LOG_LEVELS[config[CONF_LEVEL]] + var = cg.new_Pvariable(config[CONF_ID], level, time) + await cg.register_component(var, config) + await cg.register_parented(var, parent) + cg.add(var.set_strip(config[CONF_STRIP])) + cg.add(var.set_facility(config[CONF_FACILITY])) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp new file mode 100644 index 0000000000..9d2cda549b --- /dev/null +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -0,0 +1,49 @@ +#include "esphome_syslog.h" + +#include "esphome/components/logger/logger.h" +#include "esphome/core/application.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace syslog { + +// Map log levels to syslog severity using an array, indexed by ESPHome log level (1-7) +constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { + 3, // NONE + 3, // ERROR + 4, // WARN + 5, // INFO + 6, // CONFIG + 7, // DEBUG + 7, // VERBOSE + 7 // VERY_VERBOSE +}; + +void Syslog::setup() { + logger::global_logger->add_on_log_callback( + [this](int level, const char *tag, const char *message) { this->log_(level, tag, message); }); +} + +void Syslog::log_(const int level, const char *tag, const char *message) const { + if (level > this->log_level_) + return; + // Syslog PRI calculation: facility * 8 + severity + int severity = 7; + if ((unsigned) level <= 7) { + severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; + } + int pri = this->facility_ * 8 + severity; + auto timestamp = this->time_->now().strftime("%b %d %H:%M:%S"); + unsigned len = strlen(message); + // remove color formatting + if (this->strip_ && message[0] == 0x1B && len > 11) { + message += 7; + len -= 11; + } + + auto data = str_sprintf("<%d>%s %s %s: %.*s", pri, timestamp.c_str(), App.get_name().c_str(), tag, len, message); + this->parent_->send_packet((const uint8_t *) data.data(), data.size()); +} + +} // namespace syslog +} // namespace esphome diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h new file mode 100644 index 0000000000..3fa077b466 --- /dev/null +++ b/esphome/components/syslog/esphome_syslog.h @@ -0,0 +1,25 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/components/udp/udp_component.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace syslog { +class Syslog : public Component, public Parented { + public: + Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} + void setup() override; + void set_strip(bool strip) { this->strip_ = strip; } + void set_facility(int facility) { this->facility_ = facility; } + + protected: + int log_level_; + void log_(int level, const char *tag, const char *message) const; + time::RealTimeClock *time_; + bool strip_{true}; + int facility_{16}; +}; +} // namespace syslog +} // namespace esphome diff --git a/tests/components/syslog/common.yaml b/tests/components/syslog/common.yaml new file mode 100644 index 0000000000..cd6e63c9ec --- /dev/null +++ b/tests/components/syslog/common.yaml @@ -0,0 +1,15 @@ +wifi: + ssid: MySSID + password: password1 + +udp: + addresses: ["239.0.60.53"] + +time: + platform: host + +syslog: + port: 514 + strip: true + level: info + facility: 16 diff --git a/tests/components/syslog/test.bk72xx-ard.yaml b/tests/components/syslog/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.esp32-ard.yaml b/tests/components/syslog/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.esp32-c3-ard.yaml b/tests/components/syslog/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.esp32-c3-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.esp32-c3-idf.yaml b/tests/components/syslog/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.esp32-idf.yaml b/tests/components/syslog/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.esp8266-ard.yaml b/tests/components/syslog/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/syslog/test.host.yaml b/tests/components/syslog/test.host.yaml new file mode 100644 index 0000000000..e735c37e4d --- /dev/null +++ b/tests/components/syslog/test.host.yaml @@ -0,0 +1,4 @@ +packages: + common: !include common.yaml + +wifi: !remove diff --git a/tests/components/syslog/test.rp2040-ard.yaml b/tests/components/syslog/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/syslog/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml