import { Injectable, Logger } from '@nestjs/common'; /** * M-AI-04-06: Active Recall 可观测性服务 * * 提供结构化日志和内存计数器,满足验收标准: * - 日志可关联完整链路(requestId → jobId → activeRecallId) * - 统计 Legacy/Unified 请求量、成功率、耗时、重试、回滚 * * 计数器为内存级(不持久化),用于 Admin 查询和告警。 * 生产环境建议对接 Prometheus/Grafana 或 Admin 指标接口。 */ export interface ActiveRecallRequestLog { requestId: string; jobId?: string; activeRecallId: string; userId: string; engineMode: 'legacy' | 'unified'; jobType: string; queueName: string; durationMs?: number; lifecycleStatus?: string; errorCode?: string; attemptCount?: number; } @Injectable() export class ActiveRecallObservabilityService { private readonly logger = new Logger(ActiveRecallObservabilityService.name); // ── 内存计数器 ── private counters = { legacyRequests: 0, unifiedRequests: 0, unifiedCreateFailed: 0, unifiedExecuteSuccess: 0, unifiedExecuteFailed: 0, unifiedTotalDurationMs: 0, unifiedExecuteCount: 0, unifiedRetryCount: 0, projectorFailed: 0, rollbackCount: 0, }; // ── 结构化日志 ── /** * 记录 Active Recall 请求(HTTP 入口)。 * 不记录用户完整答案。 */ logRequest(log: ActiveRecallRequestLog): void { this.logger.log( `[ActiveRecall] request: requestId=${log.requestId} ` + `activeRecallId=${log.activeRecallId} userId=${log.userId} ` + `engine=${log.engineMode} jobType=${log.jobType}`, ); } /** * 记录 Job 创建成功。 */ logJobCreated(log: ActiveRecallRequestLog): void { this.logger.log( `[ActiveRecall] job_created: requestId=${log.requestId} ` + `jobId=${log.jobId} activeRecallId=${log.activeRecallId} ` + `engine=${log.engineMode} queueName=${log.queueName}`, ); } /** * 记录 Job 创建失败。 * 使用 warn 级别 — 不记录用户完整答案。 */ logJobCreateFailed(log: ActiveRecallRequestLog, error: string): void { this.logger.warn( `[ActiveRecall] job_create_failed: requestId=${log.requestId} ` + `activeRecallId=${log.activeRecallId} userId=${log.userId} ` + `engine=${log.engineMode} error=${error}`, ); } /** * 记录执行完成。 */ logExecutionCompleted(log: ActiveRecallRequestLog): void { this.logger.log( `[ActiveRecall] execution_completed: jobId=${log.jobId} ` + `activeRecallId=${log.activeRecallId} userId=${log.userId} ` + `durationMs=${log.durationMs} lifecycleStatus=${log.lifecycleStatus} ` + `attemptCount=${log.attemptCount}`, ); } /** * 记录执行失败。 */ logExecutionFailed(log: ActiveRecallRequestLog, error: string): void { this.logger.warn( `[ActiveRecall] execution_failed: jobId=${log.jobId} ` + `activeRecallId=${log.activeRecallId} userId=${log.userId} ` + `errorCode=${log.errorCode} error=${error}`, ); } /** * 记录回滚事件。 */ logRollback(userId: string, reason: string): void { this.logger.warn( `[ActiveRecall] rollback: userId=${userId} reason=${reason}`, ); } // ── 计数器操作 ── incrementLegacyRequests(): void { this.counters.legacyRequests++; } incrementUnifiedRequests(): void { this.counters.unifiedRequests++; } incrementUnifiedCreateFailed(): void { this.counters.unifiedCreateFailed++; } incrementUnifiedExecuteSuccess(durationMs: number): void { this.counters.unifiedExecuteSuccess++; this.counters.unifiedTotalDurationMs += durationMs; this.counters.unifiedExecuteCount++; } incrementUnifiedExecuteFailed(): void { this.counters.unifiedExecuteFailed++; } incrementUnifiedRetry(): void { this.counters.unifiedRetryCount++; } incrementProjectorFailed(): void { this.counters.projectorFailed++; } incrementRollback(): void { this.counters.rollbackCount++; } // ── 查询 ── getCounters() { return { ...this.counters, unifiedAvgDurationMs: this.counters.unifiedExecuteCount > 0 ? Math.round(this.counters.unifiedTotalDurationMs / this.counters.unifiedExecuteCount) : 0, }; } resetCounters(): void { this.counters = { legacyRequests: 0, unifiedRequests: 0, unifiedCreateFailed: 0, unifiedExecuteSuccess: 0, unifiedExecuteFailed: 0, unifiedTotalDurationMs: 0, unifiedExecuteCount: 0, unifiedRetryCount: 0, projectorFailed: 0, rollbackCount: 0, }; } }