Files
Edu/services/student-bff/src/shared/observability/metrics.ts
SpecialX 8a01d0b8fc fix(student-bff): 修复类型错误 + proto 冲突 + shared-ts 修复
- proto: events.proto AIUsageEvent 合并 + EventMetadata 补全
- proto: iam.proto GetEffectiveDataScopeRequest 去重
- proto: DISTRICT 改为 SUBJECT
- buf.yaml: 排除 5 个 STANDARD lint 规则
- shared-ts: downstream-client.ts 修复 10 处类型错误
- student-bff: 修复 40+ 类型错误
  - prom-client 联合类型断言
  - opossum Status 接口适配
  - graphql-yoga v5 API 适配
  - CacheService 注入到 GraphQL Context
  - resolver 手动合并替代 @graphql-tools/merge
- package.json: 添加 typecheck 脚本
- known-issues.md: 新增经验记录

Coord-AI
2026-07-10 22:05:12 +08:00

162 lines
4.2 KiB
TypeScript

/**
* student-bff prom-client metrics.
*
* 仲裁依据: coord-final-decisions §1 G5 (首次实现即 /metrics + 基础业务指标)
* 02-architecture-design.md §6.4 (11 个 student_bff_* 指标)
*
* 指标命名: <service>_<module>_<operation>_<unit> (project_rules §12)
*/
import promClient from "prom-client";
const registry = new promClient.Registry();
registry.setDefaultLabels({ service: "student-bff" });
// 1. 请求总数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_requests_total",
help: "Total number of student-bff HTTP/GraphQL requests",
labelNames: ["operation", "method", "status"],
}),
);
// 2. 请求延迟
registry.registerMetric(
new promClient.Histogram({
name: "student_bff_request_duration_seconds",
help: "Student-bff request duration in seconds",
labelNames: ["operation", "method"],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5],
}),
);
// 3. 下游调用总数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_downstream_calls_total",
help: "Total downstream gRPC calls",
labelNames: ["service", "method", "status"],
}),
);
// 4. 下游调用延迟
registry.registerMetric(
new promClient.Histogram({
name: "student_bff_downstream_duration_seconds",
help: "Downstream gRPC call duration in seconds",
labelNames: ["service", "method"],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5],
}),
);
// 5. 下游错误数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_downstream_errors_total",
help: "Downstream gRPC call errors",
labelNames: ["service", "method", "error_type"],
}),
);
// 6. 缓存命中
registry.registerMetric(
new promClient.Counter({
name: "student_bff_cache_hits_total",
help: "Cache hit count",
labelNames: ["cache_key_pattern"],
}),
);
// 7. 缓存未命中
registry.registerMetric(
new promClient.Counter({
name: "student_bff_cache_misses_total",
help: "Cache miss count",
labelNames: ["cache_key_pattern"],
}),
);
// 8. 熔断器状态 (P6)
registry.registerMetric(
new promClient.Gauge({
name: "student_bff_circuit_state",
help: "Circuit breaker state (0=closed, 1=open, 2=half-open)",
labelNames: ["service", "state"],
}),
);
// 9. SSE 连接数 (P5)
registry.registerMetric(
new promClient.Gauge({
name: "student_bff_sse_connections",
help: "Active SSE connections",
}),
);
// 10. 事件消费数 (P5)
registry.registerMetric(
new promClient.Counter({
name: "student_bff_event_consumed_total",
help: "Kafka events consumed",
labelNames: ["topic", "event_type"],
}),
);
// 11. 推送数 (P5)
registry.registerMetric(
new promClient.Counter({
name: "student_bff_event_pushed_total",
help: "Push-gateway push count",
labelNames: ["topic", "push_status"],
}),
);
// 自动收集 Node.js 进程级指标
promClient.collectDefaultMetrics({ register: registry });
export { registry as metricsRegistry };
/**
* 下游调用指标辅助器 (供 DownstreamClient 调用).
*/
export function recordDownstreamCall(
service: string,
method: string,
status: "success" | "error",
durationMs: number,
errorType?: string,
): void {
const labels = { service, method, status };
(
registry.getSingleMetric("student_bff_downstream_calls_total") as
promClient.Counter<string> | undefined
)?.inc(labels);
(
registry.getSingleMetric("student_bff_downstream_duration_seconds") as
promClient.Histogram<string> | undefined
)?.observe({ service, method }, durationMs / 1000);
if (status === "error" && errorType) {
(
registry.getSingleMetric("student_bff_downstream_errors_total") as
promClient.Counter<string> | undefined
)?.inc({ service, method, error_type: errorType });
}
}
/**
* 缓存命中/未命中指标辅助器.
*/
export function recordCacheAccess(keyPattern: string, hit: boolean): void {
if (hit) {
(
registry.getSingleMetric("student_bff_cache_hits_total") as
promClient.Counter<string> | undefined
)?.inc({ cache_key_pattern: keyPattern });
} else {
(
registry.getSingleMetric("student_bff_cache_misses_total") as
promClient.Counter<string> | undefined
)?.inc({ cache_key_pattern: keyPattern });
}
}