From 69cdacaac7cb33fb4f341effae6d9ec814e2503c Mon Sep 17 00:00:00 2001
From: Frank <f.sck@web.de>
Date: Fri, 15 Dec 2023 11:33:12 +0100
Subject: [PATCH 1/2] Enhancement: log to syslog server instead of web-serial

---
 src/utils/syslog.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++++
 src/utils/syslog.h   | 54 ++++++++++++++++++++++++
 2 files changed, 153 insertions(+)
 create mode 100644 src/utils/syslog.cpp
 create mode 100644 src/utils/syslog.h

diff --git a/src/utils/syslog.cpp b/src/utils/syslog.cpp
new file mode 100644
index 00000000..5e73f287
--- /dev/null
+++ b/src/utils/syslog.cpp
@@ -0,0 +1,99 @@
+#include <algorithm>
+#include "syslog.h"
+
+#ifdef ENABLE_SYSLOG
+
+#define SYSLOG_MAX_PACKET_SIZE 256
+
+
+//-----------------------------------------------------------------------------
+void DbgSyslog::setup(settings_t *config) {
+    mConfig  = config;
+
+    // Syslog callback overrides web-serial callback
+    registerDebugCb(std::bind(&DbgSyslog::syslogCb, this, std::placeholders::_1)); // dbg.h
+}
+
+//-----------------------------------------------------------------------------
+void DbgSyslog::syslogCb (String msg)
+{
+    if (!mSyslogIP.isSet()) {
+        //  use WiFi.hostByName to  DNS lookup for IPAddress of syslog server
+        if (WiFi.status() == WL_CONNECTED) {
+            WiFi.hostByName(SYSLOG_HOST,mSyslogIP);
+        }
+    }
+    if (!mSyslogIP.isSet()) {
+        return;
+    }
+    uint16_t msgLength = msg.length();
+    uint16_t msgPos = 0;
+
+    do {
+        uint16_t charsToCopy = std::min(msgLength-msgPos,SYSLOG_BUF_SIZE - mSyslogBufFill);
+
+        while (charsToCopy > 0) {
+            mSyslogBuffer[mSyslogBufFill] = msg[msgPos];
+            msgPos++;
+            mSyslogBufFill++;
+            charsToCopy--;
+        }
+        mSyslogBuffer[mSyslogBufFill] = '\0';
+
+        bool isBufferFull = (mSyslogBufFill == SYSLOG_BUF_SIZE);
+        bool isEolFound = false;
+        if (mSyslogBufFill >= 2) {
+            isEolFound = (mSyslogBuffer[mSyslogBufFill-2] == '\r' && mSyslogBuffer[mSyslogBufFill-1] == '\n');
+        }
+        // Get severity from input message
+        if (msgLength >= 2) {
+            if (':' == msg[1]) {
+                switch(msg[0]) {
+                    case 'E': mSyslogSeverity = PRI_ERROR; break;
+                    case 'W': mSyslogSeverity = PRI_WARNING; break;
+                    case 'I': mSyslogSeverity = PRI_INFO; break;
+                    case 'D': mSyslogSeverity = PRI_DEBUG; break;
+                    default:  mSyslogSeverity = PRI_NOTICE; break;
+                }
+            }
+        }
+
+        if (isBufferFull || isEolFound) {
+            // Send mSyslogBuffer in chunks because mSyslogBuffer is larger than syslog packet size
+            int packetStart = 0;
+            int packetSize = 122; // syslog payload depends also on hostname and app
+            char saveChar;
+            if (isEolFound) {
+                mSyslogBuffer[mSyslogBufFill-2]=0; // skip \r\n
+            }
+            while(packetStart < mSyslogBufFill) {
+                saveChar = mSyslogBuffer[packetStart+packetSize];
+                mSyslogBuffer[packetStart+packetSize] = 0;
+                log(mConfig->sys.deviceName,SYSLOG_FACILITY, mSyslogSeverity, &mSyslogBuffer[packetStart]);
+                mSyslogBuffer[packetStart+packetSize] = saveChar;
+                packetStart += packetSize;
+            }
+            mSyslogBufFill = 0;
+        }
+
+    } while (msgPos < msgLength); // Message not completely processed
+
+}
+
+//-----------------------------------------------------------------------------
+void DbgSyslog::log(const char *hostname, uint8_t facility, uint8_t severity, char* msg) {
+    // The PRI value is an integer number which calculates by the following metric:
+    uint8_t priority = (8 * facility) + severity;
+
+    // This is a unit8 instead of a char because that's what udp.write() wants
+    uint8_t buffer[SYSLOG_MAX_PACKET_SIZE];
+    int len = snprintf((char*)buffer, SYSLOG_MAX_PACKET_SIZE, "<%d>%s %s: %s", priority, hostname, SYSLOG_APP, msg);
+    //printf("syslog::log %s\n",mSyslogIP.toString().c_str());
+    //printf("syslog::log %d %s\n",len,buffer);
+    // Send the raw UDP packet
+    mSyslogUdp.beginPacket(mSyslogIP, SYSLOG_PORT);
+    mSyslogUdp.write(buffer, len);
+    mSyslogUdp.endPacket();
+}
+
+#endif
\ No newline at end of file
diff --git a/src/utils/syslog.h b/src/utils/syslog.h
new file mode 100644
index 00000000..aac9db14
--- /dev/null
+++ b/src/utils/syslog.h
@@ -0,0 +1,54 @@
+
+#ifndef __SYSLOG_H__
+#define __SYSLOG_H__
+
+#ifdef ESP8266
+    #include <ESP8266WiFi.h>
+#elif defined(ESP32)
+    #include <WiFi.h>
+#endif
+#include <WiFiUdp.h>
+#include "../config/config.h"
+#include "../config/settings.h"
+
+#ifdef ENABLE_SYSLOG
+
+#define SYSLOG_BUF_SIZE 255
+
+#define PRI_EMERGENCY 0
+#define PRI_ALERT     1
+#define PRI_CRITICAL  2
+#define PRI_ERROR     3
+#define PRI_WARNING   4
+#define PRI_NOTICE    5
+#define PRI_INFO      6
+#define PRI_DEBUG     7
+
+#define FAC_USER   1
+#define FAC_LOCAL0 16
+#define FAC_LOCAL1 17
+#define FAC_LOCAL2 18
+#define FAC_LOCAL3 19
+#define FAC_LOCAL4 20
+#define FAC_LOCAL5 21
+#define FAC_LOCAL6 22
+#define FAC_LOCAL7 23
+
+class DbgSyslog {
+    public:
+        void setup (settings_t *config);
+        void syslogCb(String msg);
+        void log(const char *hostname, uint8_t facility, uint8_t severity, char* msg);
+
+    private:
+        WiFiUDP mSyslogUdp;
+        IPAddress mSyslogIP;
+        settings_t *mConfig;
+        char mSyslogBuffer[SYSLOG_BUF_SIZE+1];
+        uint16_t mSyslogBufFill = 0;
+        int mSyslogSeverity = PRI_NOTICE;
+};
+
+#endif
+
+#endif
\ No newline at end of file

