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
- Overview - Review OpenTelemetry integration basics
- Configuration - Detailed configuration options
- Correlation IDs - Learn more about correlation strategies