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.
// 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.
The main injectable bundling ILogger<T>, Meter, and ActivitySource with no new abstractions. Most classes only need IWitness<T>.
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;
}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);dotnet add package WitnessSharp
dotnet add package WitnessSharp.AzureMonitor # optional
dotnet add package WitnessSharp.Analyzers # optional
dotnet add package WitnessSharp.Testing # test projectsConfigure via IConfiguration or options:
builder.Services.AddWitness(builder.Configuration.GetSection("Witness"))
.WithStandardInstrumentations()
.WithOtlpExporter();
// Or via options
builder.Services.AddWitness(options => options.ServiceName = "orders-api");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"
}
}
}WitnessOptions properties:
| Property | Description | Default |
|---|---|---|
ServiceName |
Service identity (service.name). |
Empty string |
ServiceNamespace |
Service namespace grouping. | null |
ServiceVersion |
Service version tag. | null |
ServiceInstanceId |
Instance identifier. | Environment.MachineName |
DeploymentEnvironment |
Environment tag. | DOTNET_ENVIRONMENT or ASPNETCORE_ENVIRONMENT |
AdditionalResourceAttributes |
Extra resource attributes. | Empty dictionary |
Configure instrumentations, exporters, and logging providers via fluent methods:
- Conveniences:
WithStandardInstrumentations(),WithAspNetCoreInstrumentation(...),WithHttpClientInstrumentation(...),WithOtlpExporter(...),WithConsoleExporter(),WithAzureMonitor(...),ClearLoggingProviders() - Escape hatches:
ConfigureTracing(...),ConfigureMetrics(...),ConfigureLogging(...)for direct access to OTel builders
Use escape hatches for custom filtering and exporters. For health-check/readiness filtering:
builder.Services.AddWitness(builder.Configuration.GetSection("Witness"))
.ConfigureTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation(options =>
{
options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health");
});
})
.WithOtlpExporter();For duration-based filtering, implement a custom BaseProcessor<Activity> and register it via ConfigureTracing(). For Azure Monitor, use:
builder.Services.AddWitness(builder.Configuration.GetSection("Witness"))
.WithStandardInstrumentations()
.WithAzureMonitor();The connection string is read from APPLICATIONINSIGHTS_CONNECTION_STRING if available. See Azure Monitor OpenTelemetry exporter docs for details.
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");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.
WitnessSharp is AOT/trim-friendly. Upstream instrumentation and exporter packages may emit warnings when publishing with PublishAot=true.
Contributions welcome. Build with dotnet build WitnessSharp.slnx, test with dotnet test WitnessSharp.slnx, then open a pull request. Follow CONTRIBUTING.md if present.
MIT. See LICENSE.