IDiginsightActivitiesOptions Interface

Configuration for activity source registration and listener setup

reference
diagnostics
activity-sources
configuration
Defines which ActivitySource instances should be listened to by Diginsight diagnostics components. Controls automatic activity listener registration based on activity source name patterns.
Author

Diginsight Team

Published

January 19, 2026

IDiginsightActivitiesOptions Interface

The IDiginsightActivitiesOptions provides configuration for activity source registration controlling which ActivitySource instances are monitored by Diginsight diagnostics.

In particular, it maintains a dictionary of activity source name patterns mapped to boolean values indicating whether activities from matching sources should be captured. This interface enables selective activity listening, allowing applications to focus on relevant activity sources while ignoring others.

IDiginsightActivitiesOptions is part of Diginsight.Diagnostics (Diginsight.Diagnostics.dll).

The primary implementation is DiginsightActivitiesOptions, which also implements several other interfaces for comprehensive activity configuration including logging, metrics, and dynamic configuration support.

Table of Contents

πŸ“‹ Overview

The IDiginsightActivitiesOptions interface controls which ActivitySource instances Diginsight monitors:

  1. Pattern-based registration: Use wildcard patterns to match activity source names
  2. Inclusion/exclusion control: Boolean values determine whether to listen to matching sources
  3. Multiple pattern support: Configure multiple patterns with different rules
  4. Dynamic discovery: Automatically applies to new activity sources created at runtime
  5. Integration point: Used by IActivityListenerRegistration implementations to determine listening behavior

This interface is consumed by several Diginsight components: - ActivityLifecycleLogEmitterRegistration: Determines which activities to log - SpanDurationMetricRecorderRegistration: Determines which activities to measure - Custom IActivityListenerRegistration implementations: Can use this for consistent filtering

Key Features

  • Flexible Pattern Matching: Support for exact names, prefix wildcards (*Suffix), suffix wildcards (Prefix*), and infix wildcards (Prefix*Suffix)
  • Boolean Control: Map patterns to true (include) or false (exclude) for fine-grained control
  • Multiple Pattern Evaluation: When multiple patterns match, ALL must agree for the source to be included
  • Case-Insensitive Matching: Activity source name comparison is case-insensitive
  • Runtime Application: Patterns evaluated when new ActivitySource instances are created
  • Zero-Configuration Option: Empty dictionary means no activity sources are listened to by default
  • Complementary to IDiginsightActivitiesLogOptions: This controls which sources to listen to; IDiginsightActivitiesLogOptions controls how to log them

Activity Source Name Patterns

Activity sources are identified by names (e.g., "MyApp.Services", "System.Net.Http"). The ActivitySources dictionary maps patterns to boolean values:

Pattern Matches Example Sources
"MyApp" Exact name only MyApp
"MyApp.*" Names starting with prefix MyApp.Services, MyApp.Data.Repositories
"*.Controllers" Names ending with suffix WebApi.Controllers, Admin.Controllers
"MyApp.*.Service" Names with prefix and suffix MyApp.Users.Service, MyApp.Orders.Service
"*" All activity sources Everything

Boolean Values: - true: Include activities from matching sources - false: Exclude activities from matching sources (explicit denial)

Registration Behavior

The listening decision follows this logic:

// Pseudo-code for ShouldListenTo logic
bool ShouldListenTo(ActivitySource source)
{
    // Find all patterns that match the source name
    var matchingPatterns = ActivitySources
        .Where(kvp => PatternMatches(source.Name, kvp.Key))
        .Select(kvp => kvp.Value);
    
    // If no patterns match, don't listen
    if (!matchingPatterns.Any())
        return false;
    
    // All matching patterns must be true to listen
    return matchingPatterns.All(value => value == true);
}

Key Behaviors: - No matches: Source is NOT listened to (opt-in by default) - All matches are true: Source IS listened to - Any match is false: Source is NOT listened to (explicit exclusion wins) - Mixed true/false: Source is NOT listened to (requires unanimous agreement)


πŸ” Additional Details

Pattern Matching Rules

Pattern matching is implemented by ActivityUtils.NameMatchesPattern with the following rules:

1. Exact Match (no wildcards):

Pattern: "MyApp.Services"
Matches: "MyApp.Services" only
Does NOT match: "MyApp.Services.UserService", "MyServices", "myapp.services" (wait, it's case-insensitive!)
Actually matches: "myapp.services", "MYAPP.SERVICES" (case-insensitive)

2. Suffix Wildcard:

Pattern: "MyApp.*"
Matches: "MyApp.Services", "MyApp.Data", "MyApp.Controllers.AdminController"
Does NOT match: "MyApplication", "YourApp.Services"

3. Prefix Wildcard:

Pattern: "*.Controllers"
Matches: "WebApi.Controllers", "Admin.Controllers", "Controllers"
Does NOT match: "WebApi.Controllers.Admin", "MyControllers"

4. Infix Wildcard:

Pattern: "MyApp.*.Service"
Matches: "MyApp.Users.Service", "MyApp.Data.UserRepository.Service"
Does NOT match: "MyApp.Service", "MyApp.UserService", "YourApp.Users.Service"

5. Match All:

Pattern: "*"
Matches: Everything (any non-empty activity source name)

Invalid Patterns:

Pattern: "MyApp.*.*.Service"  // ❌ Multiple wildcards (more than 2 segments)
// Throws ArgumentException: "Invalid activity name pattern"

Case Sensitivity: - All comparisons are case-insensitive using StringComparison.OrdinalIgnoreCase - "MyApp.*" matches "myapp.services", "MYAPP.DATA", "MyApp.Controllers"

Boolean Logic for Multiple Matches

When an activity source name matches multiple patterns, the boolean values determine the final decision:

Scenario 1: All True (Include)

ActivitySources = {
    ["MyApp.*"] = true,      // Matches "MyApp.Services"
    ["*.Services"] = true     // Matches "MyApp.Services"
}
// Result: "MyApp.Services" IS listened to (all matches are true)

Scenario 2: Mixed True/False (Exclude)

ActivitySources = {
    ["MyApp.*"] = true,           // Matches "MyApp.HighFrequency"
    ["*.HighFrequency"] = false   // Matches "MyApp.HighFrequency" (explicit exclusion)
}
// Result: "MyApp.HighFrequency" is NOT listened to (any false = excluded)

Scenario 3: All False (Explicit Exclusion)

ActivitySources = {
    ["*"] = true,              // Matches everything
    ["System.*"] = false       // Matches "System.Net.Http"
}
// Result: "System.Net.Http" is NOT listened to (explicit exclusion)

Use Cases: - Include broad, exclude specific: ["*"] = true + ["System.*"] = false - Include specific, exclude subset: ["MyApp.*"] = true + ["MyApp.Internal.*"] = false - Explicit allow-listing: Only include patterns with true, no catch-all

Integration with ActivityListener

The IDiginsightActivitiesOptions is consumed by IActivityListenerRegistration implementations:

// Simplified from ActivityLifecycleLogEmitterRegistration
public class ActivityLifecycleLogEmitterRegistration : IActivityListenerRegistration
{
    private readonly IDiginsightActivitiesOptions activitiesOptions;
    
    public bool ShouldListenTo(ActivitySource activitySource)
    {
        string activitySourceName = activitySource.Name;
        
        // Find all matching patterns
        IEnumerable<bool> matches = activitiesOptions.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(activitySourceName, kvp.Key))
            .Select(kvp => kvp.Value)
            .ToArray();
        
        // Listen only if:
        // 1. At least one pattern matches
        // 2. ALL matching patterns are true
        return matches.Any() && matches.All(value => value);
    }
}

