feat: Add nodemon log file configuration and enhance logging in NodemonService

- Introduced PATHS_NODEMON_LOG_FILE to configure the log file for nodemon, allowing for better log management.
- Updated log stream handling in NodemonService to write to the specified nodemon log file.
- Enhanced integration tests to validate logging behavior and ensure proper file creation for both application and nodemon logs.
This commit is contained in:
Eli Bosley
2025-11-25 12:28:28 -05:00
parent 3462e7688d
commit fa837db09f
5 changed files with 45 additions and 14 deletions

View File

@@ -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

View File

@@ -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');

View File

@@ -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);

View File

@@ -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)');

View File

@@ -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<string, string>;
const env = {
...process.env,
PATHS_LOGS_FILE,
PATHS_NODEMON_LOG_FILE,
NODEMON_CONFIG_PATH,
NODEMON_PID_PATH,
UNRAID_API_CWD,
...overrides,
} as Record<string, string>;
let logStream: ReturnType<typeof createWriteStream> | 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<void>((resolve, reject) => {
const cleanup = () => {