Your service can look healthy while one customer's operations are failing.
Measure customer impact, not just HTTP response times. Trellis SLI emits business-operation latency metrics in milliseconds, with the customer resource, location, operation, and outcome attached. Build availability and latency objectives that reveal problems aggregate request metrics can hide.
Try the live demo | Get started | Focused examples | Integration guide | Watch the video
Real metrics from the included demo, with simulated incidents. Compare the service-wide cards with the per-project charts: one struggling project tells a different story.
| The problem | What becomes visible |
|---|---|
| Averages hide customer-specific incidents. | Break down operations by CustomerResourceId, LocationId, and Operation instead of treating every request as the same experience. |
| 200 OK does not mean fast enough. | Opt into exact within/exceeded latency-threshold counts. Measure compliance directly rather than estimating it from p95. |
| A fast failure is still a failure. | Track availability separately from latency. A fast 429 can meet the latency objective while failing availability. |
| Caller mistakes look like service outages. | Distinguish ClientError from Failure, keeping caller errors visible without including them in the default SLO population. |
Use the same SLI model in ASP.NET Core MVC, Minimal APIs, versioned APIs, workers, and console apps. Built on System.Diagnostics.Metrics and OpenTelemetry; use your existing metrics backend.
One command. Docker only. No local .NET SDK required. With Docker running Linux containers, start the complete demo from the repository root:
docker compose -f .\sample\Observability\Grafana\docker-compose.yml --profile demo up --build -dOpen the live SLI dashboard. The Project Tracker API, continuous traffic generator, Collector, Prometheus, and Grafana all run locally.
Healthy background traffic continues while slowdowns, failures, caller errors, throttling, and recovery repeat automatically. Start with SyncProject / All projects, then isolate project-red and compare it with the healthy projects.
Follow the guided walkthrough for what to investigate, how the SLIs are calculated, source-code pointers, and shutdown instructions. This is a local learning environment, not a production deployment.
The default meter is Trellis.SLI. Its operation.duration histogram records elapsed time in milliseconds, with these stable dimensions:
| Dimension | Meaning |
|---|---|
CustomerResourceId |
The target resource, such as the project, tenant, or subscription being operated on. Not the caller's identity or a request ID. |
LocationId |
Where the service runs, set once from deployment configuration. |
Operation |
A named business operation or HTTP method plus route template. Route values are never substituted into the default operation name. |
Outcome |
Success, Failure, ClientError, or Ignored. |
ASP.NET Core adds HTTP method/status and request-abort diagnostics. The API-versioning package adds http.api.version.
Built-in http.server.request.duration metrics remain useful: their unit is seconds, not a limit on timing precision, and they include status/error diagnostics. Trellis adds an explicit SLI outcome policy and service-specific dimensions, with the same model available outside HTTP.
For default availability, use Success / (Success + Failure). Common caller errors are ClientError; 429 and 5xx are Failure. Observed request aborts are Ignored. For latency, opt into operation.duration.threshold.count to count operations within/exceeding configured millisecond boundaries; the duration histogram remains unchanged.
Latency thresholds classify elapsed time; they do not enforce timeouts or determine success/failure.
Choose the eligible population deliberately. The demo treats availability and latency as separate objectives; a successful-and-fast objective can instead require both. See status semantics and latency SLO recipes, including slow-abort and no-data handling.
Requires .NET 10. Choose the package for your application:
| Application | Package |
|---|---|
| Console, worker, or shared library | Trellis.ServiceLevelIndicators |
| ASP.NET Core MVC or Minimal APIs | Trellis.ServiceLevelIndicators.Asp |
| ASP.NET Core with Asp.Versioning | Trellis.ServiceLevelIndicators.Asp.ApiVersioning |
The ASP.NET Core package includes core; the API-versioning package includes ASP.NET Core integration.
Source-tree availability: result selectors, endpoint exclusions, and the instrumentation failure policy were added after 10.0.0-preview.37. Use a package containing those changes for those APIs. The Docker demo builds the current source directly.
From an ASP.NET Core project, install SLI and the OpenTelemetry hosting/exporter packages:
dotnet add package Trellis.ServiceLevelIndicators.Asp --prerelease
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocolReplace Program.cs with:
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using Trellis.ServiceLevelIndicators;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(builder.Environment.ApplicationName))
.WithMetrics(metrics =>
{
metrics.AddServiceLevelIndicatorInstrumentation();
metrics.AddOtlpExporter();
});
builder.Services.AddServiceLevelIndicator(options =>
{
options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
options.AutomaticallyEmitted = false;
options.LatencyThresholds.Add(TimeSpan.FromMilliseconds(250));
});
var app = builder.Build();
app.UseServiceLevelIndicator();
app.MapGet("/projects/{projectId:regex(^project-(blue|green|red)$)}",
([CustomerResourceId] string projectId) => Results.Ok(new { projectId }))
.AddServiceLevelIndicator("GetProject");
app.Run();Run with dotnet run --urls http://localhost:5200 and request http://localhost:5200/projects/project-blue. The sample bounds project IDs deliberately; adapt these to your application's stable resource identifiers and cardinality budget.
The OTLP exporter defaults to http://localhost:4317. Set OTEL_EXPORTER_OTLP_ENDPOINT for your collector. The included observability stack can receive these metrics. Use your actual deployment location instead of the example region; no Azure SDK dependency is needed.
For MVC, register controllers and chain .AddMvc() after AddServiceLevelIndicator(...) so SLI attributes are applied. See the MVC integration guide or versioned MVC example.
Use the core package without any ASP.NET Core dependency. Prefer Measure / MeasureAsync for automatic outcome handling:
async Task SyncProjectAsync(ServiceLevelIndicator sli, CancellationToken cancellationToken)
{
await sli.MeasureAsync("SyncProject", () => Task.Delay(75, cancellationToken));
}Configure a stable CustomerResourceId for the workload. Successful completion records Success; exceptions record Failure; OperationCanceledException records Ignored. Exceptions still propagate to the application. Result selectors can classify failures returned as values.
Raw StartMeasuring(...) scopes default to Ignored, not success or failure. If you use one, explicitly classify every completion, exception, and cancellation path. See the core guide and runnable console example.
Start with the always-on demo for the full experience. These smaller examples isolate individual integration APIs without maintaining competing demo stacks:
| Example | What it teaches |
|---|---|
| Always-on Project Tracker | MVC instrumentation, continuous incidents, exact latency counts, and customer-specific Grafana SLIs |
| Console | Core-only measurement, custom meter ownership/registration, and console export |
| Minimal API | Endpoint opt-in, named operations, target-resource metadata, and bounded dimensions; health and docs remain unmeasured |
| API-versioned MVC | The same project operation separated by http.api.version |
Run the focused examples for commands, URLs, and expected metrics. The web examples share the demo's Collector/Prometheus/Grafana stack; their Api.http files contain ready-to-send requests.
Keep metric dimensions stable: no personal names, emails, request IDs, timestamps, or arbitrary free text. CustomerResourceId identifies the target, even when a caller acts across tenants; enrichment must not silently replace it with the caller's tenant.
| Topic | Guide |
|---|---|
| Integration and configuration | Usage reference |
| Outcome policies and aborts | Status semantics |
| Exact latency objectives and queries | Threshold latency objectives |
| Custom meters and disposal | Custom meter registration |
| Endpoint exclusions | Exclude endpoints |
| Additional dimensions | Enrichment and bounded attributes |
| Recording/classifier/enrichment failures | Instrumentation failure policy |
| Coding-assistant package guides | Optional API guidance |
| SQL cancellation fixture | Provider cancellation integration |
API references: Core | ASP.NET Core | API Versioning.
xavierjohn/Trellis— the framework:Result<T>,Maybe<T>, value objects, DDD primitives, ASP.NET / EF Core / Mediator integration. SLI metrics integrate naturally with Trellis pipeline behaviors.xavierjohn/Trellis.Microservices— microservice trust-boundary packages: YARP gateway minting internal JWTs + consumer-side actor provider.xavierjohn/Trellis.Microservices.Template—dotnet new trellis-microservicesProject Tracker starter.xavierjohn/Trellis.AspTemplate—dotnet new trellis-aspsingle-service Clean Architecture template.
MIT.
