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