From b6e23facddda6ac5833a0f2e009e4d2872131504 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 26 Feb 2025 17:30:22 -0500 Subject: [PATCH] refactor, remove winston, implement file-roation instead --- components/legacy/logger/logger.spec.ts | 33 --------- components/legacy/logger/logger.ts | 33 +-------- components/legacy/logger/rotate-log-file.ts | 60 ++++++++++++++++ components/legacy/logger/winston-logger.ts | 78 --------------------- workspace.jsonc | 1 - 5 files changed, 63 insertions(+), 142 deletions(-) delete mode 100644 components/legacy/logger/logger.spec.ts create mode 100644 components/legacy/logger/rotate-log-file.ts delete mode 100644 components/legacy/logger/winston-logger.ts diff --git a/components/legacy/logger/logger.spec.ts b/components/legacy/logger/logger.spec.ts deleted file mode 100644 index 8270d81e48d2..000000000000 --- a/components/legacy/logger/logger.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { expect } from 'chai'; - -import { getFormat } from './winston-logger'; - -describe('winston-logger', () => { - describe('baseFileTransportOpts', () => { - it('should print metadata', () => { - const myFormat = getFormat(); - const result = myFormat.transform({ - [Symbol.for('level')]: 'error', - level: 'error', - message: 'my message', - metadata: { foo: 'bar' }, - }); - expect(result[Symbol.for('message')]).to.have.string('"foo": "bar"'); - }); - it('should not throw when the metadata object has a circular structure', () => { - const foo: { bar?: Record } = {}; - const bar = { foo }; - foo.bar = bar; - const metadata = { foo }; - const myFormat = getFormat(); - const result = myFormat.transform({ - [Symbol.for('level')]: 'error', - level: 'error', - message: 'my message', - metadata, - }); - expect(result[Symbol.for('message')]).to.have.string('logging failed to stringify the metadata Json'); - expect(result[Symbol.for('message')]).to.have.string('error: Converting circular structure to JSON'); - }); - }); -}); diff --git a/components/legacy/logger/logger.ts b/components/legacy/logger/logger.ts index ad561ac3b534..c6c5d3f78d83 100644 --- a/components/legacy/logger/logger.ts +++ b/components/legacy/logger/logger.ts @@ -1,9 +1,3 @@ -/** - * leave the Winston for now to get the file-rotation we're missing from Pino and the "profile" - * functionality. - * also, Winston should start BEFORE Pino. otherwise, Pino starts creating the debug.log file first - * and it throws an error if the file doesn't exists on Docker/CI. - */ import chalk from 'chalk'; import fs from 'fs-extra'; import path from 'path'; @@ -16,11 +10,11 @@ import pMapSeries from 'p-map-series'; import { Analytics } from '@teambit/legacy.analytics'; import { getConfig } from '@teambit/config-store'; import { defaultErrorHandler } from '@teambit/cli'; -import { CFG_LOG_JSON_FORMAT, CFG_LOG_LEVEL, CFG_NO_WARNINGS } from '@teambit/legacy.constants'; -import { getWinstonLogger } from './winston-logger'; +import { CFG_LOG_JSON_FORMAT, CFG_LOG_LEVEL, CFG_NO_WARNINGS, DEBUG_LOG } from '@teambit/legacy.constants'; import { getPinoLogger } from './pino-logger'; import { Profiler } from './profiler'; import { loader } from '@teambit/legacy.loader'; +import { rotateLogIfNeeded } from './rotate-log-file'; export { Level as LoggerLevel }; @@ -37,8 +31,7 @@ const DEFAULT_LEVEL = 'debug'; const logLevel = getLogLevel(); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const { winstonLogger, createExtensionLogger } = getWinstonLogger(logLevel, jsonFormat); +rotateLogIfNeeded(DEBUG_LOG); const { pinoLogger, pinoLoggerConsole, pinoSSELogger } = getPinoLogger(logLevel, jsonFormat); @@ -370,26 +363,6 @@ export function writeLogToScreen(levelOrPrefix = '') { if (levelOrPrefix === 'profile') { logger.shouldConsoleProfiler = true; } - // @todo: implement - // const prefixes = levelOrPrefix.split(','); - // const filterPrefix = winston.format((info) => { - // if (isLevel) return info; - // if (prefixes.some((prefix) => info.message.startsWith(prefix))) return info; - // return false; - // }); - // logger.logger.add( - // new winston.transports.Console({ - // level: isLevel ? levelOrPrefix : 'info', - // format: winston.format.combine( - // filterPrefix(), - // winston.format.metadata(), - // winston.format.errors({ stack: true }), - // winston.format.printf((info) => `${info.message} ${getMetadata(info)}`) - // ), - // }) - // ); } -export { createExtensionLogger }; - export default logger; diff --git a/components/legacy/logger/rotate-log-file.ts b/components/legacy/logger/rotate-log-file.ts new file mode 100644 index 000000000000..adbdff889d49 --- /dev/null +++ b/components/legacy/logger/rotate-log-file.ts @@ -0,0 +1,60 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; + +/** + * Rotates a log file if it exceeds a certain size (e.g. 10MB). + * + * - If "debug.log" is bigger than `maxSize`, + * then move older logs forward (debug9.log → debug10.log, etc.) + * rename debug.log → debug1.log, + * and finally create an empty debug.log. + * + * @param logPath Path to the main log file, e.g. "debug.log" + * @param maxSize Maximum size in bytes before rotation + * @param maxFiles Maximum number of rotated files to keep + */ +export function rotateLogIfNeeded( + logPath: string, + maxSize: number = 10 * 1024 * 1024, // 10 MB + maxFiles: number = 9 +): void { + // Check if log file exists; if not, nothing to rotate + let stat; + try { + stat = fs.statSync(logPath); + } catch (err) { + // If file doesn't exist, create it and exit. + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + fs.ensureFileSync(logPath); + return; + } + // Else re-throw + throw err; + } + + // If file size is below maxSize, no rotation needed + if (stat.size < maxSize) { + return; + } + + // Otherwise, rotate the logs: + const dir = path.dirname(logPath); + const ext = path.extname(logPath); + const base = path.basename(logPath, ext); + + // Shift older logs forward in ascending order + // i.e. debug9.log → debug10.log, debug8.log → debug9.log, ... + for (let i = maxFiles - 1; i > 0; i--) { + const oldFile = path.join(dir, `${base}${i}${ext}`); + const newFile = path.join(dir, `${base}${i + 1}${ext}`); + if (fs.pathExistsSync(oldFile)) { + fs.renameSync(oldFile, newFile); + } + } + + // Move the current log to debug1.log + fs.renameSync(logPath, path.join(dir, `${base}1${ext}`)); + + // Create a fresh, empty debug.log + fs.ensureFileSync(logPath); +} \ No newline at end of file diff --git a/components/legacy/logger/winston-logger.ts b/components/legacy/logger/winston-logger.ts deleted file mode 100644 index 2a61f290e611..000000000000 --- a/components/legacy/logger/winston-logger.ts +++ /dev/null @@ -1,78 +0,0 @@ -import path from 'path'; -import winston from 'winston'; -import { DEBUG_LOG, GLOBAL_LOGS } from '@teambit/legacy.constants'; - -// Store the extensionsLoggers to prevent create more than one logger for the same extension -// in case the extension developer use api.logger more than once -const extensionsLoggers = new Map(); - -export function getWinstonLogger(logLevel: string, jsonFormat: string) { - const baseFileTransportOpts = { - filename: DEBUG_LOG, - format: jsonFormat ? winston.format.combine(winston.format.timestamp(), winston.format.json()) : getFormat(), - level: logLevel, - maxsize: 10 * 1024 * 1024, // 10MB - maxFiles: 10, - // If true, log files will be rolled based on maxsize and maxfiles, but in ascending order. - // The filename will always have the most recent log lines. The larger the appended number, the older the log file - tailable: true, - }; - - const winstonLogger = winston.createLogger({ - transports: [new winston.transports.File(baseFileTransportOpts)], - exitOnError: false, - }); - - /** - * Create a logger instance for extension - * The extension name will be added as label so it will appear in the begining of each log line - * The logger is cached for each extension so there is no problem to use getLogger few times for the same extension - * @param {string} extensionName - */ - const createExtensionLogger = (extensionName: string) => { - // Getting logger from cache - const existingLogger = extensionsLoggers.get(extensionName); - - if (existingLogger) { - return existingLogger; - } - const extensionFileTransportOpts = Object.assign({}, baseFileTransportOpts, { - filename: path.join(GLOBAL_LOGS, 'extensions.log'), - label: extensionName, - }); - const extLogger = winston.createLogger({ - transports: [new winston.transports.File(extensionFileTransportOpts)], - exceptionHandlers: [new winston.transports.File(extensionFileTransportOpts)], - exitOnError: false, - }); - extensionsLoggers.set(extensionName, extLogger); - return extLogger; - }; - - return { winstonLogger, createExtensionLogger }; -} - -function getMetadata(info) { - if (!Object.keys(info.metadata).length) return ''; - if ((info.level === 'error' || info.level === '\u001b[31merror\u001b[39m') && info.metadata.stack) { - // this is probably an instance of Error, show the stack nicely and not serialized. - return `\n${info.metadata.stack}`; - } - try { - return JSON.stringify(info.metadata, null, 2); - } catch (err: any) { - return `logger error: logging failed to stringify the metadata Json. (error: ${err.message})`; - } -} - -export function getFormat() { - return winston.format.combine( - winston.format.metadata(), - winston.format.colorize(), - winston.format.timestamp(), - winston.format.splat(), // does nothing? - winston.format.errors({ stack: true }), - winston.format.prettyPrint({ depth: 3, colorize: true }), // does nothing? - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message} ${getMetadata(info)}`) - ); -} diff --git a/workspace.jsonc b/workspace.jsonc index 4eb7d88c8d9b..e8dc480d777d 100644 --- a/workspace.jsonc +++ b/workspace.jsonc @@ -584,7 +584,6 @@ "webpack-dev-server": "4.15.2", "webpack-manifest-plugin": "5.0.0", "webpack-merge": "5.8.0", - "winston": "3.3.3", "workbox-webpack-plugin": "7.1.0", "write-file-atomic": "5.0.0", "ws": "7.5.10",