Integration Flow: 1. Application creates ActivitySource with name (e.g., "MyApp.Services") 2. .NET runtime calls ActivityListener.ShouldListenTo for each registered listener 3. Diginsight’s IActivityListenerRegistration implementations call ShouldListenTo 4. Implementation queries IDiginsightActivitiesOptions.ActivitySources 5. Pattern matching determines if the source should be listened to 6. If yes, activities from that source will be captured and processed

ActivitySource Discovery

Activity sources can be registered at any time during application lifecycle:

Static Registration (Common):

public static class Observability
{
    // Created at class initialization
    public static readonly ActivitySource Source = new ActivitySource("MyApp.Services");
}

Dynamic Registration (Runtime):

// Created dynamically based on plugin system
var pluginSource = new ActivitySource($"MyApp.Plugins.{pluginName}");

Key Points: - Diginsight patterns apply to ALL activity sources, regardless of when they’re created - No need to pre-register activity sources with Diginsight - ShouldListenTo is called lazily when activities are started - Patterns are evaluated each time (supports dynamic configuration changes)


βš™οΈ Configuration

Configuration in appsettings.json

{
  "Diginsight": {
    "DiginsightActivitiesOptions": {
      "ActivitySources": {
        "MyApp.*": true,
        "System.Net.Http": true,
        "Microsoft.AspNetCore.*": true,
        "System.Diagnostics.*": false,
        "System.Private.*": false
      }
    }
  }
}

Configuration Properties: - ActivitySources: Dictionary mapping activity source name patterns to boolean values - Key: Pattern string (supports exact, prefix *, suffix *, and infix wildcards) - Value: true to include, false to exclude

Common Patterns:

{
  "ActivitySources": {
    // Include all application sources
    "MyApp.*": true,
    
    // Include specific third-party sources
    "System.Net.Http": true,
    "Npgsql": true,
    "Microsoft.EntityFrameworkCore": true,
    
    // Include ASP.NET Core diagnostics
    "Microsoft.AspNetCore.*": true,
    
    // Exclude system internals
    "System.Private.*": false,
    "System.Diagnostics.Eventing.*": false,
    
    // Catch-all: include everything not explicitly excluded
    "*": true
  }
}

Configuration in the startup sequence

Register Diginsight diagnostics and configure activity sources:

// In Program.cs

using Diginsight.Diagnostics;

var builder = WebApplication.CreateBuilder(args);

// Add Diginsight diagnostics
builder.Services.AddDiginsightDiagnostics();

// Configure activity sources
builder.Services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Include application activity sources
    options.ActivitySources["MyApp.*"] = true;
    
    // Include HTTP client activities
    options.ActivitySources["System.Net.Http"] = true;
    
    // Include ASP.NET Core activities
    options.ActivitySources["Microsoft.AspNetCore.*"] = true;
    
    // Exclude high-frequency internal diagnostics
    options.ActivitySources["System.Diagnostics.*"] = false;
});

// Or bind from configuration
builder.Services.Configure<DiginsightActivitiesOptions>(
    builder.Configuration.GetSection("Diginsight:DiginsightActivitiesOptions"));

var app = builder.Build();

Fluent Configuration Helper:

public static class ActivitySourceConfiguration
{
    public static IServiceCollection AddApplicationActivitySources(
        this IServiceCollection services)
    {
        return services.Configure<DiginsightActivitiesOptions>(options =>
        {
            // Include application sources
            options.ActivitySources["MyApp.*"] = true;
            
            // Include common infrastructure
            options.ActivitySources["System.Net.Http"] = true;
            options.ActivitySources["Microsoft.EntityFrameworkCore"] = true;
            options.ActivitySources["Npgsql"] = true;
            
            // Include ASP.NET Core
            options.ActivitySources["Microsoft.AspNetCore.Hosting"] = true;
            options.ActivitySources["Microsoft.AspNetCore.Routing"] = true;
            
            // Exclude noisy sources
            options.ActivitySources["System.Private.*"] = false;
        });
    }
}

// Usage
builder.Services
    .AddDiginsightDiagnostics()
    .AddApplicationActivitySources();

Configuring Multiple Activity Sources

Organize patterns by category for maintainability:

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Application layer
    AddApplicationSources(options);
    
    // Infrastructure layer
    AddInfrastructureSources(options);
    
    // Third-party integrations
    AddThirdPartySources(options);
    
    // Exclusions
    AddExclusions(options);
});

