feat: add tracing enrichment and prometheus exporter
This commit is contained in:
@@ -27,6 +27,7 @@ using TakeoutSaaS.Shared.Web.Extensions;
|
||||
using TakeoutSaaS.Shared.Web.Swagger;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.Seed.json", optional: true, reloadOnChange: true)
|
||||
@@ -37,12 +38,13 @@ builder.Host.UseSerilog((context, _, configuration) =>
|
||||
configuration
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithProperty("Service", "AdminApi")
|
||||
.WriteTo.Console()
|
||||
.WriteTo.Console(outputTemplate: logTemplate)
|
||||
.WriteTo.File(
|
||||
"logs/admin-api-.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 7,
|
||||
shared: true);
|
||||
shared: true,
|
||||
outputTemplate: logTemplate);
|
||||
});
|
||||
|
||||
builder.Services.AddSharedWebCore();
|
||||
@@ -68,6 +70,7 @@ builder.Services.AddSmsApplication(builder.Configuration);
|
||||
builder.Services.AddMessagingModule(builder.Configuration);
|
||||
builder.Services.AddMessagingApplication();
|
||||
builder.Services.AddSchedulerModule(builder.Configuration);
|
||||
builder.Services.AddHealthChecks();
|
||||
var otelSection = builder.Configuration.GetSection("Otel");
|
||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||
@@ -102,7 +105,8 @@ builder.Services.AddOpenTelemetry()
|
||||
metrics
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddRuntimeInstrumentation();
|
||||
.AddRuntimeInstrumentation()
|
||||
.AddPrometheusExporter();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
||||
{
|
||||
@@ -137,6 +141,8 @@ app.UseAuthorization();
|
||||
app.UseSharedSwagger();
|
||||
app.UseSchedulerDashboard(builder.Configuration);
|
||||
|
||||
app.MapHealthChecks("/healthz");
|
||||
app.MapPrometheusScrapingEndpoint();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
|
||||
|
||||
Binary file not shown.
@@ -17,18 +17,20 @@ using TakeoutSaaS.Shared.Web.Extensions;
|
||||
using TakeoutSaaS.Shared.Web.Swagger;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
builder.Host.UseSerilog((_, _, configuration) =>
|
||||
{
|
||||
configuration
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithProperty("Service", "MiniApi")
|
||||
.WriteTo.Console()
|
||||
.WriteTo.Console(outputTemplate: logTemplate)
|
||||
.WriteTo.File(
|
||||
"logs/mini-api-.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 7,
|
||||
shared: true);
|
||||
shared: true,
|
||||
outputTemplate: logTemplate);
|
||||
});
|
||||
|
||||
builder.Services.AddSharedWebCore();
|
||||
@@ -45,6 +47,7 @@ builder.Services.AddSmsModule(builder.Configuration);
|
||||
builder.Services.AddSmsApplication(builder.Configuration);
|
||||
builder.Services.AddMessagingModule(builder.Configuration);
|
||||
builder.Services.AddMessagingApplication();
|
||||
builder.Services.AddHealthChecks();
|
||||
var otelSection = builder.Configuration.GetSection("Otel");
|
||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||
@@ -79,7 +82,8 @@ builder.Services.AddOpenTelemetry()
|
||||
metrics
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddRuntimeInstrumentation();
|
||||
.AddRuntimeInstrumentation()
|
||||
.AddPrometheusExporter();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
||||
{
|
||||
@@ -111,6 +115,8 @@ app.UseTenantResolution();
|
||||
app.UseSharedWebCore();
|
||||
app.UseSharedSwagger();
|
||||
|
||||
app.MapHealthChecks("/healthz");
|
||||
app.MapPrometheusScrapingEndpoint();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
|
||||
|
||||
Binary file not shown.
@@ -11,18 +11,20 @@ using TakeoutSaaS.Shared.Web.Extensions;
|
||||
using TakeoutSaaS.Shared.Web.Swagger;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
builder.Host.UseSerilog((_, _, configuration) =>
|
||||
{
|
||||
configuration
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithProperty("Service", "UserApi")
|
||||
.WriteTo.Console()
|
||||
.WriteTo.Console(outputTemplate: logTemplate)
|
||||
.WriteTo.File(
|
||||
"logs/user-api-.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 7,
|
||||
shared: true);
|
||||
shared: true,
|
||||
outputTemplate: logTemplate);
|
||||
});
|
||||
|
||||
builder.Services.AddSharedWebCore();
|
||||
@@ -33,6 +35,7 @@ builder.Services.AddSharedSwagger(options =>
|
||||
options.EnableAuthorization = true;
|
||||
});
|
||||
builder.Services.AddTenantResolution(builder.Configuration);
|
||||
builder.Services.AddHealthChecks();
|
||||
var otelSection = builder.Configuration.GetSection("Otel");
|
||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||
@@ -67,7 +70,8 @@ builder.Services.AddOpenTelemetry()
|
||||
metrics
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddRuntimeInstrumentation();
|
||||
.AddRuntimeInstrumentation()
|
||||
.AddPrometheusExporter();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
||||
{
|
||||
@@ -99,6 +103,8 @@ app.UseTenantResolution();
|
||||
app.UseSharedWebCore();
|
||||
app.UseSharedSwagger();
|
||||
|
||||
app.MapHealthChecks("/healthz");
|
||||
app.MapPrometheusScrapingEndpoint();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
|
||||
|
||||
Binary file not shown.
@@ -3,11 +3,12 @@ using System.Threading;
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// 轻量级 TraceId 上下文,便于跨层访问当前请求的追踪标识。
|
||||
/// 轻量级 TraceId/SpanId 上下文,便于跨层访问当前请求的追踪标识。
|
||||
/// </summary>
|
||||
public static class TraceContext
|
||||
{
|
||||
private static readonly AsyncLocal<string?> TraceIdHolder = new();
|
||||
private static readonly AsyncLocal<string?> SpanIdHolder = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求的 TraceId。
|
||||
@@ -18,8 +19,21 @@ public static class TraceContext
|
||||
set => TraceIdHolder.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求的 SpanId。
|
||||
/// </summary>
|
||||
public static string? SpanId
|
||||
{
|
||||
get => SpanIdHolder.Value;
|
||||
set => SpanIdHolder.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理 TraceId,避免 AsyncLocal 污染其它请求。
|
||||
/// </summary>
|
||||
public static void Clear() => TraceIdHolder.Value = null;
|
||||
public static void Clear()
|
||||
{
|
||||
TraceIdHolder.Value = null;
|
||||
SpanIdHolder.Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -14,23 +15,43 @@ namespace TakeoutSaaS.Shared.Web.Middleware;
|
||||
public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger, IIdGenerator idGenerator)
|
||||
{
|
||||
private const string TraceHeader = "X-Trace-Id";
|
||||
private const string SpanHeader = "X-Span-Id";
|
||||
private const string RequestHeader = "X-Request-Id";
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var traceId = ResolveTraceId(context);
|
||||
var ownsActivity = Activity.Current is null;
|
||||
var activity = Activity.Current ?? new Activity("TakeoutSaaS.Request");
|
||||
|
||||
if (activity.Id is null)
|
||||
{
|
||||
activity.SetIdFormat(ActivityIdFormat.W3C);
|
||||
activity.Start();
|
||||
}
|
||||
|
||||
var traceId = activity.TraceId.ToString();
|
||||
var spanId = activity.SpanId.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(traceId))
|
||||
{
|
||||
traceId = ResolveTraceId(context);
|
||||
}
|
||||
|
||||
context.TraceIdentifier = traceId;
|
||||
TraceContext.TraceId = traceId;
|
||||
TraceContext.SpanId = spanId;
|
||||
|
||||
context.Response.OnStarting(() =>
|
||||
{
|
||||
context.Response.Headers[TraceHeader] = traceId;
|
||||
context.Response.Headers[SpanHeader] = spanId;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
using (logger.BeginScope(new Dictionary<string, object>
|
||||
{
|
||||
["TraceId"] = traceId
|
||||
["TraceId"] = traceId,
|
||||
["SpanId"] = spanId
|
||||
}))
|
||||
{
|
||||
try
|
||||
@@ -40,6 +61,10 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
|
||||
finally
|
||||
{
|
||||
TraceContext.Clear();
|
||||
if (ownsActivity)
|
||||
{
|
||||
activity.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,15 @@ public sealed class RequestLoggingMiddleware(RequestDelegate next, ILogger<Reque
|
||||
{
|
||||
stopwatch.Stop();
|
||||
var traceId = TraceContext.TraceId ?? context.TraceIdentifier;
|
||||
var spanId = TraceContext.SpanId ?? Activity.Current?.SpanId.ToString() ?? string.Empty;
|
||||
logger.LogInformation(
|
||||
"HTTP {Method} {Path} => {StatusCode} ({Elapsed} ms) TraceId:{TraceId}",
|
||||
"HTTP {Method} {Path} => {StatusCode} ({Elapsed} ms) TraceId:{TraceId} SpanId:{SpanId}",
|
||||
context.Request.Method,
|
||||
context.Request.Path,
|
||||
context.Response.StatusCode,
|
||||
stopwatch.Elapsed.TotalMilliseconds,
|
||||
traceId);
|
||||
traceId,
|
||||
spanId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user