api-server/src/modules/ai-job/active-recall-observability.service.ts
wangdl 0ec58669dd
Some checks failed
Deploy API Server / build-and-unit (push) Successful in 37s
Deploy API Server / current-integration (push) Failing after 1m35s
Deploy API Server / backward-compat (push) Successful in 17s
Deploy API Server / deploy (push) Has been skipped
feat(M-AI-04): Active Recall 端到端迁移至统一 Job Engine
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>
2026-06-21 14:55:33 +08:00

179 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
}
}