static void AddApplicationSources(DiginsightActivitiesOptions options)
{
    options.ActivitySources["MyApp.Controllers.*"] = true;
    options.ActivitySources["MyApp.Services.*"] = true;
    options.ActivitySources["MyApp.Repositories.*"] = true;
}

static void AddInfrastructureSources(DiginsightActivitiesOptions options)
{
    options.ActivitySources["System.Net.Http"] = true;
    options.ActivitySources["Microsoft.AspNetCore.*"] = true;
}

static void AddThirdPartySources(DiginsightActivitiesOptions options)
{
    options.ActivitySources["Npgsql"] = true;
    options.ActivitySources["StackExchange.Redis"] = true;
    options.ActivitySources["Azure.*"] = true;
}

static void AddExclusions(DiginsightActivitiesOptions options)
{
    options.ActivitySources["System.Private.*"] = false;
    options.ActivitySources["System.Diagnostics.Eventing.*"] = false;
    options.ActivitySources["MyApp.HighFrequency.*"] = false;
}

Dynamic Configuration Updates

Update activity sources at runtime using IOptionsMonitor:

public class DiagnosticsController : ControllerBase
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    
    public DiagnosticsController(IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor)
    {
        _optionsMonitor = optionsMonitor;
    }
    
    [HttpPost("enable-source/{sourceName}")]
    public IActionResult EnableActivitySource(string sourceName)
    {
        var options = _optionsMonitor.CurrentValue;
        
        // Add or update pattern
        options.ActivitySources[sourceName] = true;
        
        return Ok($"Enabled activity source: {sourceName}");
    }
    
    [HttpPost("disable-source/{sourceName}")]
    public IActionResult DisableActivitySource(string sourceName)
    {
        var options = _optionsMonitor.CurrentValue;
        
        // Explicitly exclude
        options.ActivitySources[sourceName] = false;
        
        return Ok($"Disabled activity source: {sourceName}");
    }
    
    [HttpGet("active-sources")]
    public IActionResult GetActiveSources()
    {
        var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
        
        return Ok(new
        {
            patterns = options.ActivitySources.ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value ? "included" : "excluded")
        });
    }
}

πŸ’‘ Usage Examples

Basic Usage

using Diginsight.Diagnostics;
using Microsoft.Extensions.Options;
using System.Diagnostics;

public class ActivitySourceMonitor
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    private readonly ILogger<ActivitySourceMonitor> _logger;

    public ActivitySourceMonitor(
        IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor,
        ILogger<ActivitySourceMonitor> logger)
    {
        _optionsMonitor = optionsMonitor;
        _logger = logger;
    }

    public void CheckSourceConfiguration(ActivitySource source)
    {
        var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
        
        _logger.LogInformation("Checking configuration for source: {SourceName}", source.Name);
        
        // Find matching patterns
        var matchingPatterns = options.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(source.Name, kvp.Key))
            .ToList();
        
        if (!matchingPatterns.Any())
        {
            _logger.LogInformation("  No patterns match - source will NOT be listened to");
        }
        else
        {
            foreach (var kvp in matchingPatterns)
            {
                _logger.LogInformation("  Pattern '{Pattern}': {Action}",
                    kvp.Key,
                    kvp.Value ? "INCLUDE" : "EXCLUDE");
            }
            
            bool shouldListen = matchingPatterns.All(kvp => kvp.Value);
            _logger.LogInformation("  Final decision: {Decision}",
                shouldListen ? "LISTEN" : "DO NOT LISTEN");
        }
    }
}

Explanation: - Access current options via IOptionsMonitor<DiginsightActivitiesOptions> - Cast to IDiginsightActivitiesOptions to access the interface properties - Use ActivityUtils.NameMatchesPattern for pattern matching - All matching patterns must be true for listening to occur

Pattern-Based Filtering

// Scenario: Monitor only application code, not framework internals

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Include application namespace
    options.ActivitySources["MyCompany.MyApp.*"] = true;
    
    // Include specific framework components
    options.ActivitySources["Microsoft.AspNetCore.Hosting"] = true;
    options.ActivitySources["Microsoft.AspNetCore.Routing"] = true;
    options.ActivitySources["System.Net.Http"] = true;
    
    // Exclude everything else by not adding catch-all "*" = true
});

// Result:
// βœ“ Listened: MyCompany.MyApp.Services.UserService
// βœ“ Listened: MyCompany.MyApp.Controllers.HomeController
// βœ“ Listened: Microsoft.AspNetCore.Hosting
// βœ“ Listened: System.Net.Http
// βœ— Ignored: System.Diagnostics.DiagnosticSource
// βœ— Ignored: System.Private.CoreLib
// βœ— Ignored: Microsoft.Extensions.Logging

Alternative: Include All, Exclude Specific:

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Include everything
    options.ActivitySources["*"] = true;
    
    // Explicitly exclude noisy or internal sources
    options.ActivitySources["System.Private.*"] = false;
    options.ActivitySources["System.Diagnostics.*"] = false;
    options.ActivitySources["Microsoft.Extensions.*"] = false;
});

// Result:
// βœ“ Listened: MyCompany.MyApp.Services.UserService
// βœ“ Listened: Microsoft.AspNetCore.Hosting
// βœ“ Listened: System.Net.Http
// βœ— Ignored: System.Private.CoreLib (explicit exclusion)
// βœ— Ignored: System.Diagnostics.DiagnosticSource (explicit exclusion)
// βœ— Ignored: Microsoft.Extensions.Logging (explicit exclusion)

Excluding Specific Sources

// Scenario: Include application but exclude high-frequency subsystem

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Include entire application
    options.ActivitySources["MyApp.*"] = true;
    
    // Exclude high-frequency polling subsystem
    options.ActivitySources["MyApp.Polling.*"] = false;
    
    // Exclude cache operations
    options.ActivitySources["MyApp.*.Cache"] = false;
});

