ABP vNext + Sentry + ELK Stack:打造高可用异常跟踪与日志可视化平台
🚀 ABP vNext + Sentry + ELK Stack:打造高可用异常跟踪与日志可视化平台 🎉
📚 目录
- 🚀 ABP vNext + Sentry + ELK Stack:打造高可用异常跟踪与日志可视化平台 🎉
- 技术选型
- 系统架构图
- 依赖安装与多环境配置 🧰
- 安全日志配置 🔐
- appsettings.Development.json
- appsettings.Production.json
- Elasticsearch 索引模板 📑
- 程序启动与 DI 注册 ⚙️
- 日志增强与异常捕获 🛡️
- 自定义 TenantLogEnricher
- 全局异常订阅器
- APM 事务监控示例 🔍
- HealthChecks 与 UI 🩺
- 日志生命周期管理 (ILM) 🔄
- 容器化部署示例 🐳
- Kubernetes 部署示例 ☸️
技术选型
🛠️ 工具 | 功能 | 适用场景 |
---|---|---|
ABP vNext | 模块化应用框架 | 多租户、多模块 |
Serilog | .NET 结构化日志库 | 支持多种 Sink |
Sentry | 异常与性能链路监控 | 异常聚合、Trace 分析 |
Elasticsearch | 日志索引引擎 | 大规模写入与检索 |
Kibana | 日志可视化面板 | 仪表盘和图表展示 |
HealthChecks UI | 可视化健康检查 | 服务可用性与探针监控 |
系统架构图
依赖安装与多环境配置 🧰
dotnet add package Sentry.AspNetCore
dotnet add package Serilog.Sinks.Elasticsearch
dotnet add package Serilog.Enrichers.Environment
dotnet add package Serilog.Enrichers.Thread
dotnet add package Serilog.Enrichers.CorrelationId
dotnet add package Volo.Abp.Serilog
dotnet add package AspNetCore.HealthChecks.UI
// Program.cs - 配置读取顺序
var builder = WebApplication.CreateBuilder(args);builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true).AddEnvironmentVariables();
安全日志配置 🔐
appsettings.Development.json
{"Sentry": {"Dsn": "${SENTRY_DSN}","TracesSampleRate": 1.0,"Debug": true},"Serilog": {"MinimumLevel": { "Default": "Debug" },"WriteTo": [ { "Name": "Console" } ]}
}
appsettings.Production.json
{"Sentry": {"Dsn": "${SENTRY_DSN}","TracesSampleRate": 0.2,"SendDefaultPii": true,"AttachStacktrace": true,"Debug": false,"DiagnosticsLevel": "Error"},"Serilog": {"Using": [ "Serilog.Sinks.Elasticsearch" ],"MinimumLevel": {"Default": "Information","Override": { "Microsoft": "Warning" }},"WriteTo": [{"Name": "Elasticsearch","Args": {"NodeUris": "http://elasticsearch:9200","AutoRegisterTemplate": true,"AutoRegisterTemplateVersion": "ESv7","IndexFormat": "abp-logs-{0:yyyy.MM.dd}"}}],"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]}
}
秘钥注入:
.NET 默认支持用环境变量SENTRY__DSN
(双下划线表示冒号)覆盖Sentry:Dsn
。
export SENTRY__DSN=https://xxxx@sentry.io/project
Elasticsearch 索引模板 📑
curl -X PUT "localhost:9200/_template/abp-logs-template" -H "Content-Type: application/json" -d '
{"index_patterns": ["abp-logs-*"],"settings": { "number_of_shards": 3 },"mappings": {"properties": {"TenantId": { "type": "keyword" },"Module": { "type": "keyword" },"Timestamp": { "type": "date" },"Level": { "type": "keyword" },"Message": { "type": "text" }}}
}'
程序启动与 DI 注册 ⚙️
var builder = WebApplication.CreateBuilder(args);// 1. CorrelationId 中间件
builder.Services.AddCorrelationId();// 2. Sentry SDK
builder.Services.AddSentry(o =>
{o.Dsn = builder.Configuration["Sentry:Dsn"];o.TracesSampleRate = 0.2;o.AttachStacktrace = true;o.Debug = false;
});// 3. Serilog 注册
builder.Host.UseSerilog((ctx, lc) =>
{lc.ReadFrom.Configuration(ctx.Configuration).Enrich.WithCorrelationId().Enrich.WithMachineName().Enrich.WithEnvironmentUserName().Enrich.WithProcessId().Enrich.With<TenantLogEnricher>();
});// 4. 全局异常订阅
builder.Services.AddSingleton<IExceptionSubscriber, GlobalExceptionSubscriber>();// 5. HealthChecks + UI
builder.Services.AddHealthChecks().AddSqlServer(builder.Configuration.GetConnectionString("Default"), name: "SQL").AddRedis(builder.Configuration["Redis:Configuration"], name: "Redis").AddHealthChecksUI().AddSqlServerStorage(builder.Configuration.GetConnectionString("HealthChecksUI:Storage"));var app = builder.Build();// 6. 中间件顺序
app.UseCorrelationId();
app.UseSerilogRequestLogging();
app.UseSentryTracing();
app.UseRouting();app.UseEndpoints(endpoints =>
{endpoints.MapHealthChecks("/health", new HealthCheckOptions{ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse});endpoints.MapHealthChecksUI(options => { options.UIPath = "/health-ui"; });endpoints.MapControllers();
});app.Run();
日志增强与异常捕获 🛡️
自定义 TenantLogEnricher
public class TenantLogEnricher : ILogEventEnricher
{public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory){var tenantId = CurrentTenant.Id?.ToString() ?? "host";var moduleName = Assembly.GetEntryAssembly()?.GetName().Name ?? "unknown";logEvent.AddPropertyIfAbsent(factory.CreateProperty("TenantId", tenantId));logEvent.AddPropertyIfAbsent(factory.CreateProperty("Module", moduleName));}
}
全局异常订阅器
public class GlobalExceptionSubscriber : IExceptionSubscriber
{private readonly ILogger<GlobalExceptionSubscriber> _logger;public GlobalExceptionSubscriber(ILogger<GlobalExceptionSubscriber> logger)=> _logger = logger;public Task HandleAsync(ExceptionNotificationContext context){// 业务异常也记录,级别 Warning_logger.LogWarning(context.Exception, "业务异常:{Message}", context.Exception.Message);// 全部异常上报到 SentrySentrySdk.CaptureException(context.Exception);return Task.CompletedTask;}
}
APM 事务监控示例 🔍
using var tx = SentrySdk.StartTransaction("OrderProcess", "order.process");
try
{// … 业务逻辑 …tx.Finish(SpanStatus.Ok);
}
catch (Exception)
{tx.Finish(SpanStatus.InternalError);throw;
}
HealthChecks 与 UI 🩺
// healthchecks-settings.json
{"HealthChecksUI": {"HealthChecks": [{"Name": "ABP Core","Uri": "http://localhost:5000/health"}],"EvaluationTimeOnSeconds": 30,"MinimumSecondsBetweenFailureNotifications": 60,"Storage": {"ConnectionString": "Server=...;Database=HealthChecks;User Id=...;"}}
}
已在 Program.cs 中通过 .AddSqlServerStorage(...)
完成持久化配置。
日志生命周期管理 (ILM) 🔄
# 创建 ILM 策略
PUT _ilm/policy/abp-logs-policy
{"policy": {"phases": {"hot": { "actions": { "rollover": { "max_age": "7d", "max_size": "50gb" } } },"warm": { "actions": { "forcemerge": { "max_num_segments": 1 } } },"delete": { "actions": { "delete": { "min_age": "30d" } } }}}
}# 创建 Alias 并激活 Rollover
PUT /abp-logs-write
{"aliases": { "abp-logs": {} }
}
在 appsettings.Production.json
中,将 IndexFormat
修改为:
"IndexFormat": "abp-logs-write-{0:yyyy.MM.dd}"
容器化部署示例 🐳
version: '3.8'
services:elasticsearch:image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10ports: ["9200:9200"]environment:- discovery.type=single-nodekibana:image: docker.elastic.co/kibana/kibana:7.17.10ports: ["5601:5601"]depends_on: ["elasticsearch"]logstash: # 可选:集中化管道image: docker.elastic.co/logstash/logstash:7.17.10ports: ["5044:5044"]volumes:- ./logstash/pipeline/:/usr/share/logstash/pipeline/depends_on: ["elasticsearch"]app:image: yourorg/abp-sentry-elk-demo:latestports: ["5000:80"]environment:- ASPNETCORE_ENVIRONMENT=Production- SENTRY__DSN=${SENTRY__DSN}depends_on: ["elasticsearch"]
Kubernetes 部署示例 ☸️
apiVersion: v1
kind: Secret
metadata:name: sentry-secret
stringData:DSN: https://xxxx@sentry.io/project
---
apiVersion: apps/v1
kind: Deployment
metadata:name: abp-elk-app
spec:replicas: 3strategy:type: RollingUpdaterollingUpdate:maxSurge: 1maxUnavailable: 0selector:matchLabels:app: abp-elktemplate:metadata:labels:app: abp-elkspec:containers:- name: appimage: yourorg/abp-sentry-elk-demo:latestenv:- name: ASPNETCORE_ENVIRONMENTvalue: Production- name: SENTRY__DSNvalueFrom:secretKeyRef:name: sentry-secretkey: DSNports:- containerPort: 80