From ce31bef8a125c5492f2a1cfef0dcf3d8a4e9ee11 Mon Sep 17 00:00:00 2001 From: Danshil Kokil Mungur Date: Thu, 20 Jan 2022 09:17:03 +0400 Subject: [PATCH] feat(logs): use separate json file to parse logs for log viewer (#2399) Co-authored-by: Ryan Cohen --- server/logger.ts | 20 ++++++++++-- server/routes/settings/index.ts | 56 +++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 824de630..4f736e4a 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -1,7 +1,7 @@ +import fs from 'fs'; +import path from 'path'; import * as winston from 'winston'; import 'winston-daily-rotate-file'; -import path from 'path'; -import fs from 'fs'; // Migrate away from old log const OLD_LOG_FILE = path.join(__dirname, '../config/logs/overseerr.log'); @@ -52,6 +52,22 @@ const logger = winston.createLogger({ createSymlink: true, symlinkName: 'overseerr.log', }), + new winston.transports.DailyRotateFile({ + filename: process.env.CONFIG_DIRECTORY + ? `${process.env.CONFIG_DIRECTORY}/logs/.machinelogs-%DATE%.json` + : path.join(__dirname, '../config/logs/.machinelogs-%DATE%.json'), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '1d', + createSymlink: true, + symlinkName: '.machinelogs.json', + format: winston.format.combine( + winston.format.splat(), + winston.format.timestamp(), + winston.format.json() + ), + }), ], }); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index c9908f4a..c07232e4 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -303,38 +303,46 @@ settingsRoutes.get( } const logFile = process.env.CONFIG_DIRECTORY - ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log` - : path.join(__dirname, '../../../config/logs/overseerr.log'); + ? `${process.env.CONFIG_DIRECTORY}/logs/.machinelogs.json` + : path.join(__dirname, '../../../config/logs/.machinelogs.json'); const logs: LogMessage[] = []; + const logMessageProperties = [ + 'timestamp', + 'level', + 'label', + 'message', + 'data', + ]; try { - fs.readFileSync(logFile) - .toString() - .split(/(?=\n\d{4}-\d{2})/g) + fs.readFileSync(logFile, 'utf-8') + .split('\n') .forEach((line) => { if (!line.length) return; - const jsonRegexp = new RegExp( - /[{[]{1}([,:{}[\]0-9.\-+Eaeflnr-u \n\r\t]|"[^"\n]*?")+[}\]]{1}/ - ); + const logMessage = JSON.parse(line); - const timestamp = line.match(new RegExp(/.{24}/)) || []; - const level = line.match(new RegExp(/(?<=.{24}\s\[).+?(?=\])/)) || []; - const label = - line.match(new RegExp(/(?<=.{24}\s\[.+\]\[).+?(?=\])/)) || []; - const message = - line.match(new RegExp(/(?<=\[.+\]:\s)[\s\S][^\r]+/)) || []; - const data = message[0].match(jsonRegexp) || []; - - if (level.length && filter.includes(level[0])) { - logs.push({ - timestamp: timestamp[0], - level: level[0], - label: label[0], - message: message[0].replace(jsonRegexp, ''), - data: data.length ? JSON.parse(data[0]) : undefined, - }); + if (!filter.includes(logMessage.level)) { + return; } + + if ( + !Object.keys(logMessage).every((key) => + logMessageProperties.includes(key) + ) + ) { + Object.keys(logMessage) + .filter((prop) => !logMessageProperties.includes(prop)) + .forEach((prop) => { + Object.assign(logMessage, { + data: { + [prop]: logMessage[prop], + }, + }); + }); + } + + logs.push(logMessage); }); const displayedLogs = logs.reverse().slice(skip, skip + pageSize);