// Testing which sources are listened to:
var sources = new[]
{
    new ActivitySource("MyApp.Services.UserService"),           // βœ“ Included (matches MyApp.*)
    new ActivitySource("MyApp.Controllers.HomeController"),     // βœ“ Included (matches MyApp.*)
    new ActivitySource("MyApp.Polling.HealthCheck"),            // βœ— Excluded (explicit false)
    new ActivitySource("MyApp.Services.Cache"),                 // βœ— Excluded (matches *.Cache)
    new ActivitySource("ThirdParty.Library"),                   // βœ— Not matched (no pattern)
};

Example with Multiple Exclusions:

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Broad inclusion
    options.ActivitySources["*"] = true;
    
    // Multiple specific exclusions
    options.ActivitySources["*.HealthCheck"] = false;        // All health checks
    options.ActivitySources["*.Metrics"] = false;            // All metrics collectors
    options.ActivitySources["System.*"] = false;             // All system sources
    options.ActivitySources["Microsoft.Extensions.*"] = false; // Framework internals
});

Custom Activity Listener Registration

Implement IActivityListenerRegistration using IDiginsightActivitiesOptions:

public class CustomActivityProcessor : IActivityListenerRegistration
{
    private readonly IDiginsightActivitiesOptions _activitiesOptions;
    private readonly ILogger<CustomActivityProcessor> _logger;
    
    public IActivityListenerLogic Logic { get; }
    
    public CustomActivityProcessor(
        IOptions<DiginsightActivitiesOptions> activitiesOptions,
        ILogger<CustomActivityProcessor> logger)
    {
        _activitiesOptions = activitiesOptions.Value.Freeze();
        _logger = logger;
        Logic = new CustomActivityListenerLogic(logger);
    }
    
    public bool ShouldListenTo(ActivitySource activitySource)
    {
        string sourceName = activitySource.Name;
        
        // Use same pattern matching as built-in registrations
        IEnumerable<bool> matches = _activitiesOptions.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
            .Select(kvp => kvp.Value)
            .ToArray();
        
        bool shouldListen = matches.Any() && matches.All(v => v);
        
        _logger.LogDebug(
            "Activity source '{SourceName}': {Decision}",
            sourceName,
            shouldListen ? "LISTENING" : "IGNORING");
        
        return shouldListen;
    }
}

// Register the custom listener
services.AddSingleton<IActivityListenerRegistration, CustomActivityProcessor>();

πŸš€ Advanced Usage

Wildcard Pattern Combinations

Combine different pattern types for complex filtering:

public static class AdvancedActivitySourceConfiguration
{
    public static IServiceCollection ConfigureAdvancedActivitySources(
        this IServiceCollection services)
    {
        return services.Configure<DiginsightActivitiesOptions>(options =>
        {
            // Strategy: Layered filtering with explicit rules
            
            // Layer 1: Catch-all include
            options.ActivitySources["*"] = true;
            
            // Layer 2: Exclude system internals (prefix pattern)
            options.ActivitySources["System.Private.*"] = false;
            options.ActivitySources["System.Diagnostics.Eventing.*"] = false;
            
            // Layer 3: Exclude high-frequency patterns (suffix pattern)
            options.ActivitySources["*.HealthCheck"] = false;
            options.ActivitySources["*.HeartBeat"] = false;
            options.ActivitySources["*.Polling"] = false;
            
            // Layer 4: Exclude specific patterns (infix pattern)
            options.ActivitySources["MyApp.*.Cache"] = false;
            options.ActivitySources["MyApp.*.Metrics"] = false;
            
            // Layer 5: Re-include important caches (more specific pattern)
            options.ActivitySources["MyApp.Core.Cache"] = true;
            // Note: This WON'T work if MyApp.Core.Cache also matches MyApp.*.Cache
            // because all matching patterns must be true!
        });
    }
}

Important Pattern Interaction:

// Configuration
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["*.Cache"] = false;

// Test: MyApp.Core.Cache
// Matches: ["MyApp.*" = true, "*.Cache" = false]
// Result: NOT listened to (requires ALL matches to be true)

// Solution: Be more specific with exclusions
options.ActivitySources["MyApp.Services.*.Cache"] = false; // Exclude service caches
// MyApp.Core.Cache now only matches "MyApp.*" = true β†’ Listened to!

Hierarchical Source Filtering

Organize activity sources hierarchically and filter by level:

// Application structure:
// MyApp                          (root)
// MyApp.Api                      (presentation)
// MyApp.Api.Controllers          (controllers)
// MyApp.Services                 (business logic)
// MyApp.Services.Users           (user service)
// MyApp.Data                     (data access)
// MyApp.Data.Repositories        (repositories)
// MyApp.Infrastructure           (infrastructure)

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Scenario 1: Monitor API and Services only
    options.ActivitySources["MyApp.Api.*"] = true;
    options.ActivitySources["MyApp.Services.*"] = true;
    // Data and Infrastructure are not included (no matching patterns)
    
    // Scenario 2: Monitor everything except infrastructure internals
    options.ActivitySources["MyApp.*"] = true;
    options.ActivitySources["MyApp.Infrastructure.Internal.*"] = false;
    
    // Scenario 3: Monitor specific service and its dependencies
    options.ActivitySources["MyApp.Services.Users.*"] = true;
    options.ActivitySources["MyApp.Data.Repositories.UserRepository"] = true;
});

Environment-Specific Hierarchical Filtering:

public static IServiceCollection ConfigureEnvironmentSpecificSources(
    this IServiceCollection services,
    IWebHostEnvironment environment)
{
    return services.Configure<DiginsightActivitiesOptions>(options =>
    {
        if (environment.IsDevelopment())
        {
            // Development: Monitor everything
            options.ActivitySources["*"] = true;
        }
        else if (environment.IsStaging())
        {
            // Staging: Monitor app and key infrastructure
            options.ActivitySources["MyApp.*"] = true;
            options.ActivitySources["System.Net.Http"] = true;
            options.ActivitySources["Microsoft.EntityFrameworkCore"] = true;
        }
        else // Production
        {
            // Production: Monitor business logic only
            options.ActivitySources["MyApp.Api.*"] = true;
            options.ActivitySources["MyApp.Services.*"] = true;
            
            // Exclude high-frequency operations
            options.ActivitySources["MyApp.*.Cache"] = false;
            options.ActivitySources["MyApp.*.HealthCheck"] = false;
        }
    });
}

