1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-25 15:35:14 +02:00
Files
misskey/packages/backend/test/unit/logging/Logger.ts
おさむのひと 0849dc7cdd enhance: 構造化ログAPIと正規化 (#17719)
* enhance: 構造化ログAPIと正規化

* fix changelog

* fixed copilot review

* fixed coderabbit review
2026-07-15 20:14:19 +09:00

121 lines
3.1 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { LogRecordInput } from '@/logging/types.js';
const mocks = vi.hoisted(() => ({
write: vi.fn<(input: LogRecordInput) => void>(),
}));
vi.mock('@/logging/logging-runtime.js', () => ({
logManager: {
write: mocks.write,
},
}));
import Logger from '@/logger.js';
describe('Logger', () => {
beforeEach(() => {
mocks.write.mockReset();
});
test('passes immutable multi-level root-to-leaf context to the manager', () => {
const root = new Logger('root', 'red');
const child = root.createSubLogger('child', 'green');
const leaf = child.createSubLogger('leaf', 'blue');
leaf.info('from leaf');
root.info('from root');
expect(mocks.write.mock.calls[0][0]).toMatchObject({
level: 'info',
message: 'from leaf',
context: [
{ name: 'root', color: 'red' },
{ name: 'child', color: 'green' },
{ name: 'leaf', color: 'blue' },
],
});
expect(mocks.write.mock.calls[1][0].context).toEqual([
{ name: 'root', color: 'red' },
]);
});
test('maps succ to info with the legacy success presentation', () => {
new Logger('root').succ('completed', { count: 1 }, true);
expect(mocks.write).toHaveBeenCalledWith({
level: 'info',
message: 'completed',
context: [{ name: 'root', color: undefined }],
compatibility: {
legacyLevel: 'success',
important: true,
data: { count: 1 },
},
});
});
test('supports structured log input while preserving the context hierarchy', () => {
new Logger('root').createSubLogger('child').write({
level: 'error',
eventName: 'example.failed',
message: 'failed',
attributes: { id: 'id' },
error: new Error('broken'),
});
expect(mocks.write).toHaveBeenCalledWith(expect.objectContaining({
level: 'error',
eventName: 'example.failed',
context: [{ name: 'root', color: undefined }, { name: 'child', color: undefined }],
}));
});
test('uses Error.toString and adds the Error to existing data', () => {
const logger = new Logger('root');
const error = new TypeError('broken');
const data: Record<string, unknown> = { requestId: 'request' };
logger.error(error, data, true);
expect(data).toEqual({ requestId: 'request', e: error });
expect(mocks.write).toHaveBeenCalledWith({
level: 'error',
message: 'TypeError: broken',
context: [{ name: 'root', color: undefined }],
error,
compatibility: {
legacyLevel: undefined,
important: true,
data,
},
});
});
test('creates data containing the Error when none is supplied', () => {
const error = new Error('broken');
new Logger('root').error(error);
expect(mocks.write.mock.calls[0][0].compatibility?.data).toEqual({ e: error });
});
test('keeps public methods bound when called separately', () => {
const logger = new Logger('root');
const info = logger.info;
info('bound');
expect(mocks.write).toHaveBeenCalledWith(expect.objectContaining({
level: 'info',
message: 'bound',
context: [{ name: 'root', color: undefined }],
}));
});
});