Skip to content
Kieron Lanning edited this page Apr 16, 2026 · 9 revisions

Frequently Asked Questions (FAQ)

Common questions and answers about Purview Telemetry Source Generator.

General Questions

What is Purview Telemetry Source Generator?

Purview Telemetry Source Generator is a .NET incremental source generator that generates implementation code for Activities (distributed tracing), Logging (structured logs), and Metrics based on interface definitions you create. Instead of writing boilerplate telemetry code, you define methods on an interface and the generator creates the implementation automatically.

Why use a source generator for telemetry?

Benefits:

  • 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 and uses aggressive inlining
  • Maintainability - Changes to interfaces automatically propagate to implementation

What .NET versions are supported?

  • .NET Framework 4.8 or higher
  • .NET 8 or higher

Is this compatible with OpenTelemetry?

Yes! Version 4 defaults to OpenTelemetry semantic conventions for generated names. The generated Activities, Logs, and Metrics work seamlessly with OpenTelemetry exporters and collectors.

Installation & Setup

How do I install the package?

Add the NuGet package to your .csproj:

<PackageReference Include="Purview.Telemetry.SourceGenerator" Version="4.1.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>analyzers</IncludeAssets>
</PackageReference>

See Getting Started for more details.

Why do I need PrivateAssets and IncludeAssets?

These settings ensure the source generator runs at compile time without adding runtime dependencies:

  • PrivateAssets="all" - Prevents the package from being exposed to consuming projects
  • IncludeAssets="analyzers" - Includes only what's needed for source generation

Do I need any other packages?

Depends on what you're generating:

For Activities:

  • No additional packages required (uses System.Diagnostics.Activity)

For Logging:

  • Microsoft.Extensions.Logging.Abstractions (usually already in your project)
  • Optionally: Microsoft.Extensions.Telemetry.Abstractions for Generation v2 features

For Metrics:

  • No additional packages required (uses System.Diagnostics.Metrics)

For Dependency Injection:

  • Microsoft.Extensions.DependencyInjection.Abstractions (usually already in your project)

How do I view the generated code?

Add this to your .csproj:

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Generated files appear in obj/Debug|Release/generated/Purview.Telemetry.SourceGenerator/.

Activities

When should I use Activities vs Logging vs Metrics?

Use Activities when:

  • Tracking distributed operations across services
  • Measuring end-to-end request latency
  • Creating trace spans for observability
  • Correlating related operations

Use Logging when:

  • Recording events or state changes
  • Debugging issues with structured data
  • Auditing user actions
  • Recording error details and stack traces

Use Metrics when:

  • Counting occurrences (requests, errors, events)
  • Measuring distributions (latency, size, duration)
  • Tracking gauges (queue depth, memory usage)
  • Aggregating data for dashboards

Pro tip: Use Multi-Targeting to combine all three!

Why does my Activity method return Activity? instead of Activity?

Activities can be null if:

  • No listeners are subscribed to the ActivitySource
  • Sampling determined the activity shouldn't be recorded

Always return Activity? (nullable) and check for null before using the activity. The generated code handles this correctly with the null-conditional operator.

Should I use Activity.Current or pass Activity parameters?

Always pass Activity parameters explicitly. Don't rely on Activity.Current as it may not be the activity you expect, especially in async code or when multiple activities are nested.

// Good
[Event]
void OrderProcessed(Activity? activity, int orderId);

// Avoid
[Event]
void OrderProcessed(int orderId);  // Uses Activity.Current implicitly

What's the difference between [Tag] and [Baggage]?

  • [Tag] - Added as tags to the Activity or ActivityEvent. Tags are recorded with the activity but not automatically propagated to child activities.
  • [Baggage] - Added as baggage to the Activity. Baggage is automatically propagated to all child activities and across service boundaries.

Use baggage sparingly as it increases overhead. Use tags for most properties.

Logging

What's the difference between Generation v1 and v2?

Generation v2 (default when Microsoft.Extensions.Telemetry.Abstractions is available):

  • Uses newer logging telemetry abstractions
  • Supports dynamic message template generation
  • Can enumerate arrays/IEnumerables as log properties
  • More closely matches Microsoft.Gen.Logging output

Generation v1 (legacy):

  • Uses older high-performance logging pattern
  • Simpler generated code
  • Doesn't require Microsoft.Extensions.Telemetry.Abstractions

See Logging for details.

How do I create scoped logs?

Return IDisposable? from your log method:

[Logger]
interface IOrderTelemetry
{
    [Info]
    IDisposable? ProcessingOrder(Guid orderId);
}

// Usage - automatically logs duration
using (telemetry.ProcessingOrder(orderId))
{
    // Processing logic
    // Duration logged automatically when disposed
}

Can I customize log message templates?