Integration with Custom Listeners

Create specialized listeners that use consistent filtering:

// Custom listener for security auditing
public class SecurityAuditListener : IActivityListenerRegistration
{
    private readonly IDiginsightActivitiesOptions _activitiesOptions;
    private readonly IAuditLogger _auditLogger;
    
    public IActivityListenerLogic Logic { get; }
    
    public SecurityAuditListener(
        IOptions<DiginsightActivitiesOptions> activitiesOptions,
        IAuditLogger auditLogger)
    {
        _activitiesOptions = activitiesOptions.Value.Freeze();
        _auditLogger = auditLogger;
        Logic = new SecurityAuditLogic(auditLogger);
    }
    
    public bool ShouldListenTo(ActivitySource activitySource)
    {
        // First check: Use standard Diginsight filtering
        string sourceName = activitySource.Name;
        IEnumerable<bool> matches = _activitiesOptions.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
            .Select(kvp => kvp.Value)
            .ToArray();
        
        if (!matches.Any() || !matches.All(v => v))
            return false; // Respect Diginsight configuration
        
        // Second check: Only audit security-related sources
        return sourceName.Contains("Auth", StringComparison.OrdinalIgnoreCase) ||
               sourceName.Contains("Security", StringComparison.OrdinalIgnoreCase) ||
               sourceName.Contains("Identity", StringComparison.OrdinalIgnoreCase);
    }
}

// Custom listener for performance monitoring
public class PerformanceMonitorListener : IActivityListenerRegistration
{
    private readonly IDiginsightActivitiesOptions _activitiesOptions;
    
    public IActivityListenerLogic Logic { get; }
    
    public PerformanceMonitorListener(
        IOptions<DiginsightActivitiesOptions> activitiesOptions)
    {
        _activitiesOptions = activitiesOptions.Value.Freeze();
        Logic = new PerformanceMonitorLogic();
    }
    
    public bool ShouldListenTo(ActivitySource activitySource)
    {
        // Use Diginsight filtering but only for services and API layers
        string sourceName = activitySource.Name;
        
        if (!sourceName.Contains(".Services.", StringComparison.OrdinalIgnoreCase) &&
            !sourceName.Contains(".Api.", StringComparison.OrdinalIgnoreCase))
        {
            return false; // Only monitor services and API
        }
        
        // Apply standard pattern matching
        IEnumerable<bool> matches = _activitiesOptions.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
            .Select(kvp => kvp.Value)
            .ToArray();
        
        return matches.Any() && matches.All(v => v);
    }
}

πŸ”§ Troubleshooting

Common Issues

1. Activities Not Being Captured

Activities may not appear due to activity source not being included.

// Problem: Created ActivitySource but activities not captured
var source = new ActivitySource("MyApp.NewFeature");

// Check 1: Verify source is in configuration
var options = _optionsMonitor.CurrentValue;
_logger.LogInformation("Configured patterns: {Patterns}",
    string.Join(", ", ((IDiginsightActivitiesOptions)options).ActivitySources.Keys));

// Check 2: Test pattern matching
bool matches = options.ActivitySources.Any(kvp =>
    ActivityUtils.NameMatchesPattern("MyApp.NewFeature", kvp.Key));
_logger.LogInformation("Source name matches any pattern: {Matches}", matches);

// Solution 1: Add explicit pattern
options.ActivitySources["MyApp.NewFeature"] = true;

// Solution 2: Use broader pattern
options.ActivitySources["MyApp.*"] = true;

2. Pattern Not Matching Expected Sources

Pattern may be too specific or using incorrect wildcard placement.

Ensure that: - Pattern uses case-insensitive matching (no need to worry about case) - Wildcard is at beginning, end, or both (not middle segments) - Pattern doesn’t have multiple wildcards (only supports 0, 1, or 2 segments)

Debugging Patterns:

public class PatternDebugger
{
    public void TestPattern(string sourceName, string pattern)
    {
        bool matches = ActivityUtils.NameMatchesPattern(sourceName, pattern);
        Console.WriteLine($"Pattern '{pattern}' vs Source '{sourceName}': {matches}");
    }
    
    public void TestAllPatterns(string sourceName)
    {
        var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
        
        foreach (var kvp in options.ActivitySources)
        {
            bool matches = ActivityUtils.NameMatchesPattern(sourceName, kvp.Key);
            Console.WriteLine($"  Pattern '{kvp.Key}': {(matches ? "βœ“ MATCH" : "βœ— NO MATCH")} " +
                            $"(Action: {(kvp.Value ? "INCLUDE" : "EXCLUDE")})");
        }
    }
}

// Usage
debugger.TestAllPatterns("MyApp.Services.UserService");
// Output:
//   Pattern 'MyApp.*': βœ“ MATCH (Action: INCLUDE)
//   Pattern '*.Services.*': βœ“ MATCH (Action: INCLUDE)
//   Pattern 'System.*': βœ— NO MATCH (Action: EXCLUDE)

3. Exclusion Not Working

Exclusion (false value) may be overridden by other patterns.

  • Symptom: Source is still listened to despite false pattern
  • Cause: Another pattern matches with true value, but exclusion requires ALL matches to be true
  • Wait, that’s backwards! Let me re-read the logic…

Actually, looking at the code again:

return matches.Any() && matches.All(static x => x);

This means: β€œListen if there’s at least one match AND all matches are true”

So if ANY pattern matches with false, the source is NOT listened to. Let me correct the issue:

  • Symptom: Source is NOT being listened to despite true patterns
  • Cause: Another pattern also matches with false value
  • Solution: Make exclusion patterns more specific or remove them
