Skip to content

Latest commit

 

History

History
310 lines (231 loc) · 8.06 KB

File metadata and controls

310 lines (231 loc) · 8.06 KB

ServiceLevelIndicators Usage Reference

This document is a compact reference for choosing the correct package and wiring it into a .NET application.

Package Matrix

Scenario Package Purpose
Measure code in any .NET app ServiceLevelIndicators Core latency SLI measurement for console apps, workers, background jobs, and shared libraries
Automatically measure ASP.NET Core endpoints ServiceLevelIndicators.Asp Middleware, MVC, and Minimal API integration
Add API version as a metric dimension ServiceLevelIndicators.Asp.ApiVersioning Adds http.api.version enrichment for apps using Asp.Versioning

Metric Contract

These values are part of the library contract and should be treated as stable unless you are intentionally making a breaking change.

Metric element Value
Meter name ServiceLevelIndicator by default
Instrument name operation.duration
Unit milliseconds (ms)
Required tag CustomerResourceId
Required tag LocationId
Standard tag Operation
Standard tag activity.status.code

For ASP.NET Core, the library also emits http.response.status.code and can optionally emit http.request.method and http.api.version.

Core Package

Install:

dotnet add package ServiceLevelIndicators

Register with OpenTelemetry:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddServiceLevelIndicatorInstrumentation();
        metrics.AddOtlpExporter();
    });

Register the service:

builder.Services.Configure<ServiceLevelIndicatorOptions>(options =>
{
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
    options.CustomerResourceId = "tenant-a";
});

builder.Services.AddSingleton<ServiceLevelIndicator>();

Measure work:

async Task ProcessOrder(ServiceLevelIndicator sli)
{
    using var op = sli.StartMeasuring("ProcessOrder");
    op.AddAttribute("OrderType", "Standard");

    await Task.Delay(50);

    op.SetActivityStatusCode(ActivityStatusCode.Ok);
}

Direct recording is also available when you already know the elapsed time:

sli.Record("ProcessOrder", elapsedTime: 42);

Custom Meter Registration

If you provide a custom Meter in ServiceLevelIndicatorOptions, register that same meter with OpenTelemetry.

var sliMeter = new Meter("MyCompany.ServiceLevelIndicator");

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddServiceLevelIndicatorInstrumentation(sliMeter);
        metrics.AddOtlpExporter();
    });

builder.Services.Configure<ServiceLevelIndicatorOptions>(options =>
{
    options.Meter = sliMeter;
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
    options.CustomerResourceId = "tenant-a";
});

builder.Services.AddSingleton<ServiceLevelIndicator>();

Available registration overloads:

metrics.AddServiceLevelIndicatorInstrumentation();
metrics.AddServiceLevelIndicatorInstrumentation("MyCompany.ServiceLevelIndicator");
metrics.AddServiceLevelIndicatorInstrumentation(sliMeter);

ASP.NET Core MVC

Install:

dotnet add package ServiceLevelIndicators.Asp

Register services:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddServiceLevelIndicatorInstrumentation();
        metrics.AddOtlpExporter();
    });

builder.Services.AddServiceLevelIndicator(options =>
{
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
    options.CustomerResourceId = "tenant-a";
})
.AddMvc();

Add middleware:

app.UseServiceLevelIndicator();

Common MVC customization points:

builder.Services.AddServiceLevelIndicator(options =>
{
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
})
.AddMvc()
.AddHttpMethod()
.Enrich(context =>
{
    context.SetCustomerResourceId("tenant-a");
    context.AddAttribute("ProductTier", "Premium");
});

Action-level attributes:

[HttpGet("orders/{customerId}/{orderType}")]
[ServiceLevelIndicator(Operation = "GetOrder")]
public IActionResult Get(
    [CustomerResourceId] string customerId,
    [Measure(Name = "OrderType")] string orderType)
    => Ok();

ASP.NET Core Minimal APIs

