feat(student-bff): 完整实现 student-bff 聚合层
包含 src 全部实现、Dockerfile、shared-ts/bff 包等
This commit is contained in:
13
services/student-bff/src/shared/observability/logger.ts
Normal file
13
services/student-bff/src/shared/observability/logger.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* student-bff pino logger.
|
||||
*
|
||||
* 仲裁依据: coord-final-decisions §1 G4 (首次实现即 pino 结构化日志)
|
||||
* coord-final-decisions §2 B8 (复用 shared-ts BFF logger 工厂)
|
||||
*/
|
||||
import { createBffLogger, type Logger } from "@edu/shared-ts/bff";
|
||||
|
||||
export const logger: Logger = createBffLogger("student-bff", {
|
||||
level: process.env.LOG_LEVEL ?? "info",
|
||||
});
|
||||
|
||||
export type { Logger };
|
||||
159
services/student-bff/src/shared/observability/metrics.ts
Normal file
159
services/student-bff/src/shared/observability/metrics.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 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")
|
||||
?.inc(labels);
|
||||
registry
|
||||
.getSingleMetric("student_bff_downstream_duration_seconds")
|
||||
?.observe({ service, method }, durationMs / 1000);
|
||||
if (status === "error" && errorType) {
|
||||
registry
|
||||
.getSingleMetric("student_bff_downstream_errors_total")
|
||||
?.inc({ service, method, error_type: errorType });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存命中/未命中指标辅助器.
|
||||
*/
|
||||
export function recordCacheAccess(
|
||||
keyPattern: string,
|
||||
hit: boolean,
|
||||
): void {
|
||||
if (hit) {
|
||||
registry
|
||||
.getSingleMetric("student_bff_cache_hits_total")
|
||||
?.inc({ cache_key_pattern: keyPattern });
|
||||
} else {
|
||||
registry
|
||||
.getSingleMetric("student_bff_cache_misses_total")
|
||||
?.inc({ cache_key_pattern: keyPattern });
|
||||
}
|
||||
}
|
||||
42
services/student-bff/src/shared/observability/tracer.ts
Normal file
42
services/student-bff/src/shared/observability/tracer.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* student-bff OpenTelemetry tracer.
|
||||
*
|
||||
* 仲裁依据: coord-final-decisions §1 G6 (首次实现即 OTel SDK + OTLP exporter + 完整资源属性 + 全链路 span)
|
||||
* 02-architecture-design.md §6.5 (serviceName: student-bff)
|
||||
*/
|
||||
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||
import { NodeSDK } from "@opentelemetry/sdk-node";
|
||||
import { env } from "../../config/env.js";
|
||||
import { logger } from "./logger.js";
|
||||
|
||||
let sdk: NodeSDK | null = null;
|
||||
|
||||
export function initTracer(): void {
|
||||
if (!env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
||||
logger.warn("OTEL_EXPORTER_OTLP_ENDPOINT not set, tracer disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
sdk = new NodeSDK({
|
||||
serviceName: env.OTEL_SERVICE_NAME,
|
||||
traceExporter: new OTLPTraceExporter({
|
||||
url: `${env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
|
||||
}),
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
logger.info(
|
||||
{ endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT, service: env.OTEL_SERVICE_NAME },
|
||||
"Tracer initialized with auto-instrumentations",
|
||||
);
|
||||
}
|
||||
|
||||
export async function shutdownTracer(): Promise<void> {
|
||||
if (sdk) {
|
||||
await sdk.shutdown();
|
||||
sdk = null;
|
||||
logger.debug("Tracer shutdown complete");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user