// Problem configuration
options.ActivitySources["MyApp.*"] = true;       // Matches MyApp.Services
options.ActivitySources["*.Services"] = false;   // Also matches MyApp.Services
// Result: MyApp.Services is NOT listened to (exclusion wins)

// Solution 1: More specific exclusion
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["ThirdParty.*.Services"] = false; // Only exclude third-party
// Result: MyApp.Services IS listened to

// Solution 2: More specific inclusion
options.ActivitySources["MyApp.Services"] = true; // Exact match
options.ActivitySources["*.Services"] = false;    // Exclude other services
// Wait, this won't work either! MyApp.Services matches BOTH patterns
// Result: Still NOT listened to (exclusion wins)

// Actual Solution: Remove conflicting pattern
options.ActivitySources["MyApp.*"] = true;
// Don't add "*.Services" = false
// Result: MyApp.Services IS listened to

4. Changes Not Taking Effect

Configuration updates may not reflect immediately for existing activity sources.

  • Symptom: Updated ActivitySources dictionary but behavior unchanged
  • Cause: Activity listeners are registered once at startup
  • Solution: Restart application or use hot-reload mechanisms
// Note: ShouldListenTo is called when ActivitySource is first used
// Existing ActivitySource instances may have cached listener state
// New ActivitySource instances will use updated configuration

// To force re-evaluation: Create new ActivitySource instance
var oldSource = new ActivitySource("MyApp.Services"); // Uses old config
// ... update configuration ...
var newSource = new ActivitySource("MyApp.Services");  // Uses new config

Debugging

Enable detailed logging to troubleshoot activity source registration:

// In appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Diginsight.Diagnostics": "Debug",
      "System.Diagnostics": "Debug"  // .NET activity system logs
    }
  }
}

// Or in code
services.Configure<LoggerFilterOptions>(options =>
{
    options.AddFilter("Diginsight.Diagnostics", LogLevel.Debug);
    options.AddFilter("System.Diagnostics", LogLevel.Debug);
});

Debugging Techniques: - Log all configured patterns at startup - Log ShouldListenTo decisions for each activity source - Test pattern matching with known source names - Verify activity source name matches expected value - Check that activity sources are actually creating activities

Diagnostic Endpoint:

public class ActivitySourceDiagnosticsController : ControllerBase
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    
    [HttpGet("/_diagnostics/activity-sources")]
    public IActionResult GetActivitySourceConfiguration()
    {
        var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
        
        return Ok(new
        {
            patterns = options.ActivitySources.Select(kvp => new
            {
                pattern = kvp.Key,
                action = kvp.Value ? "include" : "exclude"
            }),
            totalPatterns = options.ActivitySources.Count
        });
    }
    
    [HttpPost("/_diagnostics/test-pattern")]
    public IActionResult TestPattern([FromBody] PatternTestRequest request)
    {
        var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
        
        var matchingPatterns = options.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(request.SourceName, kvp.Key))
            .Select(kvp => new
            {
                pattern = kvp.Key,
                action = kvp.Value ? "include" : "exclude"
            })
            .ToList();
        
        bool wouldListen = matchingPatterns.Any() && 
                          matchingPatterns.All(p => p.action == "include");
        
        return Ok(new
        {
            sourceName = request.SourceName,
            matchingPatterns,
            decision = wouldListen ? "LISTEN" : "IGNORE"
        });
    }
}

public record PatternTestRequest(string SourceName);

Performance Considerations

Activity Source Registration Overhead: - Pattern matching: ~1-10 microseconds per pattern per source - Performed once per ActivitySource instance (cached in .NET runtime) - Negligible impact for typical configurations (<50 patterns)

Optimization Strategies:

// Strategy 1: Use specific patterns over broad catch-alls
// βœ“ Good: Specific patterns (fast matching)
options.ActivitySources["MyApp.Services.*"] = true;
options.ActivitySources["MyApp.Controllers.*"] = true;

// βœ— Avoid: Catch-all with many exclusions (slower)
options.ActivitySources["*"] = true;
options.ActivitySources["System.*"] = false;
options.ActivitySources["Microsoft.*"] = false;
// ... 20 more exclusions ...

// Strategy 2: Minimize pattern count
// βœ“ Good: 5-20 patterns total
// ⚠ Acceptable: 20-50 patterns
// βœ— Problematic: >50 patterns (consider refactoring)

// Strategy 3: Use simple patterns when possible
// βœ“ Good: Exact match (fastest)
options.ActivitySources["MyApp.Services"] = true;

// βœ“ Good: Single wildcard (fast)
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["*.Controllers"] = true;

// ⚠ Acceptable: Infix wildcard (slightly slower)
options.ActivitySources["MyApp.*.Service"] = true;

Monitoring Recommendations: - Monitor number of active ActivitySource instances - Track ShouldListenTo call frequency (should be low after startup) - Alert on unusual activity source creation patterns - Audit pattern configuration complexity periodically


πŸ“š Reference

Interface Definition

Namespace: Diginsight.Diagnostics
Assembly: Diginsight.Diagnostics.dll

public interface IDiginsightActivitiesOptions
{
    IReadOnlyDictionary<string, bool> ActivitySources { get; }
}

Properties

Property Type Description Default
ActivitySources IReadOnlyDictionary<string, bool> Dictionary mapping activity source name patterns to boolean include/exclude values. Keys are patterns (supporting wildcards), values are true for include or false for explicit exclusion. Empty dictionary means no sources are listened to by default. Empty dictionary

Implementing Classes

  • DiginsightActivitiesOptions: Primary concrete implementation
    • Implements IDiginsightActivitiesOptions
    • Also implements IDiginsightActivitiesLogOptions, IMetricRecordingOptions
    • Supports IDynamicallyConfigurable and IVolatilelyConfigurable
    • Provides mutable IDictionary<string, bool> ActivitySources property
    • IDiginsightActivitiesOptions.ActivitySources returns read-only view
    • Located in Diginsight.Diagnostics namespace

