ABP vNext + Sentry + ELK Stack:打造高可用异常跟踪与日志可视化平台

发布于:2025-06-24 ⋅ 阅读:(13) ⋅ 点赞:(0)

🚀 ABP vNext + Sentry + ELK Stack:打造高可用异常跟踪与日志可视化平台 🎉



技术选型

🛠️ 工具 功能 适用场景
ABP vNext 模块化应用框架 多租户、多模块
Serilog .NET 结构化日志库 支持多种 Sink
Sentry 异常与性能链路监控 异常聚合、Trace 分析
Elasticsearch 日志索引引擎 大规模写入与检索
Kibana 日志可视化面板 仪表盘和图表展示
HealthChecks UI 可视化健康检查 服务可用性与探针监控

系统架构图

写日志
写入
上报
捕获
探针
ABP vNext App
Serilog Logging
Elasticsearch Sink
Elasticsearch
Kibana Dashboard
Sentry SDK
Sentry Dashboard
IExceptionSubscriber
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);
        // 全部异常上报到 Sentry
        SentrySdk.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.10
    ports: ["9200:9200"]
    environment:
      - discovery.type=single-node

  kibana:
    image: docker.elastic.co/kibana/kibana:7.17.10
    ports: ["5601:5601"]
    depends_on: ["elasticsearch"]

  logstash:  # 可选:集中化管道
    image: docker.elastic.co/logstash/logstash:7.17.10
    ports: ["5044:5044"]
    volumes:
      - ./logstash/pipeline/:/usr/share/logstash/pipeline/
    depends_on: ["elasticsearch"]

  app:
    image: yourorg/abp-sentry-elk-demo:latest
    ports: ["5000:80"]
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - SENTRY__DSN=${SENTRY__DSN}
    depends_on: ["elasticsearch"]
AppCluster
写日志
写日志
LoggingStack
Kibana
Elasticsearch
Logstash
App1
App2

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: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: abp-elk
  template:
    metadata:
      labels:
        app: abp-elk
    spec:
      containers:
      - name: app
        image: yourorg/abp-sentry-elk-demo:latest
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: Production
        - name: SENTRY__DSN
          valueFrom:
            secretKeyRef:
              name: sentry-secret
              key: DSN
        ports:
        - containerPort: 80


网站公告

今日签到

点亮在社区的每一天
去签到