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
2 changes: 1 addition & 1 deletion .github/workflows/nuget-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (no v prefix), e.g. 0.3.0-pre.1. Empty = auto patch-bump from the latest reachable v* tag.'
description: 'Version to publish (no v prefix), e.g. 3.1.3. Empty = auto patch-bump from the latest reachable v* tag.'
required: false

concurrency:
Expand Down
223 changes: 106 additions & 117 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,115 @@
# Changelog

## Unreleased
Notable changes to `Qyl.OpenTelemetry.AutoInstrumentation`. Versions track the Qyl.OpenTelemetry
stack line; releases are tag-driven (`v*`) and CI-published. The `<Version>` in
`Directory.Build.props` is the local-build floor and is CI-overridden from the release tag at pack
time.

## [Unreleased]

- Build: `QylInstrumentation.Version` (the OTel instrumentation-scope version stamped on every
emitted span/metric) is now baked from the build `<Version>` via a generated compile-time const
instead of a hardcoded literal — no reflection. A new `verify-version-sync` gate keeps the props
floor, the README examples, and that scope version aligned with the latest release tag.

## [4.0.0] - 2026-07-01

### Changed

- **BREAKING:** dropped the `WebApplicationBuilder.Build()` interceptor. The ASP.NET Core
server-request middleware is now registered via an `IStartupFilter` through
`AddQylAspNetCoreInstrumentation()`, removing the cross-generator coordination layer and its
opt-out property; middleware span semantics (request/response header + query-string capture) are
preserved (#20).
- Build: added the `core.slnf` solution filter (core packages, no demo projects) (#18).

## [3.1.2] - 2026-06-30

### Fixed

- Generator no longer intercepts `RequestDelegate.Invoke`; delegate-invocation call-sites produced
CS9207 (#17).

## [3.1.1] - 2026-06-30

### Changed

- Generator opts out of `WebApplicationBuilder.Build()` interception (#16).
- Generator: dropped dead `InterceptorTarget` mirrors and un-shadowed factory helpers (#15).

## [3.1.0] - 2026-06-29

### Added

- **Telemetry Capability Graph (First-Light, steps 1–3).** `TelemetryCapabilityGraphGenerator`
bakes the compile-time TCG into the core assembly as `QylTelemetryCapabilityGraph`
(`.Json` / `.SchemaVersion` / `.CapabilityCount`), and the
`Qyl.OpenTelemetry.AutoInstrumentation.Publishing` package emits it as a true OTel `LogRecord` at
host startup via `ILogger` (no OTel SDK dependency), proven by `demos/Qyl.RealTcgPublishingDemo`
(#12, #13, #14).

### Fixed

- Restored the green NativeAOT goal-gate floor and the slim-builder AOT demo (#10).

## [3.0.2] - 2026-06-24 — initial public release

First release under the canonical name, rebaselining the pre-public `0.x` line (the orphaned
`v0.*-pre` tags are skipped by the publish bump regex).

### Changed

- **Canonical rename** `Qyl.AutoInstrumentation` → `Qyl.OpenTelemetry.AutoInstrumentation`, with the
package version aligned onto the Qyl.OpenTelemetry stack line at 3.0.2.
- Migrated to Central Package Management; onboarded Renovate with the shared preset.
- Split the Roslyn generator into detection / shapes / descriptors partials (no generated-output
change).

### Added

- Apache-2.0 `LICENSE` (OpenTelemetry-ecosystem standard, incl. patent grant).
- Pure-managed, NativeAOT-ready runtime substrate: core APIs, source-generated
`[InterceptsLocation]` interceptors, DiagnosticListener subscribers, `build/`/`buildTransitive/`
assets, and `[ModuleInitializer]` activation. Hosting / EFCore / SqlClient packages isolate heavy
dependencies and their AOT-warning boundaries.
- Real consumer demos (managed + NativeAOT where the library supports it): HttpClient, ASP.NET Core,
EFCore, gRPC, and Microsoft.Data.SqlClient, plus Confluent.Kafka, RabbitMQ.Client, MongoDB.Driver,
StackExchange.Redis, Quartz, and MassTransit against real Docker-backed brokers/servers.
NServiceBus 9.2.11 is a documented managed-only boundary (endpoint creation requires
Reflection.Emit).
- Behavioral verification gates: package layout, ProjectReference behavior, public API baselines,
XML docs, environment options, conformance, source-generator snapshots, source-interceptor
behavior, smoke, WebAPI AOT, and OTLP verified/collector fixtures.

### Telemetry semantics

- Expanded the metrics registration surface with the useful part of the superseded
instrumentation research: ASP.NET Core now registers the current framework-owned built-in
meters beyond components, HTTP client metrics also register `System.Net.NameResolution`, and
NServiceBus metrics also register the vendor incoming-pipeline meter. The old
profiler/startup-hook/EventPipe substrate remains superseded and was not reintroduced.
- Added the upstream `OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES` option to qyl's
source-generated `MeterProviderBuilder.AddMeter(...)` path, preserving exact meter-name case
and de-duplicating custom meters against built-in registrations.
- Adopted the upstream OpenTelemetry .NET privacy model and removed the
`QYL_AUTOINSTRUMENTATION_CAPTURE_SENSITIVE_VALUES` option entirely: `url.full` is always
emitted on client spans and `url.path`/`url.query` on server spans, with query values
redacted per key (`?token=Redacted`, keys stay) exactly like upstream; the
`OTEL_DOTNET_EXPERIMENTAL_*_DISABLE_URL_QUERY_REDACTION` flags only switch redacted to raw.
`db.namespace` is always emitted; `db.query.text` sits solely behind the upstream
`SET_DBSTATEMENT_FOR_TEXT` flags. Bootstrap sets `System.Net.Http.DisableUriRedaction`
because the BCL otherwise collapses query strings to `*` before qyl can redact per value.
(This supersedes the interim fix that gated URL emission behind the sensitive-values flag;
the original bug — the redaction flag alone emitting fully unredacted URLs — stays fixed.)
- Span names are now OTel-semconv-shaped low-cardinality values composed by
`QylActivityNames` helpers instead of fixed literals: HTTP client spans use the normalized
method (`GET`, fallback `HTTP` for `_OTHER`), HTTP server spans use `{method} {route}`,
gRPC client spans use the full `{rpc.service}/{rpc.method}` name, and database spans use
`DB {operation}` / `SQL {operation}`. The `QylActivityNames` string constants were removed.
- Demo assertions and the WebApiAotDemo report filter now use the current semconv registry
keys `db.system.name` / `rpc.system.name` that the runtime actually emits; the stale
`db.system` / `rpc.system` keys had kept the EFCore and gRPC demos red and silently dropped
`db.system.name` from the WebApiAotDemo verified.

### Real consumer demos

- Added real Confluent.Kafka, RabbitMQ.Client, and MongoDB.Driver demos that prove
source-generated interceptors (producer/consumer, `BasicPublishAsync`, and
`IMongoCollection<T>` commands) against real Docker-backed brokers under managed and
NativeAOT runs, including pinned error-path spans and the app-side `TrimmerRootAssembly`
boundaries for Confluent.Kafka and MongoDB.Driver.
- Added real StackExchange.Redis, Quartz, MassTransit, and NServiceBus demos completing the
source-interceptor proof surface: Redis is AOT-warning-free, Quartz proves source-visible
`IJob.Execute` delegation under a real scheduler (rooted for AOT), MassTransit 8.x publishes
to real RabbitMQ under NativeAOT via an app-side source-generated `JsonSerializerContext`,
and NServiceBus 9.2.11 is a documented managed-only boundary because its endpoint creation
requires Reflection.Emit.

### Documentation and handoff

- Replaced the release-history-heavy README with a current operational guide for the .NET 10
NativeAOT auto-instrumentation runtime.
- Added `AGENTS.md` as the canonical repo-local continuation contract and made `CLAUDE.md` a
symlink to avoid duplicate agent instructions.
- Rewrote the coverage ledger into a neutral current-state ledger, removing stale ceremonial and
substrate-era ceremony from the user-facing path.
- Removed stale `claude/**` workflow triggering from the WebAPI AOT demo workflow.

## 0.3.0-pre.1 - current slim-history baseline

This baseline was rebuilt into five semantic commits. The final tree is the AOT
auto-instrumentation product state after the history rewrite.

### Repository foundation

- Established the .NET 10 solution, SDK pinning, NuGet metadata, analyzer versions, and repo-wide
AOT/trim/single-file warning floor.
- Centralized package versioning and package README inclusion in `Directory.Build.props`.

### AOT auto-instrumentation runtime

- Added the pure-managed runtime substrate: core APIs, source-generator targets,
DiagnosticListener subscribers, build/buildTransitive assets, and module-initializer activation.
- Added the Roslyn source-generator project for contract-gated interceptors and generated semantic
registries.
- Added package-specific surfaces for Hosting, EFCore, and Microsoft.Data.SqlClient to keep heavy
dependencies and AOT warning boundaries isolated.
- Replaced runtime reflection discovery with build-time generated registries and explicit contract
classification.

### Real consumer demos

- Added real demos for live synthetic coverage, HttpClient, ASP.NET Core, EFCore, gRPC, and
Microsoft.Data.SqlClient.
- Proved managed and NativeAOT execution paths where the upstream library supports them.
- Kept EFCore compiled-model and SqlClient globalization/AOT-warning boundaries explicit.

### Behavioral verification gates

- Added package-layout, ProjectReference, public API, XML-doc, environment-option, conformance,
source-generator snapshot, source-interceptor, smoke, WebAPI AOT, OTLP verified, OTLP collector,
consumer behavior, and NativeAOT consumer gates.
- Added CI workflows for smoke, WebAPI AOT, and OTLP collector fixtures.
- Added BenchmarkDotNet hot-path measurements as evidence, not as shipped runtime surface.

### Runtime semantics and coverage

- Added runtime semantics documentation for stable OpenTelemetry attributes, privacy defaults,
bounded names, and library-specific payload extraction.
- Added the 60-item OpenTelemetry .NET auto-instrumentation contract manifest and generated
coverage matrix.
- Added RFC 0001 describing the AOT-native source-interceptor substrate and verification contract.

## Continuation plan

### Highest-value product work

- Finish a full OTLP export normalizer so the verified-OTLP gate validates real exported telemetry
`QYL_AUTOINSTRUMENTATION_CAPTURE_SENSITIVE_VALUES` option: `url.full` is always emitted on client
spans and `url.path` / `url.query` on server spans, with query values redacted per key
(`?token=Redacted`, keys stay); the `OTEL_DOTNET_EXPERIMENTAL_*_DISABLE_URL_QUERY_REDACTION` flags
only switch redacted to raw. `db.namespace` is always emitted; `db.query.text` sits solely behind
the upstream `SET_DBSTATEMENT_FOR_TEXT` flag. Bootstrap sets
`System.Net.Http.DisableUriRedaction` so the BCL does not collapse query strings to `*` before qyl
redacts per value.
- Added the upstream `OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES` option on the source-generated
`MeterProviderBuilder.AddMeter(...)` path (exact meter-name case preserved, de-duplicated against
built-ins), and expanded built-in meter registration (ASP.NET Core framework meters,
`System.Net.NameResolution` for HTTP client, and the NServiceBus incoming-pipeline meter).
- Span names are OTel-semconv-shaped low-cardinality values composed by `QylActivityNames`
(`{method}` / `{method} {route}` / `{rpc.service}/{rpc.method}` / `DB {operation}`), not fixed
literals.

---

## Continuation notes

Useful-for-continuation direction, not a commit dump:

- Finish an OTLP export normalizer so the verified-OTLP gate validates real exported telemetry
end-to-end, not only OTLP-shaped committed fixtures.
- Turn BenchmarkDotNet evidence into stable allocation/latency budgets once CI runner noise is
characterized.
- Add an update command for the 60-item contract manifest that regenerates the coverage matrix,
source-generator contract data, and snapshots in one path.
- Improve package UX: document the exact package choice per app type and add clearer build errors
when analyzer/buildTransitive assets are missing.
- Expand source-generator interception only for source-visible, semantically stable call-sites.
Do not chase hidden binary internals with reflection or runtime patching.

### Engineering hygiene

- Keep `main` history slim. Prefer semantic commits over checkpoint commits.
- Keep release tags aligned with final validated commits after history rewrites.
- Keep `CLAUDE.md` as a symlink to `AGENTS.md`.
- Preserve EFCore/SqlClient warning boundaries instead of hiding upstream NativeAOT issues inside
generic packages.
- Continue using the combined goal verifier before handoff or release work.
- Add a single update path for the OpenTelemetry .NET auto-instrumentation contract that regenerates
the coverage matrix, generator contract data, and snapshots together.
- Expand source-generator interception only for source-visible, semantically stable call-sites — do
not chase hidden binary internals with reflection or runtime patching.
9 changes: 6 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>

<!-- One version source of truth across all packed projects. Bump only after validation. -->
<!-- Aligned onto the Qyl.OpenTelemetry stack version line (SemConv 3.0.2). -->
<Version>3.0.2</Version>
<!-- Local-build version floor, aligned onto the Qyl.OpenTelemetry stack line. This is the
value baked into local builds (and into QylInstrumentation.Version via the assembly's
informational version). Releases are CI-owned: nuget-publish.yml overrides -p:Version from
the latest v* tag at pack time. Keep this floor >= the latest release tag — the
verify-version-sync.py gate enforces it, so it can never fall behind again. -->
<Version>4.0.0</Version>
<Authors>qyl</Authors>
<RepositoryUrl>https://github.com/ANcpLua/Qyl.OpenTelemetry.AutoInstrumentation</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ instrumentation.

```xml
<ItemGroup>
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.Hosting" Version="0.3.0-pre.1" />
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.Hosting" Version="4.0.0" />
</ItemGroup>
```

Add package-specific references only when the app uses those libraries:

```xml
<ItemGroup>
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.EntityFrameworkCore" Version="0.3.0-pre.1" />
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.SqlClient" Version="0.3.0-pre.1" />
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.EntityFrameworkCore" Version="4.0.0" />
<PackageReference Include="Qyl.OpenTelemetry.AutoInstrumentation.SqlClient" Version="4.0.0" />
</ItemGroup>
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const Qyl.OpenTelemetry.AutoInstrumentation.QylAutoInstrumentationIds.StackExcha
const Qyl.OpenTelemetry.AutoInstrumentation.QylAutoInstrumentationIds.WcfClient = "WCFCLIENT" -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylAutoInstrumentationIds.WcfCore = "WCFCORE" -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylAutoInstrumentationIds.WcfService = "WCFSERVICE" -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylInstrumentation.Version = "0.3.0-pre.1" -> string!
static readonly Qyl.OpenTelemetry.AutoInstrumentation.QylInstrumentation.Version -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylInstrumentationDomains.AspNetCoreServer = "aspnetcore.server" -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylInstrumentationDomains.AzureSdk = "azure.sdk" -> string!
const Qyl.OpenTelemetry.AutoInstrumentation.QylInstrumentationDomains.DbClient = "db.client" -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@
Visible="false" />
</ItemGroup>

<!-- Bake <Version> into a generated internal const so QylInstrumentation.Version (the OTel scope
version stamped on every span/metric) is a pure compile-time value with ZERO reflection —
reflection in product code is banned by verify-contract-invariants.py. Always runs (no
Inputs/Outputs) so a CI `-p:Version` override is honoured; writes only on change to avoid
needless recompiles. Emitted into obj/, never committed. -->
<Target Name="GenerateQylVersionInfo" BeforeTargets="BeforeCompile;CoreCompile">
<PropertyGroup>
<_QylVersionInfoFile>$(IntermediateOutputPath)QylVersionInfo.g.cs</_QylVersionInfoFile>
</PropertyGroup>
<ItemGroup>
<_QylVersionInfoLine Include="// &lt;auto-generated /&gt;" />
<_QylVersionInfoLine Include="namespace Qyl.OpenTelemetry.AutoInstrumentation%3B" />
<_QylVersionInfoLine Include="internal static class QylVersionInfo" />
<_QylVersionInfoLine Include="{" />
<_QylVersionInfoLine Include=" public const string Version = &quot;$(Version)&quot;%3B" />
<_QylVersionInfoLine Include="}" />
</ItemGroup>
<WriteLinesToFile File="$(_QylVersionInfoFile)"
Lines="@(_QylVersionInfoLine)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
<ItemGroup>
<Compile Include="$(_QylVersionInfoFile)" />
<FileWrites Include="$(_QylVersionInfoFile)" />
</ItemGroup>
</Target>

<ItemGroup Condition="'$(PublishAot)' != 'true'">
<!-- Source generator referenced as analyzer: emits the semconv FrozenSet at compile time so the
runtime conformance check needs zero reflection (the substrate-era reflection path was JIT-only).
Expand Down
11 changes: 9 additions & 2 deletions src/Qyl.OpenTelemetry.AutoInstrumentation/QylInstrumentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ namespace Qyl.OpenTelemetry.AutoInstrumentation;
/// </summary>
public static class QylInstrumentation
{
/// <summary>Library version, baked at build time by the root <c>Directory.Build.props</c>.</summary>
public const string Version = "0.3.0-pre.1";
/// <summary>
/// Instrumentation-scope version stamped on every qyl-emitted span/metric (via
/// <see cref="QylActivitySource"/>). Baked at build time from the root
/// <c>Directory.Build.props</c> <c>&lt;Version&gt;</c> — which CI overrides from the release
/// <c>v*</c> tag at pack time — via the generated <c>QylVersionInfo</c> const (see the
/// <c>GenerateQylVersionInfo</c> target in the core project). No reflection; it always matches
/// the shipped package and is never hand-maintained.
/// </summary>
public static readonly string Version = QylVersionInfo.Version;

private static int _activated;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{
"scope": {
"name": "Qyl.OpenTelemetry.AutoInstrumentation",
"version": "3.0.2"
"version": "4.0.0"
},
"spans": [
{
Expand Down
1 change: 1 addition & 0 deletions tools/verify-aot-autoinstrumentation-goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
("package layout", [sys.executable, "tools/verify-package-layout.py"]),
("projectreference behavior", [sys.executable, "tools/verify-projectreference-behavior.py"]),
("public api baseline", [sys.executable, "tools/verify-public-api-baseline.py"]),
("version sync", [sys.executable, "tools/verify-version-sync.py"]),
("xml doc enforcement", [sys.executable, "tools/verify-xml-doc-enforcement.py"]),
("environment options behavior", [sys.executable, "tools/verify-environment-options-behavior.py"]),
("instrumentation disabled behavior", [sys.executable, "tools/verify-instrumentation-disabled-behavior.py"]),
Expand Down
Loading
Loading