Enterprise Logging & Observability with .NET: OpenTelemetry Complete Guide 2025
Enterprise applications require comprehensive observability to understand system behavior, diagnose issues, and optimize performance. OpenTelemetry provides a vendor-neutral standard for collecting telemetry data—logs, metrics, and traces—enabling consistent observability across .NET applications.
This comprehensive guide covers enterprise logging and observability for .NET applications using OpenTelemetry, including structured logging, distributed tracing, metrics collection, and production monitoring patterns. You'll learn how to implement observability, correlate telemetry data, and build production-ready monitoring solutions.
Understanding Observability
Three Pillars of Observability
1. Logs
- Event records
- Text-based
- High cardinality
- Good for debugging
2. Metrics
- Numerical measurements
- Aggregated data
- Low cardinality
- Good for monitoring
3. Traces
- Request flows
- Distributed context
- Performance analysis
- Good for troubleshooting
OpenTelemetry Setup
Basic Configuration
Configure OpenTelemetry in .NET:
builder.Services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MyApplication")
.AddConsoleExporter())
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter())
.WithLogging(builder => builder
.AddConsoleExporter());
Exporters
Configure exporters for telemetry:
builder.Services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://otel-collector:4317");
}))
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://otel-collector:4317");
}));
Structured Logging
Serilog Configuration
Configure Serilog for structured logging:
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.WriteTo.Seq("http://localhost:5341")
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.CreateLogger();
builder.Host.UseSerilog();
Structured Logging Patterns
Use structured logging:
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = request.OrderId,
["CustomerId"] = request.CustomerId
});
_logger.LogInformation(
"Creating order {OrderId} for customer {CustomerId}",
request.OrderId, request.CustomerId);
try
{
var order = await ProcessOrderAsync(request);
_logger.LogInformation(
"Order {OrderId} created successfully with {ItemCount} items",
order.Id, order.Items.Count);
return order;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to create order {OrderId} for customer {CustomerId}",
request.OrderId, request.CustomerId);
throw;
}
}
}
Distributed Tracing
Activity Creation
Create custom activities:
public class OrderProcessingService
{
private static readonly ActivitySource ActivitySource = new("OrderProcessing");
public async Task ProcessOrderAsync(Order order)
{
using var activity = ActivitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.id", order.Id);
activity?.SetTag("order.customer_id", order.CustomerId);
try
{
await ValidateOrderAsync(order);
await ProcessPaymentAsync(order);
await FulfillOrderAsync(order);
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}
Trace Correlation
Correlate traces across services:
public class TracePropagationMiddleware
{
private readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext context)
{
var activity = Activity.Current;
if (activity != null)
{
// Add trace context to response headers
context.Response.Headers.Add("trace-id", activity.TraceId.ToString());
context.Response.Headers.Add("span-id", activity.SpanId.ToString());
}
await _next(context);
}
}
Metrics Collection
Custom Metrics
Create custom metrics:
public class OrderMetrics
{
private readonly Meter _meter;
private readonly Counter<long> _ordersCreated;
private readonly Histogram<double> _orderProcessingTime;
public OrderMetrics()
{
_meter = new Meter("OrderService");
_ordersCreated = _meter.CreateCounter<long>("orders.created");
_orderProcessingTime = _meter.CreateHistogram<double>("order.processing.time");
}
public void RecordOrderCreated(string customerId)
{
_ordersCreated.Add(1, new KeyValuePair<string, object>("customer.id", customerId));
}
public void RecordProcessingTime(double duration, string orderType)
{
_orderProcessingTime.Record(duration, new KeyValuePair<string, object>("order.type", orderType));
}
}
Metric Exporters
Export metrics to monitoring systems:
builder.Services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddMeter("OrderService")
.AddPrometheusExporter(options =>
{
options.StartHttpListener = true;
options.HttpListenerPrefixes = new[] { "http://localhost:9464/" };
}));
Production Patterns
Log Aggregation
Aggregate logs from multiple services:
builder.Services.AddOpenTelemetry()
.WithLogging(builder => builder
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://log-aggregator:4318");
}));
Error Tracking
Track errors with context:
public class ErrorTrackingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorTrackingMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
var activity = Activity.Current;
_logger.LogError(ex,
"Unhandled exception in {Path} with trace {TraceId}",
context.Request.Path,
activity?.TraceId);
activity?.RecordException(ex);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
}
}
Performance Monitoring
Monitor application performance:
public class PerformanceMonitoringMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<PerformanceMonitoringMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
var activity = Activity.Current;
await _next(context);
stopwatch.Stop();
activity?.SetTag("http.duration", stopwatch.ElapsedMilliseconds);
if (stopwatch.ElapsedMilliseconds > 1000)
{
_logger.LogWarning(
"Slow request: {Path} took {Duration}ms",
context.Request.Path,
stopwatch.ElapsedMilliseconds);
}
}
}
Best Practices
1. Use Structured Logging
- Include context in logs
- Use consistent log levels
- Avoid sensitive data
- Use correlation IDs
2. Instrument Key Operations
- HTTP requests
- Database queries
- External API calls
- Business operations
3. Set Appropriate Sampling
builder.Services.AddOpenTelemetry()
.WithTracing(builder => builder
.SetSampler(new TraceIdRatioBasedSampler(0.1))); // 10% sampling
4. Monitor Resource Usage
- CPU usage
- Memory consumption
- Network I/O
- Disk I/O
5. Alert on Critical Issues
- Error rate thresholds
- Latency spikes
- Resource exhaustion
- Service degradation
Advanced Observability Patterns
Custom Metrics
Create custom business metrics:
public class BusinessMetrics
{
private readonly Meter _meter;
private readonly Counter<long> _ordersProcessed;
private readonly Histogram<double> _orderProcessingTime;
public BusinessMetrics()
{
_meter = new Meter("ECommerce.Business");
_ordersProcessed = _meter.CreateCounter<long>("orders_processed_total");
_orderProcessingTime = _meter.CreateHistogram<double>("order_processing_seconds");
}
public void RecordOrderProcessed(string orderType, double processingTime)
{
_ordersProcessed.Add(1, new KeyValuePair<string, object>("order_type", orderType));
_orderProcessingTime.Record(processingTime, new KeyValuePair<string, object>("order_type", orderType));
}
}
Custom Traces
Create custom spans for business operations:
public class CustomTracing
{
private readonly TracerProvider _tracerProvider;
public async Task ProcessOrderAsync(Order order)
{
using var activity = _tracerProvider
.GetTracer("ECommerce")
.StartActivity("ProcessOrder");
activity?.SetTag("order.id", order.Id);
activity?.SetTag("order.amount", order.Amount);
try
{
await ValidateOrderAsync(order);
activity?.AddEvent(new ActivityEvent("Order validated"));
await ProcessPaymentAsync(order);
activity?.AddEvent(new ActivityEvent("Payment processed"));
await FulfillOrderAsync(order);
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}
Real-World Scenarios
Scenario 1: Multi-Service Tracing
Trace requests across multiple services:
public class DistributedTracingService
{
public async Task<OrderResponse> ProcessOrderAsync(OrderRequest request)
{
using var activity = ActivitySource.StartActivity("ProcessOrder");
activity?.SetBaggage("order.id", request.OrderId);
// Call inventory service
using var inventoryActivity = ActivitySource.StartActivity("CheckInventory");
var inventory = await _inventoryService.CheckInventoryAsync(request.ProductId);
inventoryActivity?.SetTag("inventory.available", inventory.Available);
// Call payment service
using var paymentActivity = ActivitySource.StartActivity("ProcessPayment");
var payment = await _paymentService.ProcessPaymentAsync(request);
paymentActivity?.SetTag("payment.status", payment.Status);
// Call fulfillment service
using var fulfillmentActivity = ActivitySource.StartActivity("FulfillOrder");
var fulfillment = await _fulfillmentService.FulfillOrderAsync(request);
fulfillmentActivity?.SetTag("fulfillment.tracking", fulfillment.TrackingNumber);
return new OrderResponse { /* ... */ };
}
}
Scenario 2: Performance Monitoring
Monitor application performance:
public class PerformanceMonitor
{
private readonly Meter _meter;
private readonly Histogram<double> _requestDuration;
private readonly Counter<long> _requestCount;
public PerformanceMonitor()
{
_meter = new Meter("Application.Performance");
_requestDuration = _meter.CreateHistogram<double>("http_request_duration_seconds");
_requestCount = _meter.CreateCounter<long>("http_requests_total");
}
public async Task<T> MonitorRequestAsync<T>(
string endpoint,
Func<Task<T>> request)
{
var stopwatch = Stopwatch.StartNew();
_requestCount.Add(1, new KeyValuePair<string, object>("endpoint", endpoint));
try
{
var result = await request();
_requestDuration.Record(
stopwatch.Elapsed.TotalSeconds,
new KeyValuePair<string, object>("endpoint", endpoint),
new KeyValuePair<string, object>("status", "success"));
return result;
}
catch (Exception ex)
{
_requestDuration.Record(
stopwatch.Elapsed.TotalSeconds,
new KeyValuePair<string, object>("endpoint", endpoint),
new KeyValuePair<string, object>("status", "error"));
throw;
}
}
}
Troubleshooting Observability Issues
Common Problems
Problem 1: Missing Traces
Symptoms:
- Traces not appearing
- Incomplete trace data
Solution:
public class TraceTroubleshooter
{
public void ConfigureTracing()
{
Sdk.CreateTracerProviderBuilder()
.AddSource("MyApplication")
.AddConsoleExporter()
.AddJaegerExporter(options =>
{
options.AgentHost = "localhost";
options.AgentPort = 6831;
})
.SetSampler(new AlwaysOnSampler()) // Ensure all traces are sampled
.Build();
}
}
Problem 2: High Cardinality Metrics
Symptoms:
- Too many unique metric combinations
- Performance issues
Solution:
public class LowCardinalityMetrics
{
// Bad: High cardinality
private readonly Counter<long> _badMetric = _meter.CreateCounter<long>(
"requests_total",
"userId", "orderId", "productId"); // Too many unique combinations
// Good: Low cardinality
private readonly Counter<long> _goodMetric = _meter.CreateCounter<long>(
"requests_total",
"endpoint", "status"); // Limited unique combinations
}
Performance Optimization
Sampling Strategies
Implement intelligent sampling:
public class IntelligentSampler
{
public SamplingResult ShouldSample(SamplingParameters parameters)
{
// Sample all errors
if (parameters.Tags.ContainsKey("error"))
{
return new SamplingResult(SamplingDecision.RecordAndSample);
}
// Sample 10% of normal requests
if (Random.Shared.NextDouble() < 0.1)
{
return new SamplingResult(SamplingDecision.RecordAndSample);
}
return new SamplingResult(SamplingDecision.Drop);
}
}
Batch Export
Batch telemetry export:
public class BatchExporter
{
public void ConfigureBatchExport()
{
Sdk.CreateTracerProviderBuilder()
.AddJaegerExporter(options =>
{
options.BatchExportProcessorOptions = new BatchExportProcessorOptions<Activity>
{
MaxQueueSize = 2048,
ScheduledDelayMilliseconds = 5000,
ExporterTimeoutMilliseconds = 30000,
MaxExportBatchSize = 512
};
})
.Build();
}
}
Extended FAQ
Q: How do I correlate logs, traces, and metrics?
A: Use trace context:
public class CorrelationService
{
public void LogWithTraceContext(string message)
{
var activity = Activity.Current;
var traceId = activity?.TraceId.ToString();
var spanId = activity?.SpanId.ToString();
_logger.LogInformation(
"Message: {Message}, TraceId: {TraceId}, SpanId: {SpanId}",
message, traceId, spanId);
}
}
Q: How do I reduce observability overhead?
A: Use sampling and filtering:
Sdk.CreateTracerProviderBuilder()
.AddSource("MyApplication")
.SetSampler(new TraceIdRatioBasedSampler(0.1)) // Sample 10%
.AddProcessor(new FilteringProcessor(
new BatchActivityExportProcessor(new ConsoleExporter<Activity>()),
activity => activity.Kind == ActivityKind.Server)) // Only server activities
.Build();
Case Studies
Case Study 1: Enterprise Application Monitoring
Challenge: A large enterprise application needed comprehensive observability across 100+ microservices.
Solution: Implemented centralized observability:
public class EnterpriseObservability
{
public void ConfigureObservability()
{
// Configure OpenTelemetry
Sdk.CreateTracerProviderBuilder()
.AddSource("Enterprise.*")
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddServiceBusInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://observability-platform:4317");
})
.Build();
// Configure metrics
Sdk.CreateMeterProviderBuilder()
.AddMeter("Enterprise.*")
.AddRuntimeInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://observability-platform:4317");
})
.Build();
// Configure logging
LoggingBuilderExtensions.AddOpenTelemetry(builder =>
{
builder.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://observability-platform:4317");
});
});
}
}
Results:
- 100% service coverage
- Sub-second trace query
- 99.9% observability uptime
Migration Guide
Migrating from Application Insights
Step 1: Replace Application Insights:
// Old: Application Insights
services.AddApplicationInsightsTelemetry();
// New: OpenTelemetry
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter());
Step 2: Migrate custom telemetry:
// Old: Application Insights
_telemetryClient.TrackEvent("OrderProcessed", properties);
// New: OpenTelemetry
using var activity = _tracerProvider
.GetTracer("Application")
.StartActivity("OrderProcessed");
activity?.SetTag("order.id", orderId);
Conclusion
Enterprise observability is essential for understanding and maintaining production systems. By implementing OpenTelemetry, structured logging, distributed tracing, and metrics collection, you can build comprehensive observability into your .NET applications.
Key Takeaways:
- Use OpenTelemetry for vendor-neutral observability
- Implement structured logging with context
- Enable distributed tracing for request flows
- Collect metrics for monitoring
- Correlate telemetry across services
- Monitor production continuously
- Custom metrics for business insights
- Custom traces for business operations
- Performance optimization with sampling
- Migration planning from legacy systems
Next Steps:
- Set up OpenTelemetry
- Configure structured logging
- Add distributed tracing
- Implement metrics collection
- Set up monitoring dashboards
- Optimize performance
- Plan migration from legacy systems
Observability Best Practices
Implementation Checklist
- Set up OpenTelemetry
- Configure structured logging
- Enable distributed tracing
- Implement metrics collection
- Correlate telemetry data
- Set up monitoring dashboards
- Configure alerting
- Test observability setup
- Document observability procedures
- Review and optimize
Production Deployment
Before deploying observability:
- Test all telemetry collection
- Verify trace correlation
- Test metric collection
- Validate log aggregation
- Set up dashboards
- Configure alerts
- Document procedures
- Review performance impact
Additional Resources
OpenTelemetry Documentation
- OpenTelemetry .NET
- Instrumentation libraries
- Exporters
- Best practices
Related Topics
- Distributed tracing
- Structured logging
- Metrics collection
- Application performance monitoring
Observability Implementation Guide
Step-by-Step Setup
-
Set Up OpenTelemetry
- Install OpenTelemetry packages
- Configure tracer provider
- Configure meter provider
- Set up exporters
-
Configure Logging
- Set up structured logging
- Configure log levels
- Add log exporters
- Correlate with traces
-
Enable Tracing
- Add instrumentation
- Create custom spans
- Configure sampling
- Set up trace export
-
Collect Metrics
- Define custom metrics
- Add instrumentation
- Configure metric export
- Set up dashboards
-
Set Up Monitoring
- Create dashboards
- Configure alerts
- Set up notifications
- Review regularly
Observability Patterns
Structured Logging
Implement structured logging:
public class StructuredLogger
{
private readonly ILogger<StructuredLogger> _logger;
public void LogOrderProcessed(string orderId, double amount)
{
_logger.LogInformation(
"Order processed: {OrderId}, Amount: {Amount}",
orderId, amount);
}
}
Distributed Tracing
Create distributed traces:
public class TracingService
{
private readonly ActivitySource _activitySource;
public async Task ProcessOrderAsync(Order order)
{
using var activity = _activitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.id", order.Id);
await ValidateOrderAsync(order);
await ProcessPaymentAsync(order);
await FulfillOrderAsync(order);
}
}
Observability Advanced Patterns
Log Aggregation
Aggregate logs from multiple services:
public class LogAggregator
{
public async Task<AggregatedLogs> AggregateLogsAsync(
TimeSpan period,
string serviceName)
{
var logs = await CollectLogsAsync(period, serviceName);
return new AggregatedLogs
{
TotalLogs = logs.Count,
ErrorLogs = logs.Count(l => l.Level == LogLevel.Error),
WarningLogs = logs.Count(l => l.Level == LogLevel.Warning),
InfoLogs = logs.Count(l => l.Level == LogLevel.Information),
TopErrors = GetTopErrors(logs)
};
}
}
Trace Correlation
Correlate traces across services:
public class TraceCorrelator
{
public async Task<CorrelatedTrace> CorrelateTracesAsync(string traceId)
{
var traces = await GetTracesByTraceIdAsync(traceId);
return new CorrelatedTrace
{
TraceId = traceId,
Spans = traces.SelectMany(t => t.Spans).ToList(),
TotalDuration = CalculateTotalDuration(traces),
ServiceCount = traces.Select(t => t.ServiceName).Distinct().Count()
};
}
}
Observability Dashboard
Metrics Dashboard
Create comprehensive metrics dashboard:
public class MetricsDashboard
{
public async Task<DashboardData> GetDashboardDataAsync(TimeSpan period)
{
return new DashboardData
{
RequestMetrics = await GetRequestMetricsAsync(period),
ErrorMetrics = await GetErrorMetricsAsync(period),
PerformanceMetrics = await GetPerformanceMetricsAsync(period),
ResourceMetrics = await GetResourceMetricsAsync(period)
};
}
}
Trace Analysis
Analyze distributed traces:
public class TraceAnalyzer
{
public async Task<TraceAnalysis> AnalyzeTraceAsync(string traceId)
{
var trace = await GetTraceAsync(traceId);
return new TraceAnalysis
{
TraceId = traceId,
TotalDuration = trace.Spans.Max(s => s.EndTime) - trace.Spans.Min(s => s.StartTime),
ServiceCount = trace.Spans.Select(s => s.ServiceName).Distinct().Count(),
SlowestSpan = trace.Spans.OrderByDescending(s => s.Duration).First(),
ErrorSpans = trace.Spans.Where(s => s.Status == SpanStatus.Error).ToList()
};
}
}
Observability Best Practices
Logging Best Practices
Follow logging best practices:
public class LoggingBestPractices
{
private readonly ILogger<LoggingBestPractices> _logger;
public void LogWithContext(string message, string userId, string action)
{
// Use structured logging
_logger.LogInformation(
"User {UserId} performed {Action}: {Message}",
userId, action, message);
}
public void LogErrorWithContext(Exception ex, string userId, string action)
{
// Include context in error logs
_logger.LogError(ex,
"Error occurred when user {UserId} performed {Action}",
userId, action);
}
}
Observability Best Practices Summary
Implementation Checklist
- Set up OpenTelemetry
- Configure structured logging
- Enable distributed tracing
- Implement metrics collection
- Correlate telemetry data
- Set up monitoring dashboards
- Configure alerting
- Test observability setup
- Document observability procedures
- Review and optimize
Production Deployment
Before deploying observability:
- Test all telemetry collection
- Verify trace correlation
- Test metric collection
- Validate log aggregation
- Set up dashboards
- Configure alerts
- Document procedures
- Review performance impact
Observability Troubleshooting
Common Issues
Troubleshoot observability issues:
public class ObservabilityTroubleshooter
{
public async Task<DiagnosticsResult> DiagnoseAsync()
{
var diagnostics = new DiagnosticsResult();
// Check OpenTelemetry setup
diagnostics.OpenTelemetryConfigured = CheckOpenTelemetrySetup();
// Check exporters
diagnostics.ExportersWorking = await TestExportersAsync();
// Check trace collection
diagnostics.TracesCollected = await CheckTraceCollectionAsync();
// Check metrics collection
diagnostics.MetricsCollected = await CheckMetricsCollectionAsync();
return diagnostics;
}
}
Observability Performance Impact
Minimizing Overhead
Reduce observability overhead:
public class ObservabilityOptimizer
{
public void OptimizeSampling()
{
Sdk.CreateTracerProviderBuilder()
.SetSampler(new TraceIdRatioBasedSampler(0.1)) // Sample 10%
.Build();
}
public void OptimizeLogging()
{
// Use log level filtering
builder.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("Application", LogLevel.Information);
}
}
Observability Implementation Examples
OpenTelemetry Setup
Complete OpenTelemetry setup:
public class OpenTelemetrySetup
{
public void ConfigureObservability(IServiceCollection services)
{
// Tracing
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddSource("MyApplication")
.AddJaegerExporter());
// Metrics
services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddPrometheusExporter());
// Logging
services.AddLogging(builder => builder
.AddOpenTelemetry(options =>
{
options.AddConsoleExporter();
options.AddOtlpExporter();
}));
}
}
Custom Instrumentation
Create custom instrumentation:
public class CustomInstrumentation
{
private readonly ActivitySource _activitySource;
private readonly Meter _meter;
public CustomInstrumentation()
{
_activitySource = new ActivitySource("MyApplication");
_meter = new Meter("MyApplication");
}
public async Task ProcessOrderAsync(Order order)
{
using var activity = _activitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.id", order.Id);
var counter = _meter.CreateCounter<long>("orders_processed_total");
counter.Add(1, new KeyValuePair<string, object>("status", "processing"));
try
{
await ProcessOrderInternalAsync(order);
activity?.SetStatus(ActivityStatusCode.Ok);
counter.Add(1, new KeyValuePair<string, object>("status", "success"));
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
counter.Add(1, new KeyValuePair<string, object>("status", "error"));
throw;
}
}
}
Observability Data Analysis
Log Analysis
Analyze logs for insights:
public class LogAnalyzer
{
public async Task<LogAnalysis> AnalyzeLogsAsync(TimeSpan period)
{
var logs = await GetLogsAsync(period);
return new LogAnalysis
{
TotalLogs = logs.Count,
ErrorRate = (double)logs.Count(l => l.Level == LogLevel.Error) / logs.Count,
TopErrors = logs
.Where(l => l.Level == LogLevel.Error)
.GroupBy(l => l.Message)
.OrderByDescending(g => g.Count())
.Take(10)
.Select(g => new ErrorSummary
{
Message = g.Key,
Count = g.Count()
})
.ToList()
};
}
}
Trace Analysis
Analyze traces for performance:
public class TraceAnalyzer
{
public async Task<TraceAnalysis> AnalyzeTracesAsync(TimeSpan period)
{
var traces = await GetTracesAsync(period);
return new TraceAnalysis
{
TotalTraces = traces.Count,
AverageDuration = traces.Average(t => t.Duration),
P95Duration = CalculatePercentile(traces.Select(t => t.Duration), 0.95),
SlowestTraces = traces
.OrderByDescending(t => t.Duration)
.Take(10)
.ToList(),
ErrorTraces = traces.Where(t => t.HasErrors).ToList()
};
}
}
Observability Dashboard Creation
Custom Dashboard Builder
Build custom dashboards:
public class DashboardBuilder
{
public async Task<Dashboard> CreateDashboardAsync(DashboardConfig config)
{
var dashboard = new Dashboard
{
Name = config.Name,
Panels = new List<Panel>()
};
// Add metrics panels
foreach (var metric in config.Metrics)
{
dashboard.Panels.Add(await CreateMetricPanelAsync(metric));
}
// Add log panels
foreach (var logQuery in config.LogQueries)
{
dashboard.Panels.Add(await CreateLogPanelAsync(logQuery));
}
// Add trace panels
foreach (var traceQuery in config.TraceQueries)
{
dashboard.Panels.Add(await CreateTracePanelAsync(traceQuery));
}
return dashboard;
}
}
Alert Configuration
Configure alerts:
public class AlertConfigurator
{
public async Task<Alert> CreateAlertAsync(AlertConfig config)
{
var alert = new Alert
{
Name = config.Name,
Condition = config.Condition,
Threshold = config.Threshold,
NotificationChannels = config.NotificationChannels
};
// Set up alert evaluation
await SetupAlertEvaluationAsync(alert);
// Set up notifications
await SetupNotificationsAsync(alert);
return alert;
}
}
Observability Performance Impact
Performance Monitoring
Monitor observability performance:
public class ObservabilityPerformanceMonitor
{
public async Task<PerformanceMetrics> MonitorPerformanceAsync()
{
var metrics = new PerformanceMetrics();
// Measure instrumentation overhead
metrics.InstrumentationOverhead = await MeasureInstrumentationOverheadAsync();
// Measure export overhead
metrics.ExportOverhead = await MeasureExportOverheadAsync();
// Measure storage overhead
metrics.StorageOverhead = await MeasureStorageOverheadAsync();
// Measure query performance
metrics.QueryPerformance = await MeasureQueryPerformanceAsync();
return metrics;
}
}
Sampling Strategy
Implement sampling:
public class SamplingStrategy
{
public bool ShouldSample(Activity activity)
{
// Sample all errors
if (activity.Status == ActivityStatusCode.Error)
{
return true;
}
// Sample based on trace ID
var traceId = activity.TraceId.ToString();
var hash = traceId.GetHashCode();
return Math.Abs(hash % 100) < 10; // 10% sampling
}
}
Observability Best Practices Summary
Key Takeaways
- Use Structured Logging: Enable better search and analysis
- Implement Distributed Tracing: Track requests across services
- Collect Metrics: Monitor application and system metrics
- Implement Sampling: Reduce overhead while maintaining visibility
- Create Dashboards: Visualize observability data for better insights
Performance Optimization
- Implement sampling to reduce overhead
- Use batch export for better performance
- Optimize log levels for production
- Use async logging to avoid blocking
- Monitor observability overhead
Common Pitfalls to Avoid
- Logging too much or too little
- Not implementing distributed tracing
- Failing to collect meaningful metrics
- Not using sampling in high-volume scenarios
- Not monitoring observability overhead
Observability Implementation Checklist
Pre-Implementation
- Choose observability tools (OpenTelemetry, Application Insights, etc.)
- Design logging strategy
- Plan distributed tracing
- Design metrics collection
- Plan dashboard and alerting
Implementation
- Configure OpenTelemetry
- Add structured logging
- Implement distributed tracing
- Add metrics collection
- Create dashboards and alerts
Post-Implementation
- Monitor observability overhead
- Review and optimize sampling
- Update dashboards based on needs
- Update documentation
- Train team on observability tools
For more observability guidance, explore our OpenTelemetry Observability guide or Enterprise Integration Architecture.