/

Best Practices

Follow these best practices to get the most out of ByteHide Logger and OpenTelemetry integration while maintaining performance and observability standards.

Correlation Strategy

Consistent Correlation IDs

Always use trace IDs as correlation IDs for seamless integration:

// ✅ Good: Use trace ID as correlation ID
var correlationId = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString();

Log.WithCorrelationId(correlationId)
   .WithTags("payment", "processing")
   .Info("Processing payment");

// ❌ Avoid: Using different IDs for traces and logs
Log.WithCorrelationId(Guid.NewGuid().ToString()) // Different from trace ID
   .Info("Processing payment");

Propagate Context

Ensure context flows through async operations:

public async Task ProcessOrderAsync(Order order)
{
    using var activity = ActivitySource.StartActivity("ProcessOrder");
    var correlationId = Activity.Current?.TraceId.ToString();
    
    // Context automatically flows to child operations
    await ValidateOrderAsync(order, correlationId);
    await ChargePaymentAsync(order, correlationId);
    await UpdateInventoryAsync(order, correlationId);
}

private async Task ValidateOrderAsync(Order order, string correlationId)
{
    // Child operation inherits parent context
    Log.WithCorrelationId(correlationId)
       .WithMetadata("orderId", order.Id)
       .Info("Validating order");
}

Sampling Strategy

Environment-Based Sampling

Configure different sampling rates per environment:

// Development: 100% sampling
if (builder.Environment.IsDevelopment())
{
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing
            .SetSampler(new AlwaysOnSampler()));
}

// Staging: 50% sampling
else if (builder.Environment.IsStaging())
{
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing
            .SetSampler(new TraceIdRatioBasedSampler(0.5)));
}

// Production: 5-10% sampling
else
{
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing
            .SetSampler(new TraceIdRatioBasedSampler(0.05)));
}

Critical Path Sampling

Always sample critical operations:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .SetSampler(new ParentBasedSampler(new TraceIdRatioBasedSampler(0.1))));

// Force sampling for critical operations
using var activity = ActivitySource.StartActivity("CriticalPaymentProcess");
activity?.SetTag("sampling.priority", "1");

Log.WithCorrelationId(Activity.Current?.TraceId.ToString())
   .WithTags("payment", "critical")
   .Info("Processing critical payment");

Logging Levels

Strategic Level Usage

Use appropriate levels for different scenarios:

// ✅ Good: Strategic level usage
Log.WithCorrelationId(correlationId)
   .Debug("Entering payment validation"); // Development debugging

Log.WithCorrelationId(correlationId)
   .Info("Payment processing started"); // Business events

Log.WithCorrelationId(correlationId)
   .Warn("Payment retry attempt 2 of 3"); // Potential issues

Log.WithCorrelationId(correlationId)
   .Error("Payment processing failed", ex); // Actual errors

Log.WithCorrelationId(correlationId)
   .Critical("Payment service unavailable", ex); // System failures

Production Log Levels

Configure minimal logging for production:

if (builder.Environment.IsProduction())
{
    Log.Configure(new LogSettings
    {
        MinimumLevel = LogLevel.Warn, // Only warnings and above
        DuplicateSuppressionWindow = TimeSpan.FromMinutes(5),
        MaskSensitiveData = new[] { "password", "token", "credit" }
    });
}

Performance Optimization

Batch Processing

Configure efficient batching:

builder.Services.Configure<BatchExportProcessorOptions<Activity>>(options =>
{
    options.MaxExportBatchSize = 512;
    options.ScheduledDelay = TimeSpan.FromSeconds(5);
    options.ExportTimeout = TimeSpan.FromSeconds(30);
    options.MaxQueueSize = 2048;
});

Reduce Log Volume

Use duplicate suppression and filtering:

Log.Configure(new LogSettings
{
    DuplicateSuppressionWindow = TimeSpan.FromMinutes(5),
    MinimumLevel = LogLevel.Info,
    MaskSensitiveData = new[] { "password", "ssn", "credit_card" }
});

// Conditional logging for high-frequency operations
if (Log.IsEnabled(LogLevel.Debug))
{
    Log.WithCorrelationId(correlationId)
       .Debug($"Cache hit for key: {cacheKey}");
}

Async Logging

Use non-blocking logging patterns:

// ✅ Good: Non-blocking logging
public async Task ProcessAsync()
{
    var correlationId = Activity.Current?.TraceId.ToString();
    
    Log.WithCorrelationId(correlationId).Info("Starting process");
    
    try
    {
        await DoWorkAsync();
        Log.WithCorrelationId(correlationId).Info("Process completed");
    }
    catch (Exception ex)
    {
        Log.WithCorrelationId(correlationId).Error("Process failed", ex);
        throw;
    }
}

Security Considerations

Data Masking

Protect sensitive information:

Log.Configure(new LogSettings
{
    MaskSensitiveData = new[] 
    { 
        "password", "token", "ssn", "credit_card", 
        "email", "phone", "api_key", "secret" 
    }
});

// Manual masking for complex data
Log.WithCorrelationId(correlationId)
   .WithMetadata("creditCard", "****-****-****-1234")
   .WithMetadata("email", "user@***.com")
   .Info("Payment processed");

Secure Export

Use secure connections for production:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddOtlpExporter(options =>
        {
            options.Endpoint = new Uri("https://secure-collector.example.com:4317");
            options.Headers = $"Authorization=Bearer {apiKey}";
            options.Protocol = OtlpExportProtocol.Grpc;
        }));

Error Handling

Graceful Degradation

Handle export failures gracefully:

public class ResilientLogExporter : BaseExporter<LogRecord>
{
    private readonly ILogger<ResilientLogExporter> _logger;
    
    public override ExportResult Export(in Batch<LogRecord> batch)
    {
        try
        {
            return ExportToBackend(batch);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to export logs, falling back to local storage");
            SaveToLocalStorage(batch);
            return ExportResult.Failure;
        }
    }
}

Circuit Breaker Pattern

Prevent cascade failures:

public class CircuitBreakerExporter : BaseExporter<LogRecord>
{
    private readonly CircuitBreaker _circuitBreaker;
    
    public override ExportResult Export(in Batch<LogRecord> batch)
    {
        if (_circuitBreaker.State == CircuitBreakerState.Open)
        {
            // Export blocked, save locally
            SaveToLocalStorage(batch);
            return ExportResult.Failure;
        }
        
        try
        {
            var result = ExportToBackend(batch);
            _circuitBreaker.OnSuccess();
            return result;
        }
        catch (Exception ex)
        {
            _circuitBreaker.OnFailure();
            throw;
        }
    }
}

Resource Management

Activity Source Management

Properly manage activity sources:

public class OrderService : IDisposable
{
    private static readonly ActivitySource ActivitySource = new("OrderService", "1.0.0");
    
    public async Task ProcessOrderAsync(Order order)
    {
        using var activity = ActivitySource.StartActivity("ProcessOrder");
        // Activity automatically disposed
        
        var correlationId = Activity.Current?.TraceId.ToString();
        Log.WithCorrelationId(correlationId).Info("Processing order");
    }
    
    public void Dispose()
    {
        ActivitySource?.Dispose();
    }
}

Memory Management

Monitor memory usage:

// Configure appropriate queue sizes
builder.Services.Configure<BatchExportProcessorOptions<Activity>>(options =>
{
    options.MaxQueueSize = 2048; // Adjust based on available memory
    options.MaxExportBatchSize = 512;
});

// Monitor queue usage
Log.WithTags("observability", "memory")
   .WithMetadata("queueSize", currentQueueSize)
   .WithMetadata("memoryUsage", GC.GetTotalMemory(false))
   .Info("Queue status check");

Monitoring and Alerting

Health Checks

Monitor export health:

public class OpenTelemetryHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        // Check export status
        var isHealthy = CheckExportStatus();
        
        if (isHealthy)
        {
            Log.WithTags("health", "export").Debug("Export health check passed");
            return Task.FromResult(HealthCheckResult.Healthy("Export is working"));
        }
        
        Log.WithTags("health", "export").Warn("Export health check failed");
        return Task.FromResult(HealthCheckResult.Unhealthy("Export is failing"));
    }
}

Export Metrics

Track export performance:

Log.WithTags("metrics", "export")
   .WithMetadata("exportDuration", exportDuration.TotalMilliseconds)
   .WithMetadata("batchSize", batchSize)
   .WithMetadata("exportStatus", exportResult.ToString())
   .Info("Export metrics");

Testing Strategy

Unit Testing

Test logging without actual export:

[Test]
public async Task ProcessOrder_ShouldLogWithCorrelation()
{
    // Arrange
    using var activity = new Activity("test").Start();
    var order = new Order { Id = 123 };
    
    // Act
    await _orderService.ProcessOrderAsync(order);
    
    // Assert
    var logs = _logCapture.GetLogs();
    Assert.That(logs.Any(l => 
        l.CorrelationId == activity.TraceId.ToString() && 
        l.Message.Contains("Processing order")));
}

Integration Testing

Test with real exporters:

[Test]
public async Task EndToEnd_ShouldExportToJaeger()
{
    // Arrange
    var services = new ServiceCollection();
    services.AddOpenTelemetry()
        .WithTracing(tracing => tracing
            .AddJaegerExporter(options =>
            {
                options.AgentHost = "localhost";
                options.AgentPort = 6831;
            }));
    
    // Act & Assert
    using var activity = ActivitySource.StartActivity("test");
    Log.WithCorrelationId(activity?.TraceId.ToString())
       .Info("Integration test log");
    
    // Verify export succeeded
    await Task.Delay(1000); // Allow time for export
}

Common Anti-Patterns

Avoid these common mistakes:

// ❌ Don't create new correlation IDs unnecessarily
Log.WithCorrelationId(Guid.NewGuid().ToString())
   .Info("Should use existing trace ID");

// ❌ Don't log sensitive data
Log.WithMetadata("password", userPassword)
   .Info("User login");

// ❌ Don't block on logging
await Log.WriteAsync("message"); // Blocking call

// ❌ Don't create activities without using them
var activity = ActivitySource.StartActivity("test");
// Forgot to dispose activity

// ✅ Do use proper patterns
var correlationId = Activity.Current?.TraceId.ToString();
Log.WithCorrelationId(correlationId)
   .WithMetadata("userId", userId)
   .Info("User login successful");

Performance Checklist

Performance Checklist

  • ✅ Configure appropriate sampling rates (5-10% for production)
  • ✅ Use batch processors with reasonable batch sizes (512-1024)
  • ✅ Set minimum log levels for production (Warn or Error)
  • ✅ Enable duplicate suppression for high-frequency logs
  • ✅ Use secure, efficient exporters (OTLP over gRPC)
  • ✅ Monitor export queue sizes and memory usage
  • ✅ Implement circuit breakers for export failures
  • ✅ Mask sensitive data consistently
  • ✅ Use correlation IDs consistently across services
  • ✅ Test export performance under load

Next Steps

Previous
Exporter Setup