Skip to content

Getting Started

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

Getting Started with Purview Telemetry Source Generator

This guide will help you get started with the Purview Telemetry Source Generator in minutes.

Installation

Add the NuGet package to your project:

Package Manager Console

Install-Package Purview.Telemetry.SourceGenerator -Version 4.1.0

.NET CLI

dotnet add package Purview.Telemetry.SourceGenerator --version 4.1.0

Project File (.csproj or Directory.Build.props)

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

Quick Start Examples

Example 1: Simple Activity Tracking

Track operations with distributed tracing:

using Purview.Telemetry;

[ActivitySource("OrderService")]
interface IOrderTelemetry
{
    [Activity]
    Activity? ProcessingOrder([Baggage]int orderId, [Tag]string customerName);
    
    [Event]
    void OrderValidated(Activity? activity, [Tag]decimal totalAmount);
    
    [Event]
    void OrderCompleted(Activity? activity, [Tag]TimeSpan processingTime);
}

// Register with DI
services.AddOrderTelemetry();

// Use in your code
public class OrderService(IOrderTelemetry telemetry)
{
    public async Task ProcessOrderAsync(int orderId, string customerName)
    {
        using var activity = telemetry.ProcessingOrder(orderId, customerName);
        
        var order = await ValidateOrderAsync(orderId);
        telemetry.OrderValidated(activity, order.TotalAmount);
        
        await SaveOrderAsync(order);
        
        telemetry.OrderCompleted(activity, stopwatch.Elapsed);
    }
}

Example 2: Structured Logging

Generate structured logs with compile-time safety:

using Purview.Telemetry;

[Logger]
interface IPaymentTelemetry
{
    [Info]
    void ProcessingPayment(Guid paymentId, decimal amount);
    
    [Warning]
    void PaymentRetry(Guid paymentId, int attemptNumber);
    
    [Error]
    void PaymentFailed(Exception ex, Guid paymentId);
    
    [Debug]
    void PaymentDetails(string processor, string transactionId);
}

// Register with DI
services.AddPaymentTelemetry();

// Use in your code
public class PaymentService(IPaymentTelemetry telemetry)
{
    public async Task<bool> ProcessPaymentAsync(Guid paymentId, decimal amount)
    {
        telemetry.ProcessingPayment(paymentId, amount);
        
        try
        {
            var result = await _processor.ChargeAsync(paymentId, amount);
            telemetry.PaymentDetails(result.Processor, result.TransactionId);
            return true;
        }
        catch (Exception ex)
        {
            telemetry.PaymentFailed(ex, paymentId);
            return false;
        }
    }
}

Example 3: Performance Metrics

Track performance metrics with counters and histograms:

using Purview.Telemetry;

[Meter("InventoryService")]
interface IInventoryMetrics
{
    [AutoCounter]
    void InventoryChecked([Tag]string warehouse);
    
    [Histogram]
    void InventoryLookupDuration([InstrumentMeasurement]int milliseconds, [Tag]string warehouse);
    
    [Counter]
    void ItemsProcessed([InstrumentMeasurement]int count, [Tag]string itemType);
}

// Register with DI
services.AddInventoryMetrics();

// Use in your code
public class InventoryService(IInventoryMetrics metrics)
{
    public async Task<int> CheckInventoryAsync(string warehouse)
    {
        metrics.InventoryChecked(warehouse);
        
        var sw = Stopwatch.StartNew();
        var count = await _repository.GetCountAsync(warehouse);
        metrics.InventoryLookupDuration((int)sw.ElapsedMilliseconds, warehouse);
        
        return count;
    }
    
    public void ProcessItems(string itemType, int count)
    {
        // ... processing logic ...
        metrics.ItemsProcessed(count, itemType);
    }
}

Example 4: Multi-Target (Combined Telemetry)

Generate Activities, Logs, AND Metrics from a single interface:

using Purview.Telemetry;

[ActivitySource("UserService")]
[Logger]
[Meter("UserService")]
interface IUserServiceTelemetry
{
    // Multi-target: Creates Activity + Logs Info + Increments Counter
    [Activity]
    [Info]
    [AutoCounter]
    Activity? AuthenticatingUser([Baggage]string username, [Tag]string ipAddress);
    
    // Logs only
    [Warning]
    void InvalidLoginAttempt(string username, string reason);
    
    // Activity event + Log
    [Event]
    [Debug]
    void UserAuthenticated(Activity? activity, [Tag]Guid userId, [Tag]string role);
    
    // Metric only
    [Histogram]
    void AuthenticationDuration([InstrumentMeasurement]int milliseconds, [Tag]bool success);
}

// Register with DI (single registration for all telemetry types)
services.AddUserServiceTelemetry();

