From f6c6025689c5e1ac7ff00a79e03cf71ab4b79357 Mon Sep 17 00:00:00 2001 From: thorsten Date: Tue, 9 Jun 2026 19:15:05 +0200 Subject: [PATCH 01/14] feat: default Enabled to true and make config section name configurable - TelemetryOptions.Enabled now defaults to true (set false to disable) - AddTelemetry(IConfiguration) overloads accept an optional sectionName to bind a section other than the default "Telemetry" - Update README (table, examples, quick start, custom-section docs) and AGENTS.md Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 16 +++++--- README.md | 35 ++++++++++++---- .../Program.cs | 1 + .../TelemetryOptionsTests.cs | 4 +- ...lemetryServiceCollectionExtensionsTests.cs | 41 +++++++++++++++++++ .../TelemetryOptions.cs | 6 +-- .../TelemetryServiceCollectionExtensions.cs | 29 ++++++++----- 7 files changed, 103 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 39c6ce5..dee5b97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,14 +26,18 @@ release-notes/ # v{VERSION}.md per release ```csharp // IServiceCollection extensions -services.AddTelemetry(configuration); // binds "Telemetry" section -services.AddTelemetry(configuration, o => { ... }); // bind + code callback (combined) -services.AddTelemetry(o => { o.Enabled = true; o.Endpoint = new Uri("..."); }); +services.AddTelemetry(configuration); // binds "Telemetry" section +services.AddTelemetry(configuration, "CustomSection"); // custom section name +services.AddTelemetry(configuration, o => { ... }); // bind + code callback (combined) +services.AddTelemetry(configuration, o => { ... }, "Sec"); // combined + custom section +services.AddTelemetry(o => { o.Endpoint = new Uri("..."); }); ``` -`TelemetryOptions` is the single configuration model. `Enabled = false` is the -safe default; `AddTelemetry()` is a no-op when disabled. `Endpoint` is -`[Required]` and validated at registration time when `Enabled = true`. +`TelemetryOptions` is the single configuration model. `Enabled` defaults to +`true`; set it to `false` to make `AddTelemetry()` a no-op. `Endpoint` is +`[Required]` and validated at registration time when `Enabled = true`. The +configuration section name (`Telemetry`) is overridable via the `sectionName` +parameter on the `IConfiguration` overloads. ## Build & test diff --git a/README.md b/README.md index e4a829a..e9dac0b 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,14 @@ builder.Services.AddTelemetry(builder.Configuration); ```json { "Telemetry": { - "Enabled": true, "Endpoint": "http://localhost:4318", "ServiceName": "my-api" } } ``` -That's it โ€” tracing, metrics and logging are exported via OTLP. +That's it โ€” tracing, metrics and logging are exported via OTLP. Telemetry is +enabled by default; set `"Enabled": false` to turn it off. --- @@ -61,7 +61,7 @@ All options live under the `Telemetry` key in `appsettings.json`. | Property | Type | Default | Description | |---|---|---|---| -| `Enabled` | `bool` | `false` | Must be `true` to activate telemetry. | +| `Enabled` | `bool` | `true` | Set to `false` to disable telemetry (no OpenTelemetry services are registered). | | `Endpoint` | `Uri` | *(required)* | OTLP collector endpoint, e.g. `http://localhost:4318`. | | `Headers` | `string` | `""` | Exporter headers. Format: `key1=value1,key2=value2`. | | `Protocol` | `string` | `HttpProtobuf` | `HttpProtobuf` (port 4318) or `Grpc` (port 4317). | @@ -84,6 +84,25 @@ All options live under the `Telemetry` key in `appsettings.json`. > > For every key with its default value, see the [Full configuration reference](#-full-configuration-reference) below. +### Custom section name + +The section defaults to `Telemetry`, but you can bind any section by passing its name: + +```csharp +builder.Services.AddTelemetry(builder.Configuration, "MyTelemetry"); +// or together with a code callback: +builder.Services.AddTelemetry(builder.Configuration, o => { /* ... */ }, "MyTelemetry"); +``` + +```json +{ + "MyTelemetry": { + "Endpoint": "http://localhost:4318", + "ServiceName": "my-api" + } +} +``` + --- ## ๐Ÿงฉ Code Configuration @@ -172,12 +191,12 @@ o.ConfigureTracing = tracing => tracing.AddSource("MyApp"); ## ๐Ÿ“‹ Full configuration reference -Every key with its **default** value (only `Enabled` and `Endpoint` are required to get started): +Every key with its **default** value (only `Endpoint` is required to get started โ€” telemetry is enabled by default): ```jsonc { "Telemetry": { - "Enabled": false, // master switch โ€” set true to activate + "Enabled": true, // master switch โ€” set false to disable "Endpoint": "http://localhost:4318", // OTLP collector endpoint (required) "Headers": "", // exporter headers: "key1=value1,key2=value2" "Protocol": "HttpProtobuf", // "HttpProtobuf" (4318) or "Grpc" (4317) @@ -260,7 +279,7 @@ gRPC endpoint is exposed on NodePort `31889` (Helm) or host port `31889` (Docker Traces, metrics and logs from the sample app shown live in the Aspire Dashboard UI: -![.NET Aspire Dashboard](./assets/Aspire-Dashboard.webp) +![](./assets/Aspire-Dashboard.webp) ### Jaeger โ€” `appsettings.jaeger.json` @@ -275,7 +294,7 @@ Traces, metrics and logs from the sample app shown live in the Aspire Dashboard Traces from the sample app shown in the Jaeger UI: -![Jaeger](./assets/Jaeger.webp) +![](./assets/Jaeger.webp) ### OpenObserve โ€” HTTP/protobuf โ€” `appsettings.openobserve-http.json` @@ -291,7 +310,7 @@ Traces from the sample app shown in the Jaeger UI: The same telemetry explored in the OpenObserve UI: -![OpenObserve](./assets/OpenObserve.webp) +![](./assets/OpenObserve.webp) --- diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs b/src/OpenTelemetryExtension.Configuration.Sample/Program.cs index de22d03..622506e 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample/Program.cs @@ -46,6 +46,7 @@ public static void Main(string[] args) logger.LogWarning("Get Weatherforecast endpoint called"); await Task.Delay(TimeSpan.FromSeconds(10)); + logger.LogWarning("Demo response delay of 10 seconds completed"); var entities = await db.WeatherForecasts.ToListAsync(); return Results.Ok(entities); diff --git a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs index 77abbce..4dac54a 100644 --- a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs +++ b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs @@ -11,8 +11,8 @@ public void Defaults_SectionName_IsCorrect() => Assert.Equal("Telemetry", TelemetryOptions.SectionName); [Fact] - public void Defaults_Enabled_IsFalse() - => Assert.False(new TelemetryOptions().Enabled); + public void Defaults_Enabled_IsTrue() + => Assert.True(new TelemetryOptions().Enabled); [Fact] public void Defaults_Endpoint_IsNull() diff --git a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs index 434185c..d838d2f 100644 --- a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs +++ b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs @@ -532,6 +532,47 @@ public void AddTelemetry_IConfigurationWithNullConfigure_DoesNotThrow() Assert.Null(ex); } + [Fact] + public void AddTelemetry_IConfiguration_CustomSectionName_Binds() + { + var config = BuildConfig(new Dictionary + { + ["MyTelemetry:Enabled"] = "true", + ["MyTelemetry:Endpoint"] = "http://localhost:4318", + }); + var services = NewServices(); + + var ex = Record.Exception(() => services.AddTelemetry(config, "MyTelemetry")); + + Assert.Null(ex); + } + + [Fact] + public void AddTelemetry_IConfiguration_CustomSectionNameWithConfigure_Binds() + { + var invoked = false; + var config = BuildConfig(new Dictionary + { + ["MyTelemetry:Enabled"] = "true", + ["MyTelemetry:Endpoint"] = "http://localhost:4318", + }); + var services = NewServices(); + + services.AddTelemetry(config, o => o.ConfigureTracing = _ => invoked = true, "MyTelemetry"); + services.BuildServiceProvider().GetService(); + + Assert.True(invoked); + } + + [Fact] + public void AddTelemetry_IConfiguration_MissingCustomSection_Throws() + { + var config = BuildConfig(new Dictionary { ["Telemetry:Enabled"] = "true" }); + var services = NewServices(); + + Assert.Throws(() => services.AddTelemetry(config, "MyTelemetry")); + } + // โ”€โ”€ ShouldInstrument (request filter logic) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ [Theory] diff --git a/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs b/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs index c37f798..7e326ad 100644 --- a/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs +++ b/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs @@ -15,7 +15,7 @@ namespace OpenTelemetryExtension.Configuration; /// /// "Telemetry": { /// "Endpoint": "http://localhost:4318", -/// "Enabled": false, +/// "Enabled": true, /// "Headers": "", /// "Protocol": "HttpProtobuf", /// "ServiceName": null, @@ -43,9 +43,9 @@ public sealed class TelemetryOptions /// /// Whether telemetry is enabled at all. /// If false, no OpenTelemetry services are registered. - /// Default: false โ€” explicit opt-in required. + /// Default: true. /// - public bool Enabled { get; set; } = false; + public bool Enabled { get; set; } = true; /// /// OTLP endpoint for logs, traces and metrics. diff --git a/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs b/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs index 8a26a71..08c54d1 100644 --- a/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs +++ b/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs @@ -20,7 +20,7 @@ namespace OpenTelemetryExtension.Configuration; /// /// Two registration overloads are provided: /// -/// โ€” binds from appsettings.json. +/// โ€” binds from appsettings.json. /// โ€” configures inline in code. /// /// When is false no OpenTelemetry @@ -34,19 +34,23 @@ public static class TelemetryServiceCollectionExtensions /// /// The to add services to. /// - /// The application configuration. Must contain a Telemetry section - /// matching . See . + /// The application configuration. Must contain a section matching + /// . See . + /// + /// + /// The configuration section to bind. Defaults to + /// (Telemetry) when null or empty. /// /// The original for chaining. /// - /// Thrown when the Telemetry configuration section is missing or empty. + /// Thrown when the configuration section is missing or empty. /// /// /// Thrown when the bound fail validation, /// e.g. when is null. /// - public static IServiceCollection AddTelemetry(this IServiceCollection services, IConfiguration configuration) - => services.AddTelemetry(configuration, configure: null); + public static IServiceCollection AddTelemetry(this IServiceCollection services, IConfiguration configuration, string? sectionName = null) + => services.AddTelemetry(configuration, configure: null, sectionName); /// /// Registers OpenTelemetry tracing, metrics and logging by binding the @@ -64,9 +68,13 @@ public static IServiceCollection AddTelemetry(this IServiceCollection services, /// An optional delegate applied after binding, e.g. to register additional /// instrumentation via . /// + /// + /// The configuration section to bind. Defaults to + /// (Telemetry) when null or empty. + /// /// The original for chaining. /// - /// Thrown when the Telemetry configuration section is missing or empty. + /// Thrown when the configuration section is missing or empty. /// /// /// Thrown when the resulting fail validation, @@ -80,12 +88,13 @@ public static IServiceCollection AddTelemetry(this IServiceCollection services, /// }); /// /// - public static IServiceCollection AddTelemetry(this IServiceCollection services, IConfiguration configuration, Action? configure) + public static IServiceCollection AddTelemetry(this IServiceCollection services, IConfiguration configuration, Action? configure, string? sectionName = null) { - var section = configuration.GetSection(TelemetryOptions.SectionName); + var name = string.IsNullOrWhiteSpace(sectionName) ? TelemetryOptions.SectionName : sectionName!; + var section = configuration.GetSection(name); if (!section.Exists()) { - throw new InvalidOperationException($"Configuration section '{TelemetryOptions.SectionName}' is missing."); + throw new InvalidOperationException($"Configuration section '{name}' is missing."); } var options = new TelemetryOptions(); From 67f4566f2effe66de1a525261fabb5b3fb93aaf8 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:37:56 +0200 Subject: [PATCH 02/14] release: v2.0.0 Added: - AdditionalTracingSources / AdditionalMeters config options (AddSource/AddMeter from appsettings.json, no code required) - Optional sectionName parameter on AddTelemetry(IConfiguration) overloads Changed: - Enabled now defaults to true - netstandard2.0 build no longer references OpenTelemetry.Instrumentation.AspNetCore (web-only target); WPF/console consumers stay lean - Updated Sample project NuGet packages to latest Removed: - EnableSqlClientInstrumentation and the built-in SqlClient instrumentation dependency; database instrumentation is now added via ConfigureTracing or AdditionalTracingSources (BREAKING) Co-Authored-By: Claude Opus 4.8 --- OpenTelemetryExtension.slnx | 4 +- README.md | 156 ++++++++++++++++-- release-notes/v2.0.0.md | 20 +++ ...metryExtension.Configuration.Sample.csproj | 12 +- .../Program.cs | 12 +- .../appsettings.json | 4 +- .../TelemetryOptionsTests.cs | 13 +- ...lemetryServiceCollectionExtensionsTests.cs | 47 ++++-- .../appsettings.json | 1 - ...penTelemetryExtension.Configuration.csproj | 13 +- .../TelemetryOptions.cs | 26 ++- .../TelemetryServiceCollectionExtensions.cs | 17 +- 12 files changed, 273 insertions(+), 52 deletions(-) create mode 100644 release-notes/v2.0.0.md diff --git a/OpenTelemetryExtension.slnx b/OpenTelemetryExtension.slnx index 0c90d06..f20ef9d 100644 --- a/OpenTelemetryExtension.slnx +++ b/OpenTelemetryExtension.slnx @@ -14,8 +14,8 @@ - - + + diff --git a/README.md b/README.md index e9dac0b..48606ce 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,23 @@ Configurable OpenTelemetry setup for .NET applications providing **tracing, metr - **One-call setup** โ€” tracing, metrics and logging via a single `AddTelemetry()`, configured from `appsettings.json` or code - **All three signals over OTLP** โ€” HTTP/protobuf or gRPC, to any OTLP-compatible backend -- **Built-in instrumentation** โ€” ASP.NET Core, `HttpClient`, SQL Client and .NET runtime metrics, each toggleable -- **Sensible defaults** โ€” sampling, health-check path exclusion and exception recording work out of the box +- **Built-in instrumentation** โ€” `HttpClient`, SQL Client and .NET runtime metrics everywhere; ASP.NET Core instrumentation on web targets โ€” each toggleable +- **Sensible defaults** โ€” configurable sampling, health-check path exclusion and exception recording work out of the box - **Startup validation** โ€” misconfiguration fails fast with a clear error - **Extensible** โ€” `ConfigureTracing`/`ConfigureMetrics`/`ConfigureLogging` hooks for custom sources, meters and providers -- **Broad target support** โ€” `netstandard2.0` and `net10.0` +- **Works on any .NET** โ€” ASP.NET Core, WPF, console and more; the `netstandard2.0` build pulls **no** ASP.NET Core dependencies --- +## โœ… Requirements + +- A .NET target compatible with **`netstandard2.0`** โ€” i.e. .NET Framework 4.6.1+, .NET 6/8/9/10, or .NET 10 directly. +- An **OTLP-compatible backend** to receive the telemetry (collector, Jaeger, OpenObserve, the .NET Aspire Dashboard, โ€ฆ). See [Running Locally with a Backend](#-running-locally-with-a-backend). +- ASP.NET Core instrumentation requires a **web target** (`net10.0` build); it is not included in the `netstandard2.0` build used by WPF/console apps. + +--- + ## ๐Ÿ“ฆ Installation ```bash @@ -50,8 +58,10 @@ builder.Services.AddTelemetry(builder.Configuration); } ``` -That's it โ€” tracing, metrics and logging are exported via OTLP. Telemetry is -enabled by default; set `"Enabled": false` to turn it off. +That's it โ€” tracing, metrics and logging are exported via OTLP. + +> You need an **OTLP-compatible backend** listening at `Endpoint`. No backend yet? +> See [Running Locally with a Backend](#-running-locally-with-a-backend) for one-command setups. --- @@ -67,13 +77,14 @@ All options live under the `Telemetry` key in `appsettings.json`. | `Protocol` | `string` | `HttpProtobuf` | `HttpProtobuf` (port 4318) or `Grpc` (port 4317). | | `ServiceName` | `string?` | `null` | Service name shown in the backend. | | `ResourceAttributes` | `object` | `{}` | Extra resource attributes, e.g. `{ "deployment.environment": "production", "team": "backend" }`. | +| `AdditionalTracingSources` | `string[]` | `[]` | Extra `ActivitySource` names to collect (e.g. `"Npgsql"`, your own app sources) โ€” registered via `AddSource`. | +| `AdditionalMeters` | `string[]` | `[]` | Extra `Meter` names to collect (e.g. `"MyApp.Orders"`) โ€” registered via `AddMeter`. | | `SampleRatio` | `double` | `1.0` | Fraction of traces to sample. `0.1` = 10%, `1.0` = all. | | `EnableTracing` | `bool` | `true` | Enables distributed tracing. | | `EnableMetrics` | `bool` | `true` | Enables metrics collection. | | `EnableLogging` | `bool` | `true` | Enables log export via OTLP. | | `EnableAspNetCoreInstrumentation` | `bool` | `true` | Instruments incoming HTTP requests. | | `EnableHttpClientInstrumentation` | `bool` | `true` | Instruments outgoing `HttpClient` requests. | -| `EnableSqlClientInstrumentation` | `bool` | `false` | Instruments SQL calls. Opt-in โ€” not all apps use SQL. | | `EnableRuntimeInstrumentation` | `bool` | `true` | Collects GC, memory and thread pool metrics. | | `RecordExceptions` | `bool` | `true` | Records exception stack traces on spans. | | `ExcludedPaths` | `string[]` | `["/health"]` | Paths excluded from tracing. | @@ -187,9 +198,130 @@ o.ConfigureTracing = tracing => tracing.AddSource("MyApp"); > gave the `Meter`/`ActivitySource` โ€” that name is how OpenTelemetry routes the > data. +### Databases + +Database instrumentation is **not** built in โ€” it depends entirely on your +driver, so it is added through the `ConfigureTracing` hook. This keeps the +package free of database-specific dependencies; you only pull in what you use. + +```csharp +// SQL Server โ€” install the package, then register it: +// dotnet add package OpenTelemetry.Instrumentation.SqlClient +o.ConfigureTracing = t => t.AddSqlClientInstrumentation(); + +// EF Core โ€” dedicated instrumentation package: +// dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore +o.ConfigureTracing = t => t.AddEntityFrameworkCoreInstrumentation(); + +// Drivers with a built-in ActivitySource โ€” just register its name: +o.ConfigureTracing = t => t.AddSource("Npgsql"); // PostgreSQL (Npgsql) +o.ConfigureTracing = t => t.AddSource("MySqlConnector"); // MySQL (MySqlConnector) +``` + +Oracle (`Oracle.ManagedDataAccess.Core`) emits an `ActivitySource` in recent +versions and is wired up the same way via `AddSource(...)`. + +**No code for source-based drivers:** if the driver only needs an `ActivitySource` +name (Npgsql, MySqlConnector, Oracle, your own app sources), you can enable it +purely from `appsettings.json` โ€” no `ConfigureTracing` call required: + +```json +{ + "Telemetry": { + "Endpoint": "http://localhost:4318", + "AdditionalTracingSources": [ "Npgsql", "MyApp" ], + "AdditionalMeters": [ "MyApp.Orders" ] + } +} +``` + +> Package-based instrumentation (SQL Server, EF Core) still needs the one-line +> `ConfigureTracing` call above, because it requires its NuGet package โ€” a config +> string alone can't pull in a dependency. + +**Toggling SQL instrumentation from `appsettings.json`** + +Because `EnableSqlClientInstrumentation` is not part of `TelemetryOptions` (the +package is optional), you can add it as a custom key and read it in the callback: + +`appsettings.json`: + +```json +{ + "Telemetry": { + "Endpoint": "http://localhost:4318", + "ServiceName": "my-api", + "EnableSqlClientInstrumentation": true + } +} +``` + +`Program.cs`: + +```csharp +builder.Services.AddTelemetry(builder.Configuration, opt => + opt.ConfigureTracing = tracing => + { + if (builder.Configuration.GetValue("Telemetry:EnableSqlClientInstrumentation")) + { + // Microsoft SQL Server / System.Data.SqlClient + // NuGet: OpenTelemetry.Instrumentation.SqlClient + tracing.AddSqlClientInstrumentation(sql => + sql.RecordException = opt.RecordExceptions); + + // PostgreSQL (Npgsql) + // NuGet: OpenTelemetry.Instrumentation.Npgsql + tracing.AddNpgsql(); + + // MySQL (MySqlConnector) + // NuGet: OpenTelemetry.Instrumentation.MySqlData + tracing.AddMySqlDataInstrumentation(); + } + }); +``` + +This keeps the on/off switch in config while the package dependency stays explicit in code. + +--- + +## ๐Ÿ–ฅ๏ธ Using outside the Generic Host + +`AddTelemetry()` works with **any** `IServiceCollection` โ€” ASP.NET Core, WPF, +WinForms, console, MAUI/WinUI, UWP, worker services, etc. + +**With the Generic Host** (recommended for desktop/console โ€” `Host.CreateApplicationBuilder()`), +the providers start and flush automatically: + +```csharp +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddTelemetry(builder.Configuration); +using var host = builder.Build(); +await host.RunAsync(); // telemetry starts here and flushes on shutdown +``` + +**Without a host** (e.g. a bare `ServiceCollection` in UWP or a minimal app), +build the provider and **dispose it on exit** so buffered telemetry is flushed: + +```csharp +var services = new ServiceCollection(); +services.AddTelemetry(o => +{ + o.Endpoint = new Uri("http://localhost:4318"); + o.ServiceName = "my-desktop-app"; +}); + +var provider = services.BuildServiceProvider(); +// ... app runs ... +provider.Dispose(); // flushes traces, metrics and logs +``` + +> ASP.NET Core instrumentation is only in the `net10.0` build. On the +> `netstandard2.0` build (WPF/WinForms/console/UWP) it is simply absent โ€” +> setting `EnableAspNetCoreInstrumentation` there is a harmless no-op. + --- -## ๐Ÿ“‹ Full configuration reference +## ๐Ÿ“‹ Full Configuration Reference Every key with its **default** value (only `Endpoint` is required to get started โ€” telemetry is enabled by default): @@ -202,13 +334,14 @@ Every key with its **default** value (only `Endpoint` is required to get started "Protocol": "HttpProtobuf", // "HttpProtobuf" (4318) or "Grpc" (4317) "ServiceName": null, // service name shown in the backend "ResourceAttributes": {}, // extra attributes, e.g. { "deployment.environment": "production" } + "AdditionalTracingSources": [], // extra ActivitySource names, e.g. [ "Npgsql", "MyApp" ] + "AdditionalMeters": [], // extra Meter names, e.g. [ "MyApp.Orders" ] "SampleRatio": 1.0, // 0.1 = 10% of traces, 1.0 = all "EnableTracing": true, // distributed tracing "EnableMetrics": true, // metrics collection "EnableLogging": true, // log export via OTLP "EnableAspNetCoreInstrumentation": true, // incoming HTTP requests "EnableHttpClientInstrumentation": true, // outgoing HttpClient requests - "EnableSqlClientInstrumentation": false, // SQL calls (opt-in) "EnableRuntimeInstrumentation": true, // GC, memory, thread pool metrics "RecordExceptions": true, // exception stack traces on spans "ExcludedPaths": [ "/health" ], // paths excluded from tracing @@ -222,8 +355,9 @@ Every key with its **default** value (only `Endpoint` is required to get started ## ๐Ÿ”Œ Running Locally with a Backend -The [sample project](./src/OpenTelemetryExtension.Configuration.Sample) ships a -ready-to-run configuration for every supported backend. Each backend has: +The [sample project](./src/OpenTelemetryExtension.Configuration.Sample) ships +ready-to-run configurations for several popular backends (the three below are +documented in full; more start scripts live in [`infrastructure/`](./infrastructure)). Each backend has: 1. an **infrastructure start script** (Docker Compose or Helm) in [`infrastructure/`](./infrastructure), 2. a **launch profile** that selects the matching `appsettings..json`, @@ -258,7 +392,7 @@ ready-to-run configuration for every supported backend. Each backend has: --- -## โš™๏ธ Backend Configurations +## ๐Ÿ“ Sample Backend Configurations These are the exact `appsettings..json` files used by the sample's launch profiles. diff --git a/release-notes/v2.0.0.md b/release-notes/v2.0.0.md new file mode 100644 index 0000000..658d04a --- /dev/null +++ b/release-notes/v2.0.0.md @@ -0,0 +1,20 @@ +# v2.0.0 + +_Released: 2026-06-10_ + +## Added + +- **`AdditionalTracingSources`** โ€” extra `ActivitySource` names to collect traces from, registered via `AddSource`. Enables source-based instrumentation (e.g. `Npgsql`, `MySqlConnector`, your own app sources) purely from `appsettings.json` โ€” no code required. +- **`AdditionalMeters`** โ€” extra `Meter` names to collect metrics from, registered via `AddMeter`. +- **Custom configuration section** โ€” the `AddTelemetry(IConfiguration)` overloads accept an optional `sectionName` parameter to bind a section other than the default `"Telemetry"`. + +## Changed + +- **`Enabled` now defaults to `true`** โ€” telemetry is on out of the box; set `"Enabled": false` to turn it off. +- **The `netstandard2.0` build no longer pulls in ASP.NET Core dependencies** โ€” `OpenTelemetry.Instrumentation.AspNetCore` is only referenced by the `net10.0` target, so WPF and console consumers stay lean. +- The local OpenObserve backend setups (Helm chart, Docker Compose) ship with a fixed Basic Auth header so the README examples work copy-paste. +- Updated all NuGet dependencies to their latest versions. + +## Removed + +- **`EnableSqlClientInstrumentation`** and the built-in `OpenTelemetry.Instrumentation.SqlClient` dependency. Database instrumentation is driver-specific and is now added via the `ConfigureTracing` hook (e.g. `AddSqlClientInstrumentation()`, `AddEntityFrameworkCoreInstrumentation()`) or, for drivers with a built-in `ActivitySource`, via `AdditionalTracingSources` โ€” see the README's *Databases* section for migration examples. diff --git a/src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj b/src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj index a6ed828..cbb2135 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj +++ b/src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj @@ -10,15 +10,15 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs b/src/OpenTelemetryExtension.Configuration.Sample/Program.cs index 622506e..65af729 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample/Program.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using OpenTelemetry.Trace; namespace OpenTelemetryExtension.Configuration.Sample; @@ -9,7 +10,16 @@ public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); - builder.Services.AddTelemetry(builder.Configuration); + + // EnableSqlClientInstrumentation is a custom key โ€” SqlClient is optional and not part of TelemetryOptions. + builder.Services.AddTelemetry(builder.Configuration, o => + o.ConfigureTracing = tracing => + { + if (builder.Configuration.GetValue("Telemetry:EnableSqlClientInstrumentation")) + { + tracing.AddSqlClientInstrumentation(sql => sql.RecordException = o.RecordExceptions); + } + }); builder.Services.AddHealthChecks(); builder.Services.AddAuthorization(); diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.json b/src/OpenTelemetryExtension.Configuration.Sample/appsettings.json index b466d98..c44926b 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.json +++ b/src/OpenTelemetryExtension.Configuration.Sample/appsettings.json @@ -10,8 +10,8 @@ "EnableLogging": true, "EnableAspNetCoreInstrumentation": true, "EnableHttpClientInstrumentation": true, - "EnableSqlClientInstrumentation": true, - "EnableRuntimeInstrumentation": true + "EnableRuntimeInstrumentation": true, + "EnableSqlClientInstrumentation": true }, "Logging": { "LogLevel": { diff --git a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs index 4dac54a..7dde9cb 100644 --- a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs +++ b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryOptionsTests.cs @@ -50,10 +50,6 @@ public void Defaults_EnableAspNetCoreInstrumentation_IsTrue() public void Defaults_EnableHttpClientInstrumentation_IsTrue() => Assert.True(new TelemetryOptions().EnableHttpClientInstrumentation); - [Fact] - public void Defaults_EnableSqlClientInstrumentation_IsFalse() - => Assert.False(new TelemetryOptions().EnableSqlClientInstrumentation); - [Fact] public void Defaults_EnableRuntimeInstrumentation_IsTrue() => Assert.True(new TelemetryOptions().EnableRuntimeInstrumentation); @@ -74,6 +70,14 @@ public void Defaults_RecordExceptions_IsTrue() public void Defaults_ExcludedPaths_ContainsHealth() => Assert.Equal(["/health"], new TelemetryOptions().ExcludedPaths); + [Fact] + public void Defaults_AdditionalTracingSources_IsEmpty() + => Assert.Empty(new TelemetryOptions().AdditionalTracingSources); + + [Fact] + public void Defaults_AdditionalMeters_IsEmpty() + => Assert.Empty(new TelemetryOptions().AdditionalMeters); + [Fact] public void Defaults_IncludeScopes_IsTrue() => Assert.True(new TelemetryOptions().IncludeScopes); @@ -162,7 +166,6 @@ public void Property_ConfigureLogging_CanBeSet() public void Validation_Passes_WhenEndpointIsSet() { var o = new TelemetryOptions { Endpoint = new Uri("http://localhost:4318") }; - // Should not throw Validator.ValidateObject(o, new ValidationContext(o), validateAllProperties: true); } diff --git a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs index d838d2f..2f21c69 100644 --- a/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs +++ b/src/OpenTelemetryExtension.Configuration.Tests/TelemetryServiceCollectionExtensionsTests.cs @@ -10,9 +10,7 @@ public class TelemetryServiceCollectionExtensionsTests private static IServiceCollection NewServices() => new ServiceCollection(); - // Forces the lazy OpenTelemetry configuration lambdas (resource builder, - // exporters, instrumentation) to actually execute by resolving the signal - // providers from the built container. + // Resolves signal providers to force lazy OTel builder lambdas to execute. private static void BuildAndResolveProviders(IServiceCollection services) { var provider = services.BuildServiceProvider(); @@ -90,8 +88,6 @@ public void AddTelemetry_IConfiguration_NoThrowWhenDisabledAndEndpointMissing() [Fact] public void AddTelemetry_IConfiguration_MapsAllScalarProperties() { - // Verify that the IConfiguration overload forwards all options to the Action overload - // by checking that registration succeeds with every supported appsettings key. var config = ValidConfig(new Dictionary { ["Telemetry:Enabled"] = "true", @@ -104,7 +100,6 @@ public void AddTelemetry_IConfiguration_MapsAllScalarProperties() ["Telemetry:EnableLogging"] = "true", ["Telemetry:EnableAspNetCoreInstrumentation"] = "true", ["Telemetry:EnableHttpClientInstrumentation"] = "false", - ["Telemetry:EnableSqlClientInstrumentation"] = "false", ["Telemetry:EnableRuntimeInstrumentation"] = "true", ["Telemetry:RecordExceptions"] = "false", ["Telemetry:ExcludedPaths:0"] = "/health", @@ -129,7 +124,6 @@ public void AddTelemetry_IConfiguration_Disabled_RegistersNoOtel() var services = NewServices(); services.AddTelemetry(config); - // When disabled, no OpenTelemetry SDK service (TracerProvider) should be registered. var provider = services.BuildServiceProvider(); var tracer = provider.GetService(); Assert.Null(tracer); @@ -150,7 +144,7 @@ public void AddTelemetry_Action_ThrowsWhenEndpointIsNull() { var services = NewServices(); Assert.Throws(() => - services.AddTelemetry(o => { /* Endpoint left null */ })); + services.AddTelemetry(o => { })); } [Fact] @@ -232,7 +226,6 @@ public void AddTelemetry_InstrumentationFlagsOff_DoesNotThrow() { o.EnableAspNetCoreInstrumentation = false; o.EnableHttpClientInstrumentation = false; - o.EnableSqlClientInstrumentation = false; o.EnableRuntimeInstrumentation = false; })); @@ -249,7 +242,6 @@ public void AddTelemetry_AllInstrumentationFlagsOn_DoesNotThrow() { o.EnableAspNetCoreInstrumentation = true; o.EnableHttpClientInstrumentation = true; - o.EnableSqlClientInstrumentation = true; o.EnableRuntimeInstrumentation = true; o.RecordExceptions = true; o.ExcludedPaths = ["/health"]; @@ -478,6 +470,40 @@ public void AddTelemetry_IConfiguration_AllSignalsDisabled_DoesNotThrow() Assert.Null(ex); } + // โ”€โ”€ Additional sources & meters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + [Fact] + public void AddTelemetry_AdditionalTracingSourcesAndMeters_DoesNotThrow() + { + var services = NewServices(); + services.AddTelemetry(MinimalConfigure(o => + { + o.AdditionalTracingSources = ["Npgsql", "MyApp"]; + o.AdditionalMeters = ["MyApp.Orders"]; + })); + + var ex = Record.Exception(() => BuildAndResolveProviders(services)); + + Assert.Null(ex); + } + + [Fact] + public void AddTelemetry_IConfiguration_BindsAdditionalSourcesAndMeters() + { + var config = BuildConfig(new Dictionary + { + ["Telemetry:Enabled"] = "true", + ["Telemetry:Endpoint"] = "http://localhost:4318", + ["Telemetry:AdditionalTracingSources:0"] = "Npgsql", + ["Telemetry:AdditionalMeters:0"] = "MyApp.Orders", + }); + var services = NewServices(); + + var ex = Record.Exception(() => services.AddTelemetry(config)); + + Assert.Null(ex); + } + // โ”€โ”€ IConfiguration + code callback (combined) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ [Fact] @@ -513,7 +539,6 @@ public void AddTelemetry_IConfigurationWithConfigure_CallbackOverridesBoundValue [Fact] public void AddTelemetry_IConfigurationWithConfigure_CodeOnlyOptionAppliedOnTopOfConfig() { - // Endpoint comes from config (JSON), ConfigureMetrics comes from code. var invoked = false; var config = ValidConfig(new Dictionary { ["Telemetry:Enabled"] = "true" }); var services = NewServices(); diff --git a/src/OpenTelemetryExtension.Configuration.Tests/appsettings.json b/src/OpenTelemetryExtension.Configuration.Tests/appsettings.json index b3a02ea..7979492 100644 --- a/src/OpenTelemetryExtension.Configuration.Tests/appsettings.json +++ b/src/OpenTelemetryExtension.Configuration.Tests/appsettings.json @@ -12,7 +12,6 @@ "EnableLogging": true, "EnableAspNetCoreInstrumentation": true, "EnableHttpClientInstrumentation": true, - "EnableSqlClientInstrumentation": false, "EnableRuntimeInstrumentation": true, "SampleRatio": 1.0, "RecordExceptions": true, diff --git a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj index 88e147d..d2230ae 100644 --- a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj +++ b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj @@ -2,7 +2,7 @@ netstandard2.0;net10.0 - 1.1.1 + 2.0.0 false true @@ -10,7 +10,7 @@ Thorsten Alpers Copyright ยฉ $([System.DateTime]::Now.Year) Thorsten Alpers Configurable OpenTelemetry setup for .NET applications providing tracing, metrics, and logging via OTLP, configurable through code or appsettings.json. - OpenTelemetry;Otel;Tracing;Traces;Metrics;Logs;Logging;OTLP;ASP.NET Core;appsettings;Configuration;Instrumentation;Telemetry;HttpClient;SqlClient;gRPC + OpenTelemetry;Otel;Tracing;Traces;Metrics;Logs;Logging;OTLP;ASP.NET Core;WPF;WinForms;Console;UWP;MAUI;appsettings;Configuration;Instrumentation;Telemetry;HttpClient;SqlClient;gRPC https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration git @@ -24,13 +24,18 @@ + - - + + + + + diff --git a/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs b/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs index 7e326ad..d747d0f 100644 --- a/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs +++ b/src/OpenTelemetryExtension.Configuration/TelemetryOptions.cs @@ -20,13 +20,14 @@ namespace OpenTelemetryExtension.Configuration; /// "Protocol": "HttpProtobuf", /// "ServiceName": null, /// "ResourceAttributes": { "deployment.environment": "production", "team": "backend" }, +/// "AdditionalTracingSources": [ "Npgsql" ], +/// "AdditionalMeters": [ "MyApp.Orders" ], /// "SampleRatio": 1.0, /// "EnableTracing": true, /// "EnableMetrics": true, /// "EnableLogging": true, /// "EnableAspNetCoreInstrumentation": true, /// "EnableHttpClientInstrumentation": true, -/// "EnableSqlClientInstrumentation": false, /// "EnableRuntimeInstrumentation": true, /// "RecordExceptions": true, /// "ExcludedPaths": ["/health"], @@ -83,6 +84,23 @@ public sealed class TelemetryOptions /// public Dictionary ResourceAttributes { get; set; } = []; + /// + /// Additional ActivitySource names to collect traces from, registered via + /// AddSource. Use this to enable source-based instrumentation (e.g. database + /// drivers like Npgsql, MySqlConnector) or your own application sources + /// without writing code. + /// Example: [ "Npgsql", "MyApp" ] + /// Default: [] + /// + public string[] AdditionalTracingSources { get; set; } = []; + + /// + /// Additional Meter names to collect metrics from, registered via AddMeter. + /// Example: [ "MyApp.Orders" ] + /// Default: [] + /// + public string[] AdditionalMeters { get; set; } = []; + /// /// Fraction of traces to sample. 1.0 samples everything, 0.1 samples 10%. /// Uses ParentBased(TraceIdRatioBased) sampler. @@ -121,12 +139,6 @@ public sealed class TelemetryOptions /// public bool EnableHttpClientInstrumentation { get; set; } = true; - /// - /// Whether SQL database calls via SqlClient are instrumented. - /// Default: false โ€” opt-in, as not all applications use SQL. - /// - public bool EnableSqlClientInstrumentation { get; set; } = false; - /// /// Whether .NET runtime metrics (GC, memory, thread pool) are collected. /// Default: true diff --git a/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs b/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs index 08c54d1..2c12786 100644 --- a/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs +++ b/src/OpenTelemetryExtension.Configuration/TelemetryServiceCollectionExtensions.cs @@ -4,7 +4,9 @@ namespace OpenTelemetryExtension.Configuration; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; +#if !NETSTANDARD2_0 using Microsoft.AspNetCore.Http; +#endif using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -177,6 +179,7 @@ private static void ConfigureTelemetry(IServiceCollection services, TelemetryOpt { tracing.SetSampler(new ParentBasedSampler(new TraceIdRatioBasedSampler(options.SampleRatio))); +#if !NETSTANDARD2_0 if (options.EnableAspNetCoreInstrumentation) { tracing.AddAspNetCoreInstrumentation(opt => @@ -188,15 +191,16 @@ private static void ConfigureTelemetry(IServiceCollection services, TelemetryOpt } }); } +#endif if (options.EnableHttpClientInstrumentation) { tracing.AddHttpClientInstrumentation(opt => opt.RecordException = options.RecordExceptions); } - if (options.EnableSqlClientInstrumentation) + foreach (var source in options.AdditionalTracingSources) { - tracing.AddSqlClientInstrumentation(opt => opt.RecordException = options.RecordExceptions); + tracing.AddSource(source); } options.ConfigureTracing?.Invoke(tracing); @@ -216,10 +220,12 @@ private static void ConfigureTelemetry(IServiceCollection services, TelemetryOpt { builder.WithMetrics(metrics => { +#if !NETSTANDARD2_0 if (options.EnableAspNetCoreInstrumentation) { metrics.AddAspNetCoreInstrumentation(); } +#endif if (options.EnableHttpClientInstrumentation) { @@ -231,6 +237,11 @@ private static void ConfigureTelemetry(IServiceCollection services, TelemetryOpt metrics.AddRuntimeInstrumentation(); } + foreach (var meter in options.AdditionalMeters) + { + metrics.AddMeter(meter); + } + options.ConfigureMetrics?.Invoke(metrics); metrics.AddOtlpExporter(exp => @@ -268,6 +279,7 @@ private static void ConfigureTelemetry(IServiceCollection services, TelemetryOpt } } +#if !NETSTANDARD2_0 internal static bool ShouldInstrument(PathString path, string[] excludedPaths) { return !excludedPaths.Any(p => path.StartsWithSegments(p)); @@ -277,4 +289,5 @@ internal static Func CreateRequestFilter(string[] excludedPat { return ctx => ShouldInstrument(ctx.Request.Path, excludedPaths); } +#endif } From 66a1e2130934cd084fd4187754d1ea72b22001f4 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:38:08 +0200 Subject: [PATCH 03/14] chore: move auto-release skill to user-global Claude config Relocated the auto-release skill from the repo (.claude/skills/auto-release) to the user-global ~/.claude/skills. The update check now inspects only the shipped library project instead of the whole solution, so Sample/Tests package bumps no longer trigger a release. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/auto-release/SKILL.md | 62 ------------------ .../scripts/check-otel-updates.sh | 18 ----- .../skills/auto-release/scripts/smoke-test.sh | 65 ------------------- .../auto-release/templates/release-notes.md | 21 ------ 4 files changed, 166 deletions(-) delete mode 100644 .claude/skills/auto-release/SKILL.md delete mode 100644 .claude/skills/auto-release/scripts/check-otel-updates.sh delete mode 100644 .claude/skills/auto-release/scripts/smoke-test.sh delete mode 100644 .claude/skills/auto-release/templates/release-notes.md diff --git a/.claude/skills/auto-release/SKILL.md b/.claude/skills/auto-release/SKILL.md deleted file mode 100644 index 150f189..0000000 --- a/.claude/skills/auto-release/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: auto-release -description: Prepare a new NuGet release of this repository. Use when the user wants to cut/prepare a release, bump the package version, update dependencies for a release, or open a release PR to master. Decides the next SemVer version automatically and only releases when OpenTelemetry packages have updates. ---- - -# Auto-release - -Prepare a new release of this NuGet package. You decide the next version -yourself. The NuGet publish itself is a **manual** GitHub Actions trigger -(`deploy-nuget.yml`, `workflow_dispatch`) and is NOT part of your job โ€” you -prepare the repository and open the PR. - -## Workflow - -1. **Check for OpenTelemetry NuGet updates first** - - Run the helper: `bash scripts/check-otel-updates.sh` - - Exit code **3** = no OpenTelemetry updates โ†’ **stop and do not release**. - - Exit code **0** = updates available โ†’ continue. - -2. **Determine current version & last tag** - - Read `` in `src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj`. - - Latest tag: `git tag --sort=-v:refname | head -1`. - -3. **Decide the next version (SemVer)** from `git log --oneline ..HEAD`: - - **MAJOR** breaking API changes ยท **MINOR** new features ยท **PATCH** fixes/docs/deps. - - Must be strictly greater than the current version. State it + a one-line reason. - -4. **Confirm clean tree** โ€” `git status --short`; if dirty, ask how to proceed. - -5. **Branch** โ€” `git checkout -b release/v`. - -6. **Update all NuGet packages** to latest, then `dotnet restore OpenTelemetryExtension.slnx`. - -7. **Build & test (green required)** - - `dotnet build OpenTelemetryExtension.slnx -c Release` - - `dotnet test OpenTelemetryExtension.slnx -c Release` - -8. **End-to-end smoke test** โ€” prove telemetry actually reaches a backend. - Requires a local Kubernetes cluster (k3s in WSL2) with Helm + kubectl. - OpenObserve is used because it has a real query API, so the test can - positively confirm ingested data. The helper starts OpenObserve via its Helm - chart, runs the sample, generates traffic and queries the API for records: - - `bash scripts/smoke-test.sh` - - Exit 0 = telemetry confirmed โ†’ continue. Non-zero = stop and report; do not - release if telemetry does not arrive. - -9. **Bump ``** in the csproj. - -10. **Extend docs** โ€” update `README.md` etc. for the changes/new dep versions. - -11. **Release notes** โ€” copy `templates/release-notes.md` to - `release-notes/v.md`, fill in the `{{VERSION}}`/`{{DATE}}` placeholders - and the **Added / Changed / Fixed / Removed** sections (omit empty ones). - -12. **Commit** (no tag) โ€” stage csproj(s), docs, release notes; message `release: v`. - -13. **Push & PR to master** - - `git push -u origin release/v` - - `gh pr create --base master --head release/v --title "release: v" --body ` - -14. **Report** the PR link and remind: after merge, trigger **Deploy Nuget** manually - (Actions โ†’ Deploy Nuget โ†’ Run workflow) โ€” it tags `v` and publishes. diff --git a/.claude/skills/auto-release/scripts/check-otel-updates.sh b/.claude/skills/auto-release/scripts/check-otel-updates.sh deleted file mode 100644 index 4e96609..0000000 --- a/.claude/skills/auto-release/scripts/check-otel-updates.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Lists outdated OpenTelemetry NuGet packages in the solution. -# Exit code 0 = updates available, 3 = nothing to release, 1 = error. -set -euo pipefail - -SLN="OpenTelemetryExtension.slnx" - -echo "Checking for outdated OpenTelemetry packages in $SLN ..." -outdated="$(dotnet list "$SLN" package --outdated 2>/dev/null | grep -i 'OpenTelemetry' || true)" - -if [[ -z "$outdated" ]]; then - echo "No OpenTelemetry package updates available โ€” nothing to release." - exit 3 -fi - -echo "OpenTelemetry updates available:" -echo "$outdated" -exit 0 diff --git a/.claude/skills/auto-release/scripts/smoke-test.sh b/.claude/skills/auto-release/scripts/smoke-test.sh deleted file mode 100644 index a2ed51e..0000000 --- a/.claude/skills/auto-release/scripts/smoke-test.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -# Smoke-test: start OpenObserve via Helm, run the sample, generate traffic and -# verify that telemetry actually arrived by querying the OpenObserve API. -# -# OpenObserve is used because it exposes a real query API, so we can positively -# prove that data was ingested (not just that the endpoint was reachable). -# -# Usage: bash smoke-test.sh -# -# Requires a local Kubernetes cluster (e.g. k3s in WSL2) with Helm + kubectl. -# Exit 0 = telemetry confirmed, non-zero = failed. -set -euo pipefail - -SAMPLE_DIR="src/OpenTelemetryExtension.Configuration.Sample" -SWAGGER_URL="http://localhost:5021" # http app url from launchSettings - -# 1. Start OpenObserve via its Helm chart -echo ">> Starting OpenObserve via Helm ..." -(cd infrastructure/helm && helm upgrade --install openobserve ./chart-openobserve) -echo ">> Waiting for backend to become ready ..." -sleep 20 - -# 2. Run the sample in the background with the OpenObserve HTTP profile -echo ">> Running sample with profile: Start OpenObserve Http" -( cd "$SAMPLE_DIR" && dotnet run --launch-profile "Start OpenObserve Http" ) & -SAMPLE_PID=$! -cleanup() { kill "$SAMPLE_PID" 2>/dev/null || true; } -trap cleanup EXIT - -# 3. Wait for the app, then generate traffic -echo ">> Waiting for the sample app ..." -for _ in $(seq 1 30); do - if curl -sk "$SWAGGER_URL/health" >/dev/null 2>&1; then break; fi - sleep 2 -done -echo ">> Generating traffic ..." -for _ in $(seq 1 5); do curl -sk "$SWAGGER_URL/" >/dev/null 2>&1 || true; done - -# 4. Give the exporter time to flush, then verify ingestion via the query API. -# NodePort 30117. The _search API needs the user password (rootPassword: admin), -# not the OTLP ingestion passcode. -echo ">> Waiting for export/flush ..." -sleep 15 - -BASE="http://localhost:30117" -AUTH="admin@web.de:admin" -NOW=$(date +%s)000000 -FROM=$(( NOW - 3600000000 )) -BODY=$(cat <> Querying OpenObserve for ingested logs ..." -RESP=$(curl -s -u "$AUTH" -H "Content-Type: application/json" \ - -d "$BODY" "$BASE/api/default/_search?type=logs" || true) -echo "$RESP" - -if echo "$RESP" | grep -q '"c"' && ! echo "$RESP" | grep -q '"c":0'; then - echo ">> โœ… Telemetry confirmed in OpenObserve." - exit 0 -else - echo ">> โŒ Could not confirm telemetry. Check the sample logs and the OpenObserve UI (http://localhost:30117)." - exit 1 -fi diff --git a/.claude/skills/auto-release/templates/release-notes.md b/.claude/skills/auto-release/templates/release-notes.md deleted file mode 100644 index f4766b8..0000000 --- a/.claude/skills/auto-release/templates/release-notes.md +++ /dev/null @@ -1,21 +0,0 @@ -# v{{VERSION}} - -_Released: {{DATE}}_ - -## Added -- {{ ... new features / options ... }} - -## Changed -- {{ ... behavior or API changes ... }} -- Updated all NuGet dependencies to their latest versions. - -## Fixed -- {{ ... bug fixes ... }} - -## Removed -- {{ ... removed members / options ... }} - - From 4928994aa442034d6ecf54a156254a2ad4559e77 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:42:40 +0200 Subject: [PATCH 04/14] chore: restore release-prep skill to repo as prepare-release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the skill back into the repository (it is entirely repo-specific) and renames it auto-release -> prepare-release, since it only prepares the release (version bump, notes, PR) โ€” publishing stays a manual Deploy Nuget trigger. Documents the skill in AGENTS.md and CONTRIBUTING.md. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/prepare-release/SKILL.md | 63 +++++++++++++++++ .../prepare-release/assets/release-notes.md | 21 ++++++ .../scripts/check-otel-updates.sh | 43 ++++++++++++ .../prepare-release/scripts/smoke-test.sh | 69 +++++++++++++++++++ AGENTS.md | 5 ++ CONTRIBUTING.md | 2 + 6 files changed, 203 insertions(+) create mode 100644 .claude/skills/prepare-release/SKILL.md create mode 100644 .claude/skills/prepare-release/assets/release-notes.md create mode 100644 .claude/skills/prepare-release/scripts/check-otel-updates.sh create mode 100644 .claude/skills/prepare-release/scripts/smoke-test.sh diff --git a/.claude/skills/prepare-release/SKILL.md b/.claude/skills/prepare-release/SKILL.md new file mode 100644 index 0000000..0963791 --- /dev/null +++ b/.claude/skills/prepare-release/SKILL.md @@ -0,0 +1,63 @@ +--- +name: prepare-release +description: Prepare a new NuGet release of this repository (version bump, release notes, release PR to master). Use this whenever the user mentions releasing, publishing, shipping, cutting a release, bumping the version, updating OpenTelemetry dependencies, or preparing release notes โ€” even if they don't say "release" explicitly. Decides the next SemVer version automatically and proceeds when the shipped library project has dependency updates or there are any new commits since the last tag. +--- + +# Prepare release + +Prepare a new release of this NuGet package. You decide the next version +yourself. The NuGet publish itself is a **manual** GitHub Actions trigger +(`deploy-nuget.yml`, `workflow_dispatch`) and is NOT part of your job โ€” you +prepare the repository and open the PR. + +## Workflow + +1. **Check whether there is anything to release** + - Run the helper: `bash .claude/skills/prepare-release/scripts/check-otel-updates.sh` + - The helper only inspects the **shipped library project** (`src/OpenTelemetryExtension.Configuration/...csproj`). Dependency updates in the Sample or Tests projects are ignored โ€” they are never published and must not trigger a new version. + - Exit code **3** = nothing to release (no library dependency updates *and* no new commits since the last tag) โ†’ **stop**. + - Exit code **0** = library dependency updates and/or new commits exist โ†’ continue. + +2. **Determine current version & last tag** + - Read `` in `src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj`. + - Latest tag: `git tag --sort=-v:refname | head -1`. + +3. **Decide the next version (SemVer)** from `git log --oneline ..HEAD`: + - **MAJOR** breaking API changes ยท **MINOR** new features ยท **PATCH** fixes/docs/deps. + - Must be strictly greater than the current version. State it + a one-line reason. + +4. **Confirm clean tree** โ€” `git status --short`; if dirty, ask how to proceed. + +5. **Branch** โ€” `git checkout -b release/v`. + +6. **Update the library project's NuGet packages** to latest (`src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj` only โ€” leave Sample/Tests packages alone), then `dotnet restore OpenTelemetryExtension.slnx`. + +7. **Build & test (green required)** + - `dotnet build OpenTelemetryExtension.slnx -c Release` + - `dotnet test OpenTelemetryExtension.slnx -c Release` + +8. **End-to-end smoke test** โ€” prove telemetry actually reaches a backend. + Requires a local Kubernetes cluster (k3s in WSL2) with Helm + kubectl. + OpenObserve is used because it has a real query API, so the test can + positively confirm ingested data. The helper starts OpenObserve via its Helm + chart, runs the sample, generates traffic and queries the API for records: + - `bash .claude/skills/prepare-release/scripts/smoke-test.sh` + - Exit 0 = telemetry confirmed โ†’ continue. Non-zero = stop and report; do not + release if telemetry does not arrive. + +9. **Bump ``** in the csproj. + +10. **Extend docs** โ€” update `README.md` etc. for the changes/new dep versions. + +11. **Release notes** โ€” copy `.claude/skills/prepare-release/assets/release-notes.md` to + `release-notes/v.md`, fill in the `{{VERSION}}`/`{{DATE}}` placeholders + and the **Added / Changed / Fixed / Removed** sections (omit empty ones). + +12. **Commit** (no tag) โ€” stage csproj(s), docs, release notes; message `release: v`. + +13. **Push & PR to master** + - `git push -u origin release/v` + - `gh pr create --base master --head release/v --title "release: v" --body ` + +14. **Report** the PR link and remind: after merge, trigger **Deploy Nuget** manually + (Actions โ†’ Deploy Nuget โ†’ Run workflow) โ€” it tags `v` and publishes. diff --git a/.claude/skills/prepare-release/assets/release-notes.md b/.claude/skills/prepare-release/assets/release-notes.md new file mode 100644 index 0000000..f4766b8 --- /dev/null +++ b/.claude/skills/prepare-release/assets/release-notes.md @@ -0,0 +1,21 @@ +# v{{VERSION}} + +_Released: {{DATE}}_ + +## Added +- {{ ... new features / options ... }} + +## Changed +- {{ ... behavior or API changes ... }} +- Updated all NuGet dependencies to their latest versions. + +## Fixed +- {{ ... bug fixes ... }} + +## Removed +- {{ ... removed members / options ... }} + + diff --git a/.claude/skills/prepare-release/scripts/check-otel-updates.sh b/.claude/skills/prepare-release/scripts/check-otel-updates.sh new file mode 100644 index 0000000..5957be5 --- /dev/null +++ b/.claude/skills/prepare-release/scripts/check-otel-updates.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Decides whether there is anything worth releasing. +# +# A release is warranted when EITHER a dependency of the shipped library project +# has an update OR there are new commits since the last tag (features, fixes, +# breaking changes). Only the library project's packages matter โ€” updates in the +# Sample or Tests projects are never published and must not drive a release. +# +# Exit 0 = something to release, 3 = nothing to release, 1 = error. +set -euo pipefail + +# Run from the repository root so the relative paths below resolve regardless +# of where the script was invoked from. +cd "$(git rev-parse --show-toplevel)" + +PROJ="src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj" + +echo "Checking for outdated packages in $PROJ ..." +# Only count real package rows (they start with "> "); skip status lines such as +# "The given project `OpenTelemetry...` has no updates", which otherwise match. +outdated="$(dotnet list "$PROJ" package --outdated 2>/dev/null | grep -E '^[[:space:]]*>' | grep -i 'OpenTelemetry' || true)" + +last_tag="$(git tag --sort=-v:refname | head -1)" +if [[ -n "$last_tag" ]]; then + commits="$(git log "$last_tag"..HEAD --oneline 2>/dev/null || true)" +else + commits="$(git log --oneline 2>/dev/null || true)" +fi + +if [[ -z "$outdated" && -z "$commits" ]]; then + echo "No OpenTelemetry updates and no new commits since ${last_tag:-the start} โ€” nothing to release." + exit 3 +fi + +if [[ -n "$outdated" ]]; then + echo "OpenTelemetry updates available:" + echo "$outdated" +fi +if [[ -n "$commits" ]]; then + echo "Releasable changes since ${last_tag:-the start}:" + echo "$commits" +fi +exit 0 diff --git a/.claude/skills/prepare-release/scripts/smoke-test.sh b/.claude/skills/prepare-release/scripts/smoke-test.sh new file mode 100644 index 0000000..d422f31 --- /dev/null +++ b/.claude/skills/prepare-release/scripts/smoke-test.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Smoke-test: start OpenObserve via Helm, run the sample, generate traffic and +# verify that telemetry actually arrived by querying the OpenObserve API. +# +# OpenObserve is used because it exposes a real query API, so we can positively +# prove that data was ingested (not just that the endpoint was reachable). +# +# Usage: bash smoke-test.sh +# +# Requires a local Kubernetes cluster (e.g. k3s in WSL2) with Helm + kubectl. +# Exit 0 = telemetry confirmed, non-zero = failed. +set -euo pipefail + +# Run from the repository root so the relative paths below resolve regardless +# of where the script was invoked from. +cd "$(git rev-parse --show-toplevel)" + +SAMPLE_DIR="src/OpenTelemetryExtension.Configuration.Sample" +SWAGGER_URL="http://localhost:5021" # http app url from launchSettings + +# 1. Start OpenObserve via its Helm chart +echo ">> Starting OpenObserve via Helm ..." +(cd infrastructure/helm && helm upgrade --install openobserve ./chart-openobserve) +echo ">> Waiting for backend to become ready ..." +sleep 20 + +# 2. Run the sample in the background with the OpenObserve HTTP profile +echo ">> Running sample with profile: Start OpenObserve Http" +( cd "$SAMPLE_DIR" && dotnet run --launch-profile "Start OpenObserve Http" ) & +SAMPLE_PID=$! +cleanup() { kill "$SAMPLE_PID" 2>/dev/null || true; } +trap cleanup EXIT + +# 3. Wait for the app, then generate traffic +echo ">> Waiting for the sample app ..." +for _ in $(seq 1 30); do + if curl -sk "$SWAGGER_URL/health" >/dev/null 2>&1; then break; fi + sleep 2 +done +echo ">> Generating traffic ..." +for _ in $(seq 1 5); do curl -sk "$SWAGGER_URL/" >/dev/null 2>&1 || true; done + +# 4. Give the exporter time to flush, then verify ingestion via the query API. +# NodePort 30117. The _search API needs the user password (rootPassword: admin), +# not the OTLP ingestion passcode. +echo ">> Waiting for export/flush ..." +sleep 15 + +BASE="http://localhost:30117" +AUTH="admin@web.de:admin" +NOW=$(date +%s)000000 +FROM=$(( NOW - 3600000000 )) +BODY=$(cat <> Querying OpenObserve for ingested logs ..." +RESP=$(curl -s -u "$AUTH" -H "Content-Type: application/json" \ + -d "$BODY" "$BASE/api/default/_search?type=logs" || true) +echo "$RESP" + +if echo "$RESP" | grep -q '"c"' && ! echo "$RESP" | grep -q '"c":0'; then + echo ">> โœ… Telemetry confirmed in OpenObserve." + exit 0 +else + echo ">> โŒ Could not confirm telemetry. Check the sample logs and the OpenObserve UI (http://localhost:30117)." + exit 1 +fi diff --git a/AGENTS.md b/AGENTS.md index dee5b97..f1e4e7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,6 +92,11 @@ tests run in-process via `ServiceCollection`. - Do not change `` without also creating `release-notes/v{VERSION}.md` - NuGet publish is **manual** (`workflow_dispatch`) โ€” never triggered automatically +- The full release-prep workflow (decide SemVer, bump, update deps, build/test, + end-to-end smoke test, release notes, PR to `master`) is encoded in the + **`prepare-release`** skill at `.claude/skills/prepare-release/`. Run it via + Claude Code (`/prepare-release`) when cutting a release; it only prepares the + PR โ€” publishing stays the manual `deploy-nuget.yml` trigger. ## What NOT to do diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cec8cd8..dfdbd91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,8 @@ If your PR changes the public API or behaviour, please: PRs without a version bump are fine for documentation or refactoring that has no user-visible impact. +> **Maintainers:** the release-prep steps (version decision, dependency updates, build/test, smoke test, release notes, release PR) are automated by the `prepare-release` Claude Code skill in [`.claude/skills/prepare-release/`](./.claude/skills/prepare-release/). It prepares the PR only โ€” the actual NuGet publish remains the manual **Deploy Nuget** workflow. + ## Adding a New Instrumentation Option Follow the checklist in [CLAUDE.md](./CLAUDE.md#adding-a-new-instrumentation-option). From 56850d9722a1bcbf16a2e826fdd8d8bc3b0833eb Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:46:52 +0200 Subject: [PATCH 05/14] chore: trim and refocus NuGet PackageTags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut from 21 to 11 tags: deduplicate Traces/Tracing and Logs/Logging, drop platform stuffing (WinForms/UWP/MAUI/Console) and niche tags (HttpClient/gRPC/ Instrumentation), and add Observability. Removed the now-stale SqlClient tag โ€” that instrumentation was dropped in v2.0.0. Co-Authored-By: Claude Opus 4.8 --- .../OpenTelemetryExtension.Configuration.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj index d2230ae..7a703ad 100644 --- a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj +++ b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj @@ -10,7 +10,7 @@ Thorsten Alpers Copyright ยฉ $([System.DateTime]::Now.Year) Thorsten Alpers Configurable OpenTelemetry setup for .NET applications providing tracing, metrics, and logging via OTLP, configurable through code or appsettings.json. - OpenTelemetry;Otel;Tracing;Traces;Metrics;Logs;Logging;OTLP;ASP.NET Core;WPF;WinForms;Console;UWP;MAUI;appsettings;Configuration;Instrumentation;Telemetry;HttpClient;SqlClient;gRPC + OpenTelemetry;Otel;Observability;OTLP;Tracing;Metrics;Logging;Telemetry;Configuration;appsettings;ASP.NET Core https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration git From 0a11f20a48184f600ca96cb1b07eefa0e6c57fb5 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:51:30 +0200 Subject: [PATCH 06/14] chore: set PackageTags to the curated platform-focused list Co-Authored-By: Claude Opus 4.8 --- .../OpenTelemetryExtension.Configuration.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj index 7a703ad..a8afcec 100644 --- a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj +++ b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj @@ -10,7 +10,7 @@ Thorsten Alpers Copyright ยฉ $([System.DateTime]::Now.Year) Thorsten Alpers Configurable OpenTelemetry setup for .NET applications providing tracing, metrics, and logging via OTLP, configurable through code or appsettings.json. - OpenTelemetry;Otel;Observability;OTLP;Tracing;Metrics;Logging;Telemetry;Configuration;appsettings;ASP.NET Core + opentelemetry;maui;webapi;asp-net-core;configuration;winforms;appsettings;wpf;console https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration git From 4fa8abba2ef127ece7b7c40132d55cbb41ff2b76 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 18:55:17 +0200 Subject: [PATCH 07/14] chore: set PackageTags to the curated platform-focused list Co-Authored-By: Claude Opus 4.8 --- .../OpenTelemetryExtension.Configuration.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj index a8afcec..a0779e7 100644 --- a/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj +++ b/src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj @@ -10,7 +10,7 @@ Thorsten Alpers Copyright ยฉ $([System.DateTime]::Now.Year) Thorsten Alpers Configurable OpenTelemetry setup for .NET applications providing tracing, metrics, and logging via OTLP, configurable through code or appsettings.json. - opentelemetry;maui;webapi;asp-net-core;configuration;winforms;appsettings;wpf;console + opentelemetry;maui;webapi;asp-net;configuration;winforms;appsettings;wpf;nuget;console https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration https://github.com/thorstenalpers/OpenTelemetryExtension.Configuration git From 224988fc0970b4650ed25daa6b5eaa56ef965841 Mon Sep 17 00:00:00 2001 From: thorsten Date: Wed, 10 Jun 2026 19:08:10 +0200 Subject: [PATCH 08/14] feat: add WPF sample and split samples into Sample.WebApi / Sample.Wpf Rename the existing sample to OpenTelemetryExtension.Configuration.Sample.WebApi and add a new OpenTelemetryExtension.Configuration.Sample.Wpf (net10.0-windows) that wires AddTelemetry() through the Generic Host and emits a custom ActivitySource/Meter plus an HttpClient span on a button click. Also: - README: add a Samples overview, update sample paths, fix the stale "SQL Client built-in" feature line (SQL instrumentation became opt-in in v2.0.0). - slnx: register both sample projects and fix the dangling auto-release skill folder paths left over from the prepare-release rename. - smoke-test.sh / AGENTS.md: point at the renamed Sample.WebApi project. Co-Authored-By: Claude Opus 4.8 --- .../prepare-release/scripts/smoke-test.sh | 2 +- AGENTS.md | 11 ++-- OpenTelemetryExtension.slnx | 16 +++--- README.md | 20 ++++++-- .../AppDbContext.cs | 2 +- .../20260526114252_Initial.Designer.cs | 2 +- .../Migrations/20260526114252_Initial.cs | 0 .../Migrations/AppDbContextModelSnapshot.cs | 2 +- ...ension.Configuration.Sample.WebApi.csproj} | 0 .../Program.cs | 2 +- .../Properties/launchSettings.json | 0 .../WeatherForecast.cs | 2 +- .../appsettings.aspire.json | 0 .../appsettings.jaeger.json | 0 .../appsettings.json | 0 .../appsettings.loki.json | 0 .../appsettings.openobserve-grpc.json | 0 .../appsettings.openobserve-http.json | 0 .../appsettings.opensearch.json | 0 .../appsettings.signoz.json | 0 .../App.xaml | 5 ++ .../App.xaml.cs | 36 +++++++++++++ .../MainWindow.xaml | 15 ++++++ .../MainWindow.xaml.cs | 50 +++++++++++++++++++ ...yExtension.Configuration.Sample.Wpf.csproj | 26 ++++++++++ .../appsettings.json | 18 +++++++ 26 files changed, 188 insertions(+), 21 deletions(-) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/AppDbContext.cs (80%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/Migrations/20260526114252_Initial.Designer.cs (96%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/Migrations/20260526114252_Initial.cs (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/Migrations/AppDbContextModelSnapshot.cs (96%) rename src/{OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj => OpenTelemetryExtension.Configuration.Sample.WebApi/OpenTelemetryExtension.Configuration.Sample.WebApi.csproj} (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/Program.cs (97%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/Properties/launchSettings.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/WeatherForecast.cs (80%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.aspire.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.jaeger.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.loki.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.openobserve-grpc.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.openobserve-http.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.opensearch.json (100%) rename src/{OpenTelemetryExtension.Configuration.Sample => OpenTelemetryExtension.Configuration.Sample.WebApi}/appsettings.signoz.json (100%) create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml.cs create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/MainWindow.xaml create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/MainWindow.xaml.cs create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/OpenTelemetryExtension.Configuration.Sample.Wpf.csproj create mode 100644 src/OpenTelemetryExtension.Configuration.Sample.Wpf/appsettings.json diff --git a/.claude/skills/prepare-release/scripts/smoke-test.sh b/.claude/skills/prepare-release/scripts/smoke-test.sh index d422f31..7aaa60d 100644 --- a/.claude/skills/prepare-release/scripts/smoke-test.sh +++ b/.claude/skills/prepare-release/scripts/smoke-test.sh @@ -15,7 +15,7 @@ set -euo pipefail # of where the script was invoked from. cd "$(git rev-parse --show-toplevel)" -SAMPLE_DIR="src/OpenTelemetryExtension.Configuration.Sample" +SAMPLE_DIR="src/OpenTelemetryExtension.Configuration.Sample.WebApi" SWAGGER_URL="http://localhost:5021" # http app url from launchSettings # 1. Start OpenObserve via its Helm chart diff --git a/AGENTS.md b/AGENTS.md index f1e4e7f..75711c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,9 +12,10 @@ ASP.NET Core via a single `AddTelemetry()` call and `appsettings.json`. ``` src/ - OpenTelemetryExtension.Configuration/ # Library (netstandard2.0 + net10.0) - OpenTelemetryExtension.Configuration.Tests/ # xUnit tests (net10.0) - OpenTelemetryExtension.Configuration.Sample/ # ASP.NET Core sample app (net10.0) + OpenTelemetryExtension.Configuration/ # Library (netstandard2.0 + net10.0) + OpenTelemetryExtension.Configuration.Tests/ # xUnit tests (net10.0) + OpenTelemetryExtension.Configuration.Sample.WebApi/ # ASP.NET Core sample app (net10.0) + OpenTelemetryExtension.Configuration.Sample.Wpf/ # WPF desktop sample app (net10.0-windows) OpenTelemetryExtension.slnx # Solution file .github/workflows/ ci.yml # Build + test + coverage on push @@ -102,8 +103,8 @@ tests run in-process via `ServiceCollection`. - Do not add `using` directives already covered by global/implicit usings - Do not add `// TODO` comments โ€” raise an issue instead -- Do not modify the `*.Sample` project for library behaviour changes (it is - excluded from code coverage) +- Do not modify the `*.Sample.*` projects for library behaviour changes (they + are excluded from code coverage) - Do not add new public API surface without a corresponding test in `TelemetryOptionsTests.cs` or `TelemetryServiceCollectionExtensionsTests.cs` diff --git a/OpenTelemetryExtension.slnx b/OpenTelemetryExtension.slnx index f20ef9d..bf9f912 100644 --- a/OpenTelemetryExtension.slnx +++ b/OpenTelemetryExtension.slnx @@ -8,14 +8,15 @@ - - + + + - - + + - - + + @@ -72,7 +73,8 @@ - + + diff --git a/README.md b/README.md index 48606ce..a9c9844 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Configurable OpenTelemetry setup for .NET applications providing **tracing, metr - **One-call setup** โ€” tracing, metrics and logging via a single `AddTelemetry()`, configured from `appsettings.json` or code - **All three signals over OTLP** โ€” HTTP/protobuf or gRPC, to any OTLP-compatible backend -- **Built-in instrumentation** โ€” `HttpClient`, SQL Client and .NET runtime metrics everywhere; ASP.NET Core instrumentation on web targets โ€” each toggleable +- **Built-in instrumentation** โ€” `HttpClient` and .NET runtime metrics everywhere; ASP.NET Core instrumentation on web targets โ€” each toggleable. Database instrumentation is opt-in via a one-liner (see [Databases](#databases)) - **Sensible defaults** โ€” configurable sampling, health-check path exclusion and exception recording work out of the box - **Startup validation** โ€” misconfiguration fails fast with a clear error - **Extensible** โ€” `ConfigureTracing`/`ConfigureMetrics`/`ConfigureLogging` hooks for custom sources, meters and providers @@ -353,9 +353,23 @@ Every key with its **default** value (only `Endpoint` is required to get started --- +## ๐Ÿงช Samples + +Two runnable samples live under [`src/`](./src): + +| Sample | Project | Demonstrates | +|---|---|---| +| **Web API** | [`โ€ฆSample.WebApi`](./src/OpenTelemetryExtension.Configuration.Sample.WebApi) | ASP.NET Core minimal API configured from `appsettings.json`, ready-to-run backend profiles, EF Core and opt-in SQL instrumentation. | +| **WPF** | [`โ€ฆSample.Wpf`](./src/OpenTelemetryExtension.Configuration.Sample.Wpf) | Desktop app wiring `AddTelemetry()` through the **Generic Host**, emitting a custom `ActivitySource`/`Meter` and an `HttpClient` span on a button click. | + +The Web API sample drives the backend walkthrough below; the WPF sample exports +to `http://localhost:4318` by default โ€” point it at any of the backends here. + +--- + ## ๐Ÿ”Œ Running Locally with a Backend -The [sample project](./src/OpenTelemetryExtension.Configuration.Sample) ships +The [Web API sample](./src/OpenTelemetryExtension.Configuration.Sample.WebApi) ships ready-to-run configurations for several popular backends (the three below are documented in full; more start scripts live in [`infrastructure/`](./infrastructure)). Each backend has: @@ -370,7 +384,7 @@ documented in full; more start scripts live in [`infrastructure/`](./infrastruct - Helm scripts live in [`infrastructure/helm`](./infrastructure/helm) and need a local Kubernetes cluster (e.g. k3s in WSL2). 2. **Run the sample** with the matching profile: ```bash - cd src/OpenTelemetryExtension.Configuration.Sample + cd src/OpenTelemetryExtension.Configuration.Sample.WebApi dotnet run --launch-profile "Start Aspire" ``` Or pick the profile from the run dropdown in Visual Studio / Rider. diff --git a/src/OpenTelemetryExtension.Configuration.Sample/AppDbContext.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/AppDbContext.cs similarity index 80% rename from src/OpenTelemetryExtension.Configuration.Sample/AppDbContext.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/AppDbContext.cs index fa17968..2cd9ade 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/AppDbContext.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/AppDbContext.cs @@ -1,6 +1,6 @@ ๏ปฟusing Microsoft.EntityFrameworkCore; -namespace OpenTelemetryExtension.Configuration.Sample; +namespace OpenTelemetryExtension.Configuration.Sample.WebApi; public class AppDbContext : DbContext { diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Migrations/20260526114252_Initial.Designer.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/20260526114252_Initial.Designer.cs similarity index 96% rename from src/OpenTelemetryExtension.Configuration.Sample/Migrations/20260526114252_Initial.Designer.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/20260526114252_Initial.Designer.cs index 7c63d78..8b1a258 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/Migrations/20260526114252_Initial.Designer.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/20260526114252_Initial.Designer.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using OpenTelemetryExtension.Configuration.Sample; +using OpenTelemetryExtension.Configuration.Sample.WebApi; diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Migrations/20260526114252_Initial.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/20260526114252_Initial.cs similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/Migrations/20260526114252_Initial.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/20260526114252_Initial.cs diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Migrations/AppDbContextModelSnapshot.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/AppDbContextModelSnapshot.cs similarity index 96% rename from src/OpenTelemetryExtension.Configuration.Sample/Migrations/AppDbContextModelSnapshot.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/AppDbContextModelSnapshot.cs index 8bacae4..ba2a6b3 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/Migrations/AppDbContextModelSnapshot.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Migrations/AppDbContextModelSnapshot.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using OpenTelemetryExtension.Configuration.Sample; +using OpenTelemetryExtension.Configuration.Sample.WebApi; diff --git a/src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/OpenTelemetryExtension.Configuration.Sample.WebApi.csproj similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/OpenTelemetryExtension.Configuration.Sample.WebApi.csproj diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Program.cs similarity index 97% rename from src/OpenTelemetryExtension.Configuration.Sample/Program.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/Program.cs index 65af729..3ee86ab 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/Program.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Program.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using OpenTelemetry.Trace; -namespace OpenTelemetryExtension.Configuration.Sample; +namespace OpenTelemetryExtension.Configuration.Sample.WebApi; public class Program { diff --git a/src/OpenTelemetryExtension.Configuration.Sample/Properties/launchSettings.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/Properties/launchSettings.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/Properties/launchSettings.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/Properties/launchSettings.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/WeatherForecast.cs b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/WeatherForecast.cs similarity index 80% rename from src/OpenTelemetryExtension.Configuration.Sample/WeatherForecast.cs rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/WeatherForecast.cs index ea57614..3b5a92c 100644 --- a/src/OpenTelemetryExtension.Configuration.Sample/WeatherForecast.cs +++ b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/WeatherForecast.cs @@ -1,4 +1,4 @@ -namespace OpenTelemetryExtension.Configuration.Sample; +namespace OpenTelemetryExtension.Configuration.Sample.WebApi; public class WeatherForecast { diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.aspire.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.aspire.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.aspire.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.aspire.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.jaeger.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.jaeger.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.jaeger.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.jaeger.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.loki.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.loki.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.loki.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.loki.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.openobserve-grpc.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.openobserve-grpc.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.openobserve-grpc.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.openobserve-grpc.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.openobserve-http.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.openobserve-http.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.openobserve-http.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.openobserve-http.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.opensearch.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.opensearch.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.opensearch.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.opensearch.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample/appsettings.signoz.json b/src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.signoz.json similarity index 100% rename from src/OpenTelemetryExtension.Configuration.Sample/appsettings.signoz.json rename to src/OpenTelemetryExtension.Configuration.Sample.WebApi/appsettings.signoz.json diff --git a/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml new file mode 100644 index 0000000..49da2a2 --- /dev/null +++ b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml @@ -0,0 +1,5 @@ + + + diff --git a/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml.cs b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml.cs new file mode 100644 index 0000000..df620cb --- /dev/null +++ b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/App.xaml.cs @@ -0,0 +1,36 @@ +using System.Windows; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetryExtension.Configuration; + +namespace OpenTelemetryExtension.Configuration.Sample.Wpf; + +public partial class App : Application +{ + private IHost? _host; + + protected override async void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + var builder = Host.CreateApplicationBuilder(); + builder.Services.AddTelemetry(builder.Configuration); + builder.Services.AddSingleton(); + + _host = builder.Build(); + await _host.StartAsync(); + + _host.Services.GetRequiredService().Show(); + } + + protected override async void OnExit(ExitEventArgs e) + { + if (_host is not null) + { + await _host.StopAsync(); + _host.Dispose(); + } + + base.OnExit(e); + } +} diff --git a/src/OpenTelemetryExtension.Configuration.Sample.Wpf/MainWindow.xaml b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/MainWindow.xaml new file mode 100644 index 0000000..abd36a2 --- /dev/null +++ b/src/OpenTelemetryExtension.Configuration.Sample.Wpf/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + +