Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add syslog logger #1267

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#define WIFI_MAX_PASSWORD_STRLEN 64
#define WIFI_MAX_HOSTNAME_STRLEN 31

#define SYSLOG_MAX_HOSTNAME_STRLEN 128

#define NTP_MAX_SERVER_STRLEN 31
#define NTP_MAX_TIMEZONE_STRLEN 50
#define NTP_MAX_TIMEZONEDESCR_STRLEN 50
Expand Down Expand Up @@ -173,6 +175,12 @@ struct CONFIG_T {
bool Enabled;
} Mdns;

struct {
bool Enabled;
char Hostname[SYSLOG_MAX_HOSTNAME_STRLEN + 1];
uint16_t Port;
} Syslog;

struct {
char Server[NTP_MAX_SERVER_STRLEN + 1];
char Timezone[NTP_MAX_TIMEZONE_STRLEN + 1];
Expand Down
34 changes: 34 additions & 0 deletions include/SyslogLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <WiFiUdp.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>

class SyslogLogger {
public:
SyslogLogger();
void init(Scheduler& scheduler);
void updateSettings(const String&& hostname);
void write(const uint8_t *buffer, size_t size);

private:
void loop();
void disable();
void enable();
bool resolveAndStart();
bool isResolved() const {
return _address != INADDR_NONE;
}

Task _loopTask;
std::mutex _mutex;
WiFiUDP _udp;
IPAddress _address;
String _syslog_hostname;
String _proc_id;
String _header;
uint16_t _port;
bool _enabled;
};

extern SyslogLogger Syslog;
2 changes: 2 additions & 0 deletions include/WebApi_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ enum WebApiError {
NetworkDns1Invalid,
NetworkDns2Invalid,
NetworkApTimeoutInvalid,
NetworkSyslogHostnameLength,
NetworkSyslogPort,

NtpBase = 9000,
NtpServerLength,
Expand Down
3 changes: 3 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

#define MDNS_ENABLED false

#define SYSLOG_ENABLED false
#define SYSLOG_PORT 514

#define NTP_SERVER_OLD "pool.ntp.org"
#define NTP_SERVER "opendtu.pool.ntp.org"
#define NTP_TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
Expand Down
10 changes: 10 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ bool ConfigurationClass::write()
JsonObject mdns = doc["mdns"].to<JsonObject>();
mdns["enabled"] = config.Mdns.Enabled;

JsonObject syslog = doc["syslog"].to<JsonObject>();
syslog["enabled"] = config.Syslog.Enabled;
syslog["hostname"] = config.Syslog.Hostname;
syslog["port"] = config.Syslog.Port;

JsonObject ntp = doc["ntp"].to<JsonObject>();
ntp["server"] = config.Ntp.Server;
ntp["timezone"] = config.Ntp.Timezone;
Expand Down Expand Up @@ -450,6 +455,11 @@ bool ConfigurationClass::read()
JsonObject mdns = doc["mdns"];
config.Mdns.Enabled = mdns["enabled"] | MDNS_ENABLED;

JsonObject syslog = doc["syslog"];
config.Syslog.Enabled = syslog["enabled"] | SYSLOG_ENABLED;
strlcpy(config.Syslog.Hostname, syslog["hostname"] | "", sizeof(config.Syslog.Hostname));
config.Syslog.Port = syslog["port"] | SYSLOG_PORT;

JsonObject ntp = doc["ntp"];
strlcpy(config.Ntp.Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp.Server));
strlcpy(config.Ntp.Timezone, ntp["timezone"] | NTP_TIMEZONE, sizeof(config.Ntp.Timezone));
Expand Down
3 changes: 3 additions & 0 deletions src/MessageOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
#include <HardwareSerial.h>
#include "MessageOutput.h"
#include "SyslogLogger.h"

MessageOutputClass MessageOutput;

Expand Down Expand Up @@ -102,12 +103,14 @@ void MessageOutputClass::loop()

if (!_ws) {
while (!_lines.empty()) {
Syslog.write(_lines.front().data(), _lines.front().size());
_lines.pop(); // do not hog memory
}
return;
}

while (!_lines.empty() && _ws->availableForWriteAll()) {
Syslog.write(_lines.front().data(), _lines.front().size());
_ws->textAll(std::make_shared<message_t>(std::move(_lines.front())));
_lines.pop();
}
Expand Down
5 changes: 5 additions & 0 deletions src/NetworkSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "NetworkSettings.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "SyslogLogger.h"
#include "PinMapping.h"
#include "Utils.h"
#include "SPIPortManager.h"
Expand Down Expand Up @@ -53,6 +54,8 @@ void NetworkSettingsClass::init(Scheduler& scheduler)

scheduler.addTask(_loopTask);
_loopTask.enable();

Syslog.init(scheduler);
}

