Skip to content

Commit 25c4c5b

Browse files
authored
add syslog function to wled
1 parent 410025f commit 25c4c5b

File tree

10 files changed

+438
-6
lines changed

10 files changed

+438
-6
lines changed

wled00/bus_manager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ make_unique(Args&&... args)
2424
#endif
2525

2626
// enable additional debug output
27-
#if defined(WLED_DEBUG_HOST)
27+
#if defined(WLED_ENABLE_SYSLOG)
28+
#include "syslog.h"
29+
#define DEBUGOUT Syslog
30+
#elif defined(WLED_DEBUG_HOST)
2831
#include "net_debug.h"
2932
#define DEBUGOUT NetDebug
3033
#else

wled00/cfg.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
536536
CJSON(hueIP[i], if_hue_ip[i]);
537537
#endif
538538

539+
#ifdef WLED_ENABLE_SYSLOG
540+
JsonObject if_syslog = interfaces["syslog"];
541+
CJSON(syslogEnabled, if_syslog["en"]);
542+
getStringFromJson(syslogHost, if_syslog[F("host")], 33);
543+
CJSON(syslogPort, if_syslog["port"]);
544+
CJSON(syslogProtocol, if_syslog["proto"]);
545+
CJSON(syslogFacility, if_syslog["fac"]);
546+
CJSON(syslogSeverity, if_syslog["sev"]);
547+
#endif
548+
539549
JsonObject if_ntp = interfaces[F("ntp")];
540550
CJSON(ntpEnabled, if_ntp["en"]);
541551
getStringFromJson(ntpServerName, if_ntp[F("host")], 33); // "1.wled.pool.ntp.org"
@@ -1051,6 +1061,16 @@ void serializeConfig(JsonObject root) {
10511061
}
10521062
#endif
10531063

1064+
#ifdef WLED_ENABLE_SYSLOG
1065+
JsonObject if_syslog = interfaces.createNestedObject("syslog");
1066+
if_syslog["en"] = syslogEnabled;
1067+
if_syslog["host"] = syslogHost;
1068+
if_syslog["port"] = syslogPort;
1069+
if_syslog["proto"] = syslogProtocol;
1070+
if_syslog["fac"] = syslogFacility;
1071+
if_syslog["sev"] = syslogSeverity;
1072+
#endif
1073+
10541074
JsonObject if_ntp = interfaces.createNestedObject("ntp");
10551075
if_ntp["en"] = ntpEnabled;
10561076
if_ntp[F("host")] = ntpServerName;

wled00/data/settings_sync.htm

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,59 @@ <h3>Serial</h3>
237237
</select><br>
238238
<i>Keep at 115200 to use Improv. Some boards may not support high rates.</i>
239239
</div>
240+
<h3>Syslog</h3>
241+
<div id="NoSyslog" class="hide">
242+
<em class="warn">This firmware build does not support Syslog interface.<br></em>
243+
</div>
244+
<div id="Syslog">
245+
Enable Syslog: <input type="checkbox" name="SL_en"><br>
246+
Host: <input type="text" name="SL_host" maxlength="32"><br>
247+
Port: <input type="number" name="SL_port" min="1" max="65535" value="%SL_port%"><br>
248+
Protocol:
249+
<select name=SL_proto>
250+
<option value="0">BSD (RFC3164)</option>
251+
<option value="1">RFC5424</option>
252+
<option value="2">Raw</option>
253+
</select><br>
254+
Facility:
255+
<select name=SL_fac>
256+
<option value="0">KERN</option>
257+
<option value="1">USER</option>
258+
<option value="2">MAIL</option>
259+
<option value="3">DAEMON</option>
260+
<option value="4">AUTH</option>
261+
<option value="5">SYSLOG</option>
262+
<option value="6">LPR</option>
263+
<option value="7">NEWS</option>
264+
<option value="8">UUCP</option>
265+
<option value="9">CRON</option>
266+
<option value="10">AUTHPRIV</option>
267+
<option value="11">FTP</option>
268+
<option value="12">NTP</option>
269+
<option value="13">LOG_AUDIT</option>
270+
<option value="14">LOG_ALERT</option>
271+
<option value="15">CLOCK_DAEMON</option>
272+
<option value="16">LOCAL0</option>
273+
<option value="17">LOCAL1</option>
274+
<option value="18">LOCAL2</option>
275+
<option value="19">LOCAL3</option>
276+
<option value="20">LOCAL4</option>
277+
<option value="21">LOCAL5</option>
278+
<option value="22">LOCAL6</option>
279+
<option value="23">LOCAL7</option>
280+
</select><br>
281+
Severity:
282+
<select name=SL_sev>
283+
<option value="0">EMERG</option>
284+
<option value="1">ALERT</option>
285+
<option value="2">CRIT</option>
286+
<option value="3">ERR</option>
287+
<option value="4">WARNING</option>
288+
<option value="5">NOTICE</option>
289+
<option value="6">INFO</option>
290+
<option value="7">DEBUG</option>
291+
</select><br>
292+
</div>
240293
<hr>
241294
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
242295
</form>