Register services and middleware:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddServiceLevelIndicatorInstrumentation();
        metrics.AddOtlpExporter();
    });

builder.Services.AddServiceLevelIndicator(options =>
{
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
    options.CustomerResourceId = "tenant-a";
    options.AutomaticallyEmitted = false;
});

app.UseServiceLevelIndicator();

Mark each endpoint that should emit SLI data:

app.MapGet("/orders/{customerId}/{orderType}",
    ([CustomerResourceId] string customerId, [Measure(Name = "OrderType")] string orderType) => Results.Ok())
    .AddServiceLevelIndicator("GetOrder");

API Versioning Package

Install:

dotnet add package ServiceLevelIndicators.Asp.ApiVersioning

Register enrichment:

builder.Services.AddServiceLevelIndicator(options =>
{
    options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3");
})
.AddMvc()
.AddApiVersion();

This adds the http.api.version metric dimension when Asp.Versioning is present.

ASP.NET Runtime Helpers

These APIs are useful inside controllers, middleware, and endpoint handlers:

var op = HttpContext.GetMeasuredOperation();
op.CustomerResourceId = "tenant-a";
op.AddAttribute("ProductTier", "Premium");

if (HttpContext.TryGetMeasuredOperation(out var measuredOperation))
{
    measuredOperation.AddAttribute("FeatureFlag", "NewCheckout");
}

Use GetMeasuredOperation() when the route is guaranteed to emit SLI metrics. Use TryGetMeasuredOperation() in shared middleware or filters.

Status Semantics

For non-HTTP code, set the outcome explicitly:

op.SetActivityStatusCode(ActivityStatusCode.Ok);

For ASP.NET Core:

Response outcome activity.status.code
2xx Ok
5xx Error
Other status codes Unset
Unhandled exceptions Error

Cardinality Guidance

Use stable dimensions that support aggregation and alerting.

Good values:

  • Tenant or subscription ID
  • Region or cloud environment
  • Product tier
  • API version
  • A bounded route category or operation type

Avoid values that can explode cardinality unless your backend is designed for them:

  • Email addresses
  • Request IDs
  • Timestamps
  • Arbitrary user input
  • Random GUIDs per request

Common Mistakes

  1. Using a custom Meter but only registering the default meter name with OpenTelemetry.
  2. Treating CustomerResourceId as a per-request unique ID instead of a stable service dimension.
  3. Forgetting AddMvc() when relying on MVC conventions and attribute-based overrides.
  4. Forgetting .AddServiceLevelIndicator() on Minimal API endpoints when AutomaticallyEmitted is false.
  5. Renaming CustomerResourceId or LocationId even though downstream systems depend on those exact names.

Public API Cheat Sheet

Core package:

  • AddServiceLevelIndicator(Action<ServiceLevelIndicatorOptions>)
  • AddServiceLevelIndicatorInstrumentation()
  • AddServiceLevelIndicatorInstrumentation(string meterName)
  • AddServiceLevelIndicatorInstrumentation(Meter meter)
  • ServiceLevelIndicator.StartMeasuring(...)
  • ServiceLevelIndicator.Record(...)
  • ServiceLevelIndicator.CreateLocationId(...)
  • ServiceLevelIndicator.CreateCustomerResourceId(...)

ASP.NET Core package:

  • UseServiceLevelIndicator()
  • IServiceLevelIndicatorBuilder.AddMvc()
  • IServiceLevelIndicatorBuilder.AddHttpMethod()
  • IServiceLevelIndicatorBuilder.Enrich(...)
  • IServiceLevelIndicatorBuilder.EnrichAsync(...)
  • EndpointConventionBuilder.AddServiceLevelIndicator(...)
  • HttpContext.GetMeasuredOperation()
  • HttpContext.TryGetMeasuredOperation(...)

API versioning package:

  • IServiceLevelIndicatorBuilder.AddApiVersion()