void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t info)
Expand Down Expand Up @@ -294,6 +297,8 @@ void NetworkSettingsClass::applyConfig()
}
MessageOutput.println("done");
setStaticIp();

Syslog.updateSettings(getHostname());
}

void NetworkSettingsClass::setHostname()
Expand Down
138 changes: 138 additions & 0 deletions src/SyslogLogger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include <HardwareSerial.h>
#include <ESPmDNS.h>
#include "defaults.h"
#include "SyslogLogger.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "NetworkSettings.h"

SyslogLogger::SyslogLogger()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&SyslogLogger::loop, this))
{
}

void SyslogLogger::init(Scheduler& scheduler)
{
// PROCID change indicates a restart.
_proc_id = String(esp_random(), HEX);

scheduler.addTask(_loopTask);
_loopTask.enable();
}

void SyslogLogger::updateSettings(const String&& hostname)
{
auto& config = Configuration.get().Syslog;

// Disable logger while it is reconfigured.
disable();

if (!config.Enabled) {
MessageOutput.println("[SyslogLogger] Syslog not enabled");
return;
}

_port = config.Port;
_syslog_hostname = config.Hostname;
if (_syslog_hostname.isEmpty()) {
MessageOutput.println("[SyslogLogger] Hostname not configured");
return;
}

MessageOutput.printf("[SyslogLogger] Logging to %s!\r\n", _syslog_hostname.c_str());

_header = "<14>1 - "; // RFC5424: Facility USER, severity INFO, version 1, NIL timestamp.
_header += hostname;
_header += " OpenDTU ";
_header += _proc_id;
// NIL values for message id and structured data
_header += " - - ";

// Enable logger.
enable();
}

void SyslogLogger::write(const uint8_t *buffer, size_t size)
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_enabled || !isResolved()) {
return;
}
for (int i = 0; i < size; i++) {
uint8_t c = buffer[i];
bool overflow = false;
if (c != '\r' && c != '\n') {
// Replace control and non-ASCII characters with '?'.
overflow = !_udp.write(c >= 0x20 && c < 0x7f ? c : '?');
}
ranma marked this conversation as resolved.
Show resolved Hide resolved
if (c == '\n' || overflow) {
_udp.endPacket();
_udp.beginPacket(_address, _port);
_udp.print(_header);
}
}
}

void SyslogLogger::disable()
{
MessageOutput.println("[SyslogLogger] Disable");
std::lock_guard<std::mutex> lock(_mutex);
if (_enabled) {
_enabled = false;
_address = INADDR_NONE;
_udp.stop();
}
}

void SyslogLogger::enable()
{
// Bind random source port.
if (!_udp.begin(0)) {
MessageOutput.println("[SyslogLogger] No sockets available");
return;
}

std::lock_guard<std::mutex> lock(_mutex);
_enabled = true;
}

bool SyslogLogger::resolveAndStart()
{
if (Configuration.get().Mdns.Enabled) {
_address = MDNS.queryHost(_syslog_hostname); // INADDR_NONE if failed
}
if (_address != INADDR_NONE) {
if (!_udp.beginPacket(_address, _port)) {
return false;
}
} else {
if (!_udp.beginPacket(_syslog_hostname.c_str(), _port)) {
return false;
}
_address = _udp.remoteIP(); // Store resolved address.
}
_udp.print(_header);
_udp.print("[SyslogLogger] Logging to ");
_udp.print(_syslog_hostname);
_udp.endPacket();
_udp.beginPacket(_address, _port);
_udp.print(_header);
return true;
}

void SyslogLogger::loop()
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_enabled || !NetworkSettings.isConnected() || isResolved()) {
return;
}
if (!resolveAndStart()) {
_enabled = false;
}
}

SyslogLogger Syslog;
23 changes: 23 additions & 0 deletions src/WebApi_network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
root["password"] = config.WiFi.Password;
root["aptimeout"] = config.WiFi.ApTimeout;
root["mdnsenabled"] = config.Mdns.Enabled;
root["syslogenabled"] = config.Syslog.Enabled;
root["sysloghostname"] = config.Syslog.Hostname;
root["syslogport"] = config.Syslog.Port;

WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}
Expand Down Expand Up @@ -163,6 +166,23 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}
if (root["syslogenabled"].as<bool>()) {
if (root["sysloghostname"].as<String>().length() == 0 || root["sysloghostname"].as<String>().length() > SYSLOG_MAX_HOSTNAME_STRLEN) {
retMsg["message"] = "Syslog Server must between 1 and " STR(SYSLOG_MAX_HOSTNAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::NetworkSyslogHostnameLength;
retMsg["param"]["max"] = SYSLOG_MAX_HOSTNAME_STRLEN;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}

if (root["syslogport"].as<uint>() == 0 || root["syslogport"].as<uint>() > 65535) {
retMsg["message"] = "Port must be a number between 1 and 65535!";
retMsg["code"] = WebApiError::NetworkSyslogPort;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}

}

CONFIG_T& config = Configuration.get();
config.WiFi.Ip[0] = ipaddress[0];
Expand Down Expand Up @@ -195,6 +215,9 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
}
config.WiFi.ApTimeout = root["aptimeout"].as<uint>();
config.Mdns.Enabled = root["mdnsenabled"].as<bool>();
config.Syslog.Enabled = root["syslogenabled"].as<bool>();
strlcpy(config.Syslog.Hostname, root["sysloghostname"].as<String>().c_str(), sizeof(config.Syslog.Hostname));
config.Syslog.Port = root["syslogport"].as<uint>();

WebApi.writeConfig(retMsg);

Expand Down
5 changes: 4 additions & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,10 @@
"ApTimeoutHint": "Zeit die der AccessPoint offen gehalten wird. Ein Wert von 0 bedeutet unendlich.",
"Minutes": "Minuten",
"EnableMdns": "mDNS aktivieren",
"MdnsSettings": "mDNS-Einstellungen"
"MdnsSettings": "mDNS-Einstellungen",
"EnableSyslog": "Syslog aktivieren",
"SyslogSettings": "Syslog-Einstellungen",
"Port": "Port:"
},
"mqttadmin": {
"MqttSettings": "MQTT-Einstellungen",
Expand Down
5 changes: 4 additions & 1 deletion webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,10 @@
"ApTimeoutHint": "Time which the AccessPoint is kept open. A value of 0 means infinite.",
"Minutes": "minutes",
"EnableMdns": "Enable mDNS",
"MdnsSettings": "mDNS Settings"
"MdnsSettings": "mDNS Settings",
"EnableSyslog": "Enable Syslog",
"SyslogSettings": "Syslog Settings",
"Port": "Port:"
},
"mqttadmin": {
"MqttSettings": "MQTT Settings",
Expand Down
3 changes: 3 additions & 0 deletions webapp/src/types/NetworkConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export interface NetworkConfig {
dns2: string;
aptimeout: number;
mdnsenabled: boolean;
syslogenabled: boolean;
sysloghostname: string;
syslogport: number;
}
23 changes: 23 additions & 0 deletions webapp/src/views/NetworkAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,29 @@
/>
</CardElement>

<CardElement :text="$t('networkadmin.SyslogSettings')" textVariant="text-bg-primary" add-space>
<InputElement
:label="$t('networkadmin.EnableSyslog')"
v-model="networkConfigList.syslogenabled"
type="checkbox"
/>

<InputElement
:label="$t('networkadmin.Hostname', { num: 1 })"
v-model="networkConfigList.sysloghostname"
type="text"
maxlength="128"
/>

<InputElement
:label="$t('networkadmin.Port')"
v-model="networkConfigList.syslogport"
type="number"
min="1"
max="65535"
/>
</CardElement>

<CardElement :text="$t('networkadmin.AdminAp')" textVariant="text-bg-primary" add-space>
<InputElement
:label="$t('networkadmin.ApTimeout')"
Expand Down