wled00/json.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,10 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
298298
bool stateResponse = root[F("v")] | false;
299299

300300
#if defined(WLED_DEBUG) && defined(WLED_DEBUG_HOST)
301-
netDebugEnabled = root[F("debug")] | netDebugEnabled;
301+
netDebugEnabled = root[F("debug")] | netDebugEnabled;
302+
#elif defined(WLED_DEBUG) && defined(WLED_ENABLE_SYSLOG)
303+
syslogEnabled = root[F("debug")] | syslogEnabled;
304+
DEBUG_PRINTF_P(PSTR("Syslog: %s\n"), syslogEnabled ? PSTR("ENABLED") : PSTR("DISABLED") );
302305
#endif
303306

304307
bool onBefore = bri;
@@ -781,6 +784,9 @@ void serializeInfo(JsonObject root)
781784
#ifdef WLED_DEBUG_HOST
782785
os |= 0x0100;
783786
if (!netDebugEnabled) os &= ~0x0080;
787+
#elif defined(WLED_ENABLE_SYSLOG)
788+
os |= 0x0100;
789+
if (!syslogEnabled) os &= ~0x0080;
784790
#endif
785791
#endif
786792
#ifndef WLED_DISABLE_ALEXA

wled00/set.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,28 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
472472
t = request->arg(F("BD")).toInt();
473473
if (t >= 96 && t <= 15000) serialBaud = t;
474474
updateBaudRate(serialBaud *100);
475+
476+
#ifdef WLED_ENABLE_SYSLOG
477+
syslogEnabled = request->hasArg(F("SL_en"));
478+
strlcpy(syslogHost, request->arg(F("SL_host")).c_str(), sizeof(syslogHost));
479+
480+
t = request->arg(F("SL_port")).toInt();
481+
if (t > 0) syslogPort = t;
482+
483+
t = request->arg(F("SL_proto")).toInt();
484+
if (t >= SYSLOG_PROTO_BSD && t <= SYSLOG_PROTO_RAW) syslogProtocol = t;
485+
486+
t = request->arg(F("SL_fac")).toInt();
487+
if (t >= SYSLOG_KERN && t <= SYSLOG_LOCAL7) syslogFacility = t;
488+
489+
t = request->arg(F("SL_sev")).toInt();
490+
if (t >= SYSLOG_EMERG && t <= SYSLOG_DEBUG) syslogSeverity = t;
491+
492+
Syslog.begin(syslogHost, syslogPort,
493+
syslogFacility, syslogSeverity, syslogProtocol);
494+
495+
Syslog.setAppName("WLED");
496+
#endif
475497
}
476498

477499
//TIME

