IronAlpine.Observability 3.0.0

dotnet add package IronAlpine.Observability --version 3.0.0
                    
NuGet\Install-Package IronAlpine.Observability -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IronAlpine.Observability" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IronAlpine.Observability" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="IronAlpine.Observability" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IronAlpine.Observability --version 3.0.0
                    
#r "nuget: IronAlpine.Observability, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IronAlpine.Observability@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IronAlpine.Observability&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=IronAlpine.Observability&version=3.0.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.