mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 20:17:45 +00:00
Establish the structured-log foundation that downstream modules consume: - LogStage union (intake|route|auth|dispatch|upstream|transform|respond|cleanup) - AsyncLocalStorage-backed log-context for end-to-end requestId propagation - Logger.stage() emit helper with optional latencyMs and structured metadata - Extended log-redaction patterns for proxy-authorization, x-goog-api-key, oauth-code, refresh-token classes; Bearer/Basic/Token value-shape masking - Public exports: getRecentLogEntries, withRequestContext, runWithRequestId, getRequestId Refs #1141, #1138
77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import {
|
|
getRequestContext,
|
|
getRequestId,
|
|
mergeRequestContext,
|
|
runWithRequestId,
|
|
withRequestContext,
|
|
} from '../../../../src/services/logging/log-context';
|
|
|
|
describe('log-context (AsyncLocalStorage)', () => {
|
|
it('returns undefined outside any context', () => {
|
|
expect(getRequestContext()).toBeUndefined();
|
|
expect(getRequestId()).toBeUndefined();
|
|
});
|
|
|
|
it('exposes the active context inside withRequestContext', () => {
|
|
withRequestContext({ requestId: 'req-1', method: 'POST' }, () => {
|
|
const ctx = getRequestContext();
|
|
expect(ctx?.requestId).toBe('req-1');
|
|
expect(ctx?.method).toBe('POST');
|
|
expect(getRequestId()).toBe('req-1');
|
|
});
|
|
expect(getRequestContext()).toBeUndefined();
|
|
});
|
|
|
|
it('isolates parallel async tasks (no cross-leak)', async () => {
|
|
const observed: string[] = [];
|
|
|
|
await Promise.all([
|
|
withRequestContext({ requestId: 'req-A' }, async () => {
|
|
await new Promise((r) => setTimeout(r, 5));
|
|
observed.push(`A:${getRequestId()}`);
|
|
}),
|
|
withRequestContext({ requestId: 'req-B' }, async () => {
|
|
await new Promise((r) => setTimeout(r, 1));
|
|
observed.push(`B:${getRequestId()}`);
|
|
}),
|
|
]);
|
|
|
|
expect(observed.sort()).toEqual(['A:req-A', 'B:req-B']);
|
|
});
|
|
|
|
it('runWithRequestId mints a UUID and exposes it inside fn', () => {
|
|
let captured: string | undefined;
|
|
const { requestId, result } = runWithRequestId(() => {
|
|
captured = getRequestId();
|
|
return 42;
|
|
});
|
|
expect(result).toBe(42);
|
|
expect(captured).toBe(requestId);
|
|
expect(requestId).toMatch(/^[0-9a-f-]{36}$/i);
|
|
});
|
|
|
|
it('mergeRequestContext returns input unchanged outside ALS', () => {
|
|
const merged = mergeRequestContext({ foo: 'bar' });
|
|
expect(merged).toEqual({ foo: 'bar' });
|
|
});
|
|
|
|
it('mergeRequestContext merges active context, with input overriding ctx', () => {
|
|
withRequestContext({ requestId: 'req-X', method: 'GET' }, () => {
|
|
const merged = mergeRequestContext({ method: 'POST', extra: 1 });
|
|
expect(merged).toEqual({ requestId: 'req-X', method: 'POST', extra: 1 });
|
|
});
|
|
});
|
|
|
|
it('preserves emit-time ordering within a single requestId', async () => {
|
|
const order: number[] = [];
|
|
await withRequestContext({ requestId: 'req-order' }, async () => {
|
|
for (let i = 0; i < 5; i++) {
|
|
await Promise.resolve();
|
|
order.push(i);
|
|
}
|
|
});
|
|
expect(order).toEqual([0, 1, 2, 3, 4]);
|
|
});
|
|
});
|