#!/usr/bin/env node 'use strict'; exports = module.exports = { start, stop }; const debug = require('debug')('syslog:server'), fs = require('fs'), net = require('net'), path = require('path'), paths = require('./src/paths.js'), util = require('util'); let gServer = null; // https://docs.docker.com/engine/logging/drivers/syslog/ // example: <34>1 2023-09-07T14:33:22Z myhost myapp 1234 5678 [exampleSDID@32473 iut="3" eventSource="Application"] An example message function parseRFC5424Message(message) { const syslogRegex = /^<(\d+)>(\d+) (\S+) (\S+) (\S+) (\S+) (\S+) (?:\[(.*?)\])?(.*)$/; const match = message.match(syslogRegex); if (!match) return null; const [, pri, version, timestamp, hostname, appName, procId, msgId, structuredData, msg] = match; return { pri: parseInt(pri, 10), // priority version: parseInt(version, 10), // version timestamp, // timestamp hostname, // hostname appName, // app name procId, // process ID msgId, // message ID structuredData: structuredData ? structuredData : null, // structured data (if present) msg: msg ? msg.trim() : null // message }; } async function start() { debug('=========================================='); debug(' Cloudron Syslog Daemon '); debug('=========================================='); gServer = net.createServer(); gServer.on('error', function (error) { console.error(`server error: ${error}`); }); gServer.on('connection', function (socket) { socket.on('data', function (msg) { const lines = msg.toString().split('\n'); // may be multiline data for (const msg of lines) { const info = parseRFC5424Message(msg); if (!info || !info.appName) return debug('Ignore unknown app log:', msg); // remove line breaks to avoid holes in the log file // we do not ignore empty log lines, to allow gaps for potential ease of readability const message = info.message.replace(/[\n\r]+/g, ''); const appLogDir = path.join(paths.LOG_DIR, info.appName); try { fs.mkdirSync(appLogDir, { recursive: true }); fs.appendFileSync(`${appLogDir}/app.log`, `${info.timestamp} ${message}\n}`); } catch (error) { debug(error); } } }); socket.on('error', function (error) { debug(`socket error: ${error}`); }); }); await fs.promises.rm(paths.SYSLOG_SOCKET_FILE, { force: true }); await util.promisify(gServer.listen.bind(gServer))(paths.SYSLOG_SOCKET_FILE); debug(`Listening on ${paths.SYSLOG_SOCKET_FILE}`); } async function stop() { await fs.promises.rm(paths.SYSLOG_SOCKET_FILE, { force: true }); gServer.unref(); // TODO : cleanup client connections. otherwise server.close() won't return gServer = null; } async function main() { await start(); process.on('SIGTERM', async function () { debug('Received SIGTERM. Shutting down.'); await stop(); setTimeout(process.exit.bind(process), 1000); }); } if (require.main === module) { main(); }