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
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
"rollForward": false
},
"dotnet-inspect": {
"version": "0.25.0",
"version": "0.26.0",
"commands": [
"dotnet-inspect"
],
"rollForward": false
},
"purview.build": {
"version": "0.5.3",
"version": "0.5.4",
"commands": [
"purview-build"
],
Expand Down
1 change: 0 additions & 1 deletion .config/purview-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"RequireSymbolPackage": false,
"RequiredContent": {
"purview.telemetry.sourcegenerator": [
".agents/**/*",
"analyzers/dotnet/cs/Purview.Telemetry.SourceGenerator.dll",
"analyzers/dotnet/cs/Purview.Telemetry.SourceGenerator.Refactorings.dll",
"README.md",
Expand Down
69 changes: 53 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ benchmarks/
└── Purview.Telemetry.Benchmarks/ # BenchmarkDotNet project (net48/net8.0/net9.0/net10.0)

.build/ # TS scripts (build-pack.ts, update-version.ts)
.agents/ # Agent skills/prompts/agents (see "Agent instructions")
.agents/ # Package-delivered skills/prompts/agents (gitignored mirror)
skills/ # Authored skills shipped by this package (see "Authoring skills")
.config/ # dotnet-tools.json, lefthook.yml
.github/workflows/ # pr.yml, release.yml (reusable purview-dev/build workflows)
scripts/ # setup-release.* (legacy changesets DSC scripts)
Expand Down Expand Up @@ -172,21 +173,57 @@ The sample projects enable `EmitCompilerGeneratedFiles`, so generated telemetry
`AnalyzerReleases.Shipped.md` at release time. `RS2003` is suppressed — see the comment in
`src/src/SourceGenerator/SourceGenerator.csproj`.

## Source-generator and testing skills

Load the relevant skill before doing specialist work:

- `.agents/skills/source-generator-codewriter-modernization/SKILL.md` — CodeWriter/XmlCommentWriter
emission, incremental pipeline design, value equality, Roslyn best practices.
- `.agents/skills/source-generator-testing/SKILL.md` — picking the right runner/base, configuring
options, `CodeQuery`, asserting incremental caching.
- `.agents/skills/tunit-test-authoring/SKILL.md` — TUnit base classes, assertion extensions,
modernising existing tests.
- `.agents/skills/sdk-configuration-reference/SKILL.md`,
`.agents/skills/sdk-project-behavior-and-detection/SKILL.md`,
`.agents/skills/project-placement-defaults/SKILL.md` — `Purview.BuildSdk` configuration,
project-type detection, and placement rules.
- `.agents/agents/sdk-consumer-setup.md` — generic agent spec for helping consuming repos adopt the SDK.
## Skills from other repositories

Specialist source-generator and SDK guidance — `CodeWriter` emission, incremental pipeline design,
test runners and `CodeQuery`, `Purview.BuildSdk` configuration — is published by the repositories
that own it. Install what you need through the skills CLI; it lands in `.agents/skills/`, which is
gitignored and recorded in `skills-lock.json`:

```bash
npx skills add purview-dev/sourcegenerator-framework
npx skills add purview-dev/build-sdk
```

Do not edit anything under `.agents/skills/` — the CLI is its only writer.

## Authoring skills

Authored skills live in the repository-root `skills/` folder. **Nothing packs them into a NuGet
package.** `Purview.BuildSdk` 1.0.6 removed skill packaging outright — the skills CLI clones the
*git* repository and only discovers a root `skills/` folder, so that is the one delivery route.
`PurviewSkillsToPack`, the `IncludeRepositorySkillsInPackage` shim in `src/Directory.Build.targets`,
and the `.agents/**/*` entry in `.config/purview-build.json` went with it.

```bash
npx skills add purview-dev/telemetry-sourcegenerator
```

| Skill | Answers |
| --- | --- |
| `skills/purview-telemetry-quickstart/` | adding the package, a first telemetry interface, registering it in DI |
| `skills/purview-telemetry-design/` | one interface or several, naming, structuring multi-target methods |
| `skills/purview-telemetry-migration/` | converting existing `ILogger`, `ActivitySource` or instrument code |
| `skills/purview-telemetry-multi-targeting/` | emitting an Activity, a log and a metric from one method |
| `skills/purview-telemetry-diagnostics/` | what a `TSG` diagnostic means and how to fix it |

Every skill carries the `purview-telemetry` prefix. Skills install into one flat directory per agent,
shared with every skill a developer has installed from anywhere, so an unprefixed name collides and
the loser is whichever installed second.

To add a skill: create `skills/<name>/SKILL.md` with spec-legal frontmatter, and nothing else. There
is no per-project registration any more.

`src/tests/Repository.UnitTests/SkillTests.cs` validates every skill on every test run, through
`Purview.Skills.Testing`'s rule set (the Agent Skills specification: frontmatter fields, name and
description constraints, bundled links, body size). The `[InheritsTests]` attribute on that class is
load-bearing — TUnit does not walk base types during discovery, so without it the run is green and
nothing is checked. That project is single-targeted at `net10.0`, because it asserts something about
the repository rather than about the runtime's reach.

A relative link from one `SKILL.md` into another skill's folder is a rule violation: only a skill's
own folder is installed with it, so such a link works here and nowhere else. Name the other skill in
prose instead, and keep deep detail in the skill's own `references/`.

## Benchmarking and performance documentation

Expand Down
11 changes: 6 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
<PropertyGroup>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<RoslynVersion>5.9.0</RoslynVersion>
<TUnitVersion>1.74.0</TUnitVersion>
<PurviewSGFVersion>1.1.0</PurviewSGFVersion>
<DotnetRuntimeVersion>10.0.12</DotnetRuntimeVersion>
<MSExtensionVersion>10.10.0</MSExtensionVersion>
<TUnitVersion>1.75.0</TUnitVersion>
<PurviewSGFVersion>1.1.1</PurviewSGFVersion>
<DotnetRuntimeVersion>10.0.13</DotnetRuntimeVersion>
<MSExtensionVersion>10.11.0</MSExtensionVersion>
<OpenTelemetryVersion>1.18.0</OpenTelemetryVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Purview.SourceGeneratorFramework" Version="$(PurviewSGFVersion)" />
<PackageVersion Include="Purview.SourceGeneratorFramework.Testing.TUnit" Version="$(PurviewSGFVersion)" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.401" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.402" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(RoslynVersion)" />
Expand All @@ -27,6 +27,7 @@
<PackageVersion Include="TUnit" Version="$(TUnitVersion)" />
<PackageVersion Include="TUnit.Mocks" Version="$(TUnitVersion)" />
<PackageVersion Include="Bogus" Version="35.6.5" />
<PackageVersion Include="Purview.Skills.Testing" Version="1.0.6" />
</ItemGroup>
<ItemGroup Label="Samples">
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(DotnetRuntimeVersion)" />
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"allowPrerelease": false
},
"msbuild-sdks": {
"Purview.BuildSdk": "1.0.5"
"Purview.BuildSdk": "1.0.6"
},
"test": {
"runner": "Microsoft.Testing.Platform"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "telemetry-sourcegenerator",
"version": "5.0.3",
"version": "5.0.4",
"description": "Generates [`ActivitySource`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitysource), [`ILogger`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger), and [`Metrics`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics) based on interface methods.",
"license": "MIT",
"readme": "README.md",
Expand All @@ -13,4 +13,4 @@
"url": "https://github.com/purview-dev/telemetry-sourcegenerator/issues"
},
"homepage": "https://purview.dev/projects/telemetry-sourcegenerator/"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: telemetry-sourcegenerator-design
name: purview-telemetry-design
description: Design best practices for Purview.Telemetry.SourceGenerator interfaces. Use when deciding between a single telemetry interface and per-area interfaces, choosing telemetry names, designing multi-target methods, or reviewing telemetry structure in a C# project.
license: ISC
license: MIT
compatibility: C# projects using Purview.Telemetry.SourceGenerator.
metadata:
author: Purview-Dev
Expand Down
168 changes: 168 additions & 0 deletions skills/purview-telemetry-diagnostics/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
name: purview-telemetry-diagnostics
description: >-
Use when a Purview.Telemetry.SourceGenerator build reports a TSG diagnostic - TSG1xxx general,
TSG2xxx logging, TSG3xxx activities, TSG4xxx metrics - to find what triggered it and how to fix it,
or when deciding whether a rule can be suppressed.
license: MIT
compatibility: C# projects using Purview.Telemetry.SourceGenerator.
metadata:
author: Purview-Dev
product: Purview.Telemetry.SourceGenerator
repository: purview-dev/telemetry-sourcegenerator
---

# Purview.Telemetry.SourceGenerator diagnostics

One Roslyn analyzer, `TelemetryDiagnosticAnalyzer`, validates telemetry interfaces and raises `TSG`
diagnostics in four families:

| Family | Covers |
| --- | --- |
| `TSG1xxx` | general — interface and method shape, targets, framework support |
| `TSG2xxx` | logging |
| `TSG3xxx` | activities |
| `TSG4xxx` | metrics |

Every rule is tracked in `AnalyzerReleases.Shipped.md` / `AnalyzerReleases.Unshipped.md` with its ID,
category and severity, validated by the Roslyn release-tracking analyzers on every build. A new or
changed diagnostic must be recorded there in the same change.

## General — `TSG1xxx`

| ID | Severity | Reported when |
| --- | --- | --- |
| `TSG1000` | Error | An unexpected exception escaped the generation pipeline. |
| `TSG1001` | Error | A method on a multi-target interface has no explicit generation attribute — inference is not supported there. |
| `TSG1002` | Error | Two attributes from the same target family on one method. Only one Activity, Logging or Metrics attribute per method. |
| `TSG1003` | Error | Two methods on the interface share a name. |
| `TSG1004` | Error | Generic interfaces are not supported. |
| `TSG1005` | Error | Generic methods are not supported. |
| `TSG1006` | Warning | `ExcludeTargets` names a target not present on the method. |
| `TSG1007` | Warning | `ExcludeTargets` leaves a target with an empty or invalid parameter set. |
| `TSG1008` | Warning | An `Activity`-typed parameter on a method with no `[Activity]`/`[Event]`/`[Context]`. |
| `TSG1010` | Error | A method carries an attribute for a target family the interface does not register. |
| `TSG1011` | Error | The compilation targets neither .NET 8+ nor .NET Framework 4.8+. Define `PURVIEW_TELEMETRY_NON_NULLABLE` to opt out. |

## Logging — `TSG2xxx`

| ID | Severity | Reported when |
| --- | --- | --- |
| `TSG2000` | Error | A non-scoped log method has more than one `Exception` parameter. |
| `TSG2001` | Error | A v1-mode log method has more than 6 non-exception parameters. |
| `TSG2002` | Info | v1 generation inferred `Error` from a lone `Exception` parameter with no explicit level. |
| `TSG2003` | Warning | No reference to `Microsoft.Extensions.Logging.ILogger`; log generation is skipped. |
| `TSG2004` | Error | Ordinal and named placeholders mixed in one message template. |
| `TSG2005` | Error | The maximum ordinal placeholder exceeds the parameter count. |
| `TSG2006` | Error | `[LogProperties]` and `[ExpandEnumerable]` on the same parameter. |
| `TSG2007` | Warning | A scoped log declares a `LogLevel`; it is ignored. |
| `TSG2008` | Warning | `[ExpandEnumerable]` has a `MaximumValueCount` above the recommended default of 5. |
| `TSG2021` | Error | A log method returns something other than `void` or `IDisposable`. (`Activity` is allowed when the method is also an Activity method.) |

## Activities — `TSG3xxx`

| ID | Severity | Reported when |
| --- | --- | --- |
| `TSG3000` | Warning | A baggage parameter is not a string; `ToString()` will be called. |
| `TSG3001` | Warning | No activity source name available anywhere; generation defaults to `purview`. |
| `TSG3002` | Error | An Activity/Event method returns something other than `void` or `Activity`. |
| `TSG3003` | Error | More than one parameter maps to the same reserved destination. |
| `TSG3004` | Error | An `Activity`-destination parameter on an Activity method — only valid on `[Event]`. |
| `TSG3005` | Error | A `timestamp` parameter on a method that is not `[Event]`. |
| `TSG3006` | Error | A start-time parameter on a Create-activity or Event method. |
| `TSG3007` | Error | A parent-context or parent-id parameter on an event. |
| `TSG3008` | Error | Activity-links parameters on an event or context method. |
| `TSG3009` | Error | An activity-tags parameter on a context method. |
| `TSG3010` | Error | An escaped parameter is not a boolean. |
| `TSG3011` | Error | Escaped parameters on an Activity or Context method — Events only. |
| `TSG3012` | Info | No Activity methods defined; `Activity.Current` is assumed. |
| `TSG3013` | Warning | An Activity method does not return the created `Activity`. |
| `TSG3014` | Warning | An Event/Context method has no `Activity` parameter. Controlled by `ActivitySourceGeneration.GenerateDiagnosticsForMissingActivity` (default `true`). |
| `TSG3015` | Info | `Activity` should be the first parameter. Same opt-in as `TSG3014`. |
| `TSG3016` | Error | A status-description parameter is not a string. |
| `TSG3017` | Error | Status-description parameters on an Activity or Context method — Events only. |
| `TSG3021` | Info | An `[Event]` records an exception under the OpenTelemetry rules but is not named `"exception"`. |
| `TSG3022` | Warning | An Activity method returns non-nullable `Activity`. |

## Metrics — `TSG4xxx`

| ID | Severity | Reported when |
| --- | --- | --- |
| `TSG4000` | Error | A method on a Metrics interface has no instrument attribute and is not excluded. |
| `TSG4001` | Error | A metrics-owned method returns something other than `void` or `bool`. |
| `TSG4002` | Error | An auto-increment instrument also declares a measurement parameter. |
| `TSG4003` | Error | More than one measurement value. |
| `TSG4004` | Error | A non-auto-increment instrument method has no measurement parameter. |
| `TSG4005` | Error | An observable instrument is not declared as `Func<T>`. |
| `TSG4006` | Error | The measurement type is not `byte`, `short`, `int`, `long`, `double`, `float`, `decimal`, `Measurement<T>` or `IEnumerable<Measurement<T>>`. |
| `TSG4007` | Error | An observable metric returns `bool`. |
| `TSG4008` | Error | An `AutoCounter` does not return `void`. |
| `TSG4009` | Warning | The instrument name matches the instrument type name. Name what is measured, not the instrument. |

## The ones people actually hit

### `TSG1001` — inference is off on a multi-target interface

A method on an interface carrying `[ActivitySource]` + `[Logger]` + `[Meter]` has no explicit
attribute. Add one per target the method should emit, or mark it `[Exclude]`:

```csharp
[ActivitySource("MyApp")]
[Logger]
[Meter]
interface IMyTelemetry
{
[Info] // explicit target
void ProcessItem(int id);
}
```

### `TSG3013` / `TSG3014` — the Activity went missing

Return the created `Activity` (as `Activity?`, per `TSG3022` — it is null when no listeners are
active) and pass it into the Event and Context methods that should attach to it. Both are
best-practice rules controlled by `ActivitySourceGeneration.GenerateDiagnosticsForMissingActivity`.

### `TSG3021` — exception event name

Name the event `exception` so the OpenTelemetry exception tags (`exception.type`,
`exception.message`, `exception.stacktrace`, `exception.escaped`) attach to *that* event rather than
a separate one:

```csharp
[Event(Name = "exception")] // the event carries the exception tags
void FailedToRetrieve(Activity? activity, Exception exception);
```

A `Name` on a **logging** attribute does not name the event — it renames the log entry:

```csharp
[Event] // event name is still the method name
[Error(Name = "exception")] // this renames the log entry
void FailedToRetrieve(Activity? activity, Exception exception);
```

The rule is not raised when the exception is not recorded under the OpenTelemetry rules:
`[Event(UseRecordExceptionRules = false)]`, a `[Baggage]` exception parameter, or an exception
parameter excluded from the Activities target (`[ExcludeTargets(Targets.Activities)]`).

### `TSG2008` — unbounded enumeration

`[ExpandEnumerable]` with `MaximumValueCount` above 5. Bring it back to the default unless you have
measured the cost.

### `TSG4002` — auto-increment counter with a measurement

`[AutoCounter]` — equivalently `[Counter(AutoIncrement = true)]` — must declare no measurement
parameter. The measurement is fixed at 1.

### `TSG1003` — duplicate method names

The method name becomes a member on the generated implementation class, so two methods cannot share
one. Rename.

## Related skills

`purview-telemetry-quickstart` to get an interface wired up, `purview-telemetry-design` for
structuring interfaces, and `purview-telemetry-migration` for converting existing `ILogger`,
`ActivitySource` or instrument code.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: telemetry-sourcegenerator-migration
name: purview-telemetry-migration
description: Migrate existing ILogger, ActivitySource, or System.Diagnostics.Metrics instrument code to Purview.Telemetry.SourceGenerator interfaces. Use when converting hand-written telemetry to generated telemetry interfaces, or when a user wants to replace ILogger, ActivitySource, or Counter/Histogram/UpDownCounter usage with Purview telemetry.
license: ISC
license: MIT
compatibility: C# projects using Purview.Telemetry.SourceGenerator.
metadata:
author: Purview-Dev
Expand Down
Loading
Loading