diff --git a/api/src/core/log.ts b/api/src/core/log.ts index 4d0311b35..c9a112aa9 100644 --- a/api/src/core/log.ts +++ b/api/src/core/log.ts @@ -1,7 +1,7 @@ import pino from 'pino'; import pretty from 'pino-pretty'; -import { API_VERSION, LOG_LEVEL, LOG_TYPE, SUPPRESS_LOGS } from '@app/environment.js'; +import { API_VERSION, LOG_LEVEL, LOG_TYPE, PATHS_LOGS_FILE, SUPPRESS_LOGS } from '@app/environment.js'; export const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const; @@ -16,7 +16,9 @@ const nullDestination = pino.destination({ }); export const logDestination = - process.env.SUPPRESS_LOGS === 'true' ? nullDestination : pino.destination(); + process.env.SUPPRESS_LOGS === 'true' + ? nullDestination + : pino.destination({ dest: PATHS_LOGS_FILE, mkdir: true }); // Since process output is piped directly to the log file, we should not colorize stdout // to avoid ANSI escape codes in the log file const stream = SUPPRESS_LOGS diff --git a/api/src/environment.ts b/api/src/environment.ts index cef628207..94107eab1 100644 --- a/api/src/environment.ts +++ b/api/src/environment.ts @@ -102,6 +102,8 @@ export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK export const PATHS_LOGS_DIR = process.env.PATHS_LOGS_DIR ?? process.env.LOGS_DIR ?? '/var/log/unraid-api'; export const PATHS_LOGS_FILE = process.env.PATHS_LOGS_FILE ?? '/var/log/graphql-api.log'; +export const PATHS_NODEMON_LOG_FILE = + process.env.PATHS_NODEMON_LOG_FILE ?? join(PATHS_LOGS_DIR, 'nodemon.log'); export const NODEMON_PATH = join(UNRAID_API_ROOT, 'node_modules', 'nodemon', 'bin', 'nodemon.js'); export const NODEMON_CONFIG_PATH = join(UNRAID_API_ROOT, 'nodemon.json'); diff --git a/api/src/unraid-api/cli/nodemon.service.integration.spec.ts b/api/src/unraid-api/cli/nodemon.service.integration.spec.ts index 6826684ee..9444faaa1 100644 --- a/api/src/unraid-api/cli/nodemon.service.integration.spec.ts +++ b/api/src/unraid-api/cli/nodemon.service.integration.spec.ts @@ -18,7 +18,8 @@ describe('NodemonService (real nodemon)', () => { let workdir: string; let scriptPath: string; let configPath: string; - let logPath: string; + let appLogPath: string; + let nodemonLogPath: string; let pidPath: string; const nodemonPath = join(process.cwd(), 'node_modules', 'nodemon', 'bin', 'nodemon.js'); @@ -26,12 +27,21 @@ describe('NodemonService (real nodemon)', () => { workdir = await mkdtemp(tmpRoot); scriptPath = join(workdir, 'app.js'); configPath = join(workdir, 'nodemon.json'); - logPath = join(workdir, 'nodemon.log'); + appLogPath = join(workdir, 'app.log'); + nodemonLogPath = join(workdir, 'nodemon.log'); pidPath = join(workdir, 'nodemon.pid'); await writeFile( scriptPath, - ["console.log('nodemon-integration-start');", 'setInterval(() => {}, 1000);'].join('\n') + [ + "const { appendFileSync } = require('node:fs');", + "const appLog = process.env.PATHS_LOGS_FILE || './app.log';", + "const nodemonLog = process.env.PATHS_NODEMON_LOG_FILE || './nodemon.log';", + "appendFileSync(appLog, 'app-log-entry\\n');", + "appendFileSync(nodemonLog, 'nodemon-log-entry\\n');", + "console.log('nodemon-integration-start');", + 'setInterval(() => {}, 1000);', + ].join('\n') ); await writeFile( @@ -64,7 +74,8 @@ describe('NodemonService (real nodemon)', () => { NODEMON_PATH: nodemonPath, NODEMON_PID_PATH: pidPath, PATHS_LOGS_DIR: workdir, - PATHS_LOGS_FILE: logPath, + PATHS_LOGS_FILE: appLogPath, + PATHS_NODEMON_LOG_FILE: nodemonLogPath, UNRAID_API_CWD: workdir, })); @@ -77,9 +88,10 @@ describe('NodemonService (real nodemon)', () => { const pid = Number.parseInt(pidText, 10); expect(Number.isInteger(pid) && pid > 0).toBe(true); - const logStats = await stat(logPath); - expect(logStats.isFile()).toBe(true); - await waitForLogEntry(logPath, 'nodemon-integration-start'); + const nodemonLogStats = await stat(nodemonLogPath); + expect(nodemonLogStats.isFile()).toBe(true); + await waitForLogEntry(nodemonLogPath, 'Starting nodemon'); + await waitForLogEntry(appLogPath, 'app-log-entry'); await service.stop(); await waitForExit(pid); diff --git a/api/src/unraid-api/cli/nodemon.service.spec.ts b/api/src/unraid-api/cli/nodemon.service.spec.ts index 6ea92e455..8f2f0386e 100644 --- a/api/src/unraid-api/cli/nodemon.service.spec.ts +++ b/api/src/unraid-api/cli/nodemon.service.spec.ts @@ -13,6 +13,7 @@ const createLogStreamMock = (fd = 42, autoOpen = true) => { fd, close: vi.fn(), destroy: vi.fn(), + write: vi.fn(), once: vi.fn(), off: vi.fn(), }; @@ -52,6 +53,7 @@ vi.mock('@app/environment.js', () => ({ NODEMON_PID_PATH: '/var/run/unraid-api/nodemon.pid', PATHS_LOGS_DIR: '/var/log/unraid-api', PATHS_LOGS_FILE: '/var/log/graphql-api.log', + PATHS_NODEMON_LOG_FILE: '/var/log/unraid-api/nodemon.log', UNRAID_API_CWD: '/usr/local/unraid-api', })); @@ -145,7 +147,9 @@ describe('NodemonService', () => { stdio: ['ignore', logStream, logStream], } ); - expect(createWriteStream).toHaveBeenCalledWith('/var/log/graphql-api.log', { flags: 'a' }); + expect(createWriteStream).toHaveBeenCalledWith('/var/log/unraid-api/nodemon.log', { + flags: 'a', + }); expect(unref).toHaveBeenCalled(); expect(mockWriteFile).toHaveBeenCalledWith('/var/run/unraid-api/nodemon.pid', '123'); expect(logger.info).toHaveBeenCalledWith('Started nodemon (pid 123)'); diff --git a/api/src/unraid-api/cli/nodemon.service.ts b/api/src/unraid-api/cli/nodemon.service.ts index 211984561..c60985436 100644 --- a/api/src/unraid-api/cli/nodemon.service.ts +++ b/api/src/unraid-api/cli/nodemon.service.ts @@ -12,6 +12,7 @@ import { NODEMON_PID_PATH, PATHS_LOGS_DIR, PATHS_LOGS_FILE, + PATHS_NODEMON_LOG_FILE, UNRAID_API_CWD, } from '@app/environment.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; @@ -34,6 +35,7 @@ export class NodemonService { async ensureNodemonDependencies() { await mkdir(PATHS_LOGS_DIR, { recursive: true }); await mkdir(dirname(PATHS_LOGS_FILE), { recursive: true }); + await mkdir(dirname(PATHS_NODEMON_LOG_FILE), { recursive: true }); await mkdir(dirname(NODEMON_PID_PATH), { recursive: true }); } @@ -215,12 +217,21 @@ export class NodemonService { const overrides = Object.fromEntries( Object.entries(options.env ?? {}).filter(([, value]) => value !== undefined) ); - const env = { ...process.env, ...overrides } as Record; + const env = { + ...process.env, + PATHS_LOGS_FILE, + PATHS_NODEMON_LOG_FILE, + NODEMON_CONFIG_PATH, + NODEMON_PID_PATH, + UNRAID_API_CWD, + ...overrides, + } as Record; let logStream: ReturnType | null = null; let nodemonProcess; try { - logStream = await this.createLogStream(); + logStream = await this.createLogStream(PATHS_NODEMON_LOG_FILE); + logStream.write('Starting nodemon...\n'); nodemonProcess = execa(NODEMON_PATH, ['--config', NODEMON_CONFIG_PATH, '--quiet'], { cwd: UNRAID_API_CWD, @@ -320,8 +331,8 @@ export class NodemonService { } } - private async createLogStream() { - const logStream = createWriteStream(PATHS_LOGS_FILE, { flags: 'a' }); + private async createLogStream(logPath: string) { + const logStream = createWriteStream(logPath, { flags: 'a' }); await new Promise((resolve, reject) => { const cleanup = () => {