Skip to content

Repository files navigation

WitnessSharp

NuGet version Build status Mutation testing License

Lean .NET observability on OpenTelemetry. IWitness<T> gives each call site one place for logs, metrics, and traces while keeping ILogger<T>, Meter, ActivitySource, and OpenTelemetry exporters directly accessible. Supports net8.0 and net10.0.

30-second quickstart

// Program.cs
builder.Services.AddWitness(builder.Configuration.GetSection("Witness"))
    .WithStandardInstrumentations()
    .WithOtlpExporter();

// In your service
public sealed class OrderService(IWitness<OrderService> witness)
{
    public void PlaceOrder(int orderId)
    {
        using var action = witness.StartAction("PlaceOrder");
        action.SetTag("order.id", orderId);
        // business logic
    }
}

AddWitness() binds WitnessOptions from the "Witness" section.

Concepts

IWitness<T>

The main injectable bundling ILogger<T>, Meter, and ActivitySource with no new abstractions. Most classes only need IWitness<T>.

WitnessedAction

Wraps an Activity. Start with witness.StartAction("Name"), attach tags/events, and dispose when done. Outcomes default to success; call Failed(Exception), Failed(string), or Cancelled() as needed.

using var action = witness.StartAction("RetrieveSummary");
try
{
    var summary = await _controller.RetrieveSummaryAsync();
    return summary;
}
catch (Exception ex)
{
    action.Failed(ex);
    throw;
}

Logging via extension methods

Write extension methods on IWitness<T> for recurring log messages. The analyzer package suggests the [LoggerMessage] pattern for performance:

public static void LogOrderPlaced(this IWitness<OrderService> witness, int orderId) =>
    witness.Logger.LogInformation("Order {OrderId} placed", orderId);

Installation

dotnet add package WitnessSharp
dotnet add package WitnessSharp.AzureMonitor  # optional
dotnet add package WitnessSharp.Analyzers     # optional
dotnet add package WitnessSharp.Testing       # test projects

Configuration reference

Configure from appsettings.json:

appsettings.json:

{
  "Witness": {
    "ServiceName": "orders-api",
    "ServiceNamespace": "Contoso.Commerce",
    "ServiceVersion": "1.3.0",
    "ServiceInstanceId": "orders-api-01",
    "DeploymentEnvironment": "Production",
    "AdditionalResourceAttributes": {
      "service.owner": "checkout",
      "cloud.region": "westeurope"
    }
  }
}

Or via C# options: builder.Services.AddWitness(options => options.ServiceName = "orders-api");

Options properties:

Property Default
ServiceName Empty string (service identity, service.name in OTel)
ServiceNamespace null (service grouping)
ServiceVersion null (version tag)
ServiceInstanceId Environment.MachineName
DeploymentEnvironment Auto-detected from environment variables
AdditionalResourceAttributes Empty dictionary

Fluent builder methods:

Method Purpose
WithStandardInstrumentations() Enable common instrumentation (ASP.NET Core, HTTP client)
WithAspNetCoreInstrumentation(...), WithHttpClientInstrumentation(...) Individual instrumentations
WithOtlpExporter(...), WithConsoleExporter() Exporters
WithAzureMonitor(...) Azure Monitor integration
ClearLoggingProviders() Remove existing logging providers
ConfigureTracing(...), ConfigureMetrics(...), ConfigureLogging(...) Direct OTel SDK customization

⚠️ Don't mix convenience methods and escape-hatch methods for the same instrumentation.

Recipes

Health-check filtering

Use escape hatches to filter health-check endpoints from traces:

builder.Services.AddWitness(builder.Configuration.GetSection("Witness"))
    .ConfigureTracing(tracing =>
    {
        tracing.AddAspNetCoreInstrumentation(options =>
        {
            options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health");
        });
    })
    .WithOtlpExporter();

Duration-based filtering

Implement a custom BaseProcessor<Activity> and register via ConfigureTracing().

Azure Monitor

Calls .WithAzureMonitor() (from WitnessSharp.AzureMonitor package). Connection string is read from APPLICATIONINSIGHTS_CONNECTION_STRING. See Azure Monitor docs.

Testing

WitnessSharp.Testing provides TestWitness<T> with assertion helpers (AssertLogged, AssertMetricRecorded, AssertActivityStarted):

using var witness = new TestWitness<OrderService>();
witness.Logger.LogInformation("Placed order 42");
witness.Meter.CreateCounter<int>("orders").Add(1);
witness.StartAction("PlaceOrder").Dispose();

witness.AssertLogged(LogLevel.Information, "Placed order");
witness.AssertMetricRecorded("orders");
witness.AssertActivityStarted("PlaceOrder");

Analyzer (WS0001)

WitnessSharp.Analyzers flags templated ILogger calls in IWitness<T> extension methods and suggests the [LoggerMessage] pattern. Configure severity via .editorconfig: dotnet_diagnostic.WS0001.severity = warning. See LoggerMessage docs.

AOT support

WitnessSharp is AOT/trim-friendly. Upstream instrumentation and exporter packages may emit warnings when publishing with PublishAot=true.

Contributing

Contributions welcome. Build with dotnet build WitnessSharp.slnx, test with dotnet test WitnessSharp.slnx, then open a pull request. Follow CONTRIBUTING.md if present.

License

MIT. See LICENSE.

Further reading

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages