Skip to content

Commit cb4f91f

Browse files
committed
feat(observability): add Seq logging sink and OpenTelemetry tracing
- Add Serilog.Sinks.Seq to ship structured logs to Seq - Add OpenTelemetry with ASP.NET Core + HttpClient instrumentation - Export traces to Seq via OTLP (http://localhost:5341/ingest/otlp) - Enrich Serilog logs with TraceId/SpanId from Activity.Current - Register AddCceOpenTelemetry in both External and Internal APIs - Add Seq configuration section to appsettings.json (empty in prod, localhost in dev) - Keep existing Prometheus metrics untouched
1 parent ff82a4d commit cb4f91f

9 files changed

Lines changed: 97 additions & 0 deletions

File tree

backend/src/CCE.Api.Common/CCE.Api.Common.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
<PackageReference Include="Sentry.Serilog" />
3939
<PackageReference Include="prometheus-net" />
4040
<PackageReference Include="prometheus-net.AspNetCore" />
41+
<PackageReference Include="Serilog.Sinks.Seq" />
42+
<PackageReference Include="OpenTelemetry" />
43+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
44+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
45+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
46+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
4147
</ItemGroup>
4248

4349
<ItemGroup>

backend/src/CCE.Api.Common/Observability/LoggingExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
using Microsoft.Extensions.Hosting;
33
using Sentry.Serilog;
44
using Serilog;
5+
using Serilog.Core;
56
using Serilog.Events;
67
using Serilog.Formatting.Compact;
8+
using Serilog.Sinks.Seq;
9+
using System.Diagnostics;
710

811
namespace CCE.Api.Common.Observability;
912

@@ -32,6 +35,7 @@ public static IHostBuilder UseCceSerilog(this IHostBuilder builder)
3235
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
3336
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
3437
.Enrich.FromLogContext()
38+
.Enrich.With(new TraceIdEnricher())
3539
.Enrich.WithProperty("app", ctx.HostingEnvironment.ApplicationName)
3640
.Enrich.WithProperty("env", ctx.HostingEnvironment.EnvironmentName)
3741
.WriteTo.Console(new CompactJsonFormatter());
@@ -49,6 +53,13 @@ public static IHostBuilder UseCceSerilog(this IHostBuilder builder)
4953
{
5054
cfg.WriteTo.Sentry(o => ConfigureSentry(o, sentryDsn, ctx.Configuration, ctx.HostingEnvironment.EnvironmentName));
5155
}
56+
57+
var seqUrl = ctx.Configuration["Seq:ServerUrl"];
58+
var seqApiKey = ctx.Configuration["Seq:ApiKey"];
59+
if (!string.IsNullOrWhiteSpace(seqUrl))
60+
{
61+
cfg.WriteTo.Seq(seqUrl, apiKey: seqApiKey);
62+
}
5263
});
5364
}
5465

@@ -77,6 +88,21 @@ public static void ConfigureSentry(
7788
options.MinimumBreadcrumbLevel = LogEventLevel.Information;
7889
}
7990

91+
private sealed class TraceIdEnricher : ILogEventEnricher
92+
{
93+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
94+
{
95+
var activity = Activity.Current;
96+
if (activity is null)
97+
{
98+
return;
99+
}
100+
101+
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TraceId", activity.TraceId.ToString()));
102+
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("SpanId", activity.SpanId.ToString()));
103+
}
104+
}
105+
80106
private static LogEventLevel? ParseLevel(string? value)
81107
=> Enum.TryParse<LogEventLevel>(value, ignoreCase: true, out var lvl) ? lvl : null;
82108
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using OpenTelemetry.Resources;
4+
using OpenTelemetry.Trace;
5+
using System;
6+
7+
namespace CCE.Api.Common.Observability;
8+
9+
/// <summary>
10+
/// Registers OpenTelemetry tracing for ASP.NET Core and HttpClient,
11+
/// exporting spans to Seq via OTLP. Disabled when Seq:EnableTracing is false
12+
/// or Seq:OtlpEndpoint is missing.
13+
/// </summary>
14+
public static class OpenTelemetryExtensions
15+
{
16+
public static IServiceCollection AddCceOpenTelemetry(
17+
this IServiceCollection services,
18+
IConfiguration configuration,
19+
string serviceName)
20+
{
21+
var otlpEndpoint = configuration["Seq:OtlpEndpoint"] ?? "http://localhost:5341/ingest/otlp";
22+
var enableTracing = configuration.GetValue<bool?>("Seq:EnableTracing") ?? true;
23+
24+
if (!enableTracing || string.IsNullOrWhiteSpace(otlpEndpoint))
25+
{
26+
return services;
27+
}
28+
29+
services.AddOpenTelemetry()
30+
.WithTracing(tracing =>
31+
{
32+
tracing
33+
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName))
34+
.AddAspNetCoreInstrumentation()
35+
.AddHttpClientInstrumentation()
36+
.AddSource("CCE")
37+
.AddOtlpExporter(opts =>
38+
{
39+
opts.Endpoint = new Uri(otlpEndpoint);
40+
});
41+
});
42+
43+
return services;
44+
}
45+
}

backend/src/CCE.Api.External/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
.AddCcePermissionPolicies()
4545
.AddCceUserSync()
4646
.AddCceHealthChecks(builder.Configuration)
47+
.AddCceOpenTelemetry(builder.Configuration, "CCE.Api.External")
4748
.AddCceOpenApi("CCE External API");
4849

4950
builder.Services.AddHttpContextAccessor();

backend/src/CCE.Api.External/appsettings.Development.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,8 @@
8585
},
8686
"Media": {
8787
"BaseUrl": "https://cce-external-api.runasp.net/media/"
88+
},
89+
"Seq": {
90+
"ServerUrl": "http://localhost:5341"
8891
}
8992
}

backend/src/CCE.Api.External/appsettings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,11 @@
6767
"application/vnd.ms-excel",
6868
"application/msword"
6969
]
70+
},
71+
"Seq": {
72+
"ServerUrl": "",
73+
"ApiKey": "",
74+
"OtlpEndpoint": "http://localhost:5341/ingest/otlp",
75+
"EnableTracing": true
7076
}
7177
}

backend/src/CCE.Api.Internal/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
.AddCcePermissionPolicies()
3737
.AddCceUserSync()
3838
.AddCceHealthChecks(builder.Configuration)
39+
.AddCceOpenTelemetry(builder.Configuration, "CCE.Api.Internal")
3940
.AddCceRateLimiter(builder.Configuration)
4041
.AddCceOpenApi("CCE Internal API");
4142

backend/src/CCE.Api.Internal/appsettings.Development.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,8 @@
7272
},
7373
"Media": {
7474
"BaseUrl": "https://cce-internal-api.runasp.net/media/"
75+
},
76+
"Seq": {
77+
"ServerUrl": "http://localhost:5341"
7578
}
7679
}

backend/src/CCE.Api.Internal/appsettings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,11 @@
6767
"application/vnd.ms-excel",
6868
"application/msword"
6969
]
70+
},
71+
"Seq": {
72+
"ServerUrl": "",
73+
"ApiKey": "",
74+
"OtlpEndpoint": "http://localhost:5341/ingest/otlp",
75+
"EnableTracing": true
7076
}
7177
}

0 commit comments

Comments
 (0)