弃用 OpenTelemetry,并建立日志驱动的真实慢接口基线

This commit is contained in:
biss committed 2026-08-11 11:32:50 +08:00
1 parent 9708f81764
commit 292d029459
9 files changed
+87 -157

No files matched your search

+3 -3
View File
@@ -44,15 +44,15 @@ Cache__AnalyticsExpirationMinutes=3
Cache__AnalyticsLocalExpirationSeconds=30
Cache__MaximumPayloadKilobytes=2048
# OpenTelemetry 默认收集 HTTP、运行时和数据库指标;配置 OTLP 地址后才会外发。
# 应用日志建立接口与慢 SQL 基线;日志系统可按 DurationMs、RequestId、QueryName 聚合。
Observability__Enabled=true
Observability__ServiceName=jiaowu-api
Observability__LogAllApiRequests=true
Observability__SlowRequestThresholdMilliseconds=1000
Observability__SlowQueryThresholdMilliseconds=500
# 默认不记录完整 SQL,避免日志或追踪系统接触业务数据。
Observability__IncludeSqlText=false
Observability__MaximumSqlTextLength=2000
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.edu.cn:4317
# OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20REPLACE_WITH_TOKEN
# 系统内“运维与审计 → 系统性能”从 Prometheus 只读查询汇总指标。
PerformanceReporting__Enabled=false
+16 -19
View File
@@ -366,39 +366,36 @@ Redis 只作为可丢弃的查询缓存。连接失败时应用回源数据库
`allkeys-lfu` 淘汰策略,不启用持久化。`compose.app.example.yml` 不创建 Redis;
如需连接外部 Redis,在 `.env` 中配置上述连接串即可。
### OpenTelemetry 与慢查询定位
### 慢接口与慢查询基线
应用已接入 OpenTelemetry 的 ASP.NET Core、HttpClient、.NET Runtime 指标,并通过
`Jiaowu.Api.Database` ActivitySource 和 Meter 记录 EF Core 数据库命令。配置
`OTEL_EXPORTER_OTLP_ENDPOINT` 后才启动 OpenTelemetry SDK 并向 OTLP Collector 外发;
未配置时不会创建无处消费的请求 Span,也不会尝试连接本地 Collector,结构化慢查询日志
仍然有效。
应用不依赖 OpenTelemetry。启用 `Observability` 后,会为每个 `/api` 请求写入结构化的
方法、路径、终结点、状态码、耗时和请求号;超过阈值或返回 5xx 的请求会提升为 Warning。
EF Core 数据库命令超过阈值时同样写入查询名称、SQL 模板哈希、数据库类型、耗时和相同的
请求号。用日志平台按 `DurationMs` 聚合即可得到真实的 P50/P95/P99 和慢接口排行。
```text
Observability__Enabled=true
Observability__ServiceName=jiaowu-api
Observability__LogAllApiRequests=true
Observability__SlowRequestThresholdMilliseconds=1000
Observability__SlowQueryThresholdMilliseconds=500
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.edu.cn:4317
```
数据库指标包括 `jiaowu.db.command.duration`、`jiaowu.db.command.slow` 和
`jiaowu.db.command.failed`。为关键 EF 查询添加 `TagWith("模块.查询名")` 后,日志和
追踪会直接显示该稳定名称;无标签查询只显示操作类型和 SQL 模板哈希。默认
`Observability__IncludeSqlText=false`,不会把 SQL、参数值或连接串发送到日志和追踪
系统。仅在受控诊断窗口内临时启用完整 SQL 模板,并限制 Collector 权限与保留时间。
为关键 EF 查询添加 `TagWith("模块.查询名")` 后,慢 SQL 日志会直接显示稳定名称;无标签
查询只显示操作类型和 SQL 模板哈希。默认 `Observability__IncludeSqlText=false`,不会把
SQL 模板、参数值或连接串写入日志。仅在受控诊断窗口内临时启用 SQL 模板记录,并限制日志
访问权限与保留时间。
应用侧阈值用于关联接口、TraceId 和查询名称;生产 MySQL 还应由数据库管理员启用慢查询
应用侧请求号用于关联接口和查询名称;生产 MySQL 还应由数据库管理员启用慢查询
日志,并将 `long_query_time` 设为与应用阈值一致。先按查询哈希/标签汇总高频慢查询,再
对脱敏后的 `SELECT` 在测试库或只读副本执行 `EXPLAIN ANALYZE`,根据实际扫描行数和循环
次数决定是否补组合索引或改写投影。`EXPLAIN ANALYZE` 会真实执行语句,不能直接用于生产
写操作。参考 [MySQL 慢查询日志](https://dev.mysql.com/doc/refman/8.0/en/slow-query-log.html)
和 [MySQL 8.4 EXPLAIN](https://dev.mysql.com/doc/refman/8.4/en/explain.html)。
OpenTelemetry Collector 将指标写入 Prometheus 后,超级管理员可直接在“组织与权限 →
运维与审计 → 系统性能”查看请求量、5xx 比例、HTTP/数据库 P95、慢查询趋势,以及最慢
接口和数据库查询排行。报表由 API 使用固定 PromQL 只读查询 Prometheus,浏览器不会
接触 Prometheus 地址或令牌;结果默认缓存 30 秒。原始 Trace 和更长时间范围仍建议在
Grafana 中下钻,配置其地址后页面会显示跳转入口。
如部署环境另行提供 Prometheus 兼容指标源,超级管理员仍可在“组织与权限 → 运维与审计 →
系统性能”查看其汇总数据。该页面只读查询外部指标源,浏览器不会接触其地址或令牌;结果
默认缓存 30 秒。应用本身不会再通过 OpenTelemetry 向该指标源写入数据。
```text
PerformanceReporting__Enabled=true
@@ -471,7 +468,7 @@ Outbox 租约恢复改为按维护周期执行,避免积压发布时每条消
消息默认保留 14 天并按每批 500 条清理,可使用 `CompletedRetentionDays`、
`MaintenanceIntervalSeconds` 和 `CleanupBatchSize` 调整。应用暴露
`Jiaowu.BackgroundJobs` Meter,其中包含发布量、处理量、发布耗时、处理耗时和清理量,
可接入现有 OpenTelemetry/运行时指标采集器。MySQL 或 RabbitMQ 暂时不可用时,未完成
可由现有日志平台或运行时指标采集器汇总。MySQL 或 RabbitMQ 暂时不可用时,未完成
消息会根据 Outbox 状态和租约继续补投。迁移服务应先应用
`BackgroundJobOutbox` 数据库迁移,再启动应用实例。
@@ -0,0 +1,49 @@
using System.Diagnostics;
using Jiaowu.Api.Infrastructure.Observability;
namespace Jiaowu.Api.Infrastructure.Middleware;
public sealed class SlowRequestLoggingMiddleware(
RequestDelegate next,
ObservabilityOptions options,
ILogger<SlowRequestLoggingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
if (!options.Enabled ||
!context.Request.Path.StartsWithSegments("/api"))
{
await next(context);
return;
}
var startedAt = Stopwatch.GetTimestamp();
await next(context);
var durationMilliseconds = Stopwatch.GetElapsedTime(startedAt).TotalMilliseconds;
var endpoint = context.GetEndpoint()?.DisplayName ?? context.Request.Path.Value ?? "/api";
if (durationMilliseconds >= options.SlowRequestThresholdMilliseconds ||
context.Response.StatusCode >= StatusCodes.Status500InternalServerError)
{
logger.LogWarning(
"Slow API request completed: {Method} {Path} ({Endpoint}) returned {StatusCode} in {DurationMs:F1} ms, request {RequestId}.",
context.Request.Method,
context.Request.Path.Value,
endpoint,
context.Response.StatusCode,
durationMilliseconds,
context.TraceIdentifier);
}
else if (options.LogAllApiRequests)
{
logger.LogInformation(
"API request completed: {Method} {Path} ({Endpoint}) returned {StatusCode} in {DurationMs:F1} ms, request {RequestId}.",
context.Request.Method,
context.Request.Path.Value,
endpoint,
context.Response.StatusCode,
durationMilliseconds,
context.TraceIdentifier);
}
}
}
Loaded 3 of 9 files, more files were not shown because too many files have changed in this diff. Show more