From 1c4d34f41598f490e7bc7247c2a00fefd527e9fb Mon Sep 17 00:00:00 2001
From: jpoirier <jpoirier@grandlyon.com>
Date: Sat, 22 Jul 2023 09:26:03 +0200
Subject: [PATCH] feat: enhance winston logs exploitability

- One file by day rather by hour, up to 20mb
- Disable zippedArchive
- Display stack traces
- Add files prefix
---
 back/environments/sample.env               |  1 +
 back/src/config/config.service.ts          |  5 ++++
 back/src/logger/logger.service.ts          | 28 +++++++++++++---------
 back/src/main.ts                           |  2 +-
 deployment/00-backend-files.config-map.yml |  1 +
 5 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/back/environments/sample.env b/back/environments/sample.env
index 23a7f3a..2005863 100644
--- a/back/environments/sample.env
+++ b/back/environments/sample.env
@@ -12,6 +12,7 @@ RATE_LIMIT_MAX=5000
 # *********************************************************************************************
 
 APP_LOG_DIR=logs
+APP_LOG_FILES_PREFIX=backend-files
 
 # error < warn < log < debug < verbose
 APP_LOG_MAX_LEVEL=debug
\ No newline at end of file
diff --git a/back/src/config/config.service.ts b/back/src/config/config.service.ts
index f990d45..564bb90 100644
--- a/back/src/config/config.service.ts
+++ b/back/src/config/config.service.ts
@@ -24,6 +24,7 @@ export class ConfigService {
     const envVarsSchema: Joi.ObjectSchema = Joi.object({
       API_KEY: Joi.string().required(),
       APP_LOG_DIR: Joi.string().required(),
+      APP_LOG_FILES_PREFIX: Joi.string().required(),
       APP_LOG_MAX_LEVEL: Joi.string().required(),
       BODY_PARSER_LIMIT: Joi.string().default('50mb'),
       DOCUMENTS_DIR: Joi.string().required(),
@@ -56,6 +57,10 @@ export class ConfigService {
     return String(this.envConfig.APP_LOG_DIR);
   }
 
+  public get appLogFilesPrefix(): string {
+    return String(this.envConfig.APP_LOG_FILES_PREFIX);
+  }
+
   public get appLogMaxLevel(): string {
     return String(this.envConfig.APP_LOG_MAX_LEVEL);
   }
diff --git a/back/src/logger/logger.service.ts b/back/src/logger/logger.service.ts
index dd39ecf..2ea7964 100644
--- a/back/src/logger/logger.service.ts
+++ b/back/src/logger/logger.service.ts
@@ -1,4 +1,4 @@
-import { ConsoleLogger, Injectable, LogLevel, NotImplementedException, Scope } from '@nestjs/common';
+import { ConsoleLogger, Injectable, LogLevel, Scope } from '@nestjs/common';
 import * as winston from 'winston';
 import * as DailyRotateFile from 'winston-daily-rotate-file';
 
@@ -8,7 +8,7 @@ export class LoggerService extends ConsoleLogger {
   appLogMaxLevelDefault: LogLevel = 'log';
   levelsNotLoggedInConsole: LogLevel[]; // Don't want to display debug/verbose in console
 
-  constructor(context: string, appLogDir: string, appLogMaxLevel: LogLevel) {
+  constructor(context: string, appLogDir: string, appLogFilesPrefix: string, appLogMaxLevel: LogLevel) {
     super(context);
 
     if (this.nestLevels.indexOf(appLogMaxLevel) < 0) {
@@ -22,7 +22,7 @@ export class LoggerService extends ConsoleLogger {
 
     this.levelsNotLoggedInConsole = this.nestLevels.slice(this.nestLevels.indexOf(this.appLogMaxLevel) + 1);
 
-    this.createWinstonLoggers(appLogDir);
+    this.createWinstonLoggers(appLogDir, appLogFilesPrefix);
   }
 
   // *********************************************************************************************
@@ -32,10 +32,9 @@ export class LoggerService extends ConsoleLogger {
 
   winstonLoggers: winston.Logger[] = [];
 
-  private createWinstonLoggers(appLogDir: string): void {
-    const DailyOptions: any = {
-      datePattern: 'YYYY-MM-DD-HH',
-      zippedArchive: true,
+  private createWinstonLoggers(appLogDir: string, appLogFilesPrefix: string): void {
+    const dailyOptions: DailyRotateFile.DailyRotateFileTransportOptions = {
+      zippedArchive: false,
       maxSize: '20m',
       maxFiles: '14d',
     };
@@ -44,9 +43,11 @@ export class LoggerService extends ConsoleLogger {
     // * Formatters
 
     const readableFormat: winston.Logform.Format = winston.format.combine(
+      winston.format.errors({ stack: true }), // Cf. https://stackoverflow.com/a/58475687
       winston.format.timestamp({ format: 'DD/MM/YYYY HH:mm:ss' }),
       winston.format.printf(
-        ({ level, message, timestamp, ...meta }) => `${timestamp}     ${level.toUpperCase()} [${meta[Symbol.for('splat')]}] ${message}`,
+        ({ level, message, timestamp, stack, ...meta }) =>
+          `${timestamp}     ${level.toUpperCase()} [${meta[Symbol.for('splat')]}] ${level == 'error' ? stack : message}`,
       ),
     );
 
@@ -55,8 +56,8 @@ export class LoggerService extends ConsoleLogger {
 
     (
       [
-        { level: 'error', filePrefix: 'error-readable-' },
-        { level: this.appLogMaxLevel, filePrefix: 'all-readable-' },
+        { level: 'error', filePrefix: appLogFilesPrefix + '-error-readable-' },
+        { level: this.appLogMaxLevel, filePrefix: appLogFilesPrefix + '-all-readable-' },
       ] as { level: LogLevel; filePrefix: string }[]
     ).forEach((logger) => {
       try {
@@ -64,7 +65,12 @@ export class LoggerService extends ConsoleLogger {
           winston.createLogger({
             level: logger.level,
             format: readableFormat,
-            transports: [new DailyRotateFile({ filename: appLogDir + `/${logger.filePrefix}%DATE%.log`, ...DailyOptions })],
+            transports: [
+              new DailyRotateFile({
+                filename: appLogDir + `/${logger.filePrefix}%DATE%.log`,
+                ...dailyOptions,
+              } as DailyRotateFile.DailyRotateFileTransportOptions),
+            ],
           }),
         );
       } catch (error) {
diff --git a/back/src/main.ts b/back/src/main.ts
index 4d94f1a..3e375ed 100644
--- a/back/src/main.ts
+++ b/back/src/main.ts
@@ -24,7 +24,7 @@ async function bootstrap() {
   app.use(express.urlencoded({ limit: config.bodyParserLimit, extended: true }));
 
   // Logger
-  app.useLogger(new LoggerService('AppModule', config.appLogDir, <LogLevel>config.appLogMaxLevel));
+  app.useLogger(new LoggerService('AppModule', config.appLogDir, config.appLogFilesPrefix, <LogLevel>config.appLogMaxLevel));
 
   // Skip SSL certificate verification
   https.globalAgent.options.rejectUnauthorized = false;
diff --git a/deployment/00-backend-files.config-map.yml b/deployment/00-backend-files.config-map.yml
index c26be55..5cc0335 100644
--- a/deployment/00-backend-files.config-map.yml
+++ b/deployment/00-backend-files.config-map.yml
@@ -5,6 +5,7 @@ metadata:
   namespace: ns-spi-NAMESPACE_ENV-syn
 data:
   APP_LOG_DIR: "{{APP_LOG_DIR}}"
+  APP_LOG_FILES_PREFIX: "backend-files"
   APP_LOG_MAX_LEVEL: "{{APP_LOG_MAX_LEVEL}}"
   DOCUMENTS_DIR: "{{DOCUMENTS_DIR}}"
   NODE_ENV: "{{NODE_ENV}}"
-- 
GitLab