Consumption Points: - ActivityLifecycleLogEmitterRegistration: Uses this interface to determine which activity sources to log - SpanDurationMetricRecorderRegistration: Uses this interface to determine which activity sources to measure - Custom IActivityListenerRegistration implementations: Can use this interface for consistent filtering


πŸ’‘ Best Practices

Pattern Design

Organize Patterns Hierarchically:

// βœ“ Recommended: Hierarchical organization
services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Level 1: Application root
    options.ActivitySources["MyCompany.MyApp.*"] = true;
    
    // Level 2: Major subsystems
    options.ActivitySources["MyCompany.MyApp.Api.*"] = true;
    options.ActivitySources["MyCompany.MyApp.Services.*"] = true;
    options.ActivitySources["MyCompany.MyApp.Data.*"] = true;
    
    // Level 3: Specific exclusions
    options.ActivitySources["MyCompany.MyApp.*.Cache"] = false;
    options.ActivitySources["MyCompany.MyApp.*.Internal.*"] = false;
});

// βœ— Avoid: Flat, unstructured patterns
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivitySources["UserService"] = true;
    options.ActivitySources["ProductService"] = true;
    options.ActivitySources["OrderService"] = true;
    // ... 50 more individual service names ...
    // Better: options.ActivitySources["*Service"] = true;
});

Guidelines: - Use namespace-based hierarchy for activity source names - Group patterns by architectural layer or functional area - Prefer broader patterns with specific exclusions over many specific inclusions - Document the rationale for each pattern in code comments - Review patterns during code reviews for consistency

Performance Optimization

Minimize Pattern Count:

// βœ“ Recommended: Concise patterns (5-20 total)
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivitySources["MyApp.*"] = true;
    options.ActivitySources["System.Net.Http"] = true;
    options.ActivitySources["Npgsql"] = true;
    options.ActivitySources["MyApp.HighFrequency.*"] = false;
});

// βœ— Avoid: Excessive patterns (>50 total)
services.Configure<DiginsightActivitiesOptions>(options =>
{
    // 50+ individual patterns for each service/controller/repository
    options.ActivitySources["MyApp.Services.UserService"] = true;
    options.ActivitySources["MyApp.Services.ProductService"] = true;
    // ... many more ...
    // Better: options.ActivitySources["MyApp.Services.*"] = true;
});

Use Simple Patterns: - Exact match > prefix/suffix wildcard > infix wildcard - Fewer patterns = faster ShouldListenTo evaluation - Pattern complexity has minimal impact, but count matters

Environment-Specific Optimization:

public static IServiceCollection ConfigureOptimalActivitySources(
    this IServiceCollection services,
    IWebHostEnvironment environment)
{
    return services.Configure<DiginsightActivitiesOptions>(options =>
    {
        if (environment.IsDevelopment())
        {
            // Development: Verbose (all sources)
            options.ActivitySources["*"] = true;
        }
        else if (environment.IsProduction())
        {
            // Production: Minimal (only business logic)
            options.ActivitySources["MyApp.Api.*"] = true;
            options.ActivitySources["MyApp.Services.*"] = true;
            
            // Exclude high-frequency
            options.ActivitySources["MyApp.*.HealthCheck"] = false;
            options.ActivitySources["MyApp.*.Metrics"] = false;
        }
    });
}

Key Recommendations: 1. Start with minimal patterns, add as needed 2. Use broader patterns instead of many specific ones 3. Exclude high-frequency sources in production 4. Monitor activity creation rate and adjust patterns 5. Profile application with activity listening enabled

Source Organization

Naming Conventions for Activity Sources:

// βœ“ Recommended: Consistent naming scheme
// Format: {Company}.{Application}.{Layer}.{Component}

public static class ActivitySources
{
    public static readonly ActivitySource Api = 
        new("MyCompany.MyApp.Api");
    
    public static readonly ActivitySource Services = 
        new("MyCompany.MyApp.Services");
    
    public static readonly ActivitySource Data = 
        new("MyCompany.MyApp.Data");
    
    public static readonly ActivitySource Infrastructure = 
        new("MyCompany.MyApp.Infrastructure");
}

// Configure patterns to match hierarchy
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivitySources["MyCompany.MyApp.*"] = true;
});

Layered Architecture Pattern:

// Layer 1: Presentation
new ActivitySource("MyApp.WebApi.Controllers")
new ActivitySource("MyApp.WebApi.Middleware")

// Layer 2: Application/Services
new ActivitySource("MyApp.Services.Users")
new ActivitySource("MyApp.Services.Orders")

// Layer 3: Domain
new ActivitySource("MyApp.Domain.UserManagement")
new ActivitySource("MyApp.Domain.OrderProcessing")

// Layer 4: Infrastructure
new ActivitySource("MyApp.Infrastructure.Database")
new ActivitySource("MyApp.Infrastructure.Messaging")

// Configuration
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivitySources["MyApp.WebApi.*"] = true;
    options.ActivitySources["MyApp.Services.*"] = true;
    options.ActivitySources["MyApp.Domain.*"] = true;
    // Infrastructure excluded by default (no pattern)
});

When to Create New Activity Sources: - One per major subsystem or architectural layer - One per third-party integration (if you control the source) - Separate sources for different functional areas - Don’t create too many (aim for 5-20 sources per application)


πŸ“– Appendices

Appendix A: Pattern Matching Algorithm

The pattern matching algorithm (ActivityUtils.NameMatchesPattern) uses the following logic:

public static bool NameMatchesPattern(string name, string pattern)
{
    string[] segments = pattern.Split('*', 3);
    
    return segments.Length switch
    {
        // No wildcards: Exact match
        1 => string.Equals(name, pattern, StringComparison.OrdinalIgnoreCase),
        
        // One wildcard: Prefix or suffix match
        2 => (segments[0], segments[1]) switch
        {
            ("", "") => true,  // Pattern is "*" β†’ match all
            ("", suffix) => name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase),
            (prefix, "") => name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase),
            (prefix, suffix) => 
                name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
                name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)
        },
        
        // Multiple wildcards: Invalid
        _ => throw new ArgumentException("Invalid activity name pattern")
    };
}

