From adfcc8b956184984442bcba66d365b388ddf582c Mon Sep 17 00:00:00 2001 From: Xavier John Date: Tue, 28 Apr 2026 14:31:06 -0700 Subject: [PATCH 1/5] docs: update API versioning and SLI documentation for clarity and accuracy --- README.md | 12 ++++++------ .../src/README.md | 2 +- Trellis.ServiceLevelIndicators.Asp/src/README.md | 8 +++++++- Trellis.ServiceLevelIndicators/src/README.md | 8 ++++++-- docs/api_reference/trellis-api-sli-apiversioning.md | 2 +- docs/api_reference/trellis-api-sli-asp.md | 10 +++++----- docs/api_reference/trellis-api-sli.md | 8 ++++---- docs/usage-reference.md | 6 +++--- 8 files changed, 33 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5bef964..96d9f91 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ Service level indicators (SLIs) are metrics used to track how a service is perfo **Trellis.ServiceLevelIndicators** emits operation latency metrics in milliseconds so service owners can monitor performance over time using dimensions that matter to their system. The metrics are emitted via the standard [.NET Meter Class](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter). -By default, a meter named `Trellis.SLI` with instrument name `operation.duration` is added to the service metrics. The metrics are emitted with the following [attributes](https://opentelemetry.io/docs/specs/otel/common/#attribute). +By default, a meter named `Trellis.SLI` with instrument name `operation.duration` is added to the service metrics. If you configure `ServiceLevelIndicatorOptions.Meter`, metrics are emitted from that meter instead. Metrics recorded with `StartMeasuring(...)` emit the following [attributes](https://opentelemetry.io/docs/specs/otel/common/#attribute). - CustomerResourceId - The **target resource** of the operation — the noun in the URL path being read or modified, normalized to a stable identifier (tenant, subscription, account, work item). **NOT** the caller, **NOT** a per-request GUID, **NOT** a user ID or email. Example: for `GET /teams/{teamId}` called by user `xa1` for team `team1`, the value is `"team1"`, not `"xa1"`. See the [ASP.NET Core package README](Trellis.ServiceLevelIndicators.Asp/src/README.md#what-customerresourceid-is--and-what-it-is-not) for the full mental model. -- LocationId - The location where the service running. eg. Public cloud, West US 3 region. [Azure Core](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) +- LocationId - The location where the service is running, such as public cloud in the West US 3 region. [Azure Core](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) - Operation - The name of the operation. -- activity.status.code - The activity status code is set based on the success or failure of the operation. [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode). +- activity.status.code - The activity status code is set on `MeasuredOperation` based on the success or failure of the operation. [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode). Direct `Record(...)` calls emit only `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call. **Trellis.ServiceLevelIndicators.Asp** adds the following dimensions. @@ -45,7 +45,7 @@ Difference between ServiceLevelIndicator and http.server.request.duration This makes the library useful when generic HTTP server metrics are not enough, especially for multi-tenant services, APIs with customer-specific objectives, or workloads that need the same SLI model outside HTTP request handling. **Trellis.ServiceLevelIndicators.Asp.ApiVersioning** adds the following dimensions. -- http.api.version - The API Version when used in conjunction with [API Versioning package](https://github.com/dotnet/aspnet-api-versioning). +- http.api.version - The resolved API version when used in conjunction with the [API Versioning package](https://github.com/dotnet/aspnet-api-versioning). The value can be a version string, `Neutral`, `Unspecified`, or an empty string for invalid or ambiguous requests. ## NuGet Packages @@ -199,7 +199,7 @@ You can measure a block of code by wrapping it in a `using` clause of `MeasuredO Example: ```csharp -async Task MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) +void MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) { using var measuredOperation = serviceLevelIndicator.StartMeasuring("OperationName"); // Do Work. @@ -352,7 +352,7 @@ To view the metrics locally using the [.NET Aspire Dashboard](https://aspire.dev docker run --rm -it -d -p 18888:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true -e DASHBOARD__OTLP__AUTHMODE=Unsecured --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest ``` 2. Run the sample web API project and call the `GET WeatherForecast` using the Open API UI. -3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the instrument `operation.duration` where `Operation = "GET WeatherForecast"`, `http.response.status.code = 200`, `LocationId = "ms-loc://az/public/westus2"`, `activity.status.code = Ok`. +3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the instrument `operation.duration` where `Operation = "GET WeatherForecast"`, `http.response.status.code = 200`, `LocationId = "ms-loc://az/public/westus3"`, `activity.status.code = Ok`. ![SLI](assets/aspire.jpg) 4. If you run the sample with API Versioning, you will see something similar to the following. ![SLI](assets/versioned.jpg) diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md index 86ee19b..9f37e47 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md @@ -43,7 +43,7 @@ This registers `ApiVersionEnrichment`, which reads the resolved API version from | Attribute | Description | |-----------|-------------| -| `http.api.version` | The resolved API version string (e.g. `1.0`, `2024-01-15`), `Neutral`, or `Unspecified` | +| `http.api.version` | The single resolved API version string (e.g. `1.0`, `2024-01-15`), `Neutral` for API-version-neutral endpoints, `Unspecified` when no version is requested and no default is assumed, or an empty string for invalid or ambiguous requests | This attribute is added alongside all the standard attributes emitted by `Trellis.ServiceLevelIndicators.Asp` (`Operation`, `CustomerResourceId`, `LocationId`, `activity.status.code`, `http.response.status.code`). diff --git a/Trellis.ServiceLevelIndicators.Asp/src/README.md b/Trellis.ServiceLevelIndicators.Asp/src/README.md index e19da19..3102287 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/README.md +++ b/Trellis.ServiceLevelIndicators.Asp/src/README.md @@ -43,6 +43,8 @@ app.UseServiceLevelIndicator(); ## Quick Start — Minimal APIs ```csharp +// Register with OpenTelemetry as shown in the MVC example above. + builder.Services.AddServiceLevelIndicator(options => { options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); @@ -60,9 +62,13 @@ If you configure a custom `Meter` in `ServiceLevelIndicatorOptions`, register th `ServiceLevelIndicator` is a sealed `IDisposable` registered as a singleton; the DI container disposes it (and the `Meter` it created) at host shutdown — no manual cleanup needed. A `Meter` you supply via `Options.Meter` is owned by you and is never disposed by SLI. +Place `UseServiceLevelIndicator()` after routing and before endpoint execution so the middleware can read endpoint metadata and measure request handling. + ## Emitted Metrics -A meter named `Trellis.SLI` with instrument `operation.duration` (milliseconds) is emitted with the following attributes: +By default, a meter named `Trellis.SLI` emits the `operation.duration` histogram in milliseconds. If you configure `ServiceLevelIndicatorOptions.Meter`, ASP.NET Core SLI metrics are emitted from that meter instead. + +Measured HTTP requests emit the following attributes: | Attribute | Description | |-----------|-------------| diff --git a/Trellis.ServiceLevelIndicators/src/README.md b/Trellis.ServiceLevelIndicators/src/README.md index c9d46f1..89196b0 100644 --- a/Trellis.ServiceLevelIndicators/src/README.md +++ b/Trellis.ServiceLevelIndicators/src/README.md @@ -66,7 +66,7 @@ builder.Services.AddServiceLevelIndicator(options => Wrap any block of code in a `using StartMeasuring` scope: ```csharp -async Task DoWork(ServiceLevelIndicator sli) +void DoWork(ServiceLevelIndicator sli) { using var op = sli.StartMeasuring("ProcessOrder"); // Do work... @@ -85,7 +85,9 @@ Custom attribute names must be unique and must not reuse SLI-reserved tags such ## Emitted Metrics -A meter named `Trellis.SLI` with instrument `operation.duration` (milliseconds) is emitted with the following attributes: +By default, a meter named `Trellis.SLI` emits the `operation.duration` histogram in milliseconds. If you configure `ServiceLevelIndicatorOptions.Meter`, metrics are emitted from that meter instead. + +`StartMeasuring(...)` emits the following attributes when the returned `MeasuredOperation` is disposed: | Attribute | Description | |-----------|-------------| @@ -94,6 +96,8 @@ A meter named `Trellis.SLI` with instrument `operation.duration` (milliseconds) | `LocationId` | Where the service is running (e.g. `ms-loc://az/public/westus3`) | | `activity.status.code` | `Ok`, `Error`, or `Unset` based on the operation outcome | +Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call; they do not add `activity.status.code`. + ## Cardinality Guidance All three required tags — `Operation`, `LocationId`, and `CustomerResourceId` — must be **low-cardinality and bounded**. Good values: tenant, subscription, environment, region, product tier, work-item type. Bad values: per-request GUIDs, user IDs / emails, timestamps, free-form user input. The same rule applies to any custom attributes you add via `MeasuredOperation.AddAttribute(...)`. diff --git a/docs/api_reference/trellis-api-sli-apiversioning.md b/docs/api_reference/trellis-api-sli-apiversioning.md index d289583..0fef47b 100644 --- a/docs/api_reference/trellis-api-sli-apiversioning.md +++ b/docs/api_reference/trellis-api-sli-apiversioning.md @@ -12,7 +12,7 @@ See also: [`trellis-api-sli.md`](trellis-api-sli.md), [`trellis-api-sli-asp.md`] | Tag | Value | |---|---| -| `http.api.version` | The single requested API version (e.g. `2023-06-06`), `"Neutral"` if the endpoint is API-version-neutral, `"Unspecified"` if no version was requested, or `""` if multiple versions were requested. | +| `http.api.version` | The single resolved API version (e.g. `2023-06-06`), `"Neutral"` if the endpoint is API-version-neutral, `"Unspecified"` if no version was requested and no default was assumed, or `""` if the request is invalid or ambiguous. | --- diff --git a/docs/api_reference/trellis-api-sli-asp.md b/docs/api_reference/trellis-api-sli-asp.md index de5e133..5cddd7b 100644 --- a/docs/api_reference/trellis-api-sli-asp.md +++ b/docs/api_reference/trellis-api-sli-asp.md @@ -16,7 +16,7 @@ When the middleware is registered, every measured request emits these tags on to | Tag | Source | |---|---| -| `Operation` | Route template (e.g. `GET Weatherforecast`) — derived from `ControllerActionDescriptor.AttributeRouteInfo.Template` for MVC, or from the route pattern for Minimal APIs. Overridable via `[ServiceLevelIndicator(operation)]` or `AddServiceLevelIndicator("operation")`. | +| `Operation` | HTTP method plus route template (e.g. `GET WeatherForecast` or `GET /teams/{teamId}`) — derived from `ControllerActionDescriptor.AttributeRouteInfo.Template` for MVC, or from the route pattern for Minimal APIs. Overridable via `[ServiceLevelIndicator(operation)]` or `AddServiceLevelIndicator("operation")`. | | `activity.status.code` | `Ok` for HTTP 2xx, `Error` for HTTP 5xx (or unhandled exception), `Unset` otherwise. | | `http.response.status.code` | `HttpContext.Response.StatusCode`. | | `http.request.method` | Added when `AddHttpMethod()` is called on the SLI builder. | @@ -43,19 +43,19 @@ Builder returned by `AddServiceLevelIndicator(...)` to chain MVC integration, HT --- -### IServiceCollectionExtensions +### ServiceLevelIndicatorCoreServiceCollectionExtensions **Declaration** ```csharp -public static class IServiceCollectionExtensions +public static class ServiceLevelIndicatorCoreServiceCollectionExtensions ``` **Methods** | Signature | Returns | Description | |---|---|---| -| `public static IServiceLevelIndicatorBuilder AddServiceLevelIndicator(this IServiceCollection services, Action configureOptions)` | `IServiceLevelIndicatorBuilder` | Registers the `ServiceLevelIndicator` singleton, configures `ServiceLevelIndicatorOptions`, and returns a builder for additional setup. | +| `public static IServiceLevelIndicatorBuilder AddServiceLevelIndicator(this IServiceCollection services, Action configureOptions)` | `IServiceLevelIndicatorBuilder` | Registers the `ServiceLevelIndicator` singleton, configures `ServiceLevelIndicatorOptions`, and returns a builder for additional setup. This extension is defined by the core package and used by the ASP.NET Core integration. | --- @@ -92,7 +92,7 @@ public static class ServiceLevelIndicatorApplicationBuilderExtensions | Signature | Returns | Description | |---|---|---| -| `public static IApplicationBuilder UseServiceLevelIndicator(this IApplicationBuilder app)` | `IApplicationBuilder` | Adds the `ServiceLevelIndicatorMiddleware` to the request pipeline. Place after routing so the endpoint is already resolved. | +| `public static IApplicationBuilder UseServiceLevelIndicator(this IApplicationBuilder app)` | `IApplicationBuilder` | Adds the `ServiceLevelIndicatorMiddleware` to the request pipeline. Place after routing and before endpoint execution so the endpoint is already resolved and request handling is measured. | --- diff --git a/docs/api_reference/trellis-api-sli.md b/docs/api_reference/trellis-api-sli.md index 7fbdc68..7835d49 100644 --- a/docs/api_reference/trellis-api-sli.md +++ b/docs/api_reference/trellis-api-sli.md @@ -10,7 +10,7 @@ See also: [`trellis-api-sli-asp.md`](trellis-api-sli-asp.md), [`trellis-api-sli- ## Default emitted dimensions -Every measurement emits the following tags on instrument `operation.duration` (ms, `Histogram`): +`StartMeasuring(...)` scopes emit the following tags on instrument `operation.duration` (ms, `Histogram`) when the returned `MeasuredOperation` is disposed: | Tag | Source | |---|---| @@ -19,7 +19,7 @@ Every measurement emits the following tags on instrument `operation.duration` (m | `Operation` | Caller-supplied operation name | | `activity.status.code` | Set on `MeasuredOperation` (`Unset` / `Ok` / `Error`) | -Additional attributes can be appended via `MeasuredOperation.AddAttribute(...)` or the `attributes` parameter of `Record`/`StartMeasuring`. Custom attributes must not reuse `CustomerResourceId`, `LocationId`, `Operation`, the configured activity-status tag name, or any other attribute name already present on the measurement. +Additional attributes can be appended via `MeasuredOperation.AddAttribute(...)` or the `attributes` parameter of `Record`/`StartMeasuring`. Custom attributes must not reuse `CustomerResourceId`, `LocationId`, `Operation`, the configured activity-status tag name, or any other attribute name already present on the measurement. Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, and any supplied custom attributes, but they do not add `activity.status.code`. --- @@ -59,8 +59,8 @@ Singleton service that creates and records SLI metrics using an OpenTelemetry `H | Signature | Returns | Description | |---|---|---| -| `public void Record(string operation, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement using the configured default `CustomerResourceId`. | -| `public void Record(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement with an explicit `CustomerResourceId`. | +| `public void Record(string operation, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement using the configured default `CustomerResourceId`. Does not add `activity.status.code`. | +| `public void Record(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement with an explicit `CustomerResourceId`. Does not add `activity.status.code`. | | `public MeasuredOperation StartMeasuring(string operation, params KeyValuePair[] attributes)` | `MeasuredOperation` | Starts a stopwatch-backed measurement; dispose the returned object to record the elapsed time as a metric. | | `public void Dispose()` | `void` | Disposes the internally-created `Meter` if this instance created it; never disposes a user-supplied meter. Idempotent. Normally invoked by the DI container at host shutdown. | | `public static string CreateCustomerResourceId(Guid serviceId)` | `string` | Builds a `ServiceTreeId://` customer resource id. Throws `ArgumentNullException` if `serviceId` is `Guid.Empty`. | diff --git a/docs/usage-reference.md b/docs/usage-reference.md index 628b35a..be68e59 100644 --- a/docs/usage-reference.md +++ b/docs/usage-reference.md @@ -22,7 +22,7 @@ These values are part of the library contract and should be treated as stable un | Required tag | `CustomerResourceId` | | Required tag | `LocationId` | | Standard tag | `Operation` | -| Standard tag | `activity.status.code` | +| Standard tag | `activity.status.code` for `StartMeasuring(...)` scopes and ASP.NET Core middleware | For ASP.NET Core, the library also emits `http.response.status.code` and can optionally emit `http.request.method` and `http.api.version`. @@ -69,7 +69,7 @@ async Task ProcessOrder(ServiceLevelIndicator sli) } ``` -Direct recording is also available when you already know the elapsed time: +Direct recording is also available when you already know the elapsed time. `Record(...)` emits `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call; it does not add `activity.status.code`. ```csharp sli.Record("ProcessOrder", elapsedTime: 42); @@ -213,7 +213,7 @@ builder.Services.AddServiceLevelIndicator(options => .AddApiVersion(); ``` -This adds the `http.api.version` metric dimension when Asp.Versioning is present. +This adds the `http.api.version` metric dimension when Asp.Versioning is present. The value is the single resolved API version, `Neutral`, `Unspecified`, or an empty string when the requested version is invalid or ambiguous. ## ASP.NET Runtime Helpers From c016cbcff48f5a91d420d69b43440acdbd28bd0e Mon Sep 17 00:00:00 2001 From: Xavier John Date: Tue, 28 Apr 2026 16:32:09 -0700 Subject: [PATCH 2/5] docs: add RFC for Trellis ServiceLevelIndicators metric contract --- docs/design/sli-metric-contract.md | 386 +++++++++++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 docs/design/sli-metric-contract.md diff --git a/docs/design/sli-metric-contract.md b/docs/design/sli-metric-contract.md new file mode 100644 index 0000000..5a0b921 --- /dev/null +++ b/docs/design/sli-metric-contract.md @@ -0,0 +1,386 @@ +# RFC: Trellis ServiceLevelIndicators metric contract + +## Status + +Draft for pre-implementation review. + +## Context + +`Trellis.ServiceLevelIndicators` is part of the Trellis AI-first .NET framework. Its role is to provide structural observability guardrails for generated and hand-written services by emitting service-level latency metrics with stable, meaningful dimensions. + +The library is currently in the .NET 10 preview/alpha phase. The changes in this RFC finalize the intended pre-1.0 metric contract; no migration guide is required for previous alpha behavior. + +## Standards and platform alignment + +The design aligns with: + +| Source | Usage | +|---|---| +| Google SRE | SLI/SLO semantics, valid requests, excluded traffic, error budgets | +| OpenTelemetry | .NET metric emission, histograms, meter registration, optional HTTP/API dimensions | +| OpenSLO | Documentation examples for portable SLO definitions; no runtime support initially | +| Microsoft Azure Monitor / Application Insights | KQL examples and Azure operational guidance | +| Trellis backend contract | Required dimensions and SLI-focused naming | + +## Metric + +The primary metric remains: + +| Metric | Type | Unit | Required dimensions | +|---|---|---|---| +| `operation.duration` | Histogram | `ms` | `CustomerResourceId`, `LocationId`, `Operation`, `Outcome` | + +`operation.count` is not added initially. Success-rate and availability queries should use the histogram count produced by `operation.duration`. A separate count metric may be reconsidered only if backend constraints prove histogram count is insufficient. + +`operation.duration` is a custom Trellis SLI metric, not an OpenTelemetry semantic-convention metric. The unit is intentionally milliseconds (`ms`), even though OpenTelemetry HTTP duration semantic conventions use seconds. + +## Required dimensions + +These dimensions are mandatory and emitted exactly: + +- `CustomerResourceId` +- `LocationId` +- `Operation` +- `Outcome` + +Dimension sources: + +| Dimension | Source / fallback | +|---|---| +| `CustomerResourceId` | Explicit/default resource identifier; fallback `Unknown` | +| `LocationId` | Required startup configuration; fail fast when missing or empty | +| `Operation` | Caller-supplied operation; ASP.NET uses explicit override, route template, then ` ` | +| `Outcome` | Explicit outcome, helper inference, middleware inference, or default `Ignored` | + +Manual/background operations throw `ArgumentException` when `Operation` is null, empty, or whitespace. ASP.NET unrouted fallback uses uppercase HTTP method formatting, for example `GET `. + +`LocationId` is startup-only. It cannot be overridden per operation or request. + +Optional HTTP/API dimensions use OpenTelemetry-style names, including: + +- `http.request.method` +- `http.response.status.code` +- `http.api.version` + +The required PascalCase dimensions intentionally deviate from OpenTelemetry attribute naming conventions because they are part of the Trellis backend contract. + +## Outcome model + +C# code should expose: + +```csharp +public enum SliOutcome +{ + Success, + Failure, + ClientError, + Ignored +} +``` + +Metrics emit the dimension: + +```text +Outcome = "Success" | "Failure" | "ClientError" | "Ignored" +``` + +Emit outcome values through explicit string mapping. Do not rely on enum `ToString()` for the wire value. +Wire values are case-sensitive. + +SLO semantics: + +| Outcome | Meaning | SLO usage | +|---|---|---| +| `Success` | Operation completed successfully | Numerator and denominator | +| `Failure` | Service failed to satisfy the operation | Counted as a valid event; not counted as good | +| `ClientError` | Client/request/input problem, reported separately | Excluded from default success-rate denominator | +| `Ignored` | Not part of SLI measurement | Excluded | + +Default success-rate formula: + +```text +success_rate = count(Outcome == "Success") / (count(Outcome == "Success") + count(Outcome == "Failure")) +``` + +`ClientError` and `Ignored` are excluded from the default denominator. Services may report `ClientError` separately or opt into policy-specific SLO formulas. + +Outcome precedence: + +1. Explicit user-set outcome. +2. Helper inference. +3. Middleware inference. +4. Default `Ignored`. + +Unhandled exceptions escaping the measured operation are an explicit exception to precedence and force `Failure`. + +## ASP.NET classification defaults + +| HTTP status/result | Outcome | +|---|---| +| 2xx | `Success` | +| 3xx | `Success` | +| 400, 401, 403, 404, 409, 412, 422 | `ClientError` | +| 429 | `Failure` by default; configurable | +| 5xx | `Failure` | +| Client disconnect / request-aborted cancellation | `Ignored` | +| Unhandled exception | `Failure` | + +Application/business cancellations remain configurable. + +429 reduces success rate by default because throttling is treated as a service capacity/backpressure signal. Services that treat 429 as client-driven may classify it as `ClientError`. + +3xx responses are `Success` by default because redirects and cache-validation responses can be successful service behavior. Redirect loops should be monitored through status-code queries or classifier overrides. + +For exceptions, record the final `HttpContext.Response.StatusCode`. If an unhandled exception escapes and no status was set, emit `http.response.status.code = 500`. + +ASP.NET emits these optional dimensions by default: + +- `http.request.method` +- `http.response.status.code` + +API version remains opt-in through `.AddApiVersion()` and emits: + +- `http.api.version` + +## Manual measurement + +Manual/background operations default to `Outcome = Ignored` unless explicitly set or inferred by helper APIs. + +Core APIs should include outcome-oriented methods such as: + +```csharp +measuredOperation.SetOutcome(SliOutcome.Success); +``` + +Core `Measure(...)` and `MeasureAsync(...)` helpers should infer: + +- `Success` on normal completion. +- `Failure` on exception. +- `Ignored` on `OperationCanceledException`. + +Core helpers are not ASP.NET-specific and do not detect client disconnects. ASP.NET middleware maps `HttpContext.RequestAborted` / request-aborted cancellation to `Ignored`; application/business cancellations remain configurable. + +Result-aware helpers for Trellis `Result` belong in the optional `Trellis.ServiceLevelIndicators.Results` package, not in the core package. + +## Activity correlation + +`activity.status.code` is removed from default metric dimensions. + +The library should continue updating `Activity.Current` status for trace correlation. Activity status is not the source of SLI outcome. + +Activity status mapping: + +| Outcome | Activity status | +|---|---| +| `Success` | `Ok` | +| `Failure` | `Error` | +| `ClientError` | `Unset` | +| `Ignored` | `Unset` | + +## Unknown customer resource + +When no customer resource is known, emit: + +```text +CustomerResourceId = "Unknown" +``` + +Diagnostics: + +- Log a one-time warning per operation/location when `CustomerResourceId = Unknown`. +- Increment `sli.diagnostics.unknown_customer_resource_id` every time `CustomerResourceId = Unknown`. +- Do not throw, drop, cap, or replace required dimensions by default. + +`Unknown` is deliberate and distinct from `ActivityStatusCode.Unset`. + +The one-time warning scope is per process lifetime. The implementation should bound the warning cache to avoid unbounded memory growth. + +Diagnostic counter contract: + +| Instrument | Type | Dimensions | +|---|---|---| +| `sli.diagnostics.unknown_customer_resource_id` | `Counter` | `Operation`, `LocationId` | + +## Meter + +The default meter name remains: + +```text +Trellis.SLI +``` + +Custom meter registration remains supported. All SLI metrics and diagnostic counters should use the configured meter consistently. + +## Implementation design + +### Core recording + +`ServiceLevelIndicator` owns the `operation.duration` histogram and the `sli.diagnostics.unknown_customer_resource_id` counter. Both instruments use the default `Trellis.SLI` meter or the configured custom meter. + +Every duration recording emits: + +- `CustomerResourceId` +- `LocationId` +- `Operation` +- `Outcome` +- any custom dimensions + +Custom dimensions must not reuse required names, reserved names, or optional dimensions already present on the measurement. Schema collisions throw immediately. The diagnostics-only rule applies to suspicious values, not to schema collisions. + +### MeasuredOperation + +`MeasuredOperation` stores `SliOutcome`, defaulting to `Ignored`. + +Public API: + +```csharp +measuredOperation.SetOutcome(SliOutcome.Success); +``` + +Internal behavior: + +- Disposing records elapsed milliseconds. +- Disposing records the explicit or inferred `Outcome`. +- Raw `StartMeasuring(...)`/`Dispose()` cannot detect escaping exceptions and records the explicit/default outcome. +- `Measure(...)`, `MeasureAsync(...)`, and ASP.NET middleware can force `Failure` when they observe an exception. +- `Dispose()` remains idempotent. +- `SetOutcome(...)` after disposal has no effect. + +### Helper APIs + +Core helper APIs should infer outcomes: + +| Result | Outcome | +|---|---| +| Normal completion | `Success` | +| Exception | `Failure` | +| `OperationCanceledException` | `Ignored` | + +Helpers rethrow exceptions and cancellations after setting the inferred outcome. +Rethrows should use `throw;` so exception stack traces are preserved. + +If user code explicitly sets `Failure` inside a helper and completes normally, explicit `Failure` remains. Helper success inference does not promote it to `Success`. Unhandled exceptions observed by helpers still force `Failure`. + +### ASP.NET middleware + +Middleware owns ASP.NET request measurement. Core helpers and ASP.NET middleware normally do not co-apply to the same `MeasuredOperation`; precedence exists for explicit outcomes, inferred outcomes, and future extension points. + +Middleware should: + +- resolve `Operation` from explicit metadata, route template, then uppercase ` `, +- set `CustomerResourceId` from endpoint metadata when present, +- default missing customer resource to `Unknown`, +- classify outcome using configured classifier first, then the default table, +- emit `http.request.method` and `http.response.status.code` by default, +- run enrichments before recording, +- rethrow unhandled exceptions after recording failure semantics. + +### Classifier extensibility + +The first implementation should provide a global HTTP outcome classifier option. Exact per-route classifier API shape is intentionally left for implementation design and can follow after the global classifier. + +Classifier behavior: + +- returned `SliOutcome` wins over default status mapping, +- no result / null falls back to default status mapping, +- unhandled exceptions still force `Failure`. + +### API versioning + +The API versioning package remains an enrichment package. It continues to emit `http.api.version` only when `.AddApiVersion()` is called and does not participate in outcome classification. + +## Dimension stability guardrails + +The rule is stable and meaningful dimensions, not low cardinality. + +`CustomerResourceId` may legitimately have very high cardinality at Microsoft/Azure scale, such as user object IDs in login services. Required dimensions must pass through exactly by default. + +Default behavior: + +- Required dimensions: diagnostics only; never mutate by default. +- Optional/custom dimensions: diagnostics only by default. +- Strict blocking or replacement: opt-in only. +- Runtime suspicious-value heuristics are off by default to avoid hot-path overhead at high scale; analyzers and documentation are the default guardrails. + +Diagnostics should look for unstable values such as: + +- request IDs, +- timestamps, +- raw paths, +- arbitrary text, +- generated-per-request GUIDs, +- emails when a stable object ID is available. + +Analyzer support is a later milestone. + +## Package architecture + +Core SLI package remains independent from `Trellis.Core`. + +Optional Trellis integration package: + +```text +Trellis.ServiceLevelIndicators.Results +``` + +Dependency direction: + +```text +Trellis.ServiceLevelIndicators.Results + -> Trellis.ServiceLevelIndicators + -> Trellis.Core +``` + +Neither core package depends on the Results integration package. + +## Documentation requirements + +Docs should include: + +- Google SRE-aligned SLO semantics. +- OpenTelemetry metric setup. +- OpenSLO examples, documentation only. +- Azure Monitor / Application Insights KQL examples. +- Success-rate queries using `operation.duration` histogram count. +- Latency percentile queries. +- Client-error rate queries. +- Unknown `CustomerResourceId` detection. +- OpenTelemetry View/cardinality-limit guidance for services that intentionally use high-cardinality `CustomerResourceId` values. + +## Testing support + +Use TDD by phase: write or adjust failing tests first, implement the smallest passing change, then refactor. + +Core and ASP.NET tests should cover outcome values, diagnostics, exception/cancellation paths, Activity status mapping, required dimensions, and custom meter behavior. Custom meter tests must verify both `operation.duration` and `sli.diagnostics.unknown_customer_resource_id` use the configured meter. + +Eventually add: + +```text +Trellis.ServiceLevelIndicators.Testing +``` + +This package should provide metric assertion helpers for AI-generated and hand-written tests. + +## Histogram views/buckets + +Provide optional helper APIs and documentation for recommended API latency histogram views/buckets. Do not force bucket configuration by default. + +## Alternatives considered + +### Keep `activity.status.code` as the primary outcome dimension + +Rejected. `activity.status.code` is trace-oriented and does not model SLI-specific states such as `ClientError` and `Ignored` clearly enough for SLO math. The library should still update `Activity.Current` for trace correlation. + +### Add `operation.count` immediately + +Rejected for the first implementation. Histograms already produce a count series, and a separate counter risks drift unless every recording path updates both instruments consistently. + +### Use seconds instead of milliseconds + +Rejected. OpenTelemetry HTTP semantic-convention duration metrics use seconds, but Trellis SLI metrics intentionally emit latency in milliseconds to match the backend contract and existing library positioning. + +## Open questions + +- Exact recommended histogram bucket boundaries for the optional helper. +- Exact per-route classifier API shape. +- Exact analyzer packaging and suppression model for the later analyzer milestone. From 9ddf0423371e8c811d250de9190fd2c780691975 Mon Sep 17 00:00:00 2001 From: Xavier John Date: Tue, 28 Apr 2026 17:22:44 -0700 Subject: [PATCH 3/5] Finalizes the pre SLI metric contract around an explicit SLI outcome model and updates core, ASP.NET, API versioning, docs, tests, and samples accordingly. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What changed - Added SliOutcome with exact emitted values: - Success - Failure - ClientError - Ignored - Updated operation.duration to emit required dimensions: - CustomerResourceId - LocationId - Operation - Outcome - Changed default CustomerResourceId fallback from Unset to Unknown. - Added diagnostic counter: - sli.diagnostics.unknown_customer_resource_id - dimensions: Operation, LocationId - Removed activity.status.code from metric dimensions while preserving Activity status correlation. - Added outcome-oriented manual APIs: - MeasuredOperation.SetOutcome(...) - ServiceLevelIndicator.Measure(...) - ServiceLevelIndicator.MeasureAsync(...) - Kept SetActivityStatusCode(...) as an obsolete compatibility shim. - Added ASP.NET outcome classification: - 2xx / 3xx → Success - 400, 401, 403, 404, 409, 412, 422 → ClientError - 429 / 5xx → Failure - request-aborted cancellations → Ignored - unhandled exceptions → Failure - ASP.NET middleware now emits by default: - http.request.method - http.response.status.code - Added ClassifyHttpOutcome(...) for global HTTP outcome override. - Kept API versioning opt-in via .AddApiVersion() and http.api.version. - Reserved framework-owned HTTP tag names to avoid custom dimension collisions: - http.request.method - http.response.status.code - Updated README files, API references, usage docs, RFC, and samples to match the new metric contract. --- README.md | 32 +-- .../src/ApiVersionEnrichment.cs | 2 +- .../src/README.md | 2 +- ...velIndicatorServiceCollectionExtensions.cs | 2 +- .../ServiceLevelIndicatorVersionedAspTests.cs | 25 +- .../tests/TestDoubleController.cs | 2 +- .../tests/TestNeutralController.cs | 2 +- .../tests/TestSingleController.cs | 2 +- .../src/CustomerResourceIdAttribute.cs | 2 +- .../src/CustomerResourceIdMetadata.cs | 2 +- .../src/EndpointBuilderExtensions.cs | 2 +- .../src/Enrich.cs | 2 +- .../src/EnrichAsync.cs | 2 +- .../src/HttpContextExtensions.cs | 2 +- .../src/HttpMethodEnrichment.cs | 2 +- .../src/IServiceLevelIndicatorFeature.cs | 2 +- .../src/MeasureAttribute.cs | 2 +- .../src/MeasureMetadata.cs | 2 +- .../src/README.md | 18 +- ...elIndicatorApplicationBuilderExtensions.cs | 2 +- .../src/ServiceLevelIndicatorAttribute.cs | 2 +- .../src/ServiceLevelIndicatorConvention.cs | 2 +- .../src/ServiceLevelIndicatorFeature.cs | 2 +- .../src/ServiceLevelIndicatorHttpOptions.cs | 14 ++ .../src/ServiceLevelIndicatorMiddleware.cs | 65 ++++-- ...velIndicatorServiceCollectionExtensions.cs | 11 +- .../src/WebEnrichmentContext.cs | 2 +- .../tests/IServiceCollectionExtensions.cs | 2 +- .../MultipleCustomerResourceIdController.cs | 2 +- .../tests/ProblemDetailsInteropTests.cs | 2 +- .../tests/ServiceLevelIndicatorAspTests.cs | 87 +++++-- .../ServiceLevelIndicatorMinimalApiTests.cs | 25 +- .../tests/TestController.cs | 11 +- .../tests/TestEnrichment.cs | 2 +- .../tests/TestHostBuilder.cs | 29 ++- .../src/IEnrichment.cs | 2 +- .../src/IEnrichmentContext.cs | 2 +- .../src/IServiceLevelIndicatorBuilder.cs | 2 +- .../src/MeasuredOperation.cs | 41 +++- Trellis.ServiceLevelIndicators/src/README.md | 12 +- .../src/ServiceLevelIndicator.cs | 198 ++++++++++++++-- ...IndicatorMeterProviderBuilderExtensions.cs | 2 +- .../src/ServiceLevelIndicatorOptions.cs | 10 +- ...velIndicatorServiceCollectionExtensions.cs | 2 +- .../src/SliOutcome.cs | 27 +++ .../tests/CustomerResourceIdTests.cs | 2 +- .../tests/LocationIdTests.cs | 2 +- ...atorMeterProviderBuilderExtensionsTests.cs | 2 +- .../tests/ServiceLevelIndicatorTests.cs | 220 ++++++++++++++++-- docs/api_reference/trellis-api-sli-asp.md | 14 +- docs/api_reference/trellis-api-sli.md | 27 ++- docs/usage-reference.md | 30 +-- sample/ConsoleApp/Program.cs | 15 +- sample/GenerateSli/Program.cs | 2 +- sample/MinApi/Program.cs | 5 +- sample/MinApi/UserExt.cs | 2 +- .../ConfigureServiceLevelIndicatorOptions.cs | 2 +- .../Controllers/WeatherForecastController.cs | 6 +- sample/WebApi/Program.cs | 5 +- sample/WebApi/SampleApiMeters.cs | 2 +- sample/WebApi/WeatherForecast.cs | 2 +- .../2023-06-06/HelloWorldController.cs | 2 +- 62 files changed, 782 insertions(+), 223 deletions(-) create mode 100644 Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorHttpOptions.cs create mode 100644 Trellis.ServiceLevelIndicators/src/SliOutcome.cs diff --git a/README.md b/README.md index 96d9f91..ad61d44 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ServiceLevelIndicators is a .NET library for emitting service-level latency metrics in milliseconds using the standard [System.Diagnostics.Metrics](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics) and OpenTelemetry pipeline. -It is designed for teams that need more than generic request timing. The library helps measure meaningful operations, attach service-specific dimensions such as customer, location, operation name, and status, and build SLO or SLA-oriented dashboards and alerts from those metrics. +It is designed for teams that need more than generic request timing. The library helps measure meaningful operations, attach service-specific dimensions such as customer, location, operation name, and SLI outcome, and build SLO or SLA-oriented dashboards and alerts from those metrics. Service level indicators (SLIs) are metrics used to track how a service is performing against expected reliability and responsiveness goals. Common examples include availability, response time, throughput, and error rate. This library focuses on latency SLIs so you can consistently measure operation duration across background work, ASP.NET Core APIs, and versioned endpoints. @@ -22,17 +22,14 @@ By default, a meter named `Trellis.SLI` with instrument name `operation.duration - CustomerResourceId - The **target resource** of the operation — the noun in the URL path being read or modified, normalized to a stable identifier (tenant, subscription, account, work item). **NOT** the caller, **NOT** a per-request GUID, **NOT** a user ID or email. Example: for `GET /teams/{teamId}` called by user `xa1` for team `team1`, the value is `"team1"`, not `"xa1"`. See the [ASP.NET Core package README](Trellis.ServiceLevelIndicators.Asp/src/README.md#what-customerresourceid-is--and-what-it-is-not) for the full mental model. - LocationId - The location where the service is running, such as public cloud in the West US 3 region. [Azure Core](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) - Operation - The name of the operation. -- activity.status.code - The activity status code is set on `MeasuredOperation` based on the success or failure of the operation. [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode). Direct `Record(...)` calls emit only `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call. +- Outcome - The SLI outcome. Exact values are `Success`, `Failure`, `ClientError`, and `Ignored`. Default success-rate queries should use `Success / (Success + Failure)`. **Trellis.ServiceLevelIndicators.Asp** adds the following dimensions. - Operation - For ASP.NET endpoints, the operation name is the HTTP method plus the route template, resolved in this order: (1) `[ServiceLevelIndicator(Operation = "...")]` attribute or `.AddServiceLevelIndicator("op")` override, (2) MVC `AttributeRouteInfo.Template`, (3) the endpoint's `RouteEndpoint.RoutePattern.RawText` (Minimal APIs / conventional routing). Route placeholders such as `{id}` are preserved, never substituted with the concrete request value. If no bounded template is available, the middleware emits the sentinel `" "` and logs a warning — see that value in your metrics as a signal to add a route template. -- The activity status code will be - "Ok" when the http response status code is in the 2xx range, - "Error" when the http response status code is in the 5xx range, - "Unset" for any other status code. +- Outcome - By default, 2xx and 3xx responses are `Success`, common caller errors such as 400/401/403/404/409/412/422 are `ClientError`, 429 and 5xx responses are `Failure`, and request-aborted cancellations are `Ignored`. - http.response.status.code - The http status code. -- http.request.method (Optional)- The http request method (GET, POST, etc) is added. +- http.request.method - The http request method (GET, POST, etc). Difference between ServiceLevelIndicator and http.server.request.duration @@ -40,7 +37,7 @@ Difference between ServiceLevelIndicator and http.server.request.duration | ---------- | ------- | ------ | Resolution | milliseconds | seconds | Customer | CustomerResourceId | N/A -| Error check | Activity or HTTP status.code | HTTP status code +| Error check | `Outcome` and HTTP status code | HTTP status code This makes the library useful when generic HTTP server metrics are not enough, especially for multi-tenant services, APIs with customer-specific objectives, or workloads that need the same SLI model outside HTTP request handling. @@ -203,7 +200,7 @@ void MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) { using var measuredOperation = serviceLevelIndicator.StartMeasuring("OperationName"); // Do Work. - measuredOperation.SetActivityStatusCode(System.Diagnostics.ActivityStatusCode.Ok); + measuredOperation.SetOutcome(SliOutcome.Success); } ``` @@ -211,7 +208,7 @@ void MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) ### Cardinality Guidance -All three required tags — `Operation`, `LocationId`, and `CustomerResourceId` — must be **low-cardinality and bounded**. The library bounds `Operation` for you via the route-template resolver and the `` sentinel; you are responsible for `LocationId` (set once from configuration) and `CustomerResourceId` (stable tenant / subscription / resource identifier). +Required tags must be stable and meaningful. The library bounds `Operation` for you via the route-template resolver and the `` sentinel; you are responsible for `LocationId` (set once from configuration) and `CustomerResourceId` (stable tenant / subscription / resource identifier). `CustomerResourceId` may be high-cardinality when the backend is designed for it, but it must not be a per-request generated value. The same discipline applies to `[Measure]` parameters and any custom attributes added via `AddAttribute(...)`. Avoid email addresses, request IDs, timestamps, or unconstrained free text unless your metrics backend is explicitly designed for high-cardinality telemetry. @@ -237,18 +234,7 @@ The default operation name is the HTTP method plus the route template (placehold .AddApiVersion(); ``` -- To add HTTP method as a dimension, add `AddHttpMethod` to Service Level Indicator. - - Example: - - ```csharp - builder.Services.AddServiceLevelIndicator(options => - { - /// Options - }) - .AddMvc() - .AddHttpMethod(); - ``` +- `http.request.method` is emitted by default by the ASP.NET Core middleware. `AddHttpMethod()` remains available as a no-op for older setup code. - Enrich SLI with the `Enrich` callback. The callback receives a `MeasuredOperation` as context that can be used to set `CustomerResourceId` or additional attributes. An async version `EnrichAsync` is also available. @@ -352,7 +338,7 @@ To view the metrics locally using the [.NET Aspire Dashboard](https://aspire.dev docker run --rm -it -d -p 18888:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true -e DASHBOARD__OTLP__AUTHMODE=Unsecured --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest ``` 2. Run the sample web API project and call the `GET WeatherForecast` using the Open API UI. -3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the instrument `operation.duration` where `Operation = "GET WeatherForecast"`, `http.response.status.code = 200`, `LocationId = "ms-loc://az/public/westus3"`, `activity.status.code = Ok`. +3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the instrument `operation.duration` where `Operation = "GET WeatherForecast"`, `Outcome = "Success"`, `http.response.status.code = 200`, and `LocationId = "ms-loc://az/public/westus3"`. ![SLI](assets/aspire.jpg) 4. If you run the sample with API Versioning, you will see something similar to the following. ![SLI](assets/versioned.jpg) diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ApiVersionEnrichment.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ApiVersionEnrichment.cs index fab7115..76a00c8 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ApiVersionEnrichment.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ApiVersionEnrichment.cs @@ -27,4 +27,4 @@ private static string GetApiVersion(HttpContext context) return "Unspecified"; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md index 9f37e47..4eda486 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/README.md @@ -45,7 +45,7 @@ This registers `ApiVersionEnrichment`, which reads the resolved API version from |-----------|-------------| | `http.api.version` | The single resolved API version string (e.g. `1.0`, `2024-01-15`), `Neutral` for API-version-neutral endpoints, `Unspecified` when no version is requested and no default is assumed, or an empty string for invalid or ambiguous requests | -This attribute is added alongside all the standard attributes emitted by `Trellis.ServiceLevelIndicators.Asp` (`Operation`, `CustomerResourceId`, `LocationId`, `activity.status.code`, `http.response.status.code`). +This attribute is added alongside all the standard attributes emitted by `Trellis.ServiceLevelIndicators.Asp` (`Operation`, `CustomerResourceId`, `LocationId`, `Outcome`, `http.request.method`, `http.response.status.code`). ## Further Reading diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicatorServiceCollectionExtensions.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicatorServiceCollectionExtensions.cs index 2d782be..c75ce10 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicatorServiceCollectionExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicatorServiceCollectionExtensions.cs @@ -11,4 +11,4 @@ public static IServiceLevelIndicatorBuilder AddApiVersion(this IServiceLevelIndi builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, ApiVersionEnrichment>()); return builder; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/ServiceLevelIndicatorVersionedAspTests.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/ServiceLevelIndicatorVersionedAspTests.cs index 3d8c92b..c292fb8 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/ServiceLevelIndicatorVersionedAspTests.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/ServiceLevelIndicatorVersionedAspTests.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Asp.ApiVersioning.Tests; +namespace Trellis.ServiceLevelIndicators.Asp.ApiVersioning.Tests; using System.Diagnostics.Metrics; using System.Net; @@ -49,7 +49,7 @@ public async Task SLI_Metrics_is_emitted_with_API_version_as_query_parameter() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET TestSingle"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), new("http.api.version", "2023-08-29"), ]; @@ -72,7 +72,7 @@ public async Task SLI_Metrics_is_emitted_with_API_version_as_header() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET TestSingle"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), new("http.api.version", "2023-08-29"), ]; @@ -98,7 +98,7 @@ public async Task SLI_Metrics_is_emitted_with_neutral_API_version() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET TestNeutral"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), ]; using var host = await CreateHost(); @@ -121,7 +121,7 @@ public async Task SLI_Metrics_is_emitted_with_default_API_version() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET TestSingle"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), ]; using var host = await CreateHostWithDefaultApiVersion(); @@ -159,7 +159,7 @@ public async Task SLI_Metrics_is_emitted_when_api_version_is_invalid(string rout new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET "), - new("activity.status.code", "Unset"), + new("Outcome", "ClientError"), new("http.response.status.code", 400), ]; var routeWithVersion = route + "?" + version; @@ -239,10 +239,21 @@ private async Task CreateHostWithDefaultApiVersion() => private void ValidateMetrics() { + _expectedTags = AddDefaultHttpMethod(_expectedTags); + _callbackCalled.Should().BeTrue(); + _actualTags.Should().NotContain(tag => tag.Key == "activity.status.code"); _actualTags.Should().BeEquivalentTo(_expectedTags); } + private static KeyValuePair[] AddDefaultHttpMethod(KeyValuePair[] expectedTags) + { + if (expectedTags.Any(tag => tag.Key == "http.request.method")) + return expectedTags; + + return [.. expectedTags, new KeyValuePair("http.request.method", "GET")]; + } + private void OnMeasurementRecorded(Instrument instrument, long measurement, ReadOnlySpan> tags, object? state) { _actualTags = tags.ToArray(); @@ -273,4 +284,4 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestDoubleController.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestDoubleController.cs index 5672edf..574f327 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestDoubleController.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestDoubleController.cs @@ -11,4 +11,4 @@ public class TestDoubleController : ControllerBase { [HttpGet] public IActionResult Get() => Ok("Hello World!"); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestNeutralController.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestNeutralController.cs index 90ea550..b90a919 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestNeutralController.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestNeutralController.cs @@ -10,4 +10,4 @@ public class TestNeutralController : ControllerBase { [HttpGet] public IActionResult Get() => Ok("Hello World!"); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestSingleController.cs b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestSingleController.cs index 9d9533a..7a5dc78 100644 --- a/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestSingleController.cs +++ b/Trellis.ServiceLevelIndicators.Asp.ApiVersioning/tests/TestSingleController.cs @@ -10,4 +10,4 @@ public class TestSingleController : ControllerBase { [HttpGet] public IActionResult Get() => Ok("Hello World!"); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdAttribute.cs b/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdAttribute.cs index 2f87c41..be639ba 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdAttribute.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdAttribute.cs @@ -7,4 +7,4 @@ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public sealed class CustomerResourceIdAttribute : Attribute { -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdMetadata.cs b/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdMetadata.cs index f13ca16..fd7aadf 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdMetadata.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/CustomerResourceIdMetadata.cs @@ -9,4 +9,4 @@ public sealed class CustomerResourceIdMetadata(string routeValueName) /// Gets the route value name mapped to the customer resource identifier. /// public string RouteValueName { get; } = routeValueName; -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/EndpointBuilderExtensions.cs b/Trellis.ServiceLevelIndicators.Asp/src/EndpointBuilderExtensions.cs index 0d52cd1..6d3e43d 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/EndpointBuilderExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/EndpointBuilderExtensions.cs @@ -54,4 +54,4 @@ private static void AddSliMetadata(EndpointBuilder endpoint) } } } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/Enrich.cs b/Trellis.ServiceLevelIndicators.Asp/src/Enrich.cs index c782732..4e299f7 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/Enrich.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/Enrich.cs @@ -14,4 +14,4 @@ public ValueTask EnrichAsync(WebEnrichmentContext context, CancellationToken can _action(context); return ValueTask.CompletedTask; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/EnrichAsync.cs b/Trellis.ServiceLevelIndicators.Asp/src/EnrichAsync.cs index f51ef2a..fde4998 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/EnrichAsync.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/EnrichAsync.cs @@ -11,4 +11,4 @@ internal sealed class EnrichAsync : IEnrichment ValueTask IEnrichment.EnrichAsync(WebEnrichmentContext context, CancellationToken cancellationToken) => _func(context, cancellationToken); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/HttpContextExtensions.cs b/Trellis.ServiceLevelIndicators.Asp/src/HttpContextExtensions.cs index a8bb6fd..56c780c 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/HttpContextExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/HttpContextExtensions.cs @@ -42,4 +42,4 @@ public static bool TryGetMeasuredOperation(this HttpContext context, [MaybeNullW measuredOperation = null; return false; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/HttpMethodEnrichment.cs b/Trellis.ServiceLevelIndicators.Asp/src/HttpMethodEnrichment.cs index c0f2a63..c65fd5b 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/HttpMethodEnrichment.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/HttpMethodEnrichment.cs @@ -10,4 +10,4 @@ public ValueTask EnrichAsync(WebEnrichmentContext context, CancellationToken can context.AddAttribute("http.request.method", context.HttpContext.Request.Method); return ValueTask.CompletedTask; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/IServiceLevelIndicatorFeature.cs b/Trellis.ServiceLevelIndicators.Asp/src/IServiceLevelIndicatorFeature.cs index 26221dc..2b58f2b 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/IServiceLevelIndicatorFeature.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/IServiceLevelIndicatorFeature.cs @@ -5,4 +5,4 @@ public interface IServiceLevelIndicatorFeature { MeasuredOperation MeasuredOperation { get; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/MeasureAttribute.cs b/Trellis.ServiceLevelIndicators.Asp/src/MeasureAttribute.cs index 40b6147..3b5d175 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/MeasureAttribute.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/MeasureAttribute.cs @@ -7,4 +7,4 @@ public sealed class MeasureAttribute(string? name = default) : Attribute { public string? Name { get; } = name; -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/MeasureMetadata.cs b/Trellis.ServiceLevelIndicators.Asp/src/MeasureMetadata.cs index 9ab3aa4..fb8f222 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/MeasureMetadata.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/MeasureMetadata.cs @@ -5,4 +5,4 @@ public sealed class MeasureMetadata(string routeValueName, string? attributeName public string RouteValueName { get; } = routeValueName; public string AttributeName { get; } = attributeName ?? routeValueName; -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/README.md b/Trellis.ServiceLevelIndicators.Asp/src/README.md index 3102287..6e1fcdf 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/README.md +++ b/Trellis.ServiceLevelIndicators.Asp/src/README.md @@ -75,9 +75,9 @@ Measured HTTP requests emit the following attributes: | `Operation` | The HTTP method + route template (e.g. `GET /teams/{teamId}`) — see [How `Operation` is resolved](#how-operation-is-resolved) below. | | `CustomerResourceId` | The **target resource** of the operation — see [What `CustomerResourceId` is — and what it is NOT](#what-customerresourceid-is--and-what-it-is-not) below. | | `LocationId` | Where the service is running | -| `activity.status.code` | `Ok` (2xx), `Error` (5xx), or `Unset` (other) | +| `Outcome` | `Success`, `Failure`, `ClientError`, or `Ignored`; 2xx/3xx responses are `Success`, common 4xx caller errors are `ClientError`, 429/5xx and unhandled exceptions are `Failure`, and request-aborted cancellations are `Ignored` | | `http.response.status.code` | The HTTP response status code | -| `http.request.method` | *(Optional)* The HTTP method — enabled via `AddHttpMethod()` | +| `http.request.method` | The HTTP method | ### What `CustomerResourceId` is — and what it is NOT @@ -104,7 +104,7 @@ app.MapGet("/teams/{teamId}", .AddServiceLevelIndicator("GetTeam"); ``` -Or set it imperatively from claims/headers via `Enrich` or `HttpContext.GetMeasuredOperation()` — but the value must still be a stable, low-cardinality resource identifier. +Or set it imperatively from claims/headers via `Enrich` or `HttpContext.GetMeasuredOperation()` — but the value must still be stable and meaningful. ### How `Operation` is resolved @@ -118,13 +118,9 @@ If none of those yield a bounded template (e.g. a synthetic problem-details endp ## Customizations -### Add HTTP method as a dimension +### HTTP method dimension -```csharp -builder.Services.AddServiceLevelIndicator(options => { /* ... */ }) - .AddMvc() - .AddHttpMethod(); -``` +`http.request.method` is emitted by default. `AddHttpMethod()` remains available as a no-op for older setup code. ### Enrich with custom data @@ -188,11 +184,11 @@ if (HttpContext.TryGetMeasuredOperation(out var op)) ## Cardinality Guidance -All three required tags — `Operation`, `LocationId`, and `CustomerResourceId` — must be **low-cardinality and bounded**: +Required tags must be stable and meaningful: - **`Operation`** is bounded for you by the route-template resolver above (one series per HTTP method × route template). Watch your metrics for the `` sentinel — it means an endpoint is missing a route template. - **`LocationId`** is set once per process from configuration — naturally bounded. -- **`CustomerResourceId`** is your responsibility. Use a stable tenant / subscription / resource identifier; do not use per-request GUIDs, user IDs, email addresses, request IDs, or raw user input. +- **`CustomerResourceId`** is your responsibility. Use a stable tenant / subscription / resource identifier; do not use per-request GUIDs, timestamps, request IDs, or raw user input. High-cardinality customer resources are acceptable when they are stable, meaningful, and supported by your metrics backend. The same discipline applies to `[Measure]` parameters and any custom attributes you add via `AddAttribute(...)`. diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorApplicationBuilderExtensions.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorApplicationBuilderExtensions.cs index 008bf41..6ffc8ea 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorApplicationBuilderExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorApplicationBuilderExtensions.cs @@ -18,4 +18,4 @@ public static IApplicationBuilder UseServiceLevelIndicator(this IApplicationBuil return app.UseMiddleware(); } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorAttribute.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorAttribute.cs index 533346c..752d900 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorAttribute.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorAttribute.cs @@ -13,4 +13,4 @@ public ServiceLevelIndicatorAttribute() { } public ServiceLevelIndicatorAttribute(string operation) => Operation = operation; public string? Operation { get; set; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorConvention.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorConvention.cs index 7fe4b5f..f1280dc 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorConvention.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorConvention.cs @@ -36,4 +36,4 @@ public void Apply(ParameterModel parameter) } } } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorFeature.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorFeature.cs index 26da258..461ec55 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorFeature.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorFeature.cs @@ -5,4 +5,4 @@ internal sealed class ServiceLevelIndicatorFeature : IServiceLevelIndicatorFeatu public ServiceLevelIndicatorFeature(MeasuredOperation measureOperation) => MeasuredOperation = measureOperation; public MeasuredOperation MeasuredOperation { get; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorHttpOptions.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorHttpOptions.cs new file mode 100644 index 0000000..fc40992 --- /dev/null +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorHttpOptions.cs @@ -0,0 +1,14 @@ +namespace Trellis.ServiceLevelIndicators; + +using Microsoft.AspNetCore.Http; + +/// +/// ASP.NET Core-specific SLI options. +/// +public sealed class ServiceLevelIndicatorHttpOptions +{ + /// + /// Optional classifier that maps the completed HTTP request to an SLI outcome. + /// + public Func? ClassifyOutcome { get; set; } +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorMiddleware.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorMiddleware.cs index 69aedf4..7d85de1 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorMiddleware.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorMiddleware.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; internal sealed partial class ServiceLevelIndicatorMiddleware { @@ -15,13 +16,20 @@ internal sealed partial class ServiceLevelIndicatorMiddleware private readonly ServiceLevelIndicator _serviceLevelIndicator; private readonly IEnumerable> _enrichments; private readonly ILogger _logger; - - public ServiceLevelIndicatorMiddleware(RequestDelegate next, ServiceLevelIndicator serviceLevelIndicator, IEnumerable> enrichments, ILogger logger) + private readonly ServiceLevelIndicatorHttpOptions _httpOptions; + + public ServiceLevelIndicatorMiddleware( + RequestDelegate next, + ServiceLevelIndicator serviceLevelIndicator, + IEnumerable> enrichments, + ILogger logger, + IOptions httpOptions) { _next = next; _serviceLevelIndicator = serviceLevelIndicator; _enrichments = enrichments; _logger = logger; + _httpOptions = httpOptions.Value; } public async Task InvokeAsync(HttpContext context) @@ -49,8 +57,12 @@ public async Task InvokeAsync(HttpContext context) { unhandledException = ex; - if (!context.Response.HasStarted && context.Response.StatusCode < StatusCodes.Status500InternalServerError) + if (!IsRequestAborted(context, ex) && + !context.Response.HasStarted && + context.Response.StatusCode < StatusCodes.Status500InternalServerError) + { context.Response.StatusCode = StatusCodes.Status500InternalServerError; + } throw; } @@ -59,7 +71,7 @@ public async Task InvokeAsync(HttpContext context) try { var webmeasurementContext = new WebEnrichmentContext(measuredOperation, context); - UpdateOperationWithResponseStatus(context, measuredOperation, unhandledException is not null); + UpdateOperationWithResponseStatus(context, measuredOperation, unhandledException); foreach (var enrichment in _enrichments) { @@ -96,19 +108,40 @@ private static void SetCustomerResourceIdFromAttribute(HttpContext context, Endp measuredOperation.CustomerResourceId = customerResourceId; } - private static void UpdateOperationWithResponseStatus(HttpContext context, MeasuredOperation measuredOperation, bool unhandledException = false) + private void UpdateOperationWithResponseStatus(HttpContext context, MeasuredOperation measuredOperation, Exception? unhandledException) { var statusCode = context.Response.StatusCode; - measuredOperation.AddAttribute("http.response.status.code", statusCode); - var activityCode = unhandledException ? ActivityStatusCode.Error : statusCode switch - { - >= StatusCodes.Status500InternalServerError => ActivityStatusCode.Error, - >= StatusCodes.Status200OK and < StatusCodes.Status300MultipleChoices => ActivityStatusCode.Ok, - _ => ActivityStatusCode.Unset, - }; - measuredOperation.SetActivityStatusCode(activityCode); + measuredOperation.Attributes.Add(new KeyValuePair("http.request.method", context.Request.Method)); + measuredOperation.Attributes.Add(new KeyValuePair("http.response.status.code", statusCode)); + + var outcome = context.RequestAborted.IsCancellationRequested + ? SliOutcome.Ignored + : unhandledException is not null + ? SliOutcome.Failure + : _httpOptions.ClassifyOutcome?.Invoke(context) ?? ClassifyStatusCode(statusCode); + + measuredOperation.SetOutcome(outcome); } + private static SliOutcome ClassifyStatusCode(int statusCode) => statusCode switch + { + >= StatusCodes.Status200OK and < StatusCodes.Status400BadRequest => SliOutcome.Success, + StatusCodes.Status400BadRequest + or StatusCodes.Status401Unauthorized + or StatusCodes.Status403Forbidden + or StatusCodes.Status404NotFound + or StatusCodes.Status409Conflict + or StatusCodes.Status412PreconditionFailed + or StatusCodes.Status422UnprocessableEntity => SliOutcome.ClientError, + StatusCodes.Status429TooManyRequests => SliOutcome.Failure, + >= StatusCodes.Status500InternalServerError => SliOutcome.Failure, + _ => SliOutcome.Ignored, + }; + + private static bool IsRequestAborted(HttpContext context, Exception? exception) => + context.RequestAborted.IsCancellationRequested && + exception is OperationCanceledException; + private bool ShouldEmitMetrics(EndpointMetadataCollection metadata) => _serviceLevelIndicator.ServiceLevelIndicatorOptions.AutomaticallyEmitted || GetSliAttribute(metadata) is not null; @@ -133,10 +166,10 @@ private string GetOperation(HttpContext context, EndpointMetadataCollection meta if (string.IsNullOrEmpty(template)) { LogMissingRouteTemplate(context.GetEndpoint()?.DisplayName ?? "(no endpoint)"); - return context.Request.Method + " "; + return context.Request.Method.ToUpperInvariant() + " "; } - return context.Request.Method + " " + template; + return context.Request.Method.ToUpperInvariant() + " " + template; } private static string? GetCustomerResourceIdAttributes(HttpContext context, EndpointMetadataCollection metadata) @@ -182,4 +215,4 @@ private void AddSliFeatureToHttpContext(HttpContext context, MeasuredOperation m private static void RemoveSliFeatureFromHttpContext(HttpContext context) => context.Features.Set(null); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorServiceCollectionExtensions.cs b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorServiceCollectionExtensions.cs index 239aac2..7b4e7a6 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorServiceCollectionExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/ServiceLevelIndicatorServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ namespace Trellis.ServiceLevelIndicators; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -19,7 +20,15 @@ public static IServiceLevelIndicatorBuilder AddMvc(this IServiceLevelIndicatorBu public static IServiceLevelIndicatorBuilder AddHttpMethod(this IServiceLevelIndicatorBuilder builder) { ArgumentNullException.ThrowIfNull(builder); - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, HttpMethodEnrichment>()); + return builder; + } + + public static IServiceLevelIndicatorBuilder ClassifyHttpOutcome(this IServiceLevelIndicatorBuilder builder, Func classifier) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(classifier); + + builder.Services.Configure(options => options.ClassifyOutcome = classifier); return builder; } diff --git a/Trellis.ServiceLevelIndicators.Asp/src/WebEnrichmentContext.cs b/Trellis.ServiceLevelIndicators.Asp/src/WebEnrichmentContext.cs index 1d53221..9b8ef16 100644 --- a/Trellis.ServiceLevelIndicators.Asp/src/WebEnrichmentContext.cs +++ b/Trellis.ServiceLevelIndicators.Asp/src/WebEnrichmentContext.cs @@ -21,4 +21,4 @@ public WebEnrichmentContext(MeasuredOperation operation, HttpContext httpContext public void AddAttribute(string name, object? value) => _operation.AddAttribute(name, value); public void SetCustomerResourceId(string id) => _operation.CustomerResourceId = id; -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/IServiceCollectionExtensions.cs b/Trellis.ServiceLevelIndicators.Asp/tests/IServiceCollectionExtensions.cs index 2508c09..1e8d8d7 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/IServiceCollectionExtensions.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/IServiceCollectionExtensions.cs @@ -11,4 +11,4 @@ public static IServiceLevelIndicatorBuilder AddTestEnrichment(this IServiceLevel builder.Services.AddSingleton>(new TestEnrichment(key, value)); return builder; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/MultipleCustomerResourceIdController.cs b/Trellis.ServiceLevelIndicators.Asp/tests/MultipleCustomerResourceIdController.cs index 021ef2c..047cee0 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/MultipleCustomerResourceIdController.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/MultipleCustomerResourceIdController.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Asp.Tests; +namespace Trellis.ServiceLevelIndicators.Asp.Tests; using System.Reflection; using Microsoft.AspNetCore.Mvc; diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/ProblemDetailsInteropTests.cs b/Trellis.ServiceLevelIndicators.Asp/tests/ProblemDetailsInteropTests.cs index d3b46a6..0bf5439 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/ProblemDetailsInteropTests.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/ProblemDetailsInteropTests.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Asp.Tests; +namespace Trellis.ServiceLevelIndicators.Asp.Tests; using System.Diagnostics.Metrics; using System.Net; diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs index a1dd845..9111f4e 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Asp.Tests; +namespace Trellis.ServiceLevelIndicators.Asp.Tests; using System; using System.Diagnostics.Metrics; @@ -51,7 +51,7 @@ public async Task SLI_Metrics_is_emitted_for_successful_API_call() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -71,7 +71,7 @@ public async Task SLI_Metrics_is_emitted_for_successful_POST_API_call() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "POST Test"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -91,13 +91,58 @@ public async Task SLI_Metrics_is_emitted_for_failed_API_call() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/bad_request"), - new("activity.status.code", "Unset"), + new("Outcome", "ClientError"), new("http.response.status.code", 400), }; ValidateMetrics(expectedTags); } + [Theory] + [InlineData("test/redirect", HttpStatusCode.Found, "GET Test/redirect", "Success", 302)] + [InlineData("test/unprocessable", HttpStatusCode.UnprocessableEntity, "GET Test/unprocessable", "ClientError", 422)] + [InlineData("test/too_many_requests", (HttpStatusCode)429, "GET Test/too_many_requests", "Failure", 429)] + public async Task SLI_Metrics_classifies_http_status_codes(string route, HttpStatusCode expectedStatus, string operation, string outcome, int statusCode) + { + using var host = await TestHostBuilder.CreateHostWithSli(_meter); + + var response = await host.GetTestClient().GetAsync(route, TestContext.Current.CancellationToken); + response.StatusCode.Should().Be(expectedStatus); + + var expectedTags = new KeyValuePair[] + { + new("CustomerResourceId", "TestCustomerResourceId"), + new("LocationId", "ms-loc://az/public/West US 3"), + new("Operation", operation), + new("Outcome", outcome), + new("http.response.status.code", statusCode), + }; + + ValidateMetrics(expectedTags); + } + + [Fact] + public async Task SLI_Metrics_uses_configured_http_outcome_classifier() + { + using var host = await TestHostBuilder.CreateHostWithSli( + _meter, + context => context.Response.StatusCode == 429 ? SliOutcome.ClientError : SliOutcome.Failure); + + var response = await host.GetTestClient().GetAsync("test/too_many_requests", TestContext.Current.CancellationToken); + response.StatusCode.Should().Be((HttpStatusCode)429); + + var expectedTags = new KeyValuePair[] + { + new("CustomerResourceId", "TestCustomerResourceId"), + new("LocationId", "ms-loc://az/public/West US 3"), + new("Operation", "GET Test/too_many_requests"), + new("Outcome", "ClientError"), + new("http.response.status.code", 429), + }; + + ValidateMetrics(expectedTags); + } + [Fact] public async Task SLI_Metrics_is_emitted_with_enriched_data() { @@ -114,7 +159,7 @@ public async Task SLI_Metrics_is_emitted_with_enriched_data() new("CustomerResourceId", "xavier@somewhere.com"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.request.method", "GET"), new("http.response.status.code", 200), new("foo", "bar"), @@ -138,7 +183,7 @@ public async Task Override_Operation_name() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "TestOperation"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -158,7 +203,7 @@ public async Task Override_CustomerResourceId() new("CustomerResourceId", "myId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/customer_resourceid/{id}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -178,7 +223,7 @@ public async Task CustomAttribute_is_added_to_SLI_dimension() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/custom_attribute/{value}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), new("CustomAttribute", "Mickey"), }; @@ -210,7 +255,7 @@ public async Task When_automatically_emit_SLI_is_Off_X2C_send_SLI_using_attribut new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/send_sli"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -258,7 +303,7 @@ public async Task TryGetMeasuredOperation_will_return_true_if_route_emits_SLI() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/try_get_measured_operation/{value}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), new("CustomAttribute", "Goofy"), }; @@ -281,7 +326,7 @@ public async Task SLI_Measure_is_emitted() new("age", "25"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/name/{first}/{surname}/{age}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -374,7 +419,7 @@ public async Task SLI_Metrics_is_emitted_for_server_error() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/server_error"), - new("activity.status.code", "Error"), + new("Outcome", "Failure"), new("http.response.status.code", 500), }; @@ -396,7 +441,7 @@ await act.Should().ThrowAsync() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET Test/throw"), - new("activity.status.code", "Error"), + new("Outcome", "Failure"), new("http.response.status.code", 500), }; @@ -434,11 +479,25 @@ private void OnMeasurementRecorded(Instrument instrument, long measurement, Read private void ValidateMetrics(KeyValuePair[] expectedTags) { + expectedTags = AddDefaultHttpMethod(expectedTags); + _callbackCalled.Should().BeTrue(); _instrument!.Name.Should().Be("operation.duration"); _instrument.Unit.Should().Be("ms"); _measurement.Should().BeInRange(TestHostBuilder.MillisecondsDelay - 10, TestHostBuilder.MillisecondsDelay + 400); + _actualTags.Should().NotContain(tag => tag.Key == "activity.status.code"); _actualTags.Should().BeEquivalentTo(expectedTags); } -} \ No newline at end of file + private static KeyValuePair[] AddDefaultHttpMethod(KeyValuePair[] expectedTags) + { + if (expectedTags.Any(tag => tag.Key == "http.request.method")) + return expectedTags; + + var operation = expectedTags.First(tag => tag.Key == "Operation").Value?.ToString(); + var method = operation?.StartsWith("POST ", StringComparison.Ordinal) == true ? "POST" : "GET"; + + return [.. expectedTags, new KeyValuePair("http.request.method", method)]; + } + +} diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorMinimalApiTests.cs b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorMinimalApiTests.cs index 8c7aca8..9809e27 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorMinimalApiTests.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorMinimalApiTests.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Asp.Tests; +namespace Trellis.ServiceLevelIndicators.Asp.Tests; using System; using System.Diagnostics.Metrics; @@ -55,7 +55,7 @@ public async Task SLI_Metrics_is_emitted_for_minimal_api_get() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET /hello"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -79,7 +79,7 @@ public async Task SLI_Metrics_is_emitted_with_custom_operation_name() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "CustomOp"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -103,7 +103,7 @@ public async Task SLI_Metrics_is_emitted_with_customer_resource_id_from_route() new("CustomerResourceId", "myResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET /resource/{id}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -128,7 +128,7 @@ public async Task SLI_Metrics_is_emitted_with_measure_attribute() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET /measured/items/{name}"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -191,7 +191,7 @@ public async Task SLI_Metrics_is_automatically_emitted_for_minimal_api_when_auto new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET /auto-sli"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), }; @@ -215,7 +215,7 @@ public async Task SLI_Metrics_is_emitted_with_enrichment_for_minimal_api() new("CustomerResourceId", "TestCustomerResourceId"), new("LocationId", "ms-loc://az/public/West US 3"), new("Operation", "GET /hello"), - new("activity.status.code", "Ok"), + new("Outcome", "Success"), new("http.response.status.code", 200), new("http.request.method", "GET"), }; @@ -336,13 +336,24 @@ private void OnMeasurementRecorded(Instrument instrument, long measurement, Read private void ValidateMetrics(KeyValuePair[] expectedTags) { + expectedTags = AddDefaultHttpMethod(expectedTags); + _callbackCalled.Should().BeTrue(); _instrument!.Name.Should().Be("operation.duration"); _instrument.Unit.Should().Be("ms"); _measurement.Should().BeInRange(MillisecondsDelay - 10, MillisecondsDelay + 400); + _actualTags.Should().NotContain(tag => tag.Key == "activity.status.code"); _actualTags.Should().BeEquivalentTo(expectedTags); } + private static KeyValuePair[] AddDefaultHttpMethod(KeyValuePair[] expectedTags) + { + if (expectedTags.Any(tag => tag.Key == "http.request.method")) + return expectedTags; + + return [.. expectedTags, new KeyValuePair("http.request.method", "GET")]; + } + protected virtual void Dispose(bool disposing) { if (!_disposedValue) diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs b/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs index ea5db0e..d65b7f6 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs @@ -16,6 +16,15 @@ public class TestController : ControllerBase [HttpGet("bad_request")] public IActionResult Bad() => BadRequest("Sad World!"); + [HttpGet("unprocessable")] + public IActionResult Unprocessable() => UnprocessableEntity("Invalid World!"); + + [HttpGet("too_many_requests")] + public IActionResult TooManyRequestsResult() => StatusCode(429, "Busy World!"); + + [HttpGet("redirect")] + public IActionResult RedirectResult() => StatusCode(302); + [HttpGet("server_error")] public IActionResult ServerError() => StatusCode(500, "Server Error!"); @@ -58,4 +67,4 @@ public IActionResult TryGetMeasuredOperation(string value) [HttpGet("name/{first}/{surname}/{age}")] public IActionResult GetCustomerResourceId([Measure] string first, [CustomerResourceId] string surname, [Measure] int age) => Ok(first + " " + surname + " " + age); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/TestEnrichment.cs b/Trellis.ServiceLevelIndicators.Asp/tests/TestEnrichment.cs index 707293e..1a3b9b6 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/TestEnrichment.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/TestEnrichment.cs @@ -13,4 +13,4 @@ public ValueTask EnrichAsync(WebEnrichmentContext context, CancellationToken can context.AddAttribute(_key, _value); return ValueTask.CompletedTask; } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/TestHostBuilder.cs b/Trellis.ServiceLevelIndicators.Asp/tests/TestHostBuilder.cs index fdbf753..22465fb 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/TestHostBuilder.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/TestHostBuilder.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -36,6 +37,32 @@ public static async Task CreateHostWithSli(Meter meter) => .UseEndpoints(endpoints => endpoints.MapControllers()))) .StartAsync(); + public static async Task CreateHostWithSli(Meter meter, Func classifier) => + await new HostBuilder() + .ConfigureWebHost(webBuilder => webBuilder + .UseTestServer() + .ConfigureServices(services => + { + services.AddControllers(); + services.AddServiceLevelIndicator(options => + { + options.Meter = meter; + options.CustomerResourceId = "TestCustomerResourceId"; + options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "West US 3"); + }) + .AddMvc() + .ClassifyHttpOutcome(classifier); + }) + .Configure(app => app.UseRouting() + .UseServiceLevelIndicator() + .Use(async (context, next) => + { + await Task.Delay(MillisecondsDelay); + await next(context); + }) + .UseEndpoints(endpoints => endpoints.MapControllers()))) + .StartAsync(); + public static async Task CreateHostWithSliEnriched(Meter meter) => await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder @@ -110,4 +137,4 @@ public static async Task CreateHostWithSliEnriched(Meter meter) => }) .UseEndpoints(endpoints => endpoints.MapControllers()))) .StartAsync(); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/src/IEnrichment.cs b/Trellis.ServiceLevelIndicators/src/IEnrichment.cs index 1784a78..0ac467a 100644 --- a/Trellis.ServiceLevelIndicators/src/IEnrichment.cs +++ b/Trellis.ServiceLevelIndicators/src/IEnrichment.cs @@ -16,4 +16,4 @@ public interface IEnrichment /// A cancellation token. /// A representing the asynchronous operation. ValueTask EnrichAsync(T context, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/src/IEnrichmentContext.cs b/Trellis.ServiceLevelIndicators/src/IEnrichmentContext.cs index 55cfbad..8fa7993 100644 --- a/Trellis.ServiceLevelIndicators/src/IEnrichmentContext.cs +++ b/Trellis.ServiceLevelIndicators/src/IEnrichmentContext.cs @@ -22,4 +22,4 @@ public interface IEnrichmentContext /// The attribute name. /// The attribute value. void AddAttribute(string name, object? value); -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/src/IServiceLevelIndicatorBuilder.cs b/Trellis.ServiceLevelIndicators/src/IServiceLevelIndicatorBuilder.cs index aacd1b9..6381390 100644 --- a/Trellis.ServiceLevelIndicators/src/IServiceLevelIndicatorBuilder.cs +++ b/Trellis.ServiceLevelIndicators/src/IServiceLevelIndicatorBuilder.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators; +namespace Trellis.ServiceLevelIndicators; using Microsoft.Extensions.DependencyInjection; diff --git a/Trellis.ServiceLevelIndicators/src/MeasuredOperation.cs b/Trellis.ServiceLevelIndicators/src/MeasuredOperation.cs index b701b5c..1a92e04 100644 --- a/Trellis.ServiceLevelIndicators/src/MeasuredOperation.cs +++ b/Trellis.ServiceLevelIndicators/src/MeasuredOperation.cs @@ -14,7 +14,8 @@ public class MeasuredOperation : IDisposable private readonly ServiceLevelIndicator _serviceLevelIndicator; private readonly Stopwatch _stopWatch; private readonly HashSet _attributeNames; - private ActivityStatusCode _activityStatusCode = ActivityStatusCode.Unset; + private SliOutcome _outcome = SliOutcome.Ignored; + private bool _outcomeExplicitlySet; private readonly object _disposeLock = new(); public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string operation, params KeyValuePair[] attributes) : @@ -23,6 +24,8 @@ public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string ope public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string operation, string customerResourceId, params KeyValuePair[] attributes) { + ArgumentException.ThrowIfNullOrWhiteSpace(operation); + _serviceLevelIndicator = serviceLevelIndicator; Operation = operation; CustomerResourceId = customerResourceId; @@ -30,7 +33,7 @@ public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string ope _attributeNames = new HashSet(attributes.Length, StringComparer.Ordinal); for (var i = 0; i < attributes.Length; i++) { - _serviceLevelIndicator.ValidateAttributeName(attributes[i].Key); + ServiceLevelIndicator.ValidateAttributeName(attributes[i].Key); ValidateUniqueAttributeName(attributes[i].Key); Attributes.Add(attributes[i]); } @@ -54,10 +57,35 @@ public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string ope public List> Attributes { get; } /// - /// Sets the recorded with the measurement. + /// Sets the outcome recorded with the measurement. + /// + /// The SLI outcome. + public void SetOutcome(SliOutcome outcome) + { + _outcome = outcome; + _outcomeExplicitlySet = true; + } + + internal void SetInferredOutcome(SliOutcome outcome) + { + if (!_outcomeExplicitlySet) + _outcome = outcome; + } + + internal void ForceOutcome(SliOutcome outcome) => _outcome = outcome; + + /// + /// Sets the outcome based on an . /// /// The activity status code. - public void SetActivityStatusCode(ActivityStatusCode activityStatusCode) => _activityStatusCode = activityStatusCode; + [Obsolete("Use SetOutcome(SliOutcome) instead.")] + public void SetActivityStatusCode(ActivityStatusCode activityStatusCode) => + SetOutcome(activityStatusCode switch + { + ActivityStatusCode.Ok => SliOutcome.Success, + ActivityStatusCode.Error => SliOutcome.Failure, + _ => SliOutcome.Ignored + }); /// /// Adds a custom attribute to the measurement. @@ -66,7 +94,7 @@ public MeasuredOperation(ServiceLevelIndicator serviceLevelIndicator, string ope /// The attribute value. public void AddAttribute(string attribute, object? value) { - _serviceLevelIndicator.ValidateAttributeName(attribute); + ServiceLevelIndicator.ValidateAttributeName(attribute); ValidateUniqueAttributeName(attribute); Attributes.Add(new KeyValuePair(attribute, value)); } @@ -91,8 +119,7 @@ protected virtual void Dispose(bool disposing) { _stopWatch.Stop(); var elapsedTime = _stopWatch.ElapsedMilliseconds; - Attributes.Add(new KeyValuePair(_serviceLevelIndicator.ServiceLevelIndicatorOptions.ActivityStatusCodeAttributeName, _activityStatusCode.ToString())); - _serviceLevelIndicator.RecordMeasurement(Operation, CustomerResourceId, elapsedTime, Attributes.ToArray()); + _serviceLevelIndicator.RecordMeasurement(Operation, CustomerResourceId, elapsedTime, _outcome, Attributes.ToArray()); } _disposed = true; diff --git a/Trellis.ServiceLevelIndicators/src/README.md b/Trellis.ServiceLevelIndicators/src/README.md index 89196b0..e21c8cc 100644 --- a/Trellis.ServiceLevelIndicators/src/README.md +++ b/Trellis.ServiceLevelIndicators/src/README.md @@ -70,7 +70,7 @@ void DoWork(ServiceLevelIndicator sli) { using var op = sli.StartMeasuring("ProcessOrder"); // Do work... - op.SetActivityStatusCode(ActivityStatusCode.Ok); + op.SetOutcome(SliOutcome.Success); } ``` @@ -81,7 +81,7 @@ var attribute = new KeyValuePair("Event", "OrderCreated"); using var op = sli.StartMeasuring("ProcessOrder", attribute); ``` -Custom attribute names must be unique and must not reuse SLI-reserved tags such as `CustomerResourceId`, `LocationId`, `Operation`, or `activity.status.code`. +Custom attribute names must be unique and must not reuse SLI-reserved tags such as `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, `activity.status.code`, `http.request.method`, or `http.response.status.code`. ## Emitted Metrics @@ -94,13 +94,13 @@ By default, a meter named `Trellis.SLI` emits the `operation.duration` histogram | `Operation` | The name of the measured operation | | `CustomerResourceId` | A **stable** identifier for the entity the operation is acting on (tenant, subscription, account, work item, etc.). NOT the caller, NOT a per-request ID, NOT a user/email. | | `LocationId` | Where the service is running (e.g. `ms-loc://az/public/westus3`) | -| `activity.status.code` | `Ok`, `Error`, or `Unset` based on the operation outcome | +| `Outcome` | `Success`, `Failure`, `ClientError`, or `Ignored` | -Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call; they do not add `activity.status.code`. +Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, and any custom attributes supplied to the call. Without an explicit outcome, manual/background measurements default to `Ignored`. ## Cardinality Guidance -All three required tags — `Operation`, `LocationId`, and `CustomerResourceId` — must be **low-cardinality and bounded**. Good values: tenant, subscription, environment, region, product tier, work-item type. Bad values: per-request GUIDs, user IDs / emails, timestamps, free-form user input. The same rule applies to any custom attributes you add via `MeasuredOperation.AddAttribute(...)`. +Required tags must be stable and meaningful. Good values: tenant, subscription, environment, region, product tier, work-item type. Bad values: per-request GUIDs, timestamps, free-form user input, or raw emails when a stable object ID is available. The same rule applies to any custom attributes you add via `MeasuredOperation.AddAttribute(...)`. ## Disposal @@ -111,7 +111,7 @@ All three required tags — `Operation`, `LocationId`, and `CustomerResourceId` | Type / Method | Description | |---------------|-------------| | `ServiceLevelIndicator.StartMeasuring(operation, attributes)` | Start a measured operation scope | -| `MeasuredOperation.SetActivityStatusCode(code)` | Set the outcome status | +| `MeasuredOperation.SetOutcome(outcome)` | Set the SLI outcome | | `MeasuredOperation.AddAttribute(name, value)` | Add a custom metric attribute | | `MeasuredOperation.CustomerResourceId` | Get/set the customer resource ID | | `ServiceLevelIndicator.CreateLocationId(cloud, region?, zone?)` | Helper to build a location ID string | diff --git a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicator.cs b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicator.cs index dc6e09a..6710339 100644 --- a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicator.cs +++ b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicator.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using System.Reflection; +using System.Threading.Tasks; using Microsoft.Extensions.Options; /// @@ -23,12 +24,18 @@ public sealed class ServiceLevelIndicator : IDisposable /// public const string DefaultMeterName = "Trellis.SLI"; + /// + /// Default value used when no customer resource identifier is available. + /// + public const string UnknownCustomerResourceId = "Unknown"; + /// /// Gets the options used to configure this instance. /// public ServiceLevelIndicatorOptions ServiceLevelIndicatorOptions { get; } private readonly Histogram _responseLatencyHistogram; + private readonly Counter _unknownCustomerResourceIdCounter; private readonly Meter _meter; private readonly bool _ownsMeter; private bool _disposed; @@ -39,7 +46,6 @@ public ServiceLevelIndicator(IOptions options) ArgumentException.ThrowIfNullOrWhiteSpace(ServiceLevelIndicatorOptions.LocationId, nameof(ServiceLevelIndicatorOptions.LocationId)); ArgumentException.ThrowIfNullOrWhiteSpace(ServiceLevelIndicatorOptions.DurationInstrumentName, nameof(ServiceLevelIndicatorOptions.DurationInstrumentName)); - ValidateActivityStatusCodeAttributeName(); if (ServiceLevelIndicatorOptions.Meter == null) { @@ -56,6 +62,7 @@ public ServiceLevelIndicator(IOptions options) } _responseLatencyHistogram = _meter.CreateHistogram(ServiceLevelIndicatorOptions.DurationInstrumentName, "ms", "Duration of the operation."); + _unknownCustomerResourceIdCounter = _meter.CreateCounter("sli.diagnostics.unknown_customer_resource_id", description: "Count of SLI measurements emitted with an unknown customer resource identifier."); } /// @@ -79,6 +86,16 @@ public void Dispose() public void Record(string operation, long elapsedTime, params KeyValuePair[] attributes) => Record(operation, ServiceLevelIndicatorOptions.CustomerResourceId, elapsedTime, attributes); + /// + /// Records an operation measurement using the default . + /// + /// The operation name. + /// Elapsed time in milliseconds. + /// The SLI outcome. + /// Additional measurement attributes. + public void Record(string operation, long elapsedTime, SliOutcome outcome, params KeyValuePair[] attributes) => + Record(operation, ServiceLevelIndicatorOptions.CustomerResourceId, elapsedTime, outcome, attributes); + /// /// Records an operation measurement with an explicit customer resource identifier. /// @@ -87,21 +104,38 @@ public void Record(string operation, long elapsedTime, params KeyValuePairElapsed time in milliseconds. /// Additional measurement attributes. public void Record(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes) + => Record(operation, customerResourceId, elapsedTime, SliOutcome.Ignored, attributes); + + /// + /// Records an operation measurement with an explicit customer resource identifier. + /// + /// The operation name. + /// The customer resource identifier. + /// Elapsed time in milliseconds. + /// The SLI outcome. + /// Additional measurement attributes. + public void Record(string operation, string customerResourceId, long elapsedTime, SliOutcome outcome, params KeyValuePair[] attributes) { ValidateAttributes(attributes); ValidateDuplicateArgumentAttributeNames(attributes); - RecordMeasurement(operation, customerResourceId, elapsedTime, attributes); + RecordMeasurement(operation, customerResourceId, elapsedTime, outcome, attributes); } - internal void RecordMeasurement(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes) + internal void RecordMeasurement(string operation, string customerResourceId, long elapsedTime, SliOutcome outcome, params KeyValuePair[] attributes) { + ArgumentException.ThrowIfNullOrWhiteSpace(operation, nameof(operation)); ValidateRecordAttributeNames(attributes); + customerResourceId = NormalizeCustomerResourceId(customerResourceId); + RecordUnknownCustomerResourceId(operation, customerResourceId); + Activity.Current?.SetStatus(GetActivityStatusCode(outcome)); + var tagList = new TagList { { "CustomerResourceId", customerResourceId }, { "LocationId", ServiceLevelIndicatorOptions.LocationId }, - { "Operation", operation } + { "Operation", operation }, + { "Outcome", ToWireValue(outcome) } }; for (var i = 0; i < attributes.Length; i++) @@ -118,33 +152,127 @@ internal void RecordMeasurement(string operation, string customerResourceId, lon /// A that records the metric on disposal. public MeasuredOperation StartMeasuring(string operation, params KeyValuePair[] attributes) => new(this, operation, attributes); - internal void ValidateAttributeName(string attribute) + /// + /// Measures a synchronous operation and infers the SLI outcome from completion or exception. + /// + public void Measure(string operation, Action action, params KeyValuePair[] attributes) { - ArgumentException.ThrowIfNullOrWhiteSpace(attribute, nameof(attribute)); + ArgumentNullException.ThrowIfNull(action); - if (attribute is "CustomerResourceId" or "LocationId" or "Operation" || - attribute == ServiceLevelIndicatorOptions.ActivityStatusCodeAttributeName) + using var measuredOperation = StartMeasuring(operation, attributes); + try { - throw new ArgumentException( - $"'{attribute}' is a reserved Service Level Indicator attribute name and cannot be used as a custom metric attribute.", - nameof(attribute)); + action(); + measuredOperation.SetInferredOutcome(SliOutcome.Success); + } + catch (OperationCanceledException) + { + measuredOperation.ForceOutcome(SliOutcome.Ignored); + throw; + } + catch + { + measuredOperation.ForceOutcome(SliOutcome.Failure); + throw; } } - private void ValidateActivityStatusCodeAttributeName() + /// + /// Measures a synchronous operation and returns its result. + /// + public T Measure(string operation, Func action, params KeyValuePair[] attributes) { - var attribute = ServiceLevelIndicatorOptions.ActivityStatusCodeAttributeName; - ArgumentException.ThrowIfNullOrWhiteSpace(attribute, nameof(ServiceLevelIndicatorOptions.ActivityStatusCodeAttributeName)); + ArgumentNullException.ThrowIfNull(action); - if (attribute is "CustomerResourceId" or "LocationId" or "Operation") + using var measuredOperation = StartMeasuring(operation, attributes); + try + { + var result = action(); + measuredOperation.SetInferredOutcome(SliOutcome.Success); + return result; + } + catch (OperationCanceledException) + { + measuredOperation.ForceOutcome(SliOutcome.Ignored); + throw; + } + catch + { + measuredOperation.ForceOutcome(SliOutcome.Failure); + throw; + } + } + + /// + /// Measures an asynchronous operation and infers the SLI outcome from completion or exception. + /// + public async Task MeasureAsync(string operation, Func action, params KeyValuePair[] attributes) + { + ArgumentNullException.ThrowIfNull(action); + + using var measuredOperation = StartMeasuring(operation, attributes); + try + { + await action().ConfigureAwait(false); + measuredOperation.SetInferredOutcome(SliOutcome.Success); + } + catch (OperationCanceledException) + { + measuredOperation.ForceOutcome(SliOutcome.Ignored); + throw; + } + catch + { + measuredOperation.ForceOutcome(SliOutcome.Failure); + throw; + } + } + + /// + /// Measures an asynchronous operation and returns its result. + /// + public async Task MeasureAsync(string operation, Func> action, params KeyValuePair[] attributes) + { + ArgumentNullException.ThrowIfNull(action); + + using var measuredOperation = StartMeasuring(operation, attributes); + try + { + var result = await action().ConfigureAwait(false); + measuredOperation.SetInferredOutcome(SliOutcome.Success); + return result; + } + catch (OperationCanceledException) + { + measuredOperation.ForceOutcome(SliOutcome.Ignored); + throw; + } + catch + { + measuredOperation.ForceOutcome(SliOutcome.Failure); + throw; + } + } + + internal static void ValidateAttributeName(string attribute) + { + ArgumentException.ThrowIfNullOrWhiteSpace(attribute, nameof(attribute)); + + if (attribute is "CustomerResourceId" + or "LocationId" + or "Operation" + or "Outcome" + or "activity.status.code" + or "http.request.method" + or "http.response.status.code") { throw new ArgumentException( - $"'{attribute}' is a reserved Service Level Indicator attribute name and cannot be used as the activity status code attribute name.", - nameof(ServiceLevelIndicatorOptions.ActivityStatusCodeAttributeName)); + $"'{attribute}' is a reserved Service Level Indicator attribute name and cannot be used as a custom metric attribute.", + nameof(attribute)); } } - private void ValidateAttributes(ReadOnlySpan> attributes) + private static void ValidateAttributes(ReadOnlySpan> attributes) { for (var i = 0; i < attributes.Length; i++) ValidateAttributeName(attributes[i].Key); @@ -184,7 +312,8 @@ private static void ValidateRecordAttributeNames(ReadOnlySpan !string.IsNullOrEmpty(s))); return id; } + + internal static string ToWireValue(SliOutcome outcome) => outcome switch + { + SliOutcome.Success => "Success", + SliOutcome.Failure => "Failure", + SliOutcome.ClientError => "ClientError", + SliOutcome.Ignored => "Ignored", + _ => throw new ArgumentOutOfRangeException(nameof(outcome), outcome, "Unknown SLI outcome.") + }; + + private static ActivityStatusCode GetActivityStatusCode(SliOutcome outcome) => outcome switch + { + SliOutcome.Success => ActivityStatusCode.Ok, + SliOutcome.Failure => ActivityStatusCode.Error, + SliOutcome.ClientError or SliOutcome.Ignored => ActivityStatusCode.Unset, + _ => ActivityStatusCode.Unset + }; + + private static string NormalizeCustomerResourceId(string customerResourceId) => + string.IsNullOrWhiteSpace(customerResourceId) ? UnknownCustomerResourceId : customerResourceId; + + private void RecordUnknownCustomerResourceId(string operation, string customerResourceId) + { + if (!string.Equals(customerResourceId, UnknownCustomerResourceId, StringComparison.Ordinal)) + return; + + _unknownCustomerResourceIdCounter.Add( + 1, + new KeyValuePair("Operation", operation), + new KeyValuePair("LocationId", ServiceLevelIndicatorOptions.LocationId)); + } } diff --git a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorMeterProviderBuilderExtensions.cs b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorMeterProviderBuilderExtensions.cs index 150063f..423bc2c 100644 --- a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorMeterProviderBuilderExtensions.cs +++ b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorMeterProviderBuilderExtensions.cs @@ -42,4 +42,4 @@ public static MeterProviderBuilder AddServiceLevelIndicatorInstrumentation(this ArgumentNullException.ThrowIfNull(meter); return builder.AddServiceLevelIndicatorInstrumentation(meter.Name); } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs index a0166d6..907973b 100644 --- a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs +++ b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs @@ -4,7 +4,7 @@ /// /// Options for configuring the Service Level Indicator. -/// DefaultCustomerResourceId & LocationId are mandatory properties. +/// CustomerResourceId & LocationId are mandatory properties. /// public class ServiceLevelIndicatorOptions { @@ -18,7 +18,7 @@ public class ServiceLevelIndicatorOptions /// CustomerResourceId is the unique identifier for the customer like subscriptionId, tenantId, etc. /// CustomerResourceId can be set for the entire service here or in each API method. /// - public string CustomerResourceId { get; set; } = "Unset"; + public string CustomerResourceId { get; set; } = ServiceLevelIndicator.UnknownCustomerResourceId; /// /// Location where the service is running. @@ -31,9 +31,9 @@ public class ServiceLevelIndicatorOptions public string DurationInstrumentName { get; set; } = "operation.duration"; /// - /// Activity Status Code attribute name. - /// [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode?view=net-7.0) + /// Obsolete. Activity status is no longer emitted as a metric dimension. /// + [Obsolete("Activity status is no longer emitted as a metric dimension. Use SliOutcome instead.")] public string ActivityStatusCodeAttributeName { get; set; } = "activity.status.code"; /// @@ -41,4 +41,4 @@ public class ServiceLevelIndicatorOptions /// If false, use the ServiceLevelIndicator Attribute to emit. /// public bool AutomaticallyEmitted { get; set; } = true; -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorServiceCollectionExtensions.cs b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorServiceCollectionExtensions.cs index c5e3876..5ecaad8 100644 --- a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorServiceCollectionExtensions.cs +++ b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators; +namespace Trellis.ServiceLevelIndicators; using Microsoft.Extensions.DependencyInjection; diff --git a/Trellis.ServiceLevelIndicators/src/SliOutcome.cs b/Trellis.ServiceLevelIndicators/src/SliOutcome.cs new file mode 100644 index 0000000..9666053 --- /dev/null +++ b/Trellis.ServiceLevelIndicators/src/SliOutcome.cs @@ -0,0 +1,27 @@ +namespace Trellis.ServiceLevelIndicators; + +/// +/// SLI outcome emitted with each operation measurement. +/// +public enum SliOutcome +{ + /// + /// The operation completed successfully and counts toward the success numerator and denominator. + /// + Success, + + /// + /// The operation failed and counts toward the success-rate denominator. + /// + Failure, + + /// + /// The operation failed because of caller input or another expected client condition. + /// + ClientError, + + /// + /// The operation should be excluded from the default success-rate denominator. + /// + Ignored +} diff --git a/Trellis.ServiceLevelIndicators/tests/CustomerResourceIdTests.cs b/Trellis.ServiceLevelIndicators/tests/CustomerResourceIdTests.cs index 6c28ada..b0fb72f 100644 --- a/Trellis.ServiceLevelIndicators/tests/CustomerResourceIdTests.cs +++ b/Trellis.ServiceLevelIndicators/tests/CustomerResourceIdTests.cs @@ -31,4 +31,4 @@ public void Cannot_create_CustomerResourceId_with_default_GUID() action.Should().Throw() .WithMessage("Value cannot be null. (Parameter 'serviceId')"); } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/tests/LocationIdTests.cs b/Trellis.ServiceLevelIndicators/tests/LocationIdTests.cs index 4cde305..039226d 100644 --- a/Trellis.ServiceLevelIndicators/tests/LocationIdTests.cs +++ b/Trellis.ServiceLevelIndicators/tests/LocationIdTests.cs @@ -37,4 +37,4 @@ public void Will_create_LocationId_with_cloud_region_zone() // Assert actual.Should().Be("ms-loc://az/Public/eastus2/1"); } -} \ No newline at end of file +} diff --git a/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorMeterProviderBuilderExtensionsTests.cs b/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorMeterProviderBuilderExtensionsTests.cs index 5ad37de..9d895fc 100644 --- a/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorMeterProviderBuilderExtensionsTests.cs +++ b/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorMeterProviderBuilderExtensionsTests.cs @@ -1,4 +1,4 @@ -namespace Trellis.ServiceLevelIndicators.Tests; +namespace Trellis.ServiceLevelIndicators.Tests; using System; using System.Diagnostics.Metrics; diff --git a/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorTests.cs b/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorTests.cs index 295ae4c..1e7e423 100644 --- a/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorTests.cs +++ b/Trellis.ServiceLevelIndicators/tests/ServiceLevelIndicatorTests.cs @@ -71,6 +71,7 @@ public void Record() new("CustomerResourceId", customerResourceId), new("LocationId", locationId), new("Operation", operation), + new("Outcome", "Ignored"), new("Attribute1", "Value1"), new("Attribute2", "Value2") ]; @@ -103,7 +104,7 @@ public async Task Will_measure_code_block() new("CustomerResourceId", customerResourceId), new("LocationId", locationId), new("Operation", "SleepWorker"), - new("activity.status.code", nameof(System.Diagnostics.ActivityStatusCode.Ok)), + new("Outcome", "Success"), ]; ValidateMetrics(sleepTime, approx: 100); @@ -112,10 +113,96 @@ async Task MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) { using var measuredOperation = serviceLevelIndicator.StartMeasuring("SleepWorker"); await Task.Delay(sleepTime); - measuredOperation.SetActivityStatusCode(System.Diagnostics.ActivityStatusCode.Ok); + measuredOperation.SetOutcome(SliOutcome.Success); } } + [Fact] + public void Measure_sets_success_outcome_when_action_completes() + { + // Arrange + var serviceLevelIndicator = CreateServiceLevelIndicator(); + + // Act + serviceLevelIndicator.Measure("MeasuredAction", () => { }); + + // Assert + _expectedTags = + [ + new("CustomerResourceId", "TestResourceId"), + new("LocationId", "TestLocationId"), + new("Operation", "MeasuredAction"), + new("Outcome", "Success"), + ]; + + ValidateMetrics(0, approx: 100); + } + + [Fact] + public void Measure_sets_failure_outcome_and_rethrows_when_action_throws() + { + // Arrange + var serviceLevelIndicator = CreateServiceLevelIndicator(); + + // Act + Action act = () => serviceLevelIndicator.Measure("MeasuredAction", () => throw new InvalidOperationException("Boom")); + + // Assert + act.Should().Throw().WithMessage("Boom"); + _expectedTags = + [ + new("CustomerResourceId", "TestResourceId"), + new("LocationId", "TestLocationId"), + new("Operation", "MeasuredAction"), + new("Outcome", "Failure"), + ]; + + ValidateMetrics(0, approx: 100); + } + + [Fact] + public void Measure_sets_ignored_outcome_and_rethrows_for_cancellation() + { + // Arrange + var serviceLevelIndicator = CreateServiceLevelIndicator(); + + // Act + Action act = () => serviceLevelIndicator.Measure("MeasuredAction", () => throw new OperationCanceledException()); + + // Assert + act.Should().Throw(); + _expectedTags = + [ + new("CustomerResourceId", "TestResourceId"), + new("LocationId", "TestLocationId"), + new("Operation", "MeasuredAction"), + new("Outcome", "Ignored"), + ]; + + ValidateMetrics(0, approx: 100); + } + + [Fact] + public async Task MeasureAsync_sets_success_outcome_when_action_completes() + { + // Arrange + var serviceLevelIndicator = CreateServiceLevelIndicator(); + + // Act + await serviceLevelIndicator.MeasureAsync("MeasuredAsyncAction", () => Task.CompletedTask); + + // Assert + _expectedTags = + [ + new("CustomerResourceId", "TestResourceId"), + new("LocationId", "TestLocationId"), + new("Operation", "MeasuredAsyncAction"), + new("Outcome", "Success"), + ]; + + ValidateMetrics(0, approx: 100); + } + [Fact] public void Uses_default_meter_when_none_provided() { @@ -160,12 +247,105 @@ public void Record_with_no_attributes() [ new("CustomerResourceId", customerResourceId), new("LocationId", locationId), - new("Operation", operation) + new("Operation", operation), + new("Outcome", "Ignored") ]; ValidateMetrics(elapsedTime); } + [Theory] + [InlineData(SliOutcome.Success, "Success")] + [InlineData(SliOutcome.Failure, "Failure")] + [InlineData(SliOutcome.ClientError, "ClientError")] + [InlineData(SliOutcome.Ignored, "Ignored")] + public void Record_emits_explicit_outcome_wire_value(SliOutcome outcome, string wireValue) + { + // Arrange + var serviceLevelIndicator = CreateServiceLevelIndicator(); + + // Act + serviceLevelIndicator.Record("TestOperation", 25, outcome); + + // Assert + _expectedTags = + [ + new("CustomerResourceId", "TestResourceId"), + new("LocationId", "TestLocationId"), + new("Operation", "TestOperation"), + new("Outcome", wireValue) + ]; + + ValidateMetrics(25); + } + + [Fact] + public void Record_uses_unknown_customer_resource_id_when_default_is_not_configured() + { + // Arrange + var serviceLevelIndicator = new ServiceLevelIndicator(Options.Create(new ServiceLevelIndicatorOptions + { + LocationId = "TestLocationId", + Meter = _meter + })); + + // Act + serviceLevelIndicator.Record("TestOperation", elapsedTime: 10); + + // Assert + _expectedTags = + [ + new("CustomerResourceId", "Unknown"), + new("LocationId", "TestLocationId"), + new("Operation", "TestOperation"), + new("Outcome", "Ignored") + ]; + + ValidateMetrics(10); + } + + [Fact] + public void Unknown_customer_resource_id_emits_diagnostic_counter_on_configured_meter() + { + // Arrange + var serviceLevelIndicator = new ServiceLevelIndicator(Options.Create(new ServiceLevelIndicatorOptions + { + LocationId = "TestLocationId", + Meter = _meter + })); + + Instrument? counterInstrument = null; + long counterMeasurement = 0; + KeyValuePair[] counterTags = []; + + _meterListener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { + if (instrument.Name == "sli.diagnostics.unknown_customer_resource_id") + { + counterInstrument = instrument; + counterMeasurement = measurement; + counterTags = tags.ToArray(); + } + else + { + OnMeasurementRecorded(instrument, measurement, tags, state); + } + }); + + // Act + serviceLevelIndicator.Record("TestOperation", elapsedTime: 10); + + // Assert + counterInstrument.Should().NotBeNull(); + counterInstrument!.Meter.Should().BeSameAs(_meter); + counterMeasurement.Should().Be(1); + counterTags.Should().BeEquivalentTo( + [ + new KeyValuePair("Operation", "TestOperation"), + new KeyValuePair("LocationId", "TestLocationId") + ]); + } + [Fact] public void Record_with_customerResourceId_override() { @@ -193,7 +373,8 @@ public void Record_with_customerResourceId_override() [ new("CustomerResourceId", overrideCustomerResourceId), new("LocationId", locationId), - new("Operation", operation) + new("Operation", operation), + new("Outcome", "Ignored") ]; ValidateMetrics(elapsedTime); @@ -224,7 +405,7 @@ public async Task MeasuredOperation_double_dispose_does_not_record_twice() // Act var measuredOperation = serviceLevelIndicator.StartMeasuring("DoubleDispose"); await Task.Delay(50, TestContext.Current.CancellationToken); - measuredOperation.SetActivityStatusCode(System.Diagnostics.ActivityStatusCode.Ok); + measuredOperation.SetOutcome(SliOutcome.Success); measuredOperation.Dispose(); measuredOperation.Dispose(); // Second dispose should be a no-op @@ -260,7 +441,8 @@ public void Customize_instrument_name() [ new("CustomerResourceId", customerResourceId), new("LocationId", locationId), - new("Operation", operation) + new("Operation", operation), + new("Outcome", "Ignored") ]; ValidateMetrics(elapsedTime, InstrumentName); @@ -380,7 +562,10 @@ public void AddServiceLevelIndicator_registers_core_services_without_asp_package [InlineData("CustomerResourceId")] [InlineData("LocationId")] [InlineData("Operation")] + [InlineData("Outcome")] [InlineData("activity.status.code")] + [InlineData("http.request.method")] + [InlineData("http.response.status.code")] public void Record_rejects_reserved_custom_attribute_names(string reservedName) { // Arrange @@ -419,7 +604,10 @@ public void Record_rejects_duplicate_custom_attribute_names_as_argument_error() [InlineData("CustomerResourceId")] [InlineData("LocationId")] [InlineData("Operation")] + [InlineData("Outcome")] [InlineData("activity.status.code")] + [InlineData("http.request.method")] + [InlineData("http.response.status.code")] public void StartMeasuring_rejects_reserved_initial_attribute_names(string reservedName) { // Arrange @@ -439,7 +627,10 @@ public void StartMeasuring_rejects_reserved_initial_attribute_names(string reser [InlineData("CustomerResourceId")] [InlineData("LocationId")] [InlineData("Operation")] + [InlineData("Outcome")] [InlineData("activity.status.code")] + [InlineData("http.request.method")] + [InlineData("http.response.status.code")] public void MeasuredOperation_AddAttribute_rejects_reserved_attribute_names(string reservedName) { // Arrange @@ -503,26 +694,17 @@ public void MeasuredOperation_AddAttribute_rejects_duplicate_attribute_names() } [Fact] - public void Constructor_rejects_activity_status_attribute_name_that_collides_with_core_tags() + public void StartMeasuring_rejects_blank_operation() { // Arrange - var options = new ServiceLevelIndicatorOptions - { - CustomerResourceId = "TestResourceId", - LocationId = "TestLocationId", - Meter = _meter, - ActivityStatusCodeAttributeName = "Operation" - }; + var serviceLevelIndicator = CreateServiceLevelIndicator(); // Act - Action act = () => - { - using var serviceLevelIndicator = new ServiceLevelIndicator(Options.Create(options)); - }; + Action act = () => serviceLevelIndicator.StartMeasuring(" "); // Assert act.Should().Throw() - .WithMessage("*reserved Service Level Indicator attribute name*"); + .Where(ex => ex.ParamName == "operation"); } [Fact] diff --git a/docs/api_reference/trellis-api-sli-asp.md b/docs/api_reference/trellis-api-sli-asp.md index 5cddd7b..cc00a84 100644 --- a/docs/api_reference/trellis-api-sli-asp.md +++ b/docs/api_reference/trellis-api-sli-asp.md @@ -12,14 +12,14 @@ See also: [`trellis-api-sli.md`](trellis-api-sli.md). ## Auto-emitted dimensions (in addition to the core ones) -When the middleware is registered, every measured request emits these tags on top of the core `Operation` / `CustomerResourceId` / `LocationId` / `activity.status.code`: +When the middleware is registered, every measured request emits these tags on top of the core `CustomerResourceId` / `LocationId` / `Operation` / `Outcome` dimensions: | Tag | Source | |---|---| | `Operation` | HTTP method plus route template (e.g. `GET WeatherForecast` or `GET /teams/{teamId}`) — derived from `ControllerActionDescriptor.AttributeRouteInfo.Template` for MVC, or from the route pattern for Minimal APIs. Overridable via `[ServiceLevelIndicator(operation)]` or `AddServiceLevelIndicator("operation")`. | -| `activity.status.code` | `Ok` for HTTP 2xx, `Error` for HTTP 5xx (or unhandled exception), `Unset` otherwise. | +| `Outcome` | `Success` for 2xx/3xx, `ClientError` for common caller errors (400/401/403/404/409/412/422), `Failure` for 429/5xx and unhandled exceptions, and `Ignored` for request-aborted cancellations. | | `http.response.status.code` | `HttpContext.Response.StatusCode`. | -| `http.request.method` | Added when `AddHttpMethod()` is called on the SLI builder. | +| `http.request.method` | `HttpContext.Request.Method`; emitted by default. | --- @@ -74,7 +74,8 @@ Extensions on `IServiceLevelIndicatorBuilder` for opting into MVC support and en | Signature | Returns | Description | |---|---|---| | `public static IServiceLevelIndicatorBuilder AddMvc(this IServiceLevelIndicatorBuilder builder)` | `IServiceLevelIndicatorBuilder` | Registers the MVC convention so that `[CustomerResourceId]` and `[Measure]` parameter attributes contribute endpoint metadata. **Required** for any MVC controller that uses these attributes. | -| `public static IServiceLevelIndicatorBuilder AddHttpMethod(this IServiceLevelIndicatorBuilder builder)` | `IServiceLevelIndicatorBuilder` | Adds the built-in enrichment that emits `http.request.method`. | +| `public static IServiceLevelIndicatorBuilder AddHttpMethod(this IServiceLevelIndicatorBuilder builder)` | `IServiceLevelIndicatorBuilder` | No-op compatibility method; `http.request.method` is emitted by default. | +| `public static IServiceLevelIndicatorBuilder ClassifyHttpOutcome(this IServiceLevelIndicatorBuilder builder, Func classifier)` | `IServiceLevelIndicatorBuilder` | Configures a global HTTP outcome classifier. The returned `SliOutcome` overrides the default status-code mapping for completed requests. | | `public static IServiceLevelIndicatorBuilder Enrich(this IServiceLevelIndicatorBuilder builder, Action action)` | `IServiceLevelIndicatorBuilder` | Registers a synchronous enrichment delegate. | | `public static IServiceLevelIndicatorBuilder EnrichAsync(this IServiceLevelIndicatorBuilder builder, Func func)` | `IServiceLevelIndicatorBuilder` | Registers an asynchronous enrichment delegate. | @@ -280,7 +281,7 @@ The `ServiceLevelIndicatorMiddleware` (registered by `UseServiceLevelIndicator() 4. Starts a `MeasuredOperation` and attaches an `IServiceLevelIndicatorFeature` to `HttpContext.Features`. 5. Optionally overrides the customer id from a `CustomerResourceIdMetadata`-tagged route value. 6. Invokes the next middleware. On unhandled exceptions, sets status to 500 (when not started) and rethrows. -7. In `finally`, sets `activity.status.code` from the response status (`Ok` for 2xx, `Error` for 5xx, `Unset` otherwise) and runs all registered `IEnrichment` enrichments. Enrichment exceptions are caught and logged. +7. In `finally`, sets `Outcome`, `http.request.method`, and `http.response.status.code`, then runs all registered `IEnrichment` enrichments. Enrichment exceptions are caught and logged. 8. Disposes the `MeasuredOperation` (which records the metric) and removes the feature. Throws `InvalidOperationException` if a second instance of the middleware tries to attach an SLI feature to the same request. @@ -301,8 +302,7 @@ builder.Services.AddServiceLevelIndicator(o => // Automatic emission is enabled by default. Set AutomaticallyEmitted = false // to opt in endpoint-by-endpoint with AddServiceLevelIndicator(). }) -.AddMvc() -.AddHttpMethod(); +.AddMvc(); var app = builder.Build(); app.UseRouting(); diff --git a/docs/api_reference/trellis-api-sli.md b/docs/api_reference/trellis-api-sli.md index 7835d49..f0fd659 100644 --- a/docs/api_reference/trellis-api-sli.md +++ b/docs/api_reference/trellis-api-sli.md @@ -17,9 +17,9 @@ See also: [`trellis-api-sli-asp.md`](trellis-api-sli-asp.md), [`trellis-api-sli- | `CustomerResourceId` | `ServiceLevelIndicatorOptions.CustomerResourceId` (or per-call override) | | `LocationId` | `ServiceLevelIndicatorOptions.LocationId` | | `Operation` | Caller-supplied operation name | -| `activity.status.code` | Set on `MeasuredOperation` (`Unset` / `Ok` / `Error`) | +| `Outcome` | `Success`, `Failure`, `ClientError`, or `Ignored` | -Additional attributes can be appended via `MeasuredOperation.AddAttribute(...)` or the `attributes` parameter of `Record`/`StartMeasuring`. Custom attributes must not reuse `CustomerResourceId`, `LocationId`, `Operation`, the configured activity-status tag name, or any other attribute name already present on the measurement. Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, and any supplied custom attributes, but they do not add `activity.status.code`. +Additional attributes can be appended via `MeasuredOperation.AddAttribute(...)` or the `attributes` parameter of `Record`/`StartMeasuring`. Custom attributes must not reuse `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, `activity.status.code`, `http.request.method`, `http.response.status.code`, or any other attribute name already present on the measurement. Direct `Record(...)` calls emit `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, and any supplied custom attributes. --- @@ -59,9 +59,15 @@ Singleton service that creates and records SLI metrics using an OpenTelemetry `H | Signature | Returns | Description | |---|---|---| -| `public void Record(string operation, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement using the configured default `CustomerResourceId`. Does not add `activity.status.code`. | -| `public void Record(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement with an explicit `CustomerResourceId`. Does not add `activity.status.code`. | +| `public void Record(string operation, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement using the configured default `CustomerResourceId` and default outcome `Ignored`. | +| `public void Record(string operation, long elapsedTime, SliOutcome outcome, params KeyValuePair[] attributes)` | `void` | Records a measurement using the configured default `CustomerResourceId` and explicit outcome. | +| `public void Record(string operation, string customerResourceId, long elapsedTime, params KeyValuePair[] attributes)` | `void` | Records a measurement with an explicit `CustomerResourceId` and default outcome `Ignored`. | +| `public void Record(string operation, string customerResourceId, long elapsedTime, SliOutcome outcome, params KeyValuePair[] attributes)` | `void` | Records a measurement with an explicit `CustomerResourceId` and outcome. | | `public MeasuredOperation StartMeasuring(string operation, params KeyValuePair[] attributes)` | `MeasuredOperation` | Starts a stopwatch-backed measurement; dispose the returned object to record the elapsed time as a metric. | +| `public void Measure(string operation, Action action, params KeyValuePair[] attributes)` | `void` | Measures a synchronous operation and infers `Success`, `Failure`, or `Ignored` for `OperationCanceledException`. | +| `public T Measure(string operation, Func action, params KeyValuePair[] attributes)` | `T` | Measures a synchronous operation and returns its result. | +| `public Task MeasureAsync(string operation, Func action, params KeyValuePair[] attributes)` | `Task` | Measures an asynchronous operation and infers outcome before rethrowing exceptions. | +| `public Task MeasureAsync(string operation, Func> action, params KeyValuePair[] attributes)` | `Task` | Measures an asynchronous operation and returns its result. | | `public void Dispose()` | `void` | Disposes the internally-created `Meter` if this instance created it; never disposes a user-supplied meter. Idempotent. Normally invoked by the DI container at host shutdown. | | `public static string CreateCustomerResourceId(Guid serviceId)` | `string` | Builds a `ServiceTreeId://` customer resource id. Throws `ArgumentNullException` if `serviceId` is `Guid.Empty`. | | `public static string CreateLocationId(string cloud, string? region = null, string? zone = null)` | `string` | Builds an `ms-loc://az///` location id, omitting empty segments. | @@ -83,10 +89,10 @@ Bound via `IOptions`. `LocationId` and `DurationIn | Name | Type | Default | Description | |---|---|---|---| | `Meter` | `Meter` | `null` (auto-created) | The meter used to create the duration histogram. Set during startup; read once when the `ServiceLevelIndicator` singleton is constructed. | -| `CustomerResourceId` | `string` | `"Unset"` | Default `CustomerResourceId` tag value (per-tenant / per-subscription identifier). Can be overridden on each call. | +| `CustomerResourceId` | `string` | `"Unknown"` | Default `CustomerResourceId` tag value. Can be overridden on each call. When emitted as `Unknown`, the diagnostic counter `sli.diagnostics.unknown_customer_resource_id` is incremented. | | `LocationId` | `string` | `""` | **Required.** Where the service is running (e.g. `ms-loc://az/public/westus3`). Must be non-empty. | | `DurationInstrumentName` | `string` | `"operation.duration"` | **Required.** Histogram instrument name. Must be non-empty. | -| `ActivityStatusCodeAttributeName` | `string` | `"activity.status.code"` | Tag name used to emit the operation's `ActivityStatusCode`. Must be non-empty and cannot be `CustomerResourceId`, `LocationId`, or `Operation`. | +| `ActivityStatusCodeAttributeName` | `string` | `"activity.status.code"` | Obsolete compatibility option. Activity status is no longer emitted as a metric dimension. | | `AutomaticallyEmitted` | `bool` | `true` | When `false`, only operations explicitly opted-in (e.g. via the `[ServiceLevelIndicator]` attribute in the ASP package) emit metrics. | --- @@ -113,7 +119,7 @@ Registers `ServiceLevelIndicator` as a singleton and configures `ServiceLevelInd public class MeasuredOperation : IDisposable ``` -Represents an in-flight measurement. The stopwatch starts in the constructor; disposing records the elapsed milliseconds plus the `activity.status.code` tag. +Represents an in-flight measurement. The stopwatch starts in the constructor; disposing records the elapsed milliseconds plus the `Outcome` tag. **Properties** @@ -134,7 +140,8 @@ Represents an in-flight measurement. The stopwatch starts in the constructor; di | Signature | Returns | Description | |---|---|---| -| `public void SetActivityStatusCode(ActivityStatusCode code)` | `void` | Sets the `ActivityStatusCode` recorded with the measurement. Default is `Unset`. | +| `public void SetOutcome(SliOutcome outcome)` | `void` | Sets the SLI outcome recorded with the measurement. Default is `Ignored`. | +| `public void SetActivityStatusCode(ActivityStatusCode code)` | `void` | Obsolete compatibility shim that maps `Ok` to `Success`, `Error` to `Failure`, and other values to `Ignored`. | | `public void AddAttribute(string attribute, object? value)` | `void` | Appends a custom attribute to be emitted with the measurement. Throws if the name collides with a reserved SLI tag. | | `public void Dispose()` | `void` | Stops the stopwatch and records the metric. Idempotent. | | `protected virtual void Dispose(bool disposing)` | `void` | Standard dispose pattern hook. | @@ -227,11 +234,11 @@ async Task DoWorkAsync(ServiceLevelIndicator sli, CancellationToken ct) try { await ProcessAsync(ct); - op.SetActivityStatusCode(ActivityStatusCode.Ok); + op.SetOutcome(SliOutcome.Success); } catch { - op.SetActivityStatusCode(ActivityStatusCode.Error); + op.SetOutcome(SliOutcome.Failure); throw; } } diff --git a/docs/usage-reference.md b/docs/usage-reference.md index be68e59..182d2e6 100644 --- a/docs/usage-reference.md +++ b/docs/usage-reference.md @@ -21,10 +21,10 @@ These values are part of the library contract and should be treated as stable un | Unit | milliseconds (`ms`) | | Required tag | `CustomerResourceId` | | Required tag | `LocationId` | -| Standard tag | `Operation` | -| Standard tag | `activity.status.code` for `StartMeasuring(...)` scopes and ASP.NET Core middleware | +| Required tag | `Operation` | +| Required tag | `Outcome` (`Success`, `Failure`, `ClientError`, or `Ignored`) | -For ASP.NET Core, the library also emits `http.response.status.code` and can optionally emit `http.request.method` and `http.api.version`. +For ASP.NET Core, the library also emits `http.request.method` and `http.response.status.code`; `http.api.version` is emitted when API version enrichment is enabled. ## Core Package @@ -65,11 +65,11 @@ async Task ProcessOrder(ServiceLevelIndicator sli) await Task.Delay(50); - op.SetActivityStatusCode(ActivityStatusCode.Ok); + op.SetOutcome(SliOutcome.Success); } ``` -Direct recording is also available when you already know the elapsed time. `Record(...)` emits `CustomerResourceId`, `LocationId`, `Operation`, and any custom attributes supplied to the call; it does not add `activity.status.code`. +Direct recording is also available when you already know the elapsed time. `Record(...)` emits `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, and any custom attributes supplied to the call. Manual measurements default to `Ignored` unless you set an outcome. ```csharp sli.Record("ProcessOrder", elapsedTime: 42); @@ -145,7 +145,6 @@ builder.Services.AddServiceLevelIndicator(options => options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); }) .AddMvc() -.AddHttpMethod() .Enrich(context => { context.SetCustomerResourceId("tenant-a"); @@ -234,20 +233,21 @@ Use `GetMeasuredOperation()` when the route is guaranteed to emit SLI metrics. U ## Status Semantics -For non-HTTP code, set the outcome explicitly: +For non-HTTP code, set the outcome explicitly or use `Measure(...)` / `MeasureAsync(...)` helpers to infer it: ```csharp -op.SetActivityStatusCode(ActivityStatusCode.Ok); +op.SetOutcome(SliOutcome.Success); ``` For ASP.NET Core: -| Response outcome | `activity.status.code` | +| Response outcome | `Outcome` | |---|---| -| `2xx` | `Ok` | -| `5xx` | `Error` | -| Other status codes | `Unset` | -| Unhandled exceptions | `Error` | +| `2xx`, `3xx` | `Success` | +| `400`, `401`, `403`, `404`, `409`, `412`, `422` | `ClientError` | +| `429`, `5xx` | `Failure` | +| Unhandled exceptions | `Failure` | +| Request-aborted cancellations | `Ignored` | ## Cardinality Guidance @@ -276,7 +276,7 @@ Avoid values that can explode cardinality unless your backend is designed for th 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. -6. Reusing reserved tag names such as `CustomerResourceId`, `LocationId`, `Operation`, or `activity.status.code` as custom attributes. +6. Reusing reserved tag names such as `CustomerResourceId`, `LocationId`, `Operation`, `Outcome`, `activity.status.code`, `http.request.method`, or `http.response.status.code` as custom attributes. ## Public API Cheat Sheet @@ -295,7 +295,7 @@ ASP.NET Core package: - `UseServiceLevelIndicator()` - `IServiceLevelIndicatorBuilder.AddMvc()` -- `IServiceLevelIndicatorBuilder.AddHttpMethod()` +- `IServiceLevelIndicatorBuilder.ClassifyHttpOutcome(...)` - `IServiceLevelIndicatorBuilder.Enrich(...)` - `IServiceLevelIndicatorBuilder.EnrichAsync(...)` - `EndpointConventionBuilder.AddServiceLevelIndicator(...)` diff --git a/sample/ConsoleApp/Program.cs b/sample/ConsoleApp/Program.cs index 1b0029a..0c01ad7 100644 --- a/sample/ConsoleApp/Program.cs +++ b/sample/ConsoleApp/Program.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using System.Reflection; +using System.Reflection; using Azure.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -45,17 +44,17 @@ .Build(); var serviceLevelIndicator = serviceProvider.GetRequiredService(); -using MeasuredOperation measuredOperation = serviceLevelIndicator.StartMeasuring("OperationWork"); try { - logger.LogInformation("Starting to do some work..."); - await Task.Delay(1000); // Simulate some work - logger.LogInformation("Work done."); - measuredOperation.SetActivityStatusCode(ActivityStatusCode.Ok); + await serviceLevelIndicator.MeasureAsync("OperationWork", async () => + { + logger.LogInformation("Starting to do some work..."); + await Task.Delay(1000); // Simulate some work + logger.LogInformation("Work done."); + }); } catch (Exception ex) { - measuredOperation.SetActivityStatusCode(ActivityStatusCode.Error); logger.LogError(ex, "An error occurred doing work."); } diff --git a/sample/GenerateSli/Program.cs b/sample/GenerateSli/Program.cs index 346491c..8601779 100644 --- a/sample/GenerateSli/Program.cs +++ b/sample/GenerateSli/Program.cs @@ -35,4 +35,4 @@ static async Task ClientRequests() Console.WriteLine($"Request {i}: Exception occurred: {ex.Message}"); } } -} \ No newline at end of file +} diff --git a/sample/MinApi/Program.cs b/sample/MinApi/Program.cs index 5336a3e..9a168e8 100644 --- a/sample/MinApi/Program.cs +++ b/sample/MinApi/Program.cs @@ -1,8 +1,8 @@ using Azure.Core; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; -using Scalar.AspNetCore; using SampleMinimalApiSli; +using Scalar.AspNetCore; using Trellis.ServiceLevelIndicators; var builder = WebApplication.CreateBuilder(args); @@ -34,8 +34,7 @@ { options.CustomerResourceId = "SampleCustomerResourceId"; options.LocationId = ServiceLevelIndicator.CreateLocationId("public", AzureLocation.WestUS3.Name); -}) -.AddHttpMethod(); +}); // Add services to the container. diff --git a/sample/MinApi/UserExt.cs b/sample/MinApi/UserExt.cs index 6be015d..1354835 100644 --- a/sample/MinApi/UserExt.cs +++ b/sample/MinApi/UserExt.cs @@ -21,4 +21,4 @@ public static void UseUserRoute(this WebApplication app) userApi.MapGet("/{name}", (string name) => $"Hello {name}").WithName("GetUserById"); } -} \ No newline at end of file +} diff --git a/sample/WebApi/ConfigureServiceLevelIndicatorOptions.cs b/sample/WebApi/ConfigureServiceLevelIndicatorOptions.cs index f52b5c3..89f6348 100644 --- a/sample/WebApi/ConfigureServiceLevelIndicatorOptions.cs +++ b/sample/WebApi/ConfigureServiceLevelIndicatorOptions.cs @@ -10,4 +10,4 @@ internal sealed class ConfigureServiceLevelIndicatorOptions : IConfigureOptions< public ConfigureServiceLevelIndicatorOptions(SampleApiMeters meters) => this.meters = meters; public void Configure(ServiceLevelIndicatorOptions options) => options.Meter = meters.Meter; -} \ No newline at end of file +} diff --git a/sample/WebApi/Controllers/WeatherForecastController.cs b/sample/WebApi/Controllers/WeatherForecastController.cs index 87c9bf0..b2873f0 100644 --- a/sample/WebApi/Controllers/WeatherForecastController.cs +++ b/sample/WebApi/Controllers/WeatherForecastController.cs @@ -20,6 +20,7 @@ public class WeatherForecastController : ControllerBase /// Should emit SLI metrics /// Operation: "GET WeatherForecast" /// CustomerResourceId = "SampleCustomerResourceId" + /// Outcome = "Success" /// [HttpGet] public IEnumerable Get() => GetWeather(); @@ -28,6 +29,7 @@ public class WeatherForecastController : ControllerBase /// Should emit SLI metrics /// Operation: "GET WeatherForecast/MyAction1" /// CustomerResourceId = "SampleCustomerResourceId" + /// Outcome = "Success" /// [HttpGet("MyAction1")] @@ -37,6 +39,7 @@ public class WeatherForecastController : ControllerBase /// Should emit SLI metrics /// Operation: "MyOperation" /// CustomerResourceId = "SampleCustomerResourceId" + /// Outcome = "Success" /// [HttpGet("MyAction2")] [ServiceLevelIndicator(Operation = "MyOperation")] @@ -46,6 +49,7 @@ public class WeatherForecastController : ControllerBase /// Should emit SLI metrics /// Operation: "GET WeatherForecast/{customerResourceId}" /// CustomerResourceId = "Your input" + /// Outcome = "Success" /// [HttpGet("{customerResourceId}")] public IEnumerable Get([CustomerResourceId] string customerResourceId) => GetWeather(); @@ -57,4 +61,4 @@ public class WeatherForecastController : ControllerBase Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); -} \ No newline at end of file +} diff --git a/sample/WebApi/Program.cs b/sample/WebApi/Program.cs index 0273fb7..9d0ebef 100644 --- a/sample/WebApi/Program.cs +++ b/sample/WebApi/Program.cs @@ -3,8 +3,8 @@ using Microsoft.Extensions.Options; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; -using Scalar.AspNetCore; using SampleWebApplicationSLI; +using Scalar.AspNetCore; using Trellis.ServiceLevelIndicators; var builder = WebApplication.CreateBuilder(args); @@ -37,8 +37,7 @@ options.CustomerResourceId = "SampleCustomerResourceId"; options.LocationId = ServiceLevelIndicator.CreateLocationId("public", AzureLocation.WestUS3.Name); }) -.AddMvc() -.AddHttpMethod(); +.AddMvc(); var app = builder.Build(); diff --git a/sample/WebApi/SampleApiMeters.cs b/sample/WebApi/SampleApiMeters.cs index 343baf9..8a09e12 100644 --- a/sample/WebApi/SampleApiMeters.cs +++ b/sample/WebApi/SampleApiMeters.cs @@ -7,4 +7,4 @@ internal class SampleApiMeters public const string MeterName = "SampleMeter"; public Meter Meter { get; } = new Meter(MeterName); -} \ No newline at end of file +} diff --git a/sample/WebApi/WeatherForecast.cs b/sample/WebApi/WeatherForecast.cs index 0893baa..906d361 100644 --- a/sample/WebApi/WeatherForecast.cs +++ b/sample/WebApi/WeatherForecast.cs @@ -24,4 +24,4 @@ public class WeatherForecast /// Temperature feeling. /// public string? Summary { get; set; } -} \ No newline at end of file +} diff --git a/sample/WebApiVersioned/Controllers/2023-06-06/HelloWorldController.cs b/sample/WebApiVersioned/Controllers/2023-06-06/HelloWorldController.cs index 091388e..a2e3cf9 100644 --- a/sample/WebApiVersioned/Controllers/2023-06-06/HelloWorldController.cs +++ b/sample/WebApiVersioned/Controllers/2023-06-06/HelloWorldController.cs @@ -39,4 +39,4 @@ public ActionResult GetCustom([CustomerResourceId] string name) if (next < 20) return StatusCode(StatusCodes.Status500InternalServerError, "Sim Server error"); return Ok("Hello World " + name); } -} \ No newline at end of file +} From a3e045e9d2b2c55fd2de910356a96768222d7ca8 Mon Sep 17 00:00:00 2001 From: Xavier John Date: Tue, 28 Apr 2026 18:02:16 -0700 Subject: [PATCH 4/5] Add local SLI observability stack and Grafana dashboard Introduced a local observability stack using OpenTelemetry Collector, Prometheus, and Grafana with configuration and setup documentation. Added a prebuilt Grafana dashboard for SLI metrics, including panels for latency, success rate, error rates, and diagnostics. Enhanced the sample Web API with endpoints to emit various SLI outcomes for demonstration and testing. Updated documentation to guide users through setup and usage. --- README.md | 2 + sample/Observability/Grafana/README.md | 64 ++ .../Grafana/assets/sli-grafana-dashboard.png | Bin 0 -> 256919 bytes .../Observability/Grafana/docker-compose.yml | 44 + .../grafana/dashboards/sli-dashboard.json | 818 ++++++++++++++++++ .../grafana/provisioning/dashboards/sli.yml | 12 + .../provisioning/datasources/prometheus.yml | 10 + .../Grafana/otel-collector-config.yaml | 26 + sample/Observability/Grafana/prometheus.yml | 9 + .../Controllers/WeatherForecastController.cs | 21 + 10 files changed, 1006 insertions(+) create mode 100644 sample/Observability/Grafana/README.md create mode 100644 sample/Observability/Grafana/assets/sli-grafana-dashboard.png create mode 100644 sample/Observability/Grafana/docker-compose.yml create mode 100644 sample/Observability/Grafana/grafana/dashboards/sli-dashboard.json create mode 100644 sample/Observability/Grafana/grafana/provisioning/dashboards/sli.yml create mode 100644 sample/Observability/Grafana/grafana/provisioning/datasources/prometheus.yml create mode 100644 sample/Observability/Grafana/otel-collector-config.yaml create mode 100644 sample/Observability/Grafana/prometheus.yml diff --git a/README.md b/README.md index ad61d44..ffea986 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,8 @@ The default operation name is the HTTP method plus the route template (placehold Try out the sample weather forecast Web API. +For a local Grafana/Prometheus/OpenTelemetry Collector experience, run the provisioned dashboard in [`sample\Observability\Grafana`](sample/Observability/Grafana/README.md). It shows SLI latency percentiles, success rate, failures, client errors, unknown customer diagnostics, and `` detection. + To view the metrics locally using the [.NET Aspire Dashboard](https://aspire.dev/dashboard/standalone/): 1. Start the Aspire dashboard: diff --git a/sample/Observability/Grafana/README.md b/sample/Observability/Grafana/README.md new file mode 100644 index 0000000..d763b3f --- /dev/null +++ b/sample/Observability/Grafana/README.md @@ -0,0 +1,64 @@ +# Local SLI Grafana dashboard + +This sample runs a local OpenTelemetry Collector, Prometheus, and Grafana stack so you can see the value of the SLI library while running one of the sample applications. + +## Start the dashboard stack + +From this directory: + +```powershell +docker compose up -d +``` + +Grafana starts at http://localhost:3000 with anonymous admin access enabled for local development. The SLI dashboard is provisioned automatically under **Dashboards > Trellis > Service Level Indicators**. + +![Trellis SLI Grafana dashboard](assets/sli-grafana-dashboard.png) + +## Run a sample app + +In another terminal, run the Web API sample and point its OTLP exporter at the local collector: + +```powershell +$env:OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4317" +dotnet run --project ..\..\WebApi\SampleWebApplicationSLI.csproj +``` + +Generate traffic: + +```powershell +Invoke-RestMethod https://localhost:63936/WeatherForecast -SkipCertificateCheck +Invoke-RestMethod https://localhost:63936/WeatherForecast/MyAction1 -SkipCertificateCheck +Invoke-RestMethod https://localhost:63936/WeatherForecast/MyAction2 -SkipCertificateCheck +Invoke-RestMethod https://localhost:63936/WeatherForecast/my-customer-resource-id -SkipCertificateCheck +``` + +You can also run the Minimal API or API-versioned samples with the same `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. + +## What the dashboard shows + +The dashboard uses the SLI metric contract: + +- `operation.duration` histogram exported to Prometheus as `operation_duration_milliseconds_*` +- `CustomerResourceId` +- `LocationId` +- `Operation` +- `Outcome` +- `http.request.method` +- `http.response.status.code` +- optional `http.api.version` + +Panels include: + +- request volume by operation and outcome +- p50/p95/p99 latency +- success rate using `Success / (Success + Failure)` +- failure and client-error rates +- HTTP status-code breakdown +- unknown customer diagnostics +- `` operation detection + +## Stop the stack + +```powershell +docker compose down +``` diff --git a/sample/Observability/Grafana/assets/sli-grafana-dashboard.png b/sample/Observability/Grafana/assets/sli-grafana-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6e33fb8e38e0657c6ff847fd5774f5795dfa0c GIT binary patch literal 256919 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 Fe*ke^Qsw{v literal 0 HcmV?d00001 diff --git a/sample/Observability/Grafana/docker-compose.yml b/sample/Observability/Grafana/docker-compose.yml new file mode 100644 index 0000000..2754088 --- /dev/null +++ b/sample/Observability/Grafana/docker-compose.yml @@ -0,0 +1,44 @@ +services: + otel-collector: + image: otel/opentelemetry-collector-contrib:0.123.0 + command: ["--config=/etc/otelcol/config.yaml"] + volumes: + - ./otel-collector-config.yaml:/etc/otelcol/config.yaml:ro + ports: + - "4317:4317" + - "4318:4318" + - "9464:9464" + + prometheus: + image: prom/prometheus:v3.3.0 + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.enable-lifecycle" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus-data:/prometheus + ports: + - "9090:9090" + depends_on: + - otel-collector + + grafana: + image: grafana/grafana:11.6.0 + environment: + GF_AUTH_ANONYMOUS_ENABLED: "true" + GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin" + GF_AUTH_DISABLE_LOGIN_FORM: "true" + GF_USERS_DEFAULT_THEME: "light" + volumes: + - grafana-data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning:ro + - ./grafana/dashboards:/var/lib/grafana/dashboards:ro + ports: + - "3000:3000" + depends_on: + - prometheus + +volumes: + prometheus-data: + grafana-data: diff --git a/sample/Observability/Grafana/grafana/dashboards/sli-dashboard.json b/sample/Observability/Grafana/grafana/dashboards/sli-dashboard.json new file mode 100644 index 0000000..51a5af4 --- /dev/null +++ b/sample/Observability/Grafana/grafana/dashboards/sli-dashboard.json @@ -0,0 +1,818 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "orange", + "value": 95 + }, + { + "color": "green", + "value": 99 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "100 * sum(rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\",Outcome=\"Success\"}[$__rate_interval])) / sum(rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\",Outcome=~\"Success|Failure\"}[$__rate_interval]))", + "legendFormat": "success rate", + "range": true, + "refId": "A" + } + ], + "title": "Success rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le, Operation) (rate(operation_duration_milliseconds_bucket{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\"}[$__rate_interval])))", + "legendFormat": "p50 {{Operation}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le, Operation) (rate(operation_duration_milliseconds_bucket{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\"}[$__rate_interval])))", + "hide": false, + "legendFormat": "p95 {{Operation}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le, Operation) (rate(operation_duration_milliseconds_bucket{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\"}[$__rate_interval])))", + "hide": false, + "legendFormat": "p99 {{Operation}}", + "range": true, + "refId": "C" + } + ], + "title": "Latency percentiles", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (Operation, Outcome) (rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\",Outcome=~\"$outcome\"}[$__rate_interval]))", + "legendFormat": "{{Operation}} / {{Outcome}}", + "range": true, + "refId": "A" + } + ], + "title": "Request volume by operation and outcome", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (Operation) (rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\",Outcome=\"Failure\"}[$__rate_interval]))", + "legendFormat": "Failure {{Operation}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (Operation) (rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\",Outcome=\"ClientError\"}[$__rate_interval]))", + "legendFormat": "ClientError {{Operation}}", + "range": true, + "refId": "B" + } + ], + "title": "Failure and client-error rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 60, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (http_response_status_code) (rate(operation_duration_milliseconds_count{Operation=~\"$operation\",CustomerResourceId=~\"$customer\",LocationId=~\"$location\"}[$__rate_interval]))", + "legendFormat": "HTTP {{http_response_status_code}}", + "range": true, + "refId": "A" + } + ], + "title": "HTTP status-code breakdown", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (Operation, LocationId) (increase(sli_diagnostics_unknown_customer_resource_id_total[$__range]))", + "legendFormat": "{{Operation}} / {{LocationId}}", + "range": true, + "refId": "A" + } + ], + "title": "Unknown customer diagnostics", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 7, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.6.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (Operation) (increase(operation_duration_milliseconds_count{Operation=~\".*.*\"}[$__range]))", + "legendFormat": "{{Operation}}", + "range": true, + "refId": "A" + } + ], + "title": " operation detection", + "type": "bargauge" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 41, + "tags": [ + "trellis", + "sli", + "opentelemetry" + ], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(operation_duration_milliseconds_count, Operation)", + "includeAll": true, + "label": "Operation", + "multi": true, + "name": "operation", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(operation_duration_milliseconds_count, Operation)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(operation_duration_milliseconds_count, CustomerResourceId)", + "includeAll": true, + "label": "CustomerResourceId", + "multi": true, + "name": "customer", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(operation_duration_milliseconds_count, CustomerResourceId)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(operation_duration_milliseconds_count, LocationId)", + "includeAll": true, + "label": "LocationId", + "multi": true, + "name": "location", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(operation_duration_milliseconds_count, LocationId)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(operation_duration_milliseconds_count, Outcome)", + "includeAll": true, + "label": "Outcome", + "multi": true, + "name": "outcome", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(operation_duration_milliseconds_count, Outcome)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Trellis Service Level Indicators", + "uid": "trellis-sli", + "version": 1, + "weekStart": "" +} diff --git a/sample/Observability/Grafana/grafana/provisioning/dashboards/sli.yml b/sample/Observability/Grafana/grafana/provisioning/dashboards/sli.yml new file mode 100644 index 0000000..8c46634 --- /dev/null +++ b/sample/Observability/Grafana/grafana/provisioning/dashboards/sli.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: Trellis + orgId: 1 + folder: Trellis + type: file + disableDeletion: false + editable: true + updateIntervalSeconds: 10 + options: + path: /var/lib/grafana/dashboards diff --git a/sample/Observability/Grafana/grafana/provisioning/datasources/prometheus.yml b/sample/Observability/Grafana/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..00f9915 --- /dev/null +++ b/sample/Observability/Grafana/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + uid: prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/sample/Observability/Grafana/otel-collector-config.yaml b/sample/Observability/Grafana/otel-collector-config.yaml new file mode 100644 index 0000000..9c361c0 --- /dev/null +++ b/sample/Observability/Grafana/otel-collector-config.yaml @@ -0,0 +1,26 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + +exporters: + prometheus: + endpoint: 0.0.0.0:9464 + enable_open_metrics: true + resource_to_telemetry_conversion: + enabled: true + debug: + verbosity: basic + +service: + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus, debug] diff --git a/sample/Observability/Grafana/prometheus.yml b/sample/Observability/Grafana/prometheus.yml new file mode 100644 index 0000000..454d12e --- /dev/null +++ b/sample/Observability/Grafana/prometheus.yml @@ -0,0 +1,9 @@ +global: + scrape_interval: 5s + evaluation_interval: 5s + +scrape_configs: + - job_name: otel-collector + static_configs: + - targets: + - otel-collector:9464 diff --git a/sample/WebApi/Controllers/WeatherForecastController.cs b/sample/WebApi/Controllers/WeatherForecastController.cs index b2873f0..22604fc 100644 --- a/sample/WebApi/Controllers/WeatherForecastController.cs +++ b/sample/WebApi/Controllers/WeatherForecastController.cs @@ -54,6 +54,27 @@ public class WeatherForecastController : ControllerBase [HttpGet("{customerResourceId}")] public IEnumerable Get([CustomerResourceId] string customerResourceId) => GetWeather(); + ///

+ /// Demo endpoint that emits Outcome = "ClientError". + /// + [HttpGet("demo/client-error/{customerResourceId}")] + public IActionResult ClientError([CustomerResourceId] string customerResourceId) => + BadRequest(new { customerResourceId, error = "Invalid forecast request." }); + + /// + /// Demo endpoint that emits Outcome = "Failure" because 429 is service-impacting by default. + /// + [HttpGet("demo/throttled/{customerResourceId}")] + public IActionResult Throttled([CustomerResourceId] string customerResourceId) => + StatusCode(StatusCodes.Status429TooManyRequests, new { customerResourceId, error = "Too many forecast requests." }); + + /// + /// Demo endpoint that emits Outcome = "Failure". + /// + [HttpGet("demo/server-error/{customerResourceId}")] + public IActionResult ServerError([CustomerResourceId] string customerResourceId) => + StatusCode(StatusCodes.Status500InternalServerError, new { customerResourceId, error = "Forecast service unavailable." }); + private static WeatherForecast[] GetWeather() => Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), From 98cfc721ec2ec7f17089e5e69da0478f32cdff01 Mon Sep 17 00:00:00 2001 From: Xavier John Date: Tue, 28 Apr 2026 18:35:24 -0700 Subject: [PATCH 5/5] Address SLI PR review feedback Clarify implemented RFC status and CustomerResourceId option defaults. Add ASP.NET request-aborted cancellation regression coverage for Outcome=Ignored. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/ServiceLevelIndicatorAspTests.cs | 21 +++++++++++++++++++ .../tests/TestController.cs | 7 +++++++ .../src/ServiceLevelIndicatorOptions.cs | 2 +- docs/design/sli-metric-contract.md | 2 +- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs index 9111f4e..2573c4b 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/ServiceLevelIndicatorAspTests.cs @@ -448,6 +448,27 @@ await act.Should().ThrowAsync() ValidateMetrics(expectedTags); } + [Fact] + public async Task SLI_Metrics_is_emitted_as_ignored_for_request_aborted_cancellation() + { + using var host = await TestHostBuilder.CreateHostWithSli(_meter); + + Func act = () => host.GetTestClient().GetAsync("test/request_aborted", TestContext.Current.CancellationToken); + + await act.Should().ThrowAsync(); + + var expectedTags = new KeyValuePair[] + { + new("CustomerResourceId", "TestCustomerResourceId"), + new("LocationId", "ms-loc://az/public/West US 3"), + new("Operation", "GET Test/request_aborted"), + new("Outcome", "Ignored"), + new("http.response.status.code", 200), + }; + + ValidateMetrics(expectedTags); + } + protected virtual void Dispose(bool disposing) { if (!_disposedValue) diff --git a/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs b/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs index d65b7f6..0ae2a54 100644 --- a/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs +++ b/Trellis.ServiceLevelIndicators.Asp/tests/TestController.cs @@ -35,6 +35,13 @@ public IActionResult Throw() throw new InvalidOperationException("Boom"); } + [HttpGet("request_aborted")] + public IActionResult RequestAborted() + { + HttpContext.Abort(); + throw new OperationCanceledException(); + } + [HttpGet("operation")] [ServiceLevelIndicator(Operation = "TestOperation")] public IActionResult GetOperation() => Ok("Hello World!"); diff --git a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs index 907973b..5371b27 100644 --- a/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs +++ b/Trellis.ServiceLevelIndicators/src/ServiceLevelIndicatorOptions.cs @@ -4,7 +4,7 @@ /// /// Options for configuring the Service Level Indicator. -/// CustomerResourceId & LocationId are mandatory properties. +/// LocationId is required. CustomerResourceId defaults to when not configured. /// public class ServiceLevelIndicatorOptions { diff --git a/docs/design/sli-metric-contract.md b/docs/design/sli-metric-contract.md index 5a0b921..eac6923 100644 --- a/docs/design/sli-metric-contract.md +++ b/docs/design/sli-metric-contract.md @@ -2,7 +2,7 @@ ## Status -Draft for pre-implementation review. +Implemented in the pre-1.0 metric contract finalization. ## Context