Not directly in v4. The message template is generated from the method name and parameters. If you need custom templates, you can:

  1. Use the [Exclude] attribute and implement the method manually
  2. Name your methods descriptively to generate meaningful messages

How do I disable logging generation?

Add this to your .csproj or Directory.Build.props:

<PropertyGroup>
  <DefineConstants>EXCLUDE_PURVIEW_TELEMETRY_LOGGING</DefineConstants>
</PropertyGroup>

This is useful when Microsoft.Extensions.Logging types aren't available in your project.

Metrics

What's the difference between Counter and AutoCounter?

[Counter] - You specify the measurement value:

[Counter]
void ItemsProcessed([InstrumentMeasurement]int count, [Tag]string type);

// Usage
telemetry.ItemsProcessed(5, "widget");  // Adds 5 to counter

[AutoCounter] - Automatically increments by 1:

[AutoCounter]
void ItemProcessed([Tag]string type);

// Usage
telemetry.ItemProcessed("widget");  // Adds 1 to counter

When should I use Histogram vs Counter?

Use Counter when:

  • Counting discrete events (requests, errors, completions)
  • Values only go up (monotonically increasing)

Use Histogram when:

  • Recording distributions (latency, size, duration)
  • Want percentiles (p50, p95, p99)
  • Values can vary widely
[AutoCounter]
void RequestCompleted([Tag]string endpoint);  // Count completions

[Histogram]
void RequestDuration([InstrumentMeasurement]int ms, [Tag]string endpoint);  // Track latency distribution

What are Observable metrics and when should I use them?

Observable metrics (Counter, Gauge, UpDownCounter) are "pull-based" - the collector calls your function to get the current value. Use them for:

  • Values that already exist (memory usage, queue depth, cache size)
  • Expensive calculations you don't want to perform on every update
  • Periodic sampling of state
[ObservableGauge]
void QueueDepth(Func<int> measurement);

// Setup once
telemetry.QueueDepth(() => _queue.Count);
// Collector calls this function periodically

Why do my metric names include the meter name in v4?

In OpenTelemetry mode (v4 default), metric instrument names include the meter name as a prefix for hierarchical naming:

[Meter("MyApp.Orders")]
interface IOrderMetrics
{
    [Counter]
    void OrderProcessed([InstrumentMeasurement]int count);
}

// Generated name: "myapp.orders.order.processed"
//                  ^^^^^^^^^^^^^^^ meter prefix (lowercase)
//                                 ^^^^^^^^^^^^^^^^ instrument name (snake_case)

This follows OpenTelemetry best practices. To revert to v3 naming, use [TelemetryGeneration(NamingConvention = NamingConvention.Legacy)].

Multi-Targeting

Can I generate Activities, Logging, AND Metrics from one interface?

Yes! This is called Multi-Targeting:

[ActivitySource("MyApp")]
[Logger]
[Meter("MyApp")]
interface IMyTelemetry
{
    // Creates Activity + Logs + Increments Counter - all from one method!
    [Activity]
    [Info]
    [AutoCounter]
    Activity? ProcessingRequest([Baggage]string requestId);
}

What's the difference between single-target and multi-target interfaces?

Single-target - Interface has only one class-level attribute ([ActivitySource], [Logger], or [Meter]):

  • Can infer some method-level attributes
  • Simpler method signatures

Multi-target - Interface has multiple class-level attributes:

  • Must explicitly specify all method-level attributes
  • No inference (to avoid ambiguity)
  • More powerful (one method generates multiple telemetry types)

Can I mix single-target and multi-target methods?

In multi-target interfaces, you can have methods that target only one telemetry type:

[ActivitySource("MyApp")]
[Logger]
[Meter]
interface IMyTelemetry
{
    // Multi-target: Activity + Log + Metric
    [Activity]
    [Info]
    [AutoCounter]
    Activity? ProcessRequest(int requestId);
    
    // Single-target: Log only
    [Warning]
    void SlowRequest(int requestId, int durationMs);
    
    // Single-target: Metric only
    [Histogram]
    void ResponseSize([InstrumentMeasurement]int bytes);
}

Configuration & Generation

How do I control the generated class name?

Use the [TelemetryGeneration] attribute:

[TelemetryGeneration(ClassName = "MyCustomTelemetry")]
[ActivitySource("MyApp")]
interface IMyTelemetry
{
    // Generated class will be named "MyCustomTelemetry" instead of "MyTelemetryCore"
}

How do I disable dependency injection generation?

[TelemetryGeneration(GenerateDependencyExtension = false)]
[ActivitySource("MyApp")]
interface IMyTelemetry
{
    // No AddMyTelemetry() extension method will be generated
}

Can I make the DI registration class public?

Yes:

[TelemetryGeneration(DependencyInjectionClassIsPublic = true)]
[ActivitySource("MyApp")]
interface IMyTelemetry
{
    // Generated DI class will be public instead of internal
}

How do I exclude a method from generation?

