Files
ccs/src/services/logging/logger.ts
T
Tam Nhu Tran 1462823be8 fix(logging): harden structured trace redaction
Redact StageOptions error payloads and summarize debug launch args.

Propagate request IDs through Cursor daemon and dashboard completion logs.

Mark remaining P2/P3 maintainability targets as partial instead of overclaiming.
2026-06-18 18:48:13 -04:00

114 lines
3.6 KiB
TypeScript

import { randomUUID } from 'crypto';
import { getResolvedLoggingConfig } from './log-config';
import { getRequestContext } from './log-context';
import { maskSecretTokens, redactContext, redactErrorInfo } from './log-redaction';
import { appendStructuredLogEntry } from './log-storage';
import type { LogEntry, LogStage, LoggingLevel } from './log-types';
const processRunId = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
interface CreateEntryOptions {
stage?: LogStage;
latencyMs?: number;
error?: LogEntry['error'];
}
function createEntry(
source: string,
level: LoggingLevel,
event: string,
message: string,
context: Record<string, unknown>,
options: CreateEntryOptions = {}
): LogEntry {
const config = getResolvedLoggingConfig();
const reqCtx = getRequestContext();
const entry: LogEntry = {
id: randomUUID(),
timestamp: new Date().toISOString(),
level,
source,
event,
message: maskSecretTokens(message),
processId: process.pid,
runId: processRunId,
context: config.redact ? redactContext(context) : context,
};
if (reqCtx?.requestId) entry.requestId = reqCtx.requestId;
if (options.stage) entry.stage = options.stage;
if (typeof options.latencyMs === 'number') entry.latencyMs = options.latencyMs;
if (options.error) entry.error = redactErrorInfo(options.error);
return entry;
}
export interface StageOptions {
/** Optional level override; default `'info'`. */
level?: LoggingLevel;
/** Optional latency in ms (typically attached to `respond`/`cleanup`). */
latencyMs?: number;
/** Optional structured error info for `cleanup` stages. */
error?: LogEntry['error'];
}
export interface Logger {
child(context: Record<string, unknown>): Logger;
debug(event: string, message: string, context?: Record<string, unknown>): void;
info(event: string, message: string, context?: Record<string, unknown>): void;
warn(event: string, message: string, context?: Record<string, unknown>): void;
error(event: string, message: string, context?: Record<string, unknown>): void;
/**
* Emit a stage-tagged log entry.
*
* Locked signature: `stage(stage, event, message, context?, options?)`.
* `options.level` defaults to `'info'`. Use `options.latencyMs` on
* `respond`/`cleanup`. Use `options.error` for structured error info.
*/
stage(
stage: LogStage,
event: string,
message: string,
context?: Record<string, unknown>,
options?: StageOptions
): void;
}
export function createLogger(source: string, baseContext: Record<string, unknown> = {}): Logger {
const write = (
level: LoggingLevel,
event: string,
message: string,
context?: Record<string, unknown>,
extra: CreateEntryOptions = {}
) => {
appendStructuredLogEntry(
createEntry(source, level, event, message, { ...baseContext, ...(context || {}) }, extra)
);
};
return {
child(context: Record<string, unknown>) {
return createLogger(source, { ...baseContext, ...context });
},
debug(event, message, context) {
write('debug', event, message, context);
},
info(event, message, context) {
write('info', event, message, context);
},
warn(event, message, context) {
write('warn', event, message, context);
},
error(event, message, context) {
write('error', event, message, context);
},
stage(stage, event, message, context, options) {
const level = options?.level ?? 'info';
write(level, event, message, context, {
stage,
latencyMs: options?.latencyMs,
error: options?.error,
});
},
};
}