{"id":17081518,"url":"https://github.com/benfoster/o9d-observability","last_synced_at":"2025-07-01T05:06:00.485Z","repository":{"id":52509919,"uuid":"351519839","full_name":"benfoster/o9d-observability","owner":"benfoster","description":"Opinionated Observability Extensions for .NET","archived":false,"fork":false,"pushed_at":"2021-08-25T15:23:36.000Z","size":1115,"stargazers_count":9,"open_issues_count":6,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-11T14:19:25.579Z","etag":null,"topics":["dotnet-core","instrumentation","metrics","observability","prometheus"],"latest_commit_sha":null,"homepage":"https://benfoster.github.io/o9d-observability/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benfoster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2021-03-25T17:25:07.000Z","updated_at":"2023-07-04T15:58:26.000Z","dependencies_parsed_at":"2022-09-06T13:11:51.487Z","dependency_job_id":null,"html_url":"https://github.com/benfoster/o9d-observability","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/benfoster/o9d-observability","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fo9d-observability","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fo9d-observability/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fo9d-observability/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fo9d-observability/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benfoster","download_url":"https://codeload.github.com/benfoster/o9d-observability/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fo9d-observability/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260648037,"owners_count":23041725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet-core","instrumentation","metrics","observability","prometheus"],"created_at":"2024-10-14T12:53:22.112Z","updated_at":"2025-07-01T05:06:00.456Z","avatar_url":"https://github.com/benfoster.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg alt=\"Observability Icon\" src=\"src/shared/icon.png\" width=\"64px\" /\u003e\n\n# Observability\n\n[![NuGet](https://img.shields.io/nuget/v/O9d.Observability.svg)](https://www.nuget.org/packages/O9d.Observability) \n[![NuGet](https://img.shields.io/nuget/dt/O9d.Observability.svg)](https://www.nuget.org/packages/O9d.Observability)\n[![License](https://img.shields.io/:license-mit-blue.svg)](https://benfoster.mit-license.org/)\n\n![Build](https://github.com/benfoster/o9d-observability/workflows/Build/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/benfoster/o9d-observability/badge.svg?branch=main)](https://coveralls.io/github/benfoster/o9d-observability?branch=main)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=benfoster_o9d-observability\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=benfoster_o9d-observability)\n\nO[pinionate]d Observability Extensions for .NET.\n\n## Quick Start\n\nIn order to make use of the Observability libraries you need to initialize the Observability Host. Currently, only ASP.NET Core hosts are supported.\n\nAdd the O9d.Observability.Hosting.AspNet package from [NuGet](https://www.nuget.org/packages/O9d.Observability.Hosting.AspNet)\n\n```\ndotnet add package O9d.Observability.Hosting.AspNet\n```\n\nYou can then update your `Startup.cs` file to initialize the host:\n\n```c#\nservices.AddObservability(builder =\u003e\n{\n\n});\n```\n\nInternally this will initialize an ASP.NET Core Hosted Service that keeps track of all registered instrumentation components.\n\nTo start instrumenting your application you need to add one of the relevant instrumentation packages (discussed in more detail below), for example, to add ASP.NET Core metrics (using Prometheus), add the [09d.AspNet package](https://www.nuget.org/packages/O9d.Metrics.AspNet):\n\n```\ndotnet add package O9d.Metrics.AspNet\n```\n\nThen update your Observability startup code:\n\n```c#\nservices.AddObservability(builder =\u003e\n{\n    builder.AddAspNetMetrics(options =\u003e {});\n});\n```\n\nOne of the design goals of this library is that it should be as unobtrusive as possible, leveraging the built-in diagnostic and activity components of the Core CLR so that adding instrumentation doesn't interfere with other application code or middleware.\n\n\n## Instrumentation Libraries\n\n### ASP.NET Core Metrics\n\nThe [09d.Metrics.AspNet package](https://www.nuget.org/packages/O9d.Metrics.AspNet) adds specific Prometheus metrics that we have found to be the most useful when operationalising HTTP services in production.\n\nAfter installing the Observability Hosting and ASP.NET Metrics Packages to your application, update your `Startup.cs` as follows:\n\n```c#\nservices.AddObservability(builder =\u003e\n{\n    builder.AddAspNetMetrics(options =\u003e {});\n});\n```\n\nBy default the library adds the following Prometheus metrics:\n\n#### `http_server_request_duration_seconds`\n\nA histogram (default) or summary that tracks the duration in seconds that HTTP requests take to process. \n\nLabels:\n\n| Name | Description  |  Example  |\n|---|---|---|\n| `operation`  | A descriptor for the operation and endpoint that was requested  | `get_customers`  |\n| `status_code` | The status code returned by your service  | `200`  |\n\n#### `http_server_requests_in_progress`\n\nA gauge that tracks the number of requests in progress. \n\nLabels:\n\n| Name | Description  |  Example  |\n|---|---|---|\n| `operation`  | A descriptor for the operation and endpoint that was requested  | `get_customers`  |\n\n#### `http_server_errors_total`\n\nA counter that tracks the number of HTTP requests resulting in an error.\n\nLabels:\n\n| Name | Description  |  Example  |\n|---|---|---|\n| `operation`  | A descriptor for the operation and endpoint that was requested  | `get_customers`  |\n| `sli_error_type` | The service level indicator error type | `external_dependency` |\n| `sli_dependency` | For dependency error types, the name of the causing dependency | `skynet` |\n\n#### Calculating Service Availability\n\nWith these metrics we can easily calculate both internal and external service availability. To calculate our client facing availability:\n\n```\nAvailability = successful_requests / (total_requests - client_failures)\n```\n\nFor example:\n\n```\nGiven 100 requests\nof which\n    70 returned HTTP 200\n    10 returned HTTP 500 (Server Error)\n    20 returned HTTP 422 (Invalid Client Request)\n\nAvailability = (100 - 30) / (100 - 20)\n= 87.5%\n```\n\nTo calculate this in Prometheus/Grafana:\n\n```\n(sum(rate(http_server_request_duration_seconds_count[10m])) - sum(rate(http_server_errors_total[10m]) OR on() vector(0))) / \n( \n    sum(rate(http_server_request_duration_seconds_count[10m])) - \n    sum(rate(http_server_errors_total{sli_error_type=\"invalid_request\"}[10m]) OR on() vector(0))\n)\n```\n\n#### Resolving the Operation\n\nThe default Prometheus libraries for ASP.NET are quite verbose and can result in a large number of series or high-cardinality labels.\n\nBy design this library only tracks _genuine_ endpoints of your application since generally, metrics about non-existent endpoints offer little value (e.g. bots trying to hit `/phpmyadmin`). Note that a metric for unmatched paths is something we're thinking about.\n\nBy default the library uses the following approach to resolve the operation name\n\n1. The name of the route if set on your controller action, for example:\n    c#\n    ```\n    [HttpGet(\"status/{code:int}\", Name = \"get_status\")]\n    ```\n2. Or, use a combination of the HTTP verb and route template e.g. `PUT /customers/{id}`\n\nIn general we recommend explicitly naming your route to avoid your metrics changing if your URI structure is updated.\n\n#### Tracking Errors\n\nBy default the following status codes are determined to be an error:\n\n- `400 - 499` - Error Type: Invalid Request\n- `\u003e500` - Error Type: Internal\n\nWhat we can't track automatically are errors that are the result of internal or external dependencies. For these you have two options:\n\n1. Set the SLI error using `HttpContext.SetSliError()`, for example:\n\n    ```c#\n    HttpContext.SetSliError(ErrorType.ExternalDependency, \"skynet\");\n    ```\n2. Throw an `SliException` (or any derived type), for example:\n    ```c#\n    throw new SliException(ErrorType.ExternalDependency, \"skynet\");\n    ```\n\n#### Customizing Metrics\n\nThe `AspNetMetricsOptions` class includes a number of options to customize the metrics created by the library. Each metric listed above has an associated `ConfigureX` property that can be used to customize the underlying Prometheus metric configuration. For example, to set the buckets used by the Request Duration Histogram metric:\n\n```c#\nservices.AddObservability(builder =\u003e\n    builder.AddAspNetMetrics(options =\u003e\n        options.ConfigureRequestDurationHistogram = histogram =\u003e\n        {\n            histogram.Buckets = new[] { 0.1, 0.2, 0.5, 0.75, 1, 2 };\n        }\n    )\n);\n```\n\n**Using a summary instead of a histogram to to track request duration**\n\nWe recommend using histograms (the default) if you are running multiple instances of your application since they can be [aggregated](https://prometheus.io/docs/practices/histograms/). If you are happy with the trade-offs of using Summary metrics, you can switch the request duration metric type like so:\n\n```c#\nservices.AddObservability(builder =\u003e\n    builder.AddAspNetMetrics(options =\u003e\n        options.RequestDurationMetricType = ObserverMetricType.Summary\n    )\n);\n```\n\n#### Grafana Dashboard\n\nWe've created a Grafana Dashboard that leverages the metrics generated by [O9d.Metrics.AspNet](https://www.nuget.org/packages/O9d.Metrics.AspNet/). You can see this in action by running the [examples](/examples/README.md) and install it from [Grafana Labs](https://grafana.com/grafana/dashboards/14308).\n\nFor the dashboard to work you should add an `app` label with the name of your application. This can be done by your agent or directly within your application using static labels:\n\n```c#\nPrometheus.Metrics.DefaultRegistry.SetStaticLabels(new Dictionary\u003cstring, string\u003e\n{\n    { \"app\", \"aspnet-example\" },\n    { \"env\", \"prod\" }\n});\n```\n\n## Extending O9d.Observability\n\nThis project was heavily inspired by the [Open Telemetry Libraries for .NET](https://github.com/open-telemetry/opentelemetry-dotnet).\n\nWe wanted to make it easy to plug in additional instrumentation without a lot of ceremony. Suppose you want to instrument operations in the DazzleDB .NET client. Fortunately the client already emits events to a Diagnostic Source and the Observability library makes it easy to tap into them. \n\n### Create an observer\n\nCreate a class that implements `IObserver\u003cKeyValuePair\u003cstring, object?\u003e\u003e` to receive Diagnostic Listener events:\n\n```c#\ninternal class DazzleDbMetricsObserver : IObserver\u003cKeyValuePair\u003cstring, object?\u003e\u003e\n{\n}\n```\n\n### Add the `O9d.Observability` package\n\n```\ndotnet add package O9d.Observability\n```\n\n### Create an extension for Observability Builder\n\n```c#\npublic static class DazzleDbObservabilityBuilderExtensions\n{\n    public static IObservabilityBuilder AddDazzleDbMetrics(this IObservabilityBuilder builder)\n    {\n        if (builder is null) throw new ArgumentNullException(nameof(builder));\n\n        return builder.AddDiagnosticSource(\"DazzleDb\", new DazzleDbMetricsObserver());\n    }\n}\n```\n\nThe above code makes use of the `AddDiagnosticSource` extension to handle the boilerplate `DiagnosticSource` subscription logic and ensure subscribers are tracked.\n\n### Package your library and update your applications\n\n```c#\nservices.AddObservability(builder =\u003e\n{\n    builder.AddDazzleDbMetrics();\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fo9d-observability","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenfoster%2Fo9d-observability","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fo9d-observability/lists"}