Purview.Telemetry.SourceGenerator
4.0.0-prerelease.7
See the version list below for details.
dotnet add package Purview.Telemetry.SourceGenerator --version 4.0.0-prerelease.7
NuGet\Install-Package Purview.Telemetry.SourceGenerator -Version 4.0.0-prerelease.7
<PackageReference Include="Purview.Telemetry.SourceGenerator" Version="4.0.0-prerelease.7"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Purview.Telemetry.SourceGenerator" Version="4.0.0-prerelease.7" />
<PackageReference Include="Purview.Telemetry.SourceGenerator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Purview.Telemetry.SourceGenerator --version 4.0.0-prerelease.7
#r "nuget: Purview.Telemetry.SourceGenerator, 4.0.0-prerelease.7"
#:package Purview.Telemetry.SourceGenerator@4.0.0-prerelease.7
#addin nuget:?package=Purview.Telemetry.SourceGenerator&version=4.0.0-prerelease.7&prerelease
#tool nuget:?package=Purview.Telemetry.SourceGenerator&version=4.0.0-prerelease.7&prerelease
Purview Telemetry Source Generator
Generates ActivitySource, ILogger, and Metrics based telemetry from methods you define on an interface.
Features
- Zero boilerplate - define methods on an interface, get full telemetry implementation generated
- Multi-target generation - generate Activities, Logging, and Metrics from a single interface
- Testable - easy mocking/substitution for unit testing
- DI-ready - automatic dependency injection registration helpers
Supported Frameworks
- .NET Framework 4.8
- .NET 8 or higher
Installation
Add to your Directory.Build.props or .csproj file:
<PackageReference Include="Purview.Telemetry.SourceGenerator" Version="4.0.0-prerelease.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>analyzers</IncludeAssets>
</PackageReference>
Quick Start
Define an interface with telemetry methods and the generator creates the implementation:
using Purview.Telemetry;
// Multi-target interface: generates Activities, Logging, AND Metrics from combined methods
[ActivitySource]
[Logger]
[Meter]
interface IEntityStoreTelemetry
{
// MULTI-TARGET: Creates Activity + Logs Info + Increments Counter - all from one method!
[Activity]
[Info]
[AutoCounter]
Activity? GettingEntityFromStore(int entityId, [Baggage]string serviceUrl);
// MULTI-TARGET: Adds ActivityEvent + Logs the duration as Trace.
[Event]
[Trace]
void GetDuration(Activity? activity, int durationInMS);
// Single-target examples (when you only need one telemetry type):
// Activity-only: Adds Baggage to the Activity
[Context]
void RetrievedEntity(Activity? activity, float totalValue, int lastUpdatedByUserId);
// Log-only: Structured log message
[Warning]
void EntityNotFound(int entityId);
// Metric-only: Histogram for tracking values
[Histogram]
void RecordEntitySize(int sizeInBytes);
}
Register with dependency injection:
// Generated extension method
services.AddEntityStoreTelemetry();
Then inject and use - a single method call emits an Activity, Log, and Metric simultaneously:
public class EntityService(IEntityStoreTelemetry telemetry)
{
public async Task<Entity> GetEntityAsync(int id, string serviceUrl, CancellationToken cancellationToken)
{
// Single call creates Activity AND logs AND increments counter
using var activity = telemetry.GettingEntityFromStore(id, serviceUrl);
var entity = await _repository.GetAsync(id, cancellationToken);
// Adds event to activity AND logs duration
telemetry.GetDuration(activity, stopwatch.ElapsedMilliseconds);
if (entity == null)
{
// Logs warning if entity not found
telemetry.EntityNotFound(id);
return null;
})
// Activity context addition
telemetry.RetrievedEntity(activity, entity.TotalValue, entity.LastUpdatedByUserId);
// Histogram only records size
telemetry.RecordEntitySize(entity.SizeInBytes);
return entity;
}
}
Telemetry Types
| Attribute | Generation Type | Description |
|---|---|---|
[ActivitySource] |
Class-level | Marks interface for Activity generation |
[Activity] |
Method | Creates and starts a new Activity |
[Event] |
Method | Adds an ActivityEvent to an Activity |
[Context] |
Method | Adds Baggage to an Activity |
[Logger] |
Class-level | Marks interface for ILogger generation |
[Log] |
Method | Generates structured log message |
[Debug], [Info], [Warning], [Error], [Critical] |
Method | Log with specific level |
[Meter] |
Class-level | Marks interface for Metrics generation |
[Counter], [AutoCounter] |
Method | Counter instrument |
[Histogram] |
Method | Histogram instrument |
[ObservableCounter], [ObservableGauge], [ObservableUpDownCounter] |
Method | Observable instruments |
For single-target interfaces (only Activities, only Logging, or only Metrics), the generator automatically infers the necessary attributes. See the wiki for details.
Documentation
Sample Project
The .NET Aspire Sample demonstrates Activities, Logs, and Metrics generation working together with the Aspire Dashboard.
The sample project has EmitCompilerGeneratedFiles enabled so you can inspect the generated output.
Performance
Benchmarked on 13th Gen Intel Core i9-13900KF, .NET SDK 10.0.200. See the Performance wiki page for full cross-runtime results.
Activities (.NET 10.0)
| Scenario | HasListener | Manual | Generated | Ratio |
|---|---|---|---|---|
| start + complete | False | 0.58 ns | 0.54 ns | 0.93× |
| start + complete | True | 220 ns / 1008 B | 222 ns / 1008 B | 1.01× |
| start + fail | True | 210 ns / 920 B | 196 ns / 920 B | 0.89× |
Generated activities are within ±2% of hand-written code with identical allocations.
Logging (.NET 10.0)
| Scenario | HasLogging | LoggerMessage.Define | Generated v1 | Generated v2 |
|---|---|---|---|---|
| single Info call | False | 0.17 ns | 0.19 ns | 0.20 ns |
| single Info call | True | 4.39 ns | 4.47 ns (1.02×) | 4.16 ns (0.95×) |
| full lifecycle (4 calls) | True | 17.30 ns | 17.78 ns (1.02×) | 19.10 ns (1.10×) |
Both v1 and v2 allocate 0 bytes per call on all runtimes. v2 (ThreadLocalState pattern) matches v1 in allocation and is within ~10% in CPU time.
Multi-Target (.NET 10.0, Activity + Logging + Metrics)
| Scenario | HasListener | Single-target | Multi-target generated | Multi-target manual |
|---|---|---|---|---|
| start + complete | True | 219 ns / 1008 B | 234 ns / 1032 B (1.07×) | 229 ns / 1032 B (1.05×) |
Adding full Activity+Logging+Metrics multi-target generation costs ~7% over Activity-only — matching hand-written multi-target code within 2%.
Metrics (.NET 10.0)
| Scenario | Generated | Notes |
|---|---|---|
| auto-counter (0 tags) | 0.37 ns | - |
| auto-counter (1 tag) | 0.36 ns | - |
| histogram (0 tags) | 0.37 ns | - |
| histogram (1 tag) | 0.37 ns | - |
| 4+ tags (TagList) | 4–6 ns | Stack-allocated TagList |
All instruments are 0 allocations. Manual baselines are JIT-eliminated on .NET 10.0 (no active listener), so absolute times are shown. On .NET 8/9 and .NET Framework, generated and manual are within ±25%.
v4 Breaking Changes
Namespace Consolidation
v4 consolidates all attributes into a single namespace. Update your using statements:
Before (v3):
using Purview.Telemetry.Activities;
using Purview.Telemetry.Logging;
using Purview.Telemetry.Metrics;
After (v4):
using Purview.Telemetry;
All attributes ([ActivitySource], [Logger], [Meter], [Activity], [Event], [Log], [Counter], etc.) are now in the unified Purview.Telemetry namespace.
OpenTelemetry-Aligned Naming (NEW in v4.0.0-alpha.5+)
v4 defaults to OpenTelemetry semantic conventions for generated telemetry names, improving observability and cross-platform compatibility. This is a breaking change if you rely on specific telemetry names.
What Changed
| Telemetry Type | v3 Behavior | v4 Default | Impact |
|---|---|---|---|
| ActivitySource Name | Assembly name lowercased: "myapp" |
Assembly name preserved: "MyApp" |
ActivitySource names change casing |
| Tag/Baggage Keys | Lowercased, smashed: "entityid" |
snake_case: "entity_id" |
Tag keys have underscores for word boundaries |
| Metric Instrument Names | Lowercased, smashed: "recordhistogram" |
Hierarchical dot.separated: "myapp.products.record.histogram" |
Includes meter name prefix + word boundaries |
| Metric Tag Keys | Lowercased, smashed: "requestcount" |
snake_case: "request_count" |
Metric tag keys have underscores |
Examples
Before (v3/Legacy):
// Generated code:
new ActivitySource("myapp") // lowercase
activity.SetTag("entityid", ...) // smashed compound
var meter = meterFactory.Create("MyApp.Products");
meter.CreateCounter<int>("recordcount") // smashed compound, no meter prefix
After (v4 OpenTelemetry mode - DEFAULT):
// Generated code:
new ActivitySource("MyApp") // preserves casing
activity.SetTag("entity_id", ...) // snake_case
var meter = meterFactory.Create("MyApp.Products");
meter.CreateCounter<int>("myapp.products.record.count") // hierarchical: meter + instrument
Note: In OpenTelemetry mode, instrument names automatically include the meter name prefix (converted to lowercase dot.separated), following OpenTelemetry best practices for hierarchical metric naming.
Reverting to v3 Naming (Legacy Mode)
If you need to maintain v3-style naming for backward compatibility, set NamingConvention = Legacy on the [TelemetryGeneration] attribute:
using Purview.Telemetry;
// Revert ALL telemetry to v3 naming (assembly-level)
[assembly: TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]
Or set per-interface:
// Legacy naming for this interface only
[TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]
interface IMyTelemetry { }
Available Naming Conventions
public enum NamingConvention
{
Legacy = 0, // v3 behaviour: lowercase, smashed compounds
OpenTelemetry = 1 // v4 default: OTel conventions (dot.separated, snake_case)
}
Recommendation: Use NamingConvention.OpenTelemetry (default) for new projects. Only use Legacy if you need exact v3 compatibility.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.0-prerelease.8 | 28 | 3/25/2026 |
| 4.0.0-prerelease.7 | 38 | 3/24/2026 |
| 4.0.0-prerelease.6 | 208 | 3/16/2026 |
| 4.0.0-prerelease.5 | 165 | 3/8/2026 |
| 4.0.0-prerelease.4 | 44 | 3/8/2026 |
| 4.0.0-prerelease.3 | 95 | 2/23/2026 |
| 4.0.0-prerelease.2 | 87 | 2/14/2026 |
| 4.0.0-prerelease.1 | 55 | 2/8/2026 |
| 4.0.0-alpha.4 | 56 | 1/27/2026 |
| 4.0.0-alpha.3 | 78 | 1/10/2026 |
| 3.2.4 | 790 | 7/27/2025 |
| 3.2.3 | 183 | 7/11/2025 |
| 3.2.0 | 419 | 4/24/2025 |
| 3.1.0 | 263 | 4/20/2025 |
| 3.0.0 | 236 | 2/19/2025 |
| 3.0.0-prerelease.7 | 128 | 2/18/2025 |
| 3.0.0-prerelease.6 | 122 | 2/18/2025 |
| 3.0.0-prerelease.5 | 153 | 2/17/2025 |
| 3.0.0-prerelease.4 | 131 | 2/17/2025 |
| 3.0.0-prerelease.3 | 153 | 2/17/2025 |