IronAlpine.Observability
3.0.0
dotnet add package IronAlpine.Observability --version 3.0.0
NuGet\Install-Package IronAlpine.Observability -Version 3.0.0
<PackageReference Include="IronAlpine.Observability" Version="3.0.0" />
<PackageVersion Include="IronAlpine.Observability" Version="3.0.0" />
<PackageReference Include="IronAlpine.Observability" />
paket add IronAlpine.Observability --version 3.0.0
#r "nuget: IronAlpine.Observability, 3.0.0"
#:package IronAlpine.Observability@3.0.0
#addin nuget:?package=IronAlpine.Observability&version=3.0.0
#tool nuget:?package=IronAlpine.Observability&version=3.0.0
IronAlpine.Observability
Serilog logging, OpenTelemetry tracing, Polly resilience, and correlation tracking.
- Target Frameworks: net9.0, net10.0
- Dependencies: Serilog, OpenTelemetry, Polly
- Package Size: ~150 KB
- Replaces: 3 v2 packages (Logging.Serilog, Observability.OpenTelemetry, Resilience)
What It Is
IronAlpine.Observability provides production-grade logging and monitoring:
- Structured Logging — Serilog with JSON sink
- Distributed Tracing — OpenTelemetry with OTLP exporter
- Correlation Context — TraceId, CorrelationId, CausationId, TenantId propagation
- Resilience Policies — Retry and circuit breaker
- Logging Behavior — Slot 1 (outermost middleware)
Installation
dotnet add package IronAlpine.Observability
Quick Setup
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 1. Host setup (Serilog initialization)
builder.Host.UseIronAlpineSerilog("TimeOffService");
// 2. Services setup
builder.Services
.AddIronAlpineTelemetry(builder.Configuration, "TimeOffService")
.AddIronAlpineResilience(builder.Configuration)
.AddIronAlpineMediator(cfg =>
{
cfg.UseLoggingBehavior(); // Slot 1
});
var app = builder.Build();
app.Run();
Structured Logging
Configuration
{
"IronAlpine": {
"Observability": {
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "http://seq:5341"
}
},
{
"Name": "File",
"Args": {
"path": "/var/log/app-.log",
"rollingInterval": "Day",
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
]
}
}
}
}
Log in Code
private readonly ILogger<TimeOffService> _logger;
public async Task ApproveAsync(Guid timeOffId, CancellationToken ct)
{
_logger.LogInformation(
"Approving TimeOff {TimeOffId}",
timeOffId);
try
{
var timeOff = await _repository.GetByIdAsync(timeOffId, ct);
timeOff.Approve(...);
await _unitOfWork.SaveChangesAsync(ct);
_logger.LogInformation(
"TimeOff {TimeOffId} approved successfully",
timeOffId);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Failed to approve TimeOff {TimeOffId}",
timeOffId);
throw;
}
}
Distributed Tracing
Configuration
{
"IronAlpine": {
"Observability": {
"Telemetry": {
"OtlpEndpoint": "http://otel-collector:4317",
"ServiceVersion": "3.0.0"
}
}
}
}
Automatic Tracing
Framework automatically traces:
- ✅ HTTP requests (via ASP.NET Core middleware)
- ✅ Database queries (via EF Core interceptor)
- ✅ Message publishing (via EventBus)
- ✅ External API calls (via HttpClient handlers)
Manual Spans
using var activity = Activity.StartActivity("ProcessTimeOffRequest");
activity?.SetTag("timeoff.id", timeOffId);
activity?.SetTag("user.id", userId);
try
{
var timeOff = await _repository.GetByIdAsync(timeOffId, ct);
activity?.SetTag("timeoff.status", timeOff.Status);
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
Correlation Context
Automatically propagate request context across services:
// EventMetadataContext — single source of truth
public record EventMetadataContext(
string? TraceId, // W3C Trace ID
string? CorrelationId, // Logical request ID
string? CausationId, // Immediate cause ID
string? TenantId, // Multi-tenant context
string? TraceParent, // W3C TraceParent header
string? TraceState, // W3C TraceState header
Dictionary<string, string>? Baggage // Custom context
);
// Access in handlers
public class EventHandler : IEventHandler<TimeOffApprovedEvent>
{
private readonly IEventMetadataAccessor _metadataAccessor;
private readonly ILogger<EventHandler> _logger;
public async Task Handle(TimeOffApprovedEvent @event, CancellationToken ct)
{
var metadata = _metadataAccessor.GetCurrent();
_logger.LogInformation(
"Processing event with TraceId={TraceId} CorrelationId={CorrelationId} TenantId={TenantId}",
metadata.TraceId,
metadata.CorrelationId,
metadata.TenantId);
}
}
// Automatically included in:
// ✅ Log entries (structured as fields)
// ✅ Kafka messages (custom headers)
// ✅ Database audit logs (CreatedBy, CreatedAt, ModifiedBy, ModifiedAt)
// ✅ OpenTelemetry spans (tags)
Logging Behavior (Slot 1)
Automatic request/response logging in the behavior pipeline:
Configuration
{
"IronAlpine": {
"Observability": {
"Logging": {
"Enabled": true,
"IncludeRequestPayload": true,
"IncludeResponsePayload": true,
"ExcludeTypes": ["SensitiveQuery", "SecretCommand"]
}
}
}
}
What Gets Logged
// Before handler executes
{
"MessageTemplate": "Handling request",
"RequestType": "ApproveTimeOffCommand",
"RequestPayload": { "TimeOffId": "...", "Comment": "..." },
"TraceId": "...",
"CorrelationId": "...",
"UserId": "..."
}
// After handler completes
{
"MessageTemplate": "Request completed",
"RequestType": "ApproveTimeOffCommand",
"DurationMs": 234,
"StatusCode": "Success",
"TraceId": "...",
"CorrelationId": "..."
}
// If exception occurs
{
"MessageTemplate": "Request failed",
"RequestType": "ApproveTimeOffCommand",
"Exception": "ValidationException",
"ExceptionMessage": "TimeOff not found",
"DurationMs": 45,
"TraceId": "...",
"CorrelationId": "..."
}
Resilience Policies
Configuration
{
"IronAlpine": {
"Observability": {
"Resilience": {
"Retry": {
"MaxRetryAttempts": 3,
"BackoffMultiplier": 2,
"InitialDelayMilliseconds": 100
},
"CircuitBreaker": {
"FailureThreshold": 0.5,
"SamplingDuration": 30,
"MinimumThroughput": 10
}
}
}
}
}
Apply Resilience
// Polly policies applied to:
// 1. Database queries (transient failures)
services.AddIronAlpineResilience(configuration);
// 2. External API calls
var httpClient = services
.AddHttpClient("ExternalService")
.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, i =>
TimeSpan.FromSeconds(Math.Pow(2, i))));
// 3. Event publishing
// Built-in retry via Kafka producer settings
Complete Setup Example
var builder = WebApplication.CreateBuilder(args);
// 1. Host-level logging setup (MUST be first)
builder.Host.UseIronAlpineSerilog("TimeOffService");
// 2. Services
builder.Services
// Observability (Serilog, tracing, resilience)
.AddIronAlpineTelemetry(builder.Configuration, "TimeOffService")
.AddIronAlpineResilience(builder.Configuration)
// Application layer
.AddIronAlpineMediator(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(TimeOffApplication).Assembly);
cfg.UseValidationBehavior(typeof(TimeOffApplication).Assembly);
cfg.UseCachingBehavior();
cfg.UseLoggingBehavior(); // Slot 1 (LoggingBehavior added here)
})
// Infrastructure layer
.AddIronAlpineData<TimeOffContext>(configuration, cfg =>
{
cfg.UseAuditingBehavior(); // Slot 4
cfg.UseTransactionBehavior(); // Slot 5
})
// Web
.AddIronAlpineWebDefaults(builder.Configuration, "TimeOffService")
.AddControllers();
var app = builder.Build();
app.Run();
Best Practices
✅ DO
// 1. Use structured logging
_logger.LogInformation(
"TimeOff {TimeOffId} approved by {UserId}",
timeOffId,
userId); // Not interpolation
// 2. Include context in events
var metadata = _metadataAccessor.GetCurrent();
var event = new TimeOffApprovedEvent
{
TimeOffId = id,
TraceId = metadata.TraceId,
CorrelationId = metadata.CorrelationId
};
// 3. Log at appropriate levels
_logger.LogInformation("Happy path"); // Info
_logger.LogWarning("Retry attempt"); // Warning
_logger.LogError(ex, "Failed"); // Error
// 4. Use activity spans for custom logic
using var activity = Activity.StartActivity("BusinessLogic");
activity?.SetTag("duration.ms", sw.ElapsedMilliseconds);
❌ DON'T
// 1. Don't use string interpolation in logs
_logger.LogInformation($"TimeOff {timeOffId} approved"); // Lost structure
// 2. Don't swallow exceptions
try { ... } catch { } // Silent failure
// 3. Don't log sensitive data
_logger.LogInformation("User password: {Password}", password); // PII exposure
// 4. Don't create activities without closing
var activity = Activity.StartActivity("MyActivity");
// Missing using or Dispose
Troubleshooting
Q: Logs not appearing?
A: Ensure (1) UseIronAlpineSerilog() called first, (2) correct sink configured (Console/Seq/File), (3) MinimumLevel not too high.
Q: Correlation ID not propagating?
A: Check (1) Web middleware registered, (2) EventMetadataAccessor injected, (3) correlation headers in HTTP requests.
Q: Traces not in Jaeger/Datadog?
A: Verify (1) OTLP endpoint accessible, (2) OpenTelemetry collector running, (3) service name configured.
Examples
See IRONALPINE_V3_DOCUMENTATION.md for detailed examples.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Mediator (>= 3.0.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.12.0)
- OpenTelemetry.Exporter.Prometheus.AspNetCore (>= 1.12.0-beta.1)
- OpenTelemetry.Extensions.Hosting (>= 1.12.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.12.0)
- OpenTelemetry.Instrumentation.EntityFrameworkCore (>= 1.12.0-beta.2)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- OpenTelemetry.Instrumentation.Runtime (>= 1.12.0)
- Polly (>= 8.6.2)
- Serilog.AspNetCore (>= 9.0.0)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.Span (>= 3.1.0)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Sinks.OpenTelemetry (>= 4.2.0)
- Serilog.Sinks.Seq (>= 9.0.0)
-
net9.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Mediator (>= 3.0.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.12.0)
- OpenTelemetry.Exporter.Prometheus.AspNetCore (>= 1.12.0-beta.1)
- OpenTelemetry.Extensions.Hosting (>= 1.12.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.12.0)
- OpenTelemetry.Instrumentation.EntityFrameworkCore (>= 1.12.0-beta.2)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- OpenTelemetry.Instrumentation.Runtime (>= 1.12.0)
- Polly (>= 8.6.2)
- Serilog.AspNetCore (>= 9.0.0)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.Span (>= 3.1.0)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Sinks.OpenTelemetry (>= 4.2.0)
- Serilog.Sinks.Seq (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on IronAlpine.Observability:
| Package | Downloads |
|---|---|
|
IronAlpine.Web
ASP.NET Core defaults, Swagger, health checks, correlation middleware, and YARP gateway transform for IronAlpine microservices. Single entry point: AddIronAlpineWebDefaults + UseIronAlpineWebDefaults. All implementations internal. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 104 | 7/1/2026 |
Stable mediator release with request/response, notification publish strategies, streaming, and dependency injection integration.