From 21848bd5ae5f163e3add306db0163fbadf41567f Mon Sep 17 00:00:00 2001
From: Frank <f.sck@web.de>
Date: Fri, 15 Dec 2023 11:33:48 +0100
Subject: [PATCH 2/2] Enhancement: log to syslog server instead of web-serial

---
 src/app.cpp                          | 3 +++
 src/app.h                            | 4 ++++
 src/config/config_override_example.h | 9 +++++++++
 3 files changed, 16 insertions(+)

diff --git a/src/app.cpp b/src/app.cpp
index d670c696..82e25724 100644
--- a/src/app.cpp
+++ b/src/app.cpp
@@ -82,6 +82,9 @@ void app::setup() {
 
     mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
 
+    #ifdef ENABLE_SYSLOG
+    mDbgSyslog.setup(mConfig); // be sure to init after mWeb.setup (webSerial uses also debug callback)
+    #endif
     // Plugins
     #if defined(PLUGIN_DISPLAY)
     if (mConfig->plugin.display.type != 0)
diff --git a/src/app.h b/src/app.h
index a71ee03c..a24cccb3 100644
--- a/src/app.h
+++ b/src/app.h
@@ -22,6 +22,7 @@
 #include "utils/crc.h"
 #include "utils/dbg.h"
 #include "utils/scheduler.h"
+#include "utils/syslog.h"
 #include "web/RestApi.h"
 #include "web/web.h"
 #include "hm/Communication.h"
@@ -311,6 +312,9 @@ class app : public IApp, public ah::Scheduler {
         #endif /* defined(ETHERNET) */
         WebType mWeb;
         RestApiType mApi;
+        #ifdef ENABLE_SYSLOG
+        DbgSyslog mDbgSyslog;
+        #endif
         //PayloadType mPayload;
         //MiPayloadType mMiPayload;
         PubSerialType mPubSerial;
diff --git a/src/config/config_override_example.h b/src/config/config_override_example.h
index c653bf10..b90bbdbd 100644
--- a/src/config/config_override_example.h
+++ b/src/config/config_override_example.h
@@ -35,4 +35,13 @@
 // #define ENABLE_PROMETHEUS_EP
 
 
+// to enable the syslog logging (will disable web-serial)
+//#define ENABLE_SYSLOG
+#ifdef ENABLE_SYSLOG
+#define SYSLOG_HOST "<hostname-or-ip-address-of-syslog-server>"
+#define SYSLOG_APP  "ahoy"
+#define SYSLOG_FACILITY FAC_USER
+#define SYSLOG_PORT 514
+#endif
+
 #endif /*__CONFIG_OVERRIDE_H__*/