wled00/syslog.cpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#include "wled.h"
2+
#ifdef WLED_ENABLE_SYSLOG
3+
4+
#include "syslog.h"
5+
6+
static const __FlashStringHelper* protoNames[] = { F("BSD"), F("RFC5424"), F("RAW") };
7+
static const char* facilityNames[] = {
8+
"KERN", "USER", "MAIL", "DAEM",
9+
"AUTH", "SYSL", "LPR", "NEWS",
10+
"UUCP", "CRON", "APRV", "FTP",
11+
"NTP", "AUDT", "ALRT", "CLCK",
12+
"LCL0", "LCL1", "LCL2", "LCL3",
13+
"LCL4", "LCL5", "LCL6", "LCL7"
14+
};
15+
16+
static const char* severityNames[] = {
17+
"EMERG","ALERT","CRIT","ERR","WARNING","NOTICE","INFO","DEBUG"
18+
};
19+
20+
SyslogPrinter::SyslogPrinter() :
21+
_facility(SYSLOG_LOCAL4),
22+
_severity(SYSLOG_DEBUG),
23+
_protocol(SYSLOG_PROTO_BSD),
24+
_appName("WLED"),
25+
_bufferIndex(0) {}
26+
27+
void SyslogPrinter::begin(const char* host, uint16_t port,
28+
uint8_t facility, uint8_t severity, uint8_t protocol) {
29+
30+
DEBUG_PRINTF_P(PSTR("===== WLED SYSLOG CONFIGURATION =====\n"));
31+
DEBUG_PRINTF_P(PSTR(" Hostname: %s\n"), syslogHost);
32+
DEBUG_PRINTF_P(PSTR(" Cached IP: %s\n"), syslogHostIP.toString().c_str());
33+
DEBUG_PRINTF_P(PSTR(" Port: %u\n"), (unsigned)syslogPort);
34+
DEBUG_PRINTF_P(PSTR(" Protocol: %u (%s)\n"),
35+
protocol,
36+
protocol <= 2 ? (const char*)protoNames[protocol] : "UNKNOWN"
37+
);
38+
DEBUG_PRINTF_P(PSTR(" Facility: %u (%s)\n"),
39+
(unsigned)facility,
40+
(facility < sizeof(facilityNames)/sizeof(facilityNames[0]))
41+
? facilityNames[facility]
42+
: PSTR("UNKNOWN"));
43+
DEBUG_PRINTF_P(PSTR(" Severity: %u (%s)\n"),
44+
(unsigned)severity,
45+
(severity < sizeof(severityNames)/sizeof(severityNames[0]))
46+
? severityNames[severity]
47+
: PSTR("UNKNOWN"));
48+
DEBUG_PRINTF_P(PSTR("======================================\n"));
49+
50+
strlcpy(syslogHost, host, sizeof(syslogHost));
51+
syslogPort = port;
52+
_facility = facility;
53+
_severity = severity;
54+
_protocol = protocol;
55+
56+
// clear any cached IP so resolveHostname() will run next write()
57+
syslogHostIP = IPAddress(0,0,0,0);
58+
}
59+
60+
void SyslogPrinter::setAppName(const String &appName) {
61+
_appName = appName;
62+
}
63+
64+
bool SyslogPrinter::resolveHostname() {
65+
if (!WLED_CONNECTED || !syslogEnabled) return false;
66+
67+
// If we already have an IP or can parse the hostname as an IP, use that
68+
if (syslogHostIP || syslogHostIP.fromString(syslogHost)) {
69+
return true;
70+
}
71+
72+
// Otherwise resolve the hostname
73+
#ifdef ESP8266
74+
WiFi.hostByName(syslogHost, syslogHostIP, 750);
75+
#else
76+
#ifdef WLED_USE_ETHERNET
77+
ETH.hostByName(syslogHost, syslogHostIP);
78+
#else
79+
WiFi.hostByName(syslogHost, syslogHostIP);
80+
#endif
81+
#endif
82+
83+
return syslogHostIP != IPAddress(0, 0, 0, 0);
84+
}
85+
86+
void SyslogPrinter::flushBuffer() {
87+
if (_bufferIndex == 0) return;
88+
89+
// Trim any trailing CR so syslog server won’t show “#015”
90+
if (_bufferIndex > 0 && _buffer[_bufferIndex-1] == '\r') _bufferIndex--;
91+
92+
// Null-terminate
93+
_buffer[_bufferIndex] = '\0';
94+
95+
// Send the buffer with default severity
96+
write((const uint8_t*)_buffer, _bufferIndex, _severity);
97+
98+
// Reset buffer index
99+
_bufferIndex = 0;
100+
}
101+
102+
size_t SyslogPrinter::write(uint8_t c) {
103+
// Store in buffer regardless of connection status
104+
if (_bufferIndex < sizeof(_buffer) - 1) {
105+
_buffer[_bufferIndex++] = c;
106+
}
107+
108+
// If newline or buffer full, flush
109+
if (c == '\n' || _bufferIndex >= sizeof(_buffer) - 1) {
110+
flushBuffer();
111+
}
112+
113+
return 1;
114+
}
115+
116+
size_t SyslogPrinter::write(const uint8_t *buf, size_t size) {
117+
return write(buf, size, _severity);
118+
}
119+
120+
size_t SyslogPrinter::write(const uint8_t *buf, size_t size, uint8_t severity) {
121+
if (!WLED_CONNECTED || buf == nullptr || !syslogEnabled) return 0;
122+
if (!resolveHostname()) return 0;
123+
124+
syslogUdp.beginPacket(syslogHostIP, syslogPort);
125+
126+
// Calculate priority value
127+
uint8_t pri = (_facility << 3) | severity;
128+
129+
// Handle different syslog protocol formats
130+
switch (_protocol) {
131+
case SYSLOG_PROTO_BSD:
132+
// RFC 3164 format: <PRI>TIMESTAMP HOSTNAME APP-NAME: MESSAGE
133+
syslogUdp.printf("<%d>", pri);
134+
135+
if (ntpEnabled && ntpConnected) {
136+
// Month abbreviation
137+
const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
138+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
139+
140+
syslogUdp.printf("%s %2d %02d:%02d:%02d ",
141+
months[month(localTime) - 1],
142+
day(localTime),
143+
hour(localTime),
144+
minute(localTime),
145+
second(localTime));
146+
} else {
147+
// No valid time available
148+
syslogUdp.print("Jan 01 00:00:00 ");
149+
}
150+
151+
// Add hostname and app name
152+
syslogUdp.print(serverDescription);
153+
syslogUdp.print(" ");
154+
syslogUdp.print(_appName);
155+
syslogUdp.print(": ");
156+
157+
// Add message content
158+
size = syslogUdp.write(buf, size);
159+
break;
160+
161+
case SYSLOG_PROTO_RFC5424:
162+
// RFC 5424 format: <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSG
163+
syslogUdp.printf("<%d>1 ", pri); // Version is always 1
164+
165+
if (ntpEnabled && ntpConnected) {
166+
syslogUdp.printf("%04d-%02d-%02dT%02d:%02d:%02dZ ",
167+
year(localTime),
168+
month(localTime),
169+
day(localTime),
170+
hour(localTime),
171+
minute(localTime),
172+
second(localTime));
173+
} else {
174+
// No valid time available
175+
syslogUdp.print("1970-01-01T00:00:00Z ");
176+
}
177+
178+
// Add hostname, app name, and other fields (using - for empty fields)
179+
syslogUdp.print(serverDescription);
180+
syslogUdp.print(" ");
181+
syslogUdp.print(_appName);
182+
syslogUdp.print(" - - - "); // PROCID, MSGID, and STRUCTURED-DATA are empty
183+
184+
// Add message content
185+
size = syslogUdp.write(buf, size);
186+
break;
187+
188+
case SYSLOG_PROTO_RAW:
189+
default:
190+
// Just send the raw message (like original NetDebug)
191+
size = syslogUdp.write(buf, size);
192+
break;
193+
}
194+
195+
syslogUdp.endPacket();
196+
return size;
197+
}
198+
199+
SyslogPrinter Syslog;
200+
#endif // WLED_ENABLE_SYSLOG

0 commit comments

Comments
 (0)