Use the [Exclude] attribute:

[ActivitySource("MyApp")]
interface IMyTelemetry
{
    [Activity]
    Activity? NormalMethod(int id);
    
    [Exclude]
    void CustomMethod(int id);  // Not generated, you implement it manually
}

// Implement excluded methods in a partial class
partial class MyTelemetryCore
{
    public void CustomMethod(int id)
    {
        // Your custom implementation
    }
}

v3 to v4 Migration

What are the breaking changes in v4?

Two major changes:

  1. Namespace Consolidation - All attributes now in Purview.Telemetry
  2. OpenTelemetry Naming - New default naming conventions (snake_case, hierarchical)

See Breaking Changes for detailed migration guide.

How do I keep v3 naming in v4?

Use the [TelemetryGeneration] attribute:

[assembly: TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]

This reverts to v3-style naming (lowercase, smashed compounds). See OpenTelemetry-Aligned Naming.

Will v4 break my dashboards/queries?

Only if you use the default OpenTelemetry naming mode. Telemetry names will change:

  • "entityid""entity_id" (tags/properties)
  • "recordcount""myapp.products.record.count" (metrics)
  • "myapp""MyApp" (ActivitySource names)

Options:

  1. Update dashboards to use new names (recommended)
  2. Use NamingConvention.Legacy to keep v3 names

Can I use v4 alongside v3?

Not in the same project. Choose one version per project. However, in a multi-project solution, different projects can use different versions (though this isn't recommended for consistency).

Troubleshooting

The generator isn't producing any code

Check:

  1. Interface has a class-level attribute ([ActivitySource], [Logger], or [Meter])
  2. Methods have appropriate method-level attributes
  3. Interface is not generic (generics aren't supported)
  4. Rebuild the project to trigger generation
  5. Check the Error List for diagnostics starting with TSG

I'm getting TSG diagnostic errors

See the Diagnostics page for a complete list of diagnostic codes and their meanings.

Generated code doesn't compile

Common causes:

  1. Missing NuGet packages (Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.DependencyInjection)
  2. Wrong parameter types on methods
  3. Incorrect use of attributes
  4. Generic interfaces or methods (not supported)

Check the Error List for specific compilation errors.

IntelliSense isn't showing the generated implementation

Solutions:

  1. Rebuild the project
  2. Restart Visual Studio / IDE
  3. Close and reopen files
  4. Clean solution (dotnet clean) then rebuild

Source generators sometimes require a rebuild for IntelliSense to update.

Can I use async methods?

The generated methods are synchronous, but your calling code can be async. The telemetry operations are lightweight and non-blocking:

public async Task ProcessAsync(int id)
{
    using var activity = telemetry.StartProcessing(id);
    
    await DoWorkAsync();  // Async work is fine
    
    telemetry.Completed(activity);
}

Performance

Does using a source generator impact performance?

No runtime performance impact! The code is generated at compile time, not runtime. The generated code:

  • Uses aggressive inlining ([MethodImpl(MethodImplOptions.AggressiveInlining)])
  • Avoids allocations where possible
  • Is as fast as hand-written code

Are there any allocations?

Minimal allocations:

  • Activities are pooled by the framework
  • Log state is struct-based in Generation v2
  • Metric instruments are created once and reused

Advanced Topics

Can I customize the generated code?

Not directly, but you can:

  1. Use [Exclude] and implement methods manually in a partial class
  2. Control naming via [TelemetryGeneration] attribute
  3. Request features via GitHub issues

Does this work with .NET Native AOT?

The source generator itself works with AOT. However, generated code may use reflection or DI patterns that need AOT compatibility testing. Full AOT support is on the roadmap.

Can I use this in a library?

Yes! The generated classes are internal by default, so they won't leak to consumers. You can expose the interface and consumers can mock it in tests.

If you need public DI registration:

[TelemetryGeneration(DependencyInjectionClassIsPublic = true)]

How do I test code that uses telemetry interfaces?

Easy - just mock the interface:

// Using Moq
var mockTelemetry = new Mock<IOrderTelemetry>();
var service = new OrderService(mockTelemetry.Object);

// Using NSubstitute
var mockTelemetry = Substitute.For<IOrderTelemetry>();
var service = new OrderService(mockTelemetry);

// Verify calls
mockTelemetry.Verify(x => x.ProcessingOrder(orderId, customerName), Times.Once);

Getting Help

Where can I get help?

How do I report a bug?

Open an issue on GitHub Issues with:

  1. Version of the package
  2. .NET version
  3. Minimal reproducible example
  4. Expected vs actual behavior
  5. Any diagnostic errors (TSG codes)

How do I request a feature?

Open an issue on GitHub Issues with:

  1. Description of the feature
  2. Use case / problem it solves
  3. Example of how you'd like to use it
  4. Any alternatives you've considered

Clone this wiki locally