// Use in your code - single call emits multiple telemetry types!
public class AuthService(IUserServiceTelemetry telemetry)
{
    public async Task<User?> AuthenticateAsync(string username, string ipAddress)
    {
        // Single call creates Activity AND logs AND increments counter!
        using var activity = telemetry.AuthenticatingUser(username, ipAddress);
        
        var sw = Stopwatch.StartNew();
        var user = await _userRepository.FindAsync(username);
        
        if (user == null)
        {
            telemetry.InvalidLoginAttempt(username, "User not found");
            telemetry.AuthenticationDuration((int)sw.ElapsedMilliseconds, false);
            return null;
        }
        
        // Adds event to Activity AND logs
        telemetry.UserAuthenticated(activity, user.Id, user.Role);
        
        // Records histogram
        telemetry.AuthenticationDuration((int)sw.ElapsedMilliseconds, true);
        
        return user;
    }
}

Next Steps

Now that you've seen the basics, explore more advanced features:

  • Activities - Deep dive into distributed tracing with Activities, Events, and Context
  • Logging - Structured logging with Generation v2 features
  • Metrics - Counters, Histograms, and Observable metrics
  • Multi-Targeting - Combine multiple telemetry types in one interface
  • Generation Options - Control code generation, dependency injection, and naming
  • Generated Output - See what code is actually generated
  • Sample Application - Full .NET Aspire sample with all features

Common Patterns

Pattern 1: Scoped Logging

Return IDisposable? to create scoped log entries:

[Logger]
interface IOrderTelemetry
{
    [Info]
    IDisposable? ProcessingOrder(Guid orderId);  // Logs at start and end
    
    [Error]
    void OrderFailed(Exception ex, Guid orderId);
}

public async Task ProcessOrderAsync(Guid orderId)
{
    using (telemetry.ProcessingOrder(orderId))
    {
        // Processing logic here
        // Automatically logs duration when disposed
    }
}

Pattern 2: Activity with Multiple Events

Track multiple stages within a single activity:

[ActivitySource("ShippingService")]
interface IShippingTelemetry
{
    [Activity]
    Activity? ShippingPackage([Baggage]string trackingNumber);
    
    [Event]
    void PackageLabeled(Activity? activity);
    
    [Event]
    void PackageWeighed(Activity? activity, [Tag]decimal weight);
    
    [Event]
    void PackageShipped(Activity? activity, [Tag]string carrier);
}

public async Task ShipAsync(string trackingNumber, Package package)
{
    using var activity = telemetry.ShippingPackage(trackingNumber);
    
    await LabelPackageAsync(package);
    telemetry.PackageLabeled(activity);
    
    var weight = await WeighPackageAsync(package);
    telemetry.PackageWeighed(activity, weight);
    
    var carrier = await SchedulePickupAsync(package);
    telemetry.PackageShipped(activity, carrier);
}

Pattern 3: Auto-Incrementing Counters

Use [AutoCounter] for simple counting without measurement parameters:

[Meter("ApiService")]
interface IApiMetrics
{
    [AutoCounter]
    void RequestReceived([Tag]string endpoint, [Tag]string method);
    
    [AutoCounter]
    void RequestFailed([Tag]string endpoint, [Tag]int statusCode);
}

// Every call automatically increments by 1
public IActionResult Get(string id)
{
    metrics.RequestReceived("/api/items/{id}", "GET");
    
    var item = _repository.Find(id);
    if (item == null)
    {
        metrics.RequestFailed("/api/items/{id}", 404);
        return NotFound();
    }
    
    return Ok(item);
}

Viewing Generated Code

To inspect the generated source code, add this to your .csproj:

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

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

Tips and Best Practices

  1. Use explicit return types - Always return Activity? from activity methods, not void
  2. Pass activities explicitly - Don't rely on Activity.Current; pass activity parameters
  3. Namespace - Use single using Purview.Telemetry; import (v4+)
  4. Naming convention - v4 defaults to OpenTelemetry conventions (snake_case tags, hierarchical metrics)
  5. DI registration - Use generated Add{InterfaceName}() extension methods
  6. Testing - Mock telemetry interfaces easily in unit tests
  7. Multi-targeting - Combine Activities + Logs + Metrics when you need complete observability

Troubleshooting

Build Errors

"Type or namespace 'ActivitySourceAttribute' could not be found"

  • Ensure you added using Purview.Telemetry; (v4) or correct namespace (v3)
  • Check that the NuGet package is installed correctly

"No implementation found for interface"

  • Verify the interface has required attributes ([ActivitySource], [Logger], or [Meter])
  • Check that methods have appropriate method-level attributes
  • Rebuild the project to trigger source generation

Runtime Issues

"ActivitySource not producing traces"

  • Ensure you've called the Add{InterfaceName}() DI registration method
  • Configure OpenTelemetry to listen to your ActivitySource name
  • Check that your tracing exporter is configured correctly

"Logs not appearing"

  • Verify ILogger is configured in your application
  • Check log level filters aren't excluding your log messages
  • Ensure DI registration was called

"Metrics not collected"

  • Configure a metrics exporter in your application
  • Verify the Meter name matches what your collector expects
  • Check that metrics are being recorded with valid measurement values

Migration from v3

If you're upgrading from v3, see the Breaking Changes guide for detailed migration instructions, especially:

Clone this wiki locally