Frequently Asked Questions (FAQ)
Frequently Asked Questions (FAQ)
Section titled “Frequently Asked Questions (FAQ)”Common questions and answers about the Purview Telemetry Source Generator.
General questions
Section titled “General questions”What is the Purview Telemetry Source Generator?
Section titled “What is the Purview Telemetry Source Generator?”A .NET incremental source generator that produces implementation code for Activities (distributed tracing), Logging (structured logs), and Metrics from interface definitions you create. Instead of writing boilerplate telemetry code, you define methods on an interface and the generator creates the implementation, DI registration helpers, and multi-target generation.
Why use a source generator for telemetry?
Section titled “Why use a source generator for telemetry?”- Zero boilerplate — no manual implementation code to write or maintain
- Type safety — compile-time validation of telemetry code
- Testability — easy to mock interfaces in unit tests
- Consistency — all telemetry follows the same patterns
- DI-ready — automatic dependency injection registration
- Performance — generated code is optimized
- Maintainability — changes to interfaces propagate automatically
What .NET versions are supported?
Section titled “What .NET versions are supported?”- .NET 8 or higher
- .NET Framework 4.8 or higher
Is this compatible with OpenTelemetry?
Section titled “Is this compatible with OpenTelemetry?”Yes. v4+ defaults to OpenTelemetry semantic conventions for generated names. The generated Activities, Logs, and Metrics work with OpenTelemetry exporters and collectors.
Installation & setup
Section titled “Installation & setup”How do I install the package?
Section titled “How do I install the package?”Add the NuGet package to your .csproj:
<PackageReference Include="Purview.Telemetry.SourceGenerator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>analyzers</IncludeAssets></PackageReference>See Getting Started for more details.
Why do I need PrivateAssets and IncludeAssets?
Section titled “Why do I need PrivateAssets and IncludeAssets?”PrivateAssets="all"— prevents the package from being exposed to consuming projectsIncludeAssets="analyzers"— includes only what is needed for source generation
Do I need any other packages?
Section titled “Do I need any other packages?”Depends on what you generate:
- Activities — no additional packages (uses
System.Diagnostics.DiagnosticSource) - Logging —
Microsoft.Extensions.Logging.Abstractions; optionallyMicrosoft.Extensions.Telemetry.Abstractionsfor v2 features - Metrics — no additional packages (uses
System.Diagnostics.Metrics) - DI —
Microsoft.Extensions.DependencyInjection.Abstractions(usually already present)
How do I view the generated code?
Section titled “How do I view the generated code?”<PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles></PropertyGroup>Generated files appear in obj/Debug|Release/<tfm>/generated/Purview.Telemetry.SourceGenerator/Purview.Telemetry.SourceGenerator.TelemetrySourceGenerator/.
Activities
Section titled “Activities”When should I use Activities vs Logging vs Metrics?
Section titled “When should I use Activities vs Logging vs Metrics?”- Activities — distributed operations across services, end-to-end latency, trace spans
- Logging — events, state changes, debugging with structured data, error details
- Metrics — counting occurrences, distributions, gauges for dashboards
Pro tip: use Multi-Targeting to combine all three.
Why does my Activity method return Activity? instead of Activity?
Section titled “Why does my Activity method return Activity? instead of Activity?”Activities can be null when no listeners are subscribed to the ActivitySource or sampling determines the activity should not be recorded. Always return Activity? and guard against null.
Should I use Activity.Current or pass Activity parameters?
Section titled “Should I use Activity.Current or pass Activity parameters?”Always pass Activity parameters explicitly. Activity.Current may not be the activity you expect, especially in async code or with nested activities.
// Good[Event]void OrderProcessed(Activity? activity, int orderId);
// Avoid[Event]void OrderProcessed(int orderId); // uses Activity.Current implicitlyWhat’s the difference between [Tag] and [Baggage]?
Section titled “What’s the difference between [Tag] and [Baggage]?”[Tag]— added as tags to the Activity or ActivityEvent; recorded with the activity but not automatically propagated to child activities[Baggage]— added as baggage; automatically propagated to child activities and across service boundaries
Use baggage sparingly as it increases overhead; use tags for most properties.
Logging
Section titled “Logging”What’s the difference between Generation v1 and v2?
Section titled “What’s the difference between Generation v1 and v2?”See Logging for the full comparison. In brief:
- Generation v2 — state-based output resembling the built-in
[LoggerMessage]generator; supports dynamic message templates,[ExpandEnumerable], and[LogProperties]; requiresMicrosoft.Extensions.Telemetry.Abstractions. - Generation v1 —
LoggerMessage.Definehigh-performance mode; limited to 6 non-exception parameters plus oneException; no[ExpandEnumerable]/[LogProperties].
The default LoggerGenerationMode.Auto selects the best mode per method.
How do I create scoped logs?
Section titled “How do I create scoped logs?”Return IDisposable? from a log method:
[Logger]interface IOrderTelemetry{ [Info] IDisposable? ProcessingOrder(Guid orderId);}
using (telemetry.ProcessingOrder(orderId)){ // Duration logged automatically when disposed}Can I customize log message templates?
Section titled “Can I customize log message templates?”Yes — [Log].MessageTemplate sets the template. Placeholders map to method parameters:
[Info("Order {OrderId} placed for {CustomerName}")]void OrderPlaced(int orderId, string customerName);If no template is specified, one is generated from the method name and parameters.
How do I disable logging generation?
Section titled “How do I disable logging generation?”<PropertyGroup> <DefineConstants>EXCLUDE_PURVIEW_TELEMETRY_LOGGING</DefineConstants></PropertyGroup>Useful when Microsoft.Extensions.Logging types are not available.
Metrics
Section titled “Metrics”What’s the difference between Counter and AutoCounter?
Section titled “What’s the difference between Counter and AutoCounter?”-
[Counter]— you specify the measurement value:[Counter]void ItemsProcessed([InstrumentMeasurement]int count, [Tag]string type); -
[AutoCounter]— automatically increments by 1 per call:[AutoCounter]void ItemProcessed([Tag]string type);
When should I use Histogram vs Counter?
Section titled “When should I use Histogram vs Counter?”- Counter — discrete events that only go up (requests, errors, completions)
- Histogram — distributions where percentiles matter (latency, size, duration)
What are observable metrics and when should I use them?
Section titled “What are observable metrics and when should I use them?”Observable instruments ([ObservableCounter], [ObservableGauge], [ObservableUpDownCounter]) are pull-based — the collector calls your Func<T> to get the current value. Use them for values that already exist (memory usage, queue depth, cache size) or expensive calculations you don’t want to run on every update.
[ObservableGauge]void QueueDepth(Func<int> measurement);
telemetry.QueueDepth(() => _queue.Count);When do metric names include the meter name?
Section titled “When do metric names include the meter name?”Only when you opt in with [assembly: MeterGeneration(MeterNameGenerationType = MeterNameGenerationType.OpenTelemetry)]. The default, MeterNameGenerationType.DotNet, prefixes instrument names with the interface name instead:
[Meter("MyApp.Products")]interface IProductTelemetry{ [Counter] void RecordCount([InstrumentMeasurement] int count);}
// Default: "product.record_count"// MeterNameGenerationType.OpenTelemetry: "my_app.products.product.record_count"// ^^^^^^^^^^^^^^^ meter name (snake_case), outermost// ^^^^^^^ interface prefix (IProductTelemetry)// ^^^^^^^^^^^^ instrument nameSee Instrument naming for the full rules.
Registering names
Section titled “Registering names”How do I register the names from every project in a large solution?
Section titled “How do I register the names from every project in a large solution?”Each assembly records its own generated names in metadata ([assembly: GeneratedTelemetryNames]), and the TelemetryNames class aggregates them across the reference graph. Opt the project that registers telemetry into the class:
[assembly: TelemetryGeneration(GenerateTelemetryNamesClass = true)]builder.AddServiceDefaults(TelemetryNames.MeterNames, TelemetryNames.ActivitySourceNames);That project needs no telemetry interfaces of its own — the assembly-level attribute is enough, and the class is filled from what its references recorded.
Libraries need no configuration at all, however many of them there are and however complex their telemetry. The aggregate is built at compile time, so it is safe under trimming and native AOT, and it covers transitive references too.
samples/SampleApp.MultiProject is a runnable example: four assemblies own telemetry, two of them only transitively referenced by the host, and one ServiceDefaults-style project receives the complete set of names (just run-mp).
Why can’t my ServiceDefaults project generate the names itself?
Section titled “Why can’t my ServiceDefaults project generate the names itself?”Because of the reference direction: an Aspire-style ServiceDefaults is referenced by the projects that own the telemetry, so their assemblies are not in its reference graph. Generate the class in each host (executable) project and pass the arrays into ServiceDefaults, as the sample application does.
Multi-targeting
Section titled “Multi-targeting”Can I generate Activities, Logging, AND Metrics from one interface?
Section titled “Can I generate Activities, Logging, AND Metrics from one interface?”Yes — this is called Multi-Targeting:
[ActivitySource("MyApp")][Logger][Meter("MyApp")]interface IMyTelemetry{ [Activity] [Info] [AutoCounter] Activity? ProcessingRequest([Baggage]string requestId);}What’s the difference between single-target and multi-target interfaces?
Section titled “What’s the difference between single-target and multi-target interfaces?”- Single-target — one class-level attribute; method-level inference is supported.
- Multi-target — multiple class-level attributes; every method must declare its targets explicitly (no inference).
Configuration & generation
Section titled “Configuration & generation”How do I control the generated class name?
Section titled “How do I control the generated class name?”[TelemetryGeneration(ClassName = "MyCustomTelemetry")][ActivitySource("MyApp")]interface IMyTelemetry { }How do I disable dependency injection generation?
Section titled “How do I disable dependency injection generation?”[TelemetryGeneration(GenerateDependencyExtension = false)][ActivitySource("MyApp")]interface IMyTelemetry { }How do I exclude a method from generation?
Section titled “How do I exclude a method from generation?”Use [Exclude] and implement it manually in a partial class:
[ActivitySource("MyApp")]interface IMyTelemetry{ [Activity] Activity? NormalMethod(int id);
[Exclude] void CustomMethod(int id);}
partial class MyTelemetryCore{ public void CustomMethod(int id) { // your custom implementation }}Migration
Section titled “Migration”What are the breaking changes in v4 and v5?
Section titled “What are the breaking changes in v4 and v5?”Two major v4 changes — namespace consolidation into Purview.Telemetry and OpenTelemetry naming — plus v5 changes including the meter default-name resolution. See Breaking Changes.
How do I keep v3 naming?
Section titled “How do I keep v3 naming?”[assembly: TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]See OpenTelemetry-Aligned Naming.
Can I use v4 alongside v3?
Section titled “Can I use v4 alongside v3?”Not in the same project. In a multi-project solution different projects can use different versions, though that is not recommended.
Troubleshooting
Section titled “Troubleshooting”The generator isn’t producing any code
Section titled “The generator isn’t producing any code”- The interface has a class-level attribute (
[ActivitySource],[Logger], or[Meter]) - Methods have appropriate method-level attributes
- The interface is not generic (generics are not supported)
- Rebuild the project to trigger generation
- Check the Error List for
TSGdiagnostics
I’m getting TSG diagnostic errors
Section titled “I’m getting TSG diagnostic errors”See Diagnostics for the complete list of codes and meanings.
Generated code doesn’t compile
Section titled “Generated code doesn’t compile”Common causes: missing packages (Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Telemetry.Abstractions for v2), wrong parameter types, incorrect attribute usage, or generic interfaces/methods.
Can I use async methods?
Section titled “Can I use async methods?”The generated methods are synchronous, but your calling code can be async — telemetry operations are lightweight and non-blocking.
Performance
Section titled “Performance”Does using a source generator impact performance?
Section titled “Does using a source generator impact performance?”No runtime impact — the code is generated at compile time. The generated code is as fast as hand-written telemetry with minimal allocations. See Performance.
Advanced topics
Section titled “Advanced topics”Can I customize the generated code?
Section titled “Can I customize the generated code?”Not directly, but you can use [Exclude] with partial-class implementations, control naming via [TelemetryGeneration], and configure defaults through the assembly-level generation attributes.
Does this work with .NET Native AOT?
Section titled “Does this work with .NET Native AOT?”The source generator itself works with AOT. Generated code uses no reflection and is trimmable; validate your full scenario with AOT analysis enabled.
Can I use this in a library?
Section titled “Can I use this in a library?”Yes. Generated implementation and DI classes are internal by default, so they don’t leak to consumers. Make the DI class public with [TelemetryGeneration(DependencyInjectionClassIsPublic = true)] if you need public registration helpers.
How do I test code that uses telemetry interfaces?
Section titled “How do I test code that uses telemetry interfaces?”Just mock the interface — see Testing:
var mockTelemetry = Substitute.For<IOrderTelemetry>();var service = new OrderService(mockTelemetry);Getting help
Section titled “Getting help”- Documentation — purview.dev
- Issues — GitHub Issues
- Sample — Sample Application
When reporting a bug, include the package version, .NET version, a minimal repro, expected vs actual behaviour, and any TSG diagnostics.