Files
cloudron-box/syslog.js

103 lines
3.4 KiB
JavaScript
Raw Normal View History

2024-03-21 18:54:01 +01:00
#!/usr/bin/env node
'use strict';
2024-03-30 19:17:04 +01:00
exports = module.exports = {
start,
stop
};
2024-03-21 18:54:01 +01:00
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 pid msgid [exampleSDID@32473 iut="3" eventSource="Application"] An example message
// the structured data can be "-" when missing
2024-09-10 13:46:13 +02:00
function parseRFC5424Message(rawMessage) {
const syslogRegex = /^<(\d+)>(\d+) (\S+) (\S+) (\S+) (\S+) (\S+) (?:\[(.*?)\]|-)?(.*)$/s; // /s means .* will match newline . (?: is non-capturing group
2024-09-10 13:46:13 +02:00
const match = rawMessage.match(syslogRegex);
if (!match) return null;
2024-09-10 13:46:13 +02:00
const [, pri, version, timestamp, hostname, appName, procId, msgId, structuredData, message] = 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)
2024-09-10 13:46:13 +02:00
message // message
};
}
2024-03-21 18:54:01 +01:00
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) {
2024-09-10 13:46:13 +02:00
socket.on('data', function (data) {
const msg = data.toString('utf8').trim(); // strip any trailing empty new lines
for (const line of msg.split('\n')) { // empirically, multiple messages can arrive in a single packet
const info = parseRFC5424Message(line);
if (!info) return debug('Unable to parse:', msg);
if (!info.appName) return debug('Ignore unknown app:', msg);
const appLogDir = path.join(paths.LOG_DIR, info.appName);
try {
fs.mkdirSync(appLogDir, { recursive: true });
fs.appendFileSync(`${appLogDir}/app.log`, `${info.timestamp} ${info.message.trim()}\n`);
} catch (error) {
debug(error);
}
}
2024-03-21 18:54:01 +01:00
});
socket.on('error', function (error) {
debug(`socket error: ${error}`);
2024-03-21 18:54:01 +01:00
});
});
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);
});
}
2024-03-30 19:17:04 +01:00
if (require.main === module) {
main();
}