Pattern Examples with Algorithm Trace:

// Example 1: Exact match
NameMatchesPattern("MyApp.Services", "MyApp.Services")
// segments = ["MyApp.Services"]
// Length = 1 β†’ Exact comparison
// Result: true (case-insensitive)

// Example 2: Prefix wildcard
NameMatchesPattern("MyApp.Services.UserService", "MyApp.*")
// segments = ["MyApp", ""]
// Length = 2, case (prefix, "")
// Check: name.StartsWith("MyApp")
// Result: true

// Example 3: Suffix wildcard
NameMatchesPattern("WebApi.Controllers", "*.Controllers")
// segments = ["", "Controllers"]
// Length = 2, case ("", suffix)
// Check: name.EndsWith("Controllers")
// Result: true

// Example 4: Infix wildcard
NameMatchesPattern("MyApp.Services.UserService", "MyApp.*.UserService")
// segments = ["MyApp", ".UserService"]
// Length = 2, case (prefix, suffix)
// Check: name.StartsWith("MyApp") && name.EndsWith(".UserService")
// Result: true

// Example 5: Match all
NameMatchesPattern("AnyName", "*")
// segments = ["", ""]
// Length = 2, case ("", "")
// Result: true (always matches)

// Example 6: Invalid pattern
NameMatchesPattern("Test", "A*B*C")
// segments = ["A", "B", "C"]
// Length = 3
// Throws ArgumentException: "Invalid activity name pattern"

Performance Characteristics: - Time complexity: O(n) where n is pattern length (string operations) - Space complexity: O(1) (no allocations for simple comparisons) - Exact match: Fastest (single string comparison) - Wildcard patterns: Slightly slower (prefix/suffix checks) - Case-insensitive: Uses optimized StringComparison.OrdinalIgnoreCase

Appendix B: Integration with IActivityListenerRegistration

The IDiginsightActivitiesOptions integrates with the activity listener registration system:

// 1. Application startup
public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    
    // Configure activity sources
    builder.Services.Configure<DiginsightActivitiesOptions>(options =>
    {
        options.ActivitySources["MyApp.*"] = true;
    });
    
    // Register Diginsight diagnostics
    builder.Services.AddDiginsightDiagnostics();
    // This registers IActivityListenerRegistration implementations
    
    var app = builder.Build();
    app.Run();
}

// 2. IActivityListenerRegistration implementation
public class ActivityLifecycleLogEmitterRegistration : IActivityListenerRegistration
{
    private readonly IDiginsightActivitiesOptions activitiesOptions;
    public IActivityListenerLogic Logic { get; }
    
    public ActivityLifecycleLogEmitterRegistration(
        ActivityLifecycleLogEmitter emitter,
        IOptions<DiginsightActivitiesOptions> activitiesOptions)
    {
        Logic = emitter;
        // Freeze options for thread safety
        this.activitiesOptions = activitiesOptions.Value.Freeze();
    }
    
    public bool ShouldListenTo(ActivitySource activitySource)
    {
        string activitySourceName = activitySource.Name;
        
        // Find all matching patterns
        IEnumerable<bool> matches = activitiesOptions.ActivitySources
            .Where(kvp => ActivityUtils.NameMatchesPattern(activitySourceName, kvp.Key))
            .Select(kvp => kvp.Value)
            .ToArray();
        
        // Listen only if at least one matches and all are true
        return matches.Any() && matches.All(value => value);
    }
}

// 3. ActivitySource creation (in application code)
public class UserService
{
    private static readonly ActivitySource Source = new("MyApp.Services.UserService");
    
    public async Task<User> GetUser(int userId)
    {
        // Activity creation triggers listener evaluation
        using var activity = Source.StartActivity("GetUser");
        // ...
    }
}

// 4. .NET Activity System flow
// When Source.StartActivity is called:
// a. .NET runtime checks if any ActivityListener is interested
// b. For each registered ActivityListener:
//    - Calls listener.ShouldListenTo(Source)
//    - If true, calls listener.Sample (if configured)
// c. Diginsight's ActivityListener uses IActivityListenerRegistration
// d. ActivityLifecycleLogEmitterRegistration.ShouldListenTo is called
// e. Pattern matching against IDiginsightActivitiesOptions.ActivitySources
// f. If matched, activity is created and logged

Integration Flow Diagram:

Application Startup
  ↓
Configure DiginsightActivitiesOptions
  β”œβ”€ ActivitySources["MyApp.*"] = true
  └─ ActivitySources["System.*"] = false
  ↓
AddDiginsightDiagnostics()
  β”œβ”€ Register ActivityLifecycleLogEmitterRegistration
  β”œβ”€ Register SpanDurationMetricRecorderRegistration
  └─ Create ActivityListener with ShouldListenTo callback
  ↓
Runtime: Create ActivitySource
  var source = new ActivitySource("MyApp.Services")
  ↓
Runtime: Start Activity
  source.StartActivity("GetUser")
  ↓
.NET Runtime: Check Listeners
  foreach (listener in ActivityListener.AllListeners)
    ↓
    Call listener.ShouldListenTo(source)
      ↓
      Diginsight: IActivityListenerRegistration.ShouldListenTo
        ↓
        Query IDiginsightActivitiesOptions.ActivitySources
          ↓
          Pattern Match: ActivityUtils.NameMatchesPattern("MyApp.Services", "MyApp.*")
          Result: true
          ↓
        All matches are true?
        Result: true (listen to this source)
  ↓
Activity Created and Logged

Thread Safety: - IDiginsightActivitiesOptions is read-only interface - Implementation (DiginsightActivitiesOptions) can be frozen - Freeze() creates immutable copy for use in listeners - Safe to use across threads without locking - Dynamic updates require new registration instances


End of Reference Documentation

Back to top