Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions .claude/skills/auto-release/scripts/check-otel-updates.sh

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
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.
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.
---

# Auto-release
# 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
Expand All @@ -12,10 +12,11 @@ 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.
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 `<Version>` in `src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj`.
Expand All @@ -29,26 +30,26 @@ prepare the repository and open the PR.

5. **Branch** — `git checkout -b release/v<version>`.

6. **Update all NuGet packages** to latest, then `dotnet restore OpenTelemetryExtension.slnx`.
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`
- `dotnet test OpenTelemetryExtension.slnx -c Release --filter "Category=Unit"` (unit tests only; integration tests need the live OpenObserve/SQL stack)

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`
- `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 `<Version>`** 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
11. **Release notes** — copy `.claude/skills/prepare-release/assets/release-notes.md` to
`release-notes/v<version>.md`, fill in the `{{VERSION}}`/`{{DATE}}` placeholders
and the **Added / Changed / Fixed / Removed** sections (omit empty ones).

Expand Down
43 changes: 43 additions & 0 deletions .claude/skills/prepare-release/scripts/check-otel-updates.sh
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
# Exit 0 = telemetry confirmed, non-zero = failed.
set -euo pipefail

SAMPLE_DIR="src/OpenTelemetryExtension.Configuration.Sample"
# 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.WebApi"
SWAGGER_URL="http://localhost:5021" # http app url from launchSettings

# 1. Start OpenObserve via its Helm chart
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
--collect:"XPlat Code Coverage" `
--results-directory TestResults/Tests `
--configuration Release `
--filter "Category=Unit" `
--logger "console;verbosity=detailed"

dotnet tool run reportgenerator `
Expand Down
15 changes: 11 additions & 4 deletions .github/workflows/deploy-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,21 @@ jobs:
with:
dotnet-version: 10.x

# Build only the published library and the unit tests. The solution also
# contains a WPF sample (net10.0-windows) that cannot build on Linux and
# integration tests that need a live backend — neither belongs in the deploy.
- name: Restore
run: dotnet restore "${{ env.Solution_Name }}"
run: |
dotnet restore "${{ env.Nuget_Project_Path }}"
dotnet restore "${{ env.Test_Project_Path }}"

- name: Build
run: dotnet build "${{ env.Solution_Name }}" --configuration Release --no-restore
run: |
dotnet build "${{ env.Nuget_Project_Path }}" --configuration Release --no-restore
dotnet build "${{ env.Test_Project_Path }}" --configuration Release --no-restore

- name: Run tests
run: dotnet test "${{ env.Test_Project_Path }}" --configuration Release --logger "console;verbosity=detailed" --logger "console;verbosity=detailed"
- name: Run unit tests
run: dotnet test "${{ env.Test_Project_Path }}" --configuration Release --no-build --filter "Category=Unit" --logger "console;verbosity=detailed"

- name: Pack NuGet Package
run: dotnet pack "${{ env.Nuget_Project_Path }}" --configuration Release --no-build --output ./nupkg
Expand Down
67 changes: 51 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ 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 unit tests (net10.0, in-process)
OpenTelemetryExtension.Configuration.IntegrationTests/ # Integration tests (net10.0) — query a live OpenObserve
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
Expand All @@ -26,24 +28,46 @@ 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

```bash
dotnet build OpenTelemetryExtension.slnx -c Release
dotnet test OpenTelemetryExtension.slnx -c Release"
dotnet test src/OpenTelemetryExtension.Configuration.Tests -c Release # unit tests
```

Tests use **xUnit + Moq**. No integration test infrastructure needed — all
tests run in-process via `ServiceCollection`.
Unit tests use **xUnit + Moq** and run in-process via `ServiceCollection` — no
infrastructure required.

**Whenever you add or change a feature, run the unit tests. When the telemetry
stack is running, also run the integration tests** (see below). **CI runs the
unit tests only** (the workflows filter on `Category=Unit`).

### Integration tests

`OpenTelemetryExtension.Configuration.IntegrationTests` exercises the real export
path: it emits logs, metrics and traces (and a SQL Server span) through
`AddTelemetry()` to a running **OpenObserve** instance and queries its `_search`
API to confirm the data was ingested.

- Needs the OpenObserve Helm chart (`infrastructure/helm/helm-install-openobserve.cmd`);
the SQL Server chart (`helm-install-sqlserver.cmd`) is required only for the SQL test.
- Every test is `[Trait("Category", "Integration")]` and **auto-skips** when the
backend (or SQL Server) is unreachable, so the suite stays green without the stack.
- Endpoints/credentials default to the Helm chart values; override via `OTEL_IT_*` env vars.
- Run: `dotnet test src/OpenTelemetryExtension.Configuration.IntegrationTests -c Release`.

## Language & framework

Expand Down Expand Up @@ -78,7 +102,11 @@ tests run in-process via `ServiceCollection`.
no reflection hacks
- Use `Record.Exception` (not `Assert.Throws<T>`) when asserting that no
exception is thrown
- Do not use `Thread.Sleep` or `Task.Delay` in tests
- Do not use `Thread.Sleep` or `Task.Delay` in **unit** tests (integration tests
may poll the backend until telemetry is queryable)
- Integration tests live in the `*.IntegrationTests` project, are marked
`[Trait("Category", "Integration")]` and assert against a live OpenObserve via
its `_search` API — see [Integration tests](#integration-tests)

## Versioning & release

Expand All @@ -88,13 +116,18 @@ tests run in-process via `ServiceCollection`.
- Do not change `<Version>` 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

- 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`

Expand All @@ -105,7 +138,9 @@ tests run in-process via `ServiceCollection`.
2. Wire it up in `TelemetryServiceCollectionExtensions` under the appropriate
signal block
3. Add default-value test in `TelemetryOptionsTests.cs`
4. Add enabled/disabled integration tests in
4. Add enabled/disabled unit tests in
`TelemetryServiceCollectionExtensionsTests.cs`
5. Add the option to the `<example>` block in the XML doc on `TelemetryOptions`
6. Update `README.md` configuration reference table
7. Run the unit tests; when the telemetry stack is running, run the integration
tests too (see [Integration tests](#integration-tests))
30 changes: 28 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,40 @@ For anything beyond a small fix, please [open an issue](https://github.com/thors
1. Fork the repository and create a branch from `develop`.
2. Make your changes.
3. Add or update tests — every public API change needs test coverage.
4. Run the build and tests locally.
4. When you add or change a feature, run the **unit tests**; if you have the
telemetry stack running, run the **integration tests** too (see
[Integration tests](#integration-tests)).
5. Open a pull request against `develop`.

## Build & Test

```bash
dotnet build OpenTelemetryExtension.slnx -c Release
dotnet test OpenTelemetryExtension.slnx -c Release
dotnet test src/OpenTelemetryExtension.Configuration.Tests -c Release # unit tests
```

### Integration tests

The separate `OpenTelemetryExtension.Configuration.IntegrationTests` project
verifies that telemetry is actually exported: it sends logs, metrics, traces and
a SQL Server span through `AddTelemetry()` to a live **OpenObserve** instance and
queries its API to confirm the data arrived.

```bash
# 1. Start the backends (local Kubernetes + Helm required)
infrastructure/helm/helm-install-openobserve.cmd
infrastructure/helm/helm-install-sqlserver.cmd # only needed for the SQL Server test

# 2. Run the integration tests
dotnet test src/OpenTelemetryExtension.Configuration.IntegrationTests -c Release
```

The tests are tagged `[Trait("Category", "Integration")]` and **skip
automatically** when OpenObserve (or SQL Server) is unreachable, so a normal
`dotnet test` run never fails just because the stack is down. Endpoints and
credentials default to the Helm chart values and can be overridden via the
`OTEL_IT_*` environment variables. **CI runs the unit tests only.**

## Code Style

- File-scoped namespaces (`namespace Foo;`)
Expand All @@ -43,6 +67,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).
Expand Down
17 changes: 10 additions & 7 deletions OpenTelemetryExtension.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
<File Path="README.md" />
</Folder>
<Folder Name="/.claude/" />
<Folder Name="/.claude/skills/auto-release/">
<File Path=".claude/skills/auto-release/SKILL.md" />
<Folder Name="/.claude/skills/" />
<Folder Name="/.claude/skills/prepare-release/">
<File Path=".claude/skills/prepare-release/SKILL.md" />
</Folder>
<Folder Name="/.claude/skills/auto-release/scripts/">
<File Path=".claude/skills/auto-release/scripts/check-otel-updates.sh" />
<Folder Name="/.claude/skills/prepare-release/scripts/">
<File Path=".claude/skills/prepare-release/scripts/check-otel-updates.sh" />
</Folder>
<Folder Name="/.claude/skills/auto-release/templates/">
<File Path=".claude/skills/auto-release/templates/release-notes.md" />
<Folder Name="/.claude/skills/prepare-release/assets/">
<File Path=".claude/skills/prepare-release/assets/release-notes.md" />
</Folder>
<Folder Name="/.github/">
<File Path=".github/copilot-instructions.md" />
Expand Down Expand Up @@ -72,7 +73,9 @@
<File Path="infrastructure/helm/chart-sqlserver/templates/deployment.yaml" />
<File Path="infrastructure/helm/chart-sqlserver/templates/service.yaml" />
</Folder>
<Project Path="src/OpenTelemetryExtension.Configuration.Sample/OpenTelemetryExtension.Configuration.Sample.csproj" DefaultStartup="true" />
<Project Path="src/OpenTelemetryExtension.Configuration.Sample.WebApi/OpenTelemetryExtension.Configuration.Sample.WebApi.csproj" DefaultStartup="true" />
<Project Path="src/OpenTelemetryExtension.Configuration.Sample.Wpf/OpenTelemetryExtension.Configuration.Sample.Wpf.csproj" />
<Project Path="src/OpenTelemetryExtension.Configuration/OpenTelemetryExtension.Configuration.csproj" />
<Project Path="src/OpenTelemetryExtension.Configuration.Tests/OpenTelemetryExtension.Configuration.Tests.csproj" />
<Project Path="src/OpenTelemetryExtension.Configuration.IntegrationTests/OpenTelemetryExtension.Configuration.IntegrationTests.csproj" />
</Solution>
Loading
Loading