Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/worker-log-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@doc-kit/core': patch
---

fix: respect `--log-level` in worker threads
31 changes: 31 additions & 0 deletions packages/core/src/logger/__tests__/logger.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -380,4 +380,35 @@ describe('createLogger', () => {
strictEqual(transport.mock.callCount(), 1); // Debug should be filtered
});
});

describe('getLogLevel', () => {
it('should return the level the logger was created with', t => {
const logger = createLogger(t.mock.fn(), LogLevel.warn);

strictEqual(logger.getLogLevel(), LogLevel.warn);
});

it('should default to info when no level is given', t => {
const logger = createLogger(t.mock.fn());

strictEqual(logger.getLogLevel(), LogLevel.info);
});

it('should reflect a level set afterwards', t => {
const logger = createLogger(t.mock.fn(), LogLevel.info);

logger.setLogLevel('fatal');

strictEqual(logger.getLogLevel(), LogLevel.fatal);
});

it('should reflect the propagated level on children', t => {
const logger = createLogger(t.mock.fn(), LogLevel.info);
const child = logger.child('module');

logger.setLogLevel(LogLevel.error);

strictEqual(child.getLogLevel(), LogLevel.error);
});
});
});
8 changes: 8 additions & 0 deletions packages/core/src/logger/logger.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ export const createLogger = (
}
};

/**
* Gets the current log level for this logger instance.
*
* @returns {number} The current numeric log level
*/
const getLogLevel = () => currentLevel;

return {
info,
warn,
Expand All @@ -173,5 +180,6 @@ export const createLogger = (
debug,
child,
setLogLevel,
getLogLevel,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import logger from '#logger/index.mjs';

/**
* Test generator that reports the log level seen inside the worker, so the
* propagation of the level across the thread boundary can be asserted.
*
* @type {GeneratorMetadata<unknown, number[]>}
*/
export default {
name: 'log-level-reporter',
version: '1.0.0',
description: 'Reports the log level active inside the worker',
dependsOn: 'ast',
processChunk: async (_input, itemIndices) =>
itemIndices.map(() => logger.getLogLevel()),
async generate() {
return [logger.getLogLevel()];
},
};
83 changes: 83 additions & 0 deletions packages/core/src/threading/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { strictEqual } from 'node:assert';
import { describe, it } from 'node:test';
import { fileURLToPath } from 'node:url';

import { LogLevel } from '../../logger/constants.mjs';
import logger from '../../logger/index.mjs';
import createWorkerPool from '../index.mjs';

const reporterSpecifier = fileURLToPath(
import.meta.resolve('./fixtures/log-level-reporter.mjs')
);

/**
* Runs a function with the logger temporarily set to the given level.
*
* @template T
* @param {number} level - Log level to apply for the duration of the callback
* @param {() => Promise<T>} fn - Callback to run
* @returns {Promise<T>}
*/
const withLogLevel = async (level, fn) => {
const original = logger.getLogLevel();

logger.setLogLevel(level);

try {
return await fn();
} finally {
logger.setLogLevel(original);
}
};

describe('createWorkerPool', () => {
it('should forward the current log level to workers', async () => {
await withLogLevel(LogLevel.fatal, async () => {
const pool = createWorkerPool(1);

try {
strictEqual(pool.options.workerData.logLevel, LogLevel.fatal);
} finally {
await pool.destroy();
}
});
});

it('should apply the forwarded log level inside the worker', async () => {
await withLogLevel(LogLevel.fatal, async () => {
const pool = createWorkerPool(1);

try {
const [levelInWorker] = await pool.run({
generatorSpecifier: reporterSpecifier,
input: [null],
itemIndices: [0],
extra: {},
configuration: {},
});

strictEqual(levelInWorker, LogLevel.fatal);
} finally {
await pool.destroy();
}
});
});

it('should leave workers at the default level when it is not changed', async () => {
const pool = createWorkerPool(1);

try {
const [levelInWorker] = await pool.run({
generatorSpecifier: reporterSpecifier,
input: [null],
itemIndices: [0],
extra: {},
configuration: {},
});

strictEqual(levelInWorker, LogLevel.info);
} finally {
await pool.destroy();
}
});
});
7 changes: 7 additions & 0 deletions packages/core/src/threading/chunk-worker.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { workerData } from 'node:worker_threads';

import { loadGenerator } from '#generators/loader.mjs';
import logger from '#logger/index.mjs';
import { setConfig } from '#utils/configuration/index.mjs';

if (workerData?.logLevel !== undefined) {
logger.setLogLevel(workerData.logLevel);
}

/**
* Processes a chunk of items using the specified generator's processChunk method.
* This is the worker entry point for Piscina.
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/threading/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export default function createWorkerPool(threads) {
minThreads: 0,
maxThreads: threads,
idleTimeout: 1_000,
workerData: { logLevel: logger.getLogLevel() },
});
}