this module is somehow parsing the syslog incorrectly causing incorrect directories being created in the logs directory (since appName got parsed incorrectly)
106 lines
3.4 KiB
JavaScript
Executable File
106 lines
3.4 KiB
JavaScript
Executable File
#!/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();
|
|
}
|