diff --git a/src/apps/main/background-processes/antivirus/try-setup-antivirus-ipc-and-initialize.ts b/src/apps/main/background-processes/antivirus/try-setup-antivirus-ipc-and-initialize.ts index 199b7b59b2..4ab98329fd 100644 --- a/src/apps/main/background-processes/antivirus/try-setup-antivirus-ipc-and-initialize.ts +++ b/src/apps/main/background-processes/antivirus/try-setup-antivirus-ipc-and-initialize.ts @@ -6,7 +6,7 @@ export async function trySetupAntivirusIpcAndInitialize() { try { logger.debug({ tag: 'ANTIVIRUS', msg: '[Main] Setting up antivirus IPC handlers' }); setupAntivirusIpc(); - logger.debug({ msg: '[Main] Antivirus IPC handlers setup complete' }); + logger.debug({ tag: 'ANTIVIRUS', msg: '[Main] Antivirus IPC handlers setup complete' }); await getAntivirusManager().initialize(); } catch (error) { logger.error({ tag: 'ANTIVIRUS', msg: '[Main] Error setting up antivirus:', error }); diff --git a/src/apps/main/logging/setup-app-log-routing.test.ts b/src/apps/main/logging/setup-app-log-routing.test.ts new file mode 100644 index 0000000000..0b4c37466c --- /dev/null +++ b/src/apps/main/logging/setup-app-log-routing.test.ts @@ -0,0 +1,76 @@ +import { resolveAppLogFilePath } from './setup-app-log-routing'; + +type Pops = { + header: string; + msg: string; +}; + +function createSerializedLogMessage({ header, msg }: Pops) { + return `{ header: '${header}', msg: '${msg}' }`; +} + +describe('setup-app-log-routing', () => { + const logsPath = '/tmp/internxt-logs'; + + describe('resolveAppLogFilePath', () => { + it('should route antivirus debug logs to the dedicated antivirus file', () => { + // When + const result = resolveAppLogFilePath({ + logsPath, + message: { + level: 'debug', + data: [createSerializedLogMessage({ header: ' - b - anti', msg: '[CLAM_AVD] Starting clamd server...' })], + }, + }); + + // Then + expect(result).toBe('/tmp/internxt-logs/drive-antivirus.log'); + }); + + it('should keep important logs in the important file even for antivirus entries', () => { + // When + const result = resolveAppLogFilePath({ + logsPath, + message: { + level: 'info', + data: [ + createSerializedLogMessage({ header: 'E - b - anti', msg: '[CLAM_AVD] clamd process unexpectedly exited' }), + ], + }, + }); + + // Then + expect(result).toBe('/tmp/internxt-logs/drive-important.log'); + }); + + it('should keep non-antivirus logs in the main log file', () => { + // When + const result = resolveAppLogFilePath({ + logsPath, + message: { + level: 'debug', + data: [createSerializedLogMessage({ header: ' - b - auth', msg: 'Starting app' })], + }, + }); + + // Then + expect(result).toBe('/tmp/internxt-logs/drive.log'); + }); + + it('should route antivirus messages even when the serialized header is missing the antivirus tag', () => { + // When + const result = resolveAppLogFilePath({ + logsPath, + message: { + level: 'debug', + data: [ + createSerializedLogMessage({ header: ' - b - ', msg: '[Main] Antivirus IPC handlers setup complete' }), + ], + }, + }); + + // Then + expect(result).toBe('/tmp/internxt-logs/drive-antivirus.log'); + }); + }); +}); diff --git a/src/apps/main/logging/setup-app-log-routing.ts b/src/apps/main/logging/setup-app-log-routing.ts new file mode 100644 index 0000000000..6141f785d5 --- /dev/null +++ b/src/apps/main/logging/setup-app-log-routing.ts @@ -0,0 +1,84 @@ +import { createRequire } from 'node:module'; +import { join } from 'node:path'; + +type Pops = { + logsPath: string; +}; + +type LogMessage = { + data?: unknown[]; + level?: string; +}; + +type ElectronLogModule = { + transports: { + file: { + resolvePathFn: (variables: unknown, message?: LogMessage) => string; + resolvePath?: (variables: unknown, message?: LogMessage) => string; + }; + }; +}; + +const DEFAULT_LOG_FILE_NAME = 'drive.log'; +const IMPORTANT_LOG_FILE_NAME = 'drive-important.log'; +const ANTIVIRUS_LOG_FILE_NAME = 'drive-antivirus.log'; +const ANTIVIRUS_HEADER_PATTERN = /header:\s'[^']*-\santi'/; +const ANTIVIRUS_MESSAGE_PATTERNS = [ + /\[CLAM_AVD\]/, + /\[freshclam/i, + /\[ANTIVIRUS_MANAGER\]/, + /window\.electron\.antivirus/i, + /\bantivirus\b/i, +]; +const ELECTRON_LOG_MODULE_IDS = ['electron-log', '@internxt/drive-desktop-core/node_modules/electron-log']; +const moduleRequire = createRequire(__filename); + +export function setupAppLogRouting({ logsPath }: Pops) { + for (const electronLog of getElectronLogModules()) { + electronLog.transports.file.resolvePathFn = (_, message) => { + return resolveAppLogFilePath({ logsPath, message }); + }; + + electronLog.transports.file.resolvePath = electronLog.transports.file.resolvePathFn; + } +} + +export function resolveAppLogFilePath({ logsPath, message }: Pops & { message?: LogMessage }) { + if (message?.level === 'info') { + return join(logsPath, IMPORTANT_LOG_FILE_NAME); + } + + if (isAntivirusLogMessage({ message })) { + return join(logsPath, ANTIVIRUS_LOG_FILE_NAME); + } + + return join(logsPath, DEFAULT_LOG_FILE_NAME); +} + +function isAntivirusLogMessage({ message }: { message?: LogMessage }) { + return message?.data?.some((value) => isSerializedAntivirusLogEntry({ value })) ?? false; +} + +function isSerializedAntivirusLogEntry({ value }: { value: unknown }) { + if (typeof value !== 'string') { + return false; + } + + return ANTIVIRUS_HEADER_PATTERN.test(value) || ANTIVIRUS_MESSAGE_PATTERNS.some((pattern) => pattern.test(value)); +} + +function getElectronLogModules() { + const modules = new Map(); + + for (const moduleId of ELECTRON_LOG_MODULE_IDS) { + try { + const electronLog = moduleRequire(moduleId) as ElectronLogModule; + const resolvedModulePath = moduleRequire.resolve(moduleId); + modules.set(resolvedModulePath, electronLog); + } catch { + continue; + } + } + + return [...modules.values()]; +} diff --git a/src/apps/main/main.ts b/src/apps/main/main.ts index c82ddc1f8b..f458c44011 100644 --- a/src/apps/main/main.ts +++ b/src/apps/main/main.ts @@ -10,10 +10,10 @@ import 'dotenv/config'; // ***** APP BOOTSTRAPPING ****************************************************** // import { PATHS } from '../../core/electron/paths'; import { setupElectronLog } from '@internxt/drive-desktop-core/build/backend'; +import { setupAppLogRouting } from './logging/setup-app-log-routing'; -setupElectronLog({ - logsPath: PATHS.LOGS, -}); +setupElectronLog({ logsPath: PATHS.LOGS }); +setupAppLogRouting({ logsPath: PATHS.LOGS }); import './virtual-root-folder/handlers'; import './auto-launch/handlers'; diff --git a/src/types/NodeClamError.d.ts b/src/types/NodeClamError.d.ts index 4a45a137ca..b9e795dc8a 100644 --- a/src/types/NodeClamError.d.ts +++ b/src/types/NodeClamError.d.ts @@ -3,7 +3,6 @@ declare module '@internxt/scan/lib/NodeClamError' { constructor(message: string); data?: { err?: Error; - [key: string]: any; }; } }