Draft syslog component for esp-idf
This commit is contained in:
@@ -13,3 +13,5 @@ packages:
|
|||||||
btproxy: !include templates/btproxy.yaml
|
btproxy: !include templates/btproxy.yaml
|
||||||
# Allow HA to use our proxy.
|
# Allow HA to use our proxy.
|
||||||
api: !include templates/api.yaml
|
api: !include templates/api.yaml
|
||||||
|
# With idf framework, we can use syslog.
|
||||||
|
syslog: !include templates/syslog.yaml
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.const import CONF_ID, CONF_IP_ADDRESS, CONF_PORT, CONF_CLIENT_ID, CONF_LEVEL, CONF_PAYLOAD, CONF_TAG
|
||||||
|
from esphome.components.logger import LOG_LEVELS, is_log_level
|
||||||
|
|
||||||
|
CONF_STRIP_COLOR_CODES = "strip_color_codes"
|
||||||
|
CONF_FORWARD_LOGGER = "forward_logger"
|
||||||
|
CONF_MIN_LOG_LEVEL = "min_log_level"
|
||||||
|
|
||||||
|
DEPENDENCIES = ['logger', 'network']
|
||||||
|
|
||||||
|
syslog_ns = cg.esphome_ns.namespace('syslog')
|
||||||
|
|
||||||
|
Syslog = syslog_ns.class_('Syslog', cg.Component)
|
||||||
|
SyslogLogAction = syslog_ns.class_('SyslogLogAction', automation.Action)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(Syslog),
|
||||||
|
cv.Optional(CONF_IP_ADDRESS, default="255.255.255.255"): cv.string_strict,
|
||||||
|
cv.Optional(CONF_PORT, default=514): cv.port,
|
||||||
|
cv.Optional(CONF_FORWARD_LOGGER, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_STRIP_COLOR_CODES, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_MIN_LOG_LEVEL, default="DEBUG"): is_log_level,
|
||||||
|
cv.Optional(CONF_CLIENT_ID): cv.string_strict,
|
||||||
|
})
|
||||||
|
|
||||||
|
SYSLOG_LOG_ACTION_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.use_id(Syslog),
|
||||||
|
cv.Required(CONF_LEVEL): cv.templatable(cv.int_range(min=0, max=7)),
|
||||||
|
cv.Required(CONF_TAG): cv.templatable(cv.string),
|
||||||
|
cv.Required(CONF_PAYLOAD): cv.templatable(cv.string),
|
||||||
|
})
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_forward_logger(config[CONF_FORWARD_LOGGER]))
|
||||||
|
cg.add(var.set_strip_color_codes(config[CONF_STRIP_COLOR_CODES]))
|
||||||
|
cg.add(var.set_server_ip_address(config[CONF_IP_ADDRESS]))
|
||||||
|
cg.add(var.set_server_port(config[CONF_PORT]))
|
||||||
|
cg.add(var.set_min_log_level(LOG_LEVELS[config[CONF_MIN_LOG_LEVEL]]))
|
||||||
|
if CONF_CLIENT_ID in config:
|
||||||
|
cg.add(var.set_hostname(config[CONF_CLIENT_ID]))
|
||||||
|
|
||||||
|
@automation.register_action('syslog.log', SyslogLogAction, SYSLOG_LOG_ACTION_SCHEMA)
|
||||||
|
def syslog_log_action_to_code(config, action_id, template_arg, args):
|
||||||
|
parent = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||||
|
|
||||||
|
template_ = yield cg.templatable(config[CONF_LEVEL], args, cg.int)
|
||||||
|
cg.add(var.set_level(template_))
|
||||||
|
template_ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
|
||||||
|
cg.add(var.set_tag(template_))
|
||||||
|
template_ = yield cg.templatable(config[CONF_PAYLOAD], args, cg.std_string)
|
||||||
|
cg.add(var.set_payload(template_))
|
||||||
|
|
||||||
|
yield var
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#include "syslog.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
#include "esphome/components/logger/logger.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
/*
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/version.h"
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
namespace syslog {
|
||||||
|
|
||||||
|
std::string remove_ansi_colors(const std::string& input) {
|
||||||
|
std::string result;
|
||||||
|
bool escape_sequence = false;
|
||||||
|
|
||||||
|
for (char c : input) {
|
||||||
|
if (c == '\033') { // Start of an escape sequence
|
||||||
|
escape_sequence = true;
|
||||||
|
} else if (escape_sequence && c == 'm') { // End of an escape sequence
|
||||||
|
escape_sequence = false;
|
||||||
|
} else if (!escape_sequence) { // Normal character
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Priority {
|
||||||
|
EMERG = 0, /* System is unusable */
|
||||||
|
ALERT = 1, /* Action must be taken immediately */
|
||||||
|
CRIT = 2, /* Critical conditions */
|
||||||
|
ERR = 3, /* Error conditions */
|
||||||
|
WARNING = 4, /* Warning conditions */
|
||||||
|
NOTICE = 5, /* Normal but significant conditions */
|
||||||
|
INFO = 6, /* Informational messages */
|
||||||
|
DEBUG = 7, /* Debug-level messages */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t esphome_to_syslog_log_levels[] = {
|
||||||
|
/* ESPHOME_LOG_LEVEL_NONE */ Priority::DEBUG,
|
||||||
|
/* ESPHOME_LOG_LEVEL_ERROR */ Priority::ERR,
|
||||||
|
/* ESPHOME_LOG_LEVEL_WARN */ Priority::WARNING,
|
||||||
|
/* ESPHOME_LOG_LEVEL_INFO */ Priority::INFO,
|
||||||
|
/* ESPHOME_LOG_LEVEL_CONFIG */ Priority::NOTICE,
|
||||||
|
/* ESPHOME_LOG_LEVEL_DEBUG */ Priority::DEBUG,
|
||||||
|
/* ESPHOME_LOG_LEVEL_VERBOSE */ Priority::DEBUG,
|
||||||
|
/* ESPHOME_LOG_LEVEL_VERY_VERBOSE */ Priority::DEBUG,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t ESPHOME_LOG_LEVELS = 8;
|
||||||
|
|
||||||
|
static const char *TAG = "syslog";
|
||||||
|
|
||||||
|
Syslog::Syslog() {
|
||||||
|
this->hostname_ = ::esphome::network::get_use_address();
|
||||||
|
this->errors_encountered_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Syslog::setup() {
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
if (logger::global_logger != nullptr && this->forward_logger_) {
|
||||||
|
logger::global_logger->add_on_log_callback(
|
||||||
|
[this](int level, const char *tag, const char *message) {
|
||||||
|
if (level > this->min_log_level_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->strip_color_codes_) {
|
||||||
|
std::string clean_message = remove_ansi_colors(message);
|
||||||
|
this->log(level, tag, clean_message);
|
||||||
|
} else {
|
||||||
|
this->log(level, tag, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->socket_fd_ = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
|
||||||
|
memset(&this->destination_, 0, sizeof(this->destination_));
|
||||||
|
this->destination_.sin_family = AF_INET;
|
||||||
|
this->destination_.sin_port = htons(this->server_port_);
|
||||||
|
this->destination_.sin_addr.s_addr = inet_addr(this->server_ip_address_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Syslog::loop() {
|
||||||
|
if (!this->latest_error_message_.empty() && this->errors_encountered_ >= 10) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send log: %s", latest_error_message_.c_str());
|
||||||
|
this->errors_encountered_ = 0;
|
||||||
|
latest_error_message_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Syslog::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Syslog:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Server IP address: %s", this->server_ip_address_.c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Server port: %d", this->server_port_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Client ID (syslog hostname): %s", this->hostname_.c_str());
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
ESP_LOGCONFIG(TAG, " Min log level: %d", this->min_log_level_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Forward logger messages: %s", this->forward_logger_ ? "yes" : "no");
|
||||||
|
ESP_LOGCONFIG(TAG, " Strip color codes: %s", this->strip_color_codes_ ? "yes" : "no");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Syslog::log(int level, const std::string &tag, const std::string &msg) {
|
||||||
|
if (level >= ESPHOME_LOG_LEVELS) {
|
||||||
|
level = ESPHOME_LOG_LEVELS - 1;
|
||||||
|
} else if (level < 0) {
|
||||||
|
level = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream payload_stream;
|
||||||
|
// rsyslog compatible protocol:
|
||||||
|
// <PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG
|
||||||
|
payload_stream
|
||||||
|
<< "<" << static_cast<int>(esphome_to_syslog_log_levels[level]) << ">1 - "
|
||||||
|
<< this->hostname_ << " "
|
||||||
|
<< App.get_name() << " - - - " << msg;
|
||||||
|
|
||||||
|
std::string payload = payload_stream.str();
|
||||||
|
ssize_t bytes_sent = ::sendto(
|
||||||
|
this->socket_fd_,
|
||||||
|
payload.c_str(), payload.length(),
|
||||||
|
0,
|
||||||
|
reinterpret_cast<sockaddr*>(&this->destination_), sizeof(this->destination_));
|
||||||
|
|
||||||
|
if (bytes_sent != payload.length()) {
|
||||||
|
// Can't log here, but loop() will pick it up.
|
||||||
|
this->latest_error_message_ = strerror(errno);
|
||||||
|
++this->errors_encountered_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace syslog
|
||||||
|
} // namespace esphome
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Potential future improvements:
|
||||||
|
|
||||||
|
- hostname instead of IP address
|
||||||
|
- custom mapping of log leves to syslog levels
|
||||||
|
- set custom facility
|
||||||
|
- protocol support: Structured Data, BSD, IPv6
|
||||||
|
- allow specifying time component to get timestamp
|
||||||
|
- code optimization (e.g. marking methods as HOT or using fewer complex ops)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/components/socket/socket.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
namespace syslog {
|
||||||
|
|
||||||
|
class Syslog : public Component {
|
||||||
|
public:
|
||||||
|
explicit Syslog();
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void set_server_ip_address(const std::string &address) { this->server_ip_address_ = address; }
|
||||||
|
void set_server_port(uint16_t port) { this->server_port_ = port; }
|
||||||
|
void set_hostname(const std::string &hostname) { this->hostname_ = hostname; }
|
||||||
|
void set_min_log_level(int log_level) { this->min_log_level_ = log_level; }
|
||||||
|
void set_forward_logger(bool forward) { this->forward_logger_ = forward; }
|
||||||
|
void set_strip_color_codes(bool strip) { this->strip_color_codes_ = strip; }
|
||||||
|
|
||||||
|
void log(int level, const std::string &tag, const std::string &msg);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string server_ip_address_;
|
||||||
|
uint16_t server_port_;
|
||||||
|
std::string hostname_;
|
||||||
|
int min_log_level_;
|
||||||
|
bool forward_logger_;
|
||||||
|
bool strip_color_codes_;
|
||||||
|
|
||||||
|
int socket_fd_;
|
||||||
|
struct sockaddr_in destination_;
|
||||||
|
|
||||||
|
int errors_encountered_;
|
||||||
|
std::string latest_error_message_;
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
class SyslogLogAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
SyslogLogAction(Syslog *parent) : parent_(parent) {}
|
||||||
|
TEMPLATABLE_VALUE(int, level)
|
||||||
|
TEMPLATABLE_VALUE(std::string, tag)
|
||||||
|
TEMPLATABLE_VALUE(std::string, payload)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
this->parent_->log(
|
||||||
|
this->level_.value(x...),
|
||||||
|
this->tag_.value(x...),
|
||||||
|
this->payload_.value(x...));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Syslog *parent_;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace syslog
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
@@ -23,6 +23,7 @@ packages:
|
|||||||
wifi: !include wifi.yaml
|
wifi: !include wifi.yaml
|
||||||
uptime: !include uptime.yaml
|
uptime: !include uptime.yaml
|
||||||
restart: !include restart.yaml
|
restart: !include restart.yaml
|
||||||
|
syslog: !include syslog.yaml
|
||||||
|
|
||||||
logger:
|
logger:
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ wifi_ap_lookup:
|
|||||||
api_encryption_key: "K3g+SB5OYgLxKtEXhWJd1Mryfy9hGol+mAq+nq/JDsY="
|
api_encryption_key: "K3g+SB5OYgLxKtEXhWJd1Mryfy9hGol+mAq+nq/JDsY="
|
||||||
sntp_servers:
|
sntp_servers:
|
||||||
- pool.ntp.org
|
- pool.ntp.org
|
||||||
|
syslog_server: 1.2.3.4
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
external_components:
|
||||||
|
# Path is relative to the main YAML file, not this device_base template!
|
||||||
|
- source: components
|
||||||
|
|
||||||
|
syslog:
|
||||||
|
ip_address: !secret syslog_server
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
# Disable logging to save bandwidth.
|
||||||
|
log_topic: null
|
||||||
|
|
||||||
|
logger:
|
||||||
|
# Disable logging to save cycles.
|
||||||
|
baud_rate: 0
|
||||||
Reference in New Issue
Block a user