@taotiejs/server
Version:
Log collecting and querying based on pinojs and clickhouse
78 lines (73 loc) • 2.23 kB
JavaScript
const dgram = require('dgram');
const snakeCase = require('lodash.snakecase');
const createTable = require('./clickhouse-create');
const receiver = (port, msgHandler) => new Promise((resolve) => {
const socket = dgram.createSocket('udp4');
socket.on('message', msg => msgHandler(msg.toString()));
socket.on('error', err => console.error('log receiver error', err.message));
socket.on('listening', () => console.info('log receiver listening on', port));
socket.bind(port, () => resolve(socket));
});
module.exports = (port, db, interval, dataSkippingIndices) => {
const projects = {};
receiver(port, (data) => {
try {
const log = JSON.parse(data);
const {
time = Date.now(),
level = 0,
hostname = '',
project,
module: mod = '',
msg = '',
} = log;
if (!project) {
console.error('project not specific', data);
return;
}
delete log.project;
delete log.module;
delete log.time;
delete log.level;
delete log.hostname;
delete log.msg;
delete log.pid;
delete log.v;
const tableName = snakeCase(project);
const logs = projects[tableName];
const record = {
timestamp: time,
level,
time: Math.round(time / 1000),
hostname,
module: mod,
message: `${msg}`,
detail: Object.keys(log).length ? JSON.stringify(log) : '',
};
if (!logs) {
projects[tableName] = [record];
createTable(tableName, dataSkippingIndices)
.then(() => {}, err => console.error('create table failed', err));
} else {
logs.push(record);
}
} catch (err) {
console.error('receive log failed', err);
}
});
setInterval(async () => {
for (const p of Object.keys(projects)) {
const logs = projects[p];
const { length } = logs;
if (length) {
try {
// eslint-disable-next-line no-await-in-loop
await db.insert(p, logs.slice(0, length));
} catch (err) {
console.error('save log failed', err, logs.slice(0, length));
}
logs.splice(0, length);
}
}
}, interval * 1000);
};