1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-28 18:54:37 +02:00
Files
misskey/packages/backend/test/unit/logging/JsonConsoleBackend.ts
おさむのひと d5a09b3919 enhance: APIアクセスログを出力可能なように(opt-in) (#17771)
* enhance: APIアクセスログを出力可能なように(opt-in)

* fix
2026-07-22 22:17:29 +09:00

136 lines
4.0 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, expect, test, vi } from 'vitest';
import type { AccessLogRecord, LogRecord } from '@/logging/types.js';
import { JsonConsoleBackend } from '@/logging/JsonConsoleBackend.js';
/** JSON形式のテストで使う共通のログを作成します。 */
function createRecord(overrides: Partial<LogRecord> = {}): LogRecord {
return {
level: 'error',
message: 'delivery failed',
context: [{ name: 'queue' }, { name: 'deliver' }],
timestamp: '2025-01-02T03:04:05.678Z',
loggerName: 'queue.deliver',
processId: 1234,
isPrimary: false,
workerId: 7,
...overrides,
};
}
describe('JsonConsoleBackend', () => {
test('writes Access log as a separate one-line schema', () => {
const output = vi.fn<(line: string) => void>();
const backend = new JsonConsoleBackend({ output });
const record: AccessLogRecord = {
type: 'access',
timestamp: '2025-01-02T03:04:05.678Z',
method: 'GET',
route: '/items/:id',
statusCode: 500,
durationMs: 12.5,
responseSizeBytes: null,
errorType: 'TypeError',
requestBody: { token: '[REDACTED]' },
processId: 1234,
isPrimary: true,
workerId: null,
traceId: 'trace',
spanId: 'span',
traceFlags: 1,
};
backend.writeAccess(record);
expect(JSON.parse(output.mock.calls[0][0])).toEqual({
type: 'access',
timestamp: '2025-01-02T03:04:05.678Z',
method: 'GET',
route: '/items/:id',
statusCode: 500,
durationMs: 12.5,
responseSizeBytes: null,
errorType: 'TypeError',
requestBody: { token: '[REDACTED]' },
processId: 1234,
isPrimary: true,
workerId: null,
trace_id: 'trace',
span_id: 'span',
trace_flags: 1,
});
});
test('writes a stable one-line JSON record', () => {
const output = vi.fn<(line: string) => void>();
const backend = new JsonConsoleBackend({ output });
backend.write(createRecord({
eventName: 'queue.job.failed',
attributes: { jobId: '123', attempt: 2 },
error: { type: 'TypeError', message: 'broken', stack: 'stack' },
}));
expect(output).toHaveBeenCalledOnce();
expect(output.mock.calls[0][0]).toMatchInlineSnapshot(`"{\"timestamp\":\"2025-01-02T03:04:05.678Z\",\"level\":\"error\",\"message\":\"delivery failed\",\"loggerName\":\"queue.deliver\",\"eventName\":\"queue.job.failed\",\"attributes\":{\"jobId\":\"123\",\"attempt\":2},\"error\":{\"type\":\"TypeError\",\"message\":\"broken\",\"stack\":\"stack\"},\"processId\":1234,\"isPrimary\":false,\"workerId\":7}"`);
});
test('omits pretty-only compatibility data and context colors', () => {
const output = vi.fn<(line: string) => void>();
const backend = new JsonConsoleBackend({ output });
const record = createRecord({
context: [{ name: 'queue', color: 'red' }],
compatibility: {
legacyLevel: 'success',
important: true,
data: { secret: 'must not be written' },
},
});
backend.write(record);
expect(JSON.parse(output.mock.calls[0][0])).toEqual({
timestamp: '2025-01-02T03:04:05.678Z',
level: 'error',
message: 'delivery failed',
loggerName: 'queue.deliver',
processId: 1234,
isPrimary: false,
workerId: 7,
});
});
test('writes trace context using the standard JSON field names', () => {
const output = vi.fn<(line: string) => void>();
const backend = new JsonConsoleBackend({ output });
backend.write(createRecord({
traceId: '0123456789abcdef0123456789abcdef',
spanId: '0123456789abcdef',
traceFlags: 0,
}));
expect(JSON.parse(output.mock.calls[0][0])).toMatchObject({
trace_id: '0123456789abcdef0123456789abcdef',
span_id: '0123456789abcdef',
trace_flags: 0,
});
});
test('escapes newlines so each record remains one physical line', () => {
const output = vi.fn<(line: string) => void>();
const backend = new JsonConsoleBackend({ output });
const message = 'first line\nsecond line "quoted"';
backend.write(createRecord({ message }));
const line = output.mock.calls[0][0];
expect(line).not.toContain('\n');
expect(JSON.parse(line).message).toBe(message);
});
});