M-AI-04-01: 审计并冻结迁移契约 (文档) M-AI-04-02: 注册 JobDefinition + SnapshotBuilder (28 tests) M-AI-04-03: Executor + BusinessValidator + ReferenceValidator (31 tests) M-AI-04-04: Projector + Artifact + 幂等写入 (19 tests) M-AI-04-05: 入口集成 (Router + CreationService + Engine + Executor) M-AI-04-06: 状态兼容 + 结构化日志 + 指标计数器 (255 tests) M-AI-04-07: 真实业务 E2E + CI 触发 + FeatureFlag 受控切换 (11 tests) P0 修复: - 跨用户 question 所有权校验 (active-recall.service.ts) - E2E infra 不可用时 HARD FAIL (fail() 替代静默 skip) 添加文件: - docs/architecture/m-ai-04-active-recall-migration-contract.md - 12 个 ai-job 模块文件 (Definition/Snapshot/Executor/Validator/Projector/Router/Observability) - test/m-ai-04-active-recall.e2e-spec.ts + setup 修改文件: - active-recall.service.ts, active-recall.module.ts - ai-job-creation.service.ts, ai-job-execution-engine.ts, ai-job.module.ts - .gitea/workflows/deploy.yml (CI 变更检测) - test/jest-e2e.json (setupFiles + globals) Co-Authored-By: Claude <noreply@anthropic.com>
179 lines
4.6 KiB
TypeScript
179 lines
4.6 KiB
TypeScript
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,
|
||
};
|
||
}
|
||
}
|