import { randomUUID } from 'crypto';
import { logger as baseLogger, LogContext, createLogger } from './logger.js';
/**
* Correlation ID management
*/
const correlationStore = new Map<string, string>();
/**
* Generate a new correlation ID
*/
export function generateCorrelationId(): string {
return randomUUID();
}
/**
* Set correlation ID for a specific context
*/
export function setCorrelationId(contextKey: string, correlationId: string): void {
correlationStore.set(contextKey, correlationId);
}
/**
* Get correlation ID for a specific context
*/
export function getCorrelationId(contextKey: string): string | undefined {
return correlationStore.get(contextKey);
}
/**
* Clear correlation ID for a specific context
*/
export function clearCorrelationId(contextKey: string): void {
correlationStore.delete(contextKey);
}
/**
* Create a logger with correlation ID
*/
export function createCorrelatedLogger(
component: string,
correlationId?: string,
additionalContext?: LogContext
): ReturnType<typeof createLogger> & {
correlationId: string;
child: (subComponent: string, childContext?: LogContext) => ReturnType<typeof createCorrelatedLogger>;
} {
const id = correlationId || generateCorrelationId();
const context = {
component,
correlationId: id,
...additionalContext
};
const baseLoggerInstance = createLogger(context);
return {
...baseLoggerInstance,
correlationId: id,
child: (subComponent: string, childContext?: LogContext) => {
return createCorrelatedLogger(
`${component}.${subComponent}`,
id,
{ ...additionalContext, ...childContext }
);
}
};
}
/**
* Async context manager for correlation IDs
*/
export class CorrelationContext {
private static asyncLocalStorage = new Map<string, string>();
/**
* Run an async function with a correlation ID
*/
static async run<T>(
correlationId: string,
fn: () => Promise<T>
): Promise<T> {
const previousId = this.asyncLocalStorage.get('correlationId');
this.asyncLocalStorage.set('correlationId', correlationId);
try {
return await fn();
} finally {
if (previousId) {
this.asyncLocalStorage.set('correlationId', previousId);
} else {
this.asyncLocalStorage.delete('correlationId');
}
}
}
/**
* Get current correlation ID from async context
*/
static getCurrentId(): string | undefined {
return this.asyncLocalStorage.get('correlationId');
}
}
/**
* Express-style middleware for correlation IDs
*/
export function correlationMiddleware(
req: any,
res: any,
next: () => void
): void {
const correlationId = req.headers['x-correlation-id'] || generateCorrelationId();
req.correlationId = correlationId;
res.setHeader('x-correlation-id', correlationId);
CorrelationContext.run(correlationId, async () => {
next();
});
}
/**
* Tool execution logger with correlation
*/
export function createToolLogger(toolName: string, correlationId?: string): ReturnType<typeof createCorrelatedLogger> {
return createCorrelatedLogger(
`tool.${toolName}`,
correlationId || CorrelationContext.getCurrentId(),
{
toolName,
timestamp: new Date().toISOString()
}
);
}
/**
* API call logger with correlation
*/
export function createApiLogger(
apiName: string,
endpoint: string,
correlationId?: string
): ReturnType<typeof createCorrelatedLogger> {
return createCorrelatedLogger(
`api.${apiName}`,
correlationId || CorrelationContext.getCurrentId(),
{
apiName,
endpoint,
timestamp: new Date().toISOString()
}
);
}
/**
* Database operation logger with correlation
*/
export function createDbLogger(
operation: string,
table?: string,
correlationId?: string
): ReturnType<typeof createCorrelatedLogger> {
return createCorrelatedLogger(
'database',
correlationId || CorrelationContext.getCurrentId(),
{
operation,
table,
timestamp: new Date().toISOString()
}
);
}
/**
* Performance tracking with correlation
*/
export class PerformanceTracker {
private logger: ReturnType<typeof createCorrelatedLogger>;
private startTime: number;
private checkpoints: Array<{ name: string; time: number; duration: number }> = [];
constructor(
operationName: string,
correlationId?: string,
context?: LogContext
) {
this.logger = createCorrelatedLogger(
`performance.${operationName}`,
correlationId || CorrelationContext.getCurrentId(),
context
);
this.startTime = Date.now();
this.logger.debug(`Operation started: ${operationName}`);
}
checkpoint(name: string): void {
const now = Date.now();
const duration = now - this.startTime;
const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
const sinceLast = lastCheckpoint ? now - lastCheckpoint.time : duration;
this.checkpoints.push({ name, time: now, duration });
this.logger.debug(`Checkpoint: ${name}`, {
totalDuration: duration,
sinceLast,
checkpoint: name
});
}
end(success: boolean = true): void {
const totalDuration = Date.now() - this.startTime;
this.logger.info('Operation completed', {
success,
totalDuration,
checkpoints: this.checkpoints.map(cp => ({
name: cp.name,
duration: cp.duration
}))
});
}
error(error: Error): void {
const totalDuration = Date.now() - this.startTime;
this.logger.error('Operation failed', error, {
totalDuration,
checkpoints: this.checkpoints.map(cp => ({
name: cp.name,
duration: cp.duration
}))
});
}
}
/**
* Structured error logging with correlation
*/
export function logStructuredError(
error: Error,
context: {
component: string;
operation: string;
correlationId?: string;
[key: string]: any;
}
): void {
const logger = createCorrelatedLogger(
context.component,
context.correlationId || CorrelationContext.getCurrentId()
);
const { operation, ...otherContext } = context;
logger.error(`${operation} failed`, error, {
errorType: error.constructor.name,
errorCode: (error as any).code,
...otherContext,
operation
});
}
// Export enhanced logger instance
export const structuredLogger = {
...baseLogger,
createCorrelated: createCorrelatedLogger,
createTool: createToolLogger,
createApi: createApiLogger,
createDb: createDbLogger,
PerformanceTracker,
CorrelationContext,
generateCorrelationId,
logStructuredError
};