🔷 Logging Patterns in C# TuskLang
Logging Patterns in C# TuskLang
Overview
Effective logging is essential for monitoring, debugging, and maintaining TuskLang applications. This guide covers structured logging, correlation IDs, log aggregation, and analysis strategies for C# applications.
📝 Structured Logging
Logging Configuration
public class LoggingConfiguration
{
public static void ConfigureLogging(IServiceCollection services, TSKConfig config)
{
var logLevel = config.Get<string>("logging.level", "Information");
var logFormat = config.Get<string>("logging.format", "json");
var logFilePath = config.Get<string>("logging.file_path", "logs/app.log");
services.AddLogging(builder =>
{
// Set minimum log level
builder.SetMinimumLevel(ParseLogLevel(logLevel));
// Console logging
builder.AddConsole(options =>
{
options.FormatterName = logFormat == "json" ? "json" : "simple";
});
// File logging
builder.AddFile(logFilePath, options =>
{
options.FileSizeLimitBytes = config.Get<long>("logging.max_file_size_mb", 100) 1024 1024;
options.RetainedFileCountLimit = config.Get<int>("logging.max_files", 10);
options.Append = true;
options.FormatLogEntry = (msg) => $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{msg.LogLevel}] {msg.Message}";
});
// JSON formatter for structured logging
if (logFormat == "json")
{
builder.AddJsonConsole(options =>
{
options.IncludeScopes = true;
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ";
});
}
});
}
private static LogLevel ParseLogLevel(string level)
{
return level.ToLower() switch
{
"trace" => LogLevel.Trace,
"debug" => LogLevel.Debug,
"information" => LogLevel.Information,
"warning" => LogLevel.Warning,
"error" => LogLevel.Error,
"critical" => LogLevel.Critical,
_ => LogLevel.Information
};
}
}
Structured Logger Service
public class StructuredLogger<T>
{
private readonly ILogger<T> _logger;
private readonly TSKConfig _config;
private readonly Dictionary<string, object> _defaultContext;
public StructuredLogger(ILogger<T> logger, TSKConfig config)
{
_logger = logger;
_config = config;
_defaultContext = new Dictionary<string, object>
{
["service"] = typeof(T).Name,
["environment"] = _config.Get<string>("app.environment", "unknown"),
["version"] = _config.Get<string>("app.version", "1.0.0")
};
}
public void LogInformation(string message, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
_logger.LogInformation("Information: {Message} {@Context}", message, logContext);
}
public void LogWarning(string message, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
_logger.LogWarning("Warning: {Message} {@Context}", message, logContext);
}
public void LogError(Exception exception, string message, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["exception_type"] = exception.GetType().Name;
logContext["exception_message"] = exception.Message;
logContext["stack_trace"] = exception.StackTrace;
if (exception.InnerException != null)
{
logContext["inner_exception"] = exception.InnerException.Message;
}
_logger.LogError(exception, "Error: {Message} {@Context}", message, logContext);
}
public void LogPerformance(string operation, TimeSpan duration, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["operation"] = operation;
logContext["duration_ms"] = duration.TotalMilliseconds;
logContext["duration_seconds"] = duration.TotalSeconds;
var logLevel = duration.TotalSeconds > 1 ? LogLevel.Warning : LogLevel.Information;
_logger.Log(logLevel, "Performance: {Operation} completed in {Duration}ms {@Context}",
operation, duration.TotalMilliseconds, logContext);
}
public void LogDatabaseOperation(string operation, string query, TimeSpan duration, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["db_operation"] = operation;
logContext["db_query"] = query;
logContext["db_duration_ms"] = duration.TotalMilliseconds;
var logLevel = duration.TotalSeconds > 0.5 ? LogLevel.Warning : LogLevel.Debug;
_logger.Log(logLevel, "Database: {Operation} completed in {Duration}ms {@Context}",
operation, duration.TotalMilliseconds, logContext);
}
public void LogApiCall(string method, string url, int statusCode, TimeSpan duration, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["http_method"] = method;
logContext["http_url"] = url;
logContext["http_status_code"] = statusCode;
logContext["http_duration_ms"] = duration.TotalMilliseconds;
var logLevel = statusCode >= 400 ? LogLevel.Warning : LogLevel.Information;
_logger.Log(logLevel, "API: {Method} {Url} returned {StatusCode} in {Duration}ms {@Context}",
method, url, statusCode, duration.TotalMilliseconds, logContext);
}
public void LogConfigurationChange(string key, object? oldValue, object? newValue, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["config_key"] = key;
logContext["config_old_value"] = oldValue;
logContext["config_new_value"] = newValue;
_logger.LogInformation("Configuration: {Key} changed from {OldValue} to {NewValue} {@Context}",
key, oldValue, newValue, logContext);
}
private Dictionary<string, object> MergeContext(Dictionary<string, object>? additionalContext)
{
var merged = new Dictionary<string, object>(_defaultContext);
if (additionalContext != null)
{
foreach (var kvp in additionalContext)
{
merged[kvp.Key] = kvp.Value;
}
}
return merged;
}
}
🔗 Correlation IDs
Correlation ID Middleware
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<CorrelationIdMiddleware> _logger;
public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var correlationId = GetOrCreateCorrelationId(context);
// Add correlation ID to response headers
context.Response.Headers["X-Correlation-ID"] = correlationId;
// Add correlation ID to log context
using (_logger.BeginScope(new Dictionary<string, object>
{
["correlation_id"] = correlationId,
["request_path"] = context.Request.Path,
["request_method"] = context.Request.Method,
["user_agent"] = context.Request.Headers["User-Agent"].ToString(),
["client_ip"] = context.Connection.RemoteIpAddress?.ToString()
}))
{
_logger.LogInformation("Request started: {Method} {Path}",
context.Request.Method, context.Request.Path);
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Request failed: {Method} {Path}",
context.Request.Method, context.Request.Path);
throw;
}
finally
{
stopwatch.Stop();
_logger.LogInformation("Request completed: {Method} {Path} - {StatusCode} in {Duration}ms",
context.Request.Method, context.Request.Path,
context.Response.StatusCode, stopwatch.ElapsedMilliseconds);
}
}
}
private string GetOrCreateCorrelationId(HttpContext context)
{
// Check if correlation ID is provided in request headers
if (context.Request.Headers.TryGetValue("X-Correlation-ID", out var correlationId))
{
return correlationId.ToString();
}
// Check if correlation ID is provided in query string
if (context.Request.Query.TryGetValue("correlationId", out var queryCorrelationId))
{
return queryCorrelationId.ToString();
}
// Generate new correlation ID
return Guid.NewGuid().ToString();
}
}public class CorrelationIdService
{
private readonly AsyncLocal<string> _correlationId = new();
public string GetCorrelationId()
{
return _correlationId.Value ?? Guid.NewGuid().ToString();
}
public void SetCorrelationId(string correlationId)
{
_correlationId.Value = correlationId;
}
public IDisposable CreateScope(string correlationId)
{
var previousId = _correlationId.Value;
_correlationId.Value = correlationId;
return new CorrelationIdScope(previousId, this);
}
private class CorrelationIdScope : IDisposable
{
private readonly string _previousId;
private readonly CorrelationIdService _service;
public CorrelationIdScope(string previousId, CorrelationIdService service)
{
_previousId = previousId;
_service = service;
}
public void Dispose()
{
_service._correlationId.Value = _previousId;
}
}
}
Scoped Logging
public class ScopedLogger<T>
{
private readonly ILogger<T> _logger;
private readonly CorrelationIdService _correlationIdService;
private readonly Dictionary<string, object> _scopeData;
public ScopedLogger(ILogger<T> logger, CorrelationIdService correlationIdService)
{
_logger = logger;
_correlationIdService = correlationIdService;
_scopeData = new Dictionary<string, object>
{
["correlation_id"] = _correlationIdService.GetCorrelationId(),
["service"] = typeof(T).Name
};
}
public IDisposable BeginScope(string operation, Dictionary<string, object>? additionalData = null)
{
var scopeData = new Dictionary<string, object>(_scopeData)
{
["operation"] = operation
};
if (additionalData != null)
{
foreach (var kvp in additionalData)
{
scopeData[kvp.Key] = kvp.Value;
}
}
return _logger.BeginScope(scopeData);
}
public void LogOperationStart(string operation, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["operation"] = operation;
logContext["event"] = "operation_start";
_logger.LogInformation("Operation started: {Operation} {@Context}", operation, logContext);
}
public void LogOperationEnd(string operation, TimeSpan duration, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["operation"] = operation;
logContext["duration_ms"] = duration.TotalMilliseconds;
logContext["event"] = "operation_end";
_logger.LogInformation("Operation completed: {Operation} in {Duration}ms {@Context}",
operation, duration.TotalMilliseconds, logContext);
}
public void LogOperationError(string operation, Exception exception, Dictionary<string, object>? context = null)
{
var logContext = MergeContext(context);
logContext["operation"] = operation;
logContext["event"] = "operation_error";
logContext["exception_type"] = exception.GetType().Name;
logContext["exception_message"] = exception.Message;
_logger.LogError(exception, "Operation failed: {Operation} {@Context}", operation, logContext);
}
private Dictionary<string, object> MergeContext(Dictionary<string, object>? additionalContext)
{
var merged = new Dictionary<string, object>(_scopeData);
if (additionalContext != null)
{
foreach (var kvp in additionalContext)
{
merged[kvp.Key] = kvp.Value;
}
}
return merged;
}
}
📊 Log Aggregation
Log Aggregator Service
public class LogAggregatorService
{
private readonly ILogger<LogAggregatorService> _logger;
private readonly TSKConfig _config;
private readonly ConcurrentQueue<LogEntry> _logBuffer;
private readonly Timer _flushTimer;
private readonly int _bufferSize;
private readonly TimeSpan _flushInterval;
public LogAggregatorService(ILogger<LogAggregatorService> logger, TSKConfig config)
{
_logger = logger;
_config = config;
_logBuffer = new ConcurrentQueue<LogEntry>();
_bufferSize = config.Get<int>("logging.buffer_size", 1000);
_flushInterval = TimeSpan.FromSeconds(config.Get<int>("logging.flush_interval_seconds", 30));
_flushTimer = new Timer(FlushLogs, null, _flushInterval, _flushInterval);
}
public void AddLogEntry(LogEntry entry)
{
_logBuffer.Enqueue(entry);
if (_logBuffer.Count >= _bufferSize)
{
FlushLogs(null);
}
}
private async void FlushLogs(object? state)
{
if (_logBuffer.IsEmpty)
{
return;
}
var logs = new List<LogEntry>();
while (_logBuffer.TryDequeue(out var entry))
{
logs.Add(entry);
}
try
{
await SendLogsToAggregatorAsync(logs);
_logger.LogDebug("Flushed {Count} log entries to aggregator", logs.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to flush logs to aggregator");
// Re-queue logs for retry
foreach (var log in logs)
{
_logBuffer.Enqueue(log);
}
}
}
private async Task SendLogsToAggregatorAsync(List<LogEntry> logs)
{
var aggregatorUrl = _config.Get<string>("logging.aggregator_url");
if (string.IsNullOrEmpty(aggregatorUrl))
{
return;
}
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(30);
var payload = new LogBatch
{
ServiceName = _config.Get<string>("app.name", "unknown"),
Environment = _config.Get<string>("app.environment", "unknown"),
Timestamp = DateTime.UtcNow,
Logs = logs
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(aggregatorUrl, content);
response.EnsureSuccessStatusCode();
}
public void Dispose()
{
_flushTimer?.Dispose();
FlushLogs(null); // Final flush
}
}public class LogEntry
{
public string CorrelationId { get; set; } = string.Empty;
public string Level { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public string Service { get; set; } = string.Empty;
public string Operation { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public Dictionary<string, object> Context { get; set; } = new();
public Exception? Exception { get; set; }
}
public class LogBatch
{
public string ServiceName { get; set; } = string.Empty;
public string Environment { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public List<LogEntry> Logs { get; set; } = new();
}
Log Filtering and Sampling
public class LogFilterService
{
private readonly TSKConfig _config;
private readonly Dictionary<string, LogLevel> _categoryLevels;
private readonly List<string> _excludedPaths;
private readonly double _samplingRate;
public LogFilterService(TSKConfig config)
{
_config = config;
_categoryLevels = LoadCategoryLevels();
_excludedPaths = LoadExcludedPaths();
_samplingRate = config.Get<double>("logging.sampling_rate", 1.0);
}
public bool ShouldLog(string category, LogLevel level, string? path = null)
{
// Check if path is excluded
if (!string.IsNullOrEmpty(path) && _excludedPaths.Any(excluded => path.StartsWith(excluded)))
{
return false;
}
// Check category-specific log level
if (_categoryLevels.TryGetValue(category, out var categoryLevel))
{
return level >= categoryLevel;
}
// Check global log level
var globalLevel = ParseLogLevel(_config.Get<string>("logging.level", "Information"));
return level >= globalLevel;
}
public bool ShouldSample(string category, LogLevel level)
{
// Always log errors and critical messages
if (level >= LogLevel.Error)
{
return true;
}
// Apply sampling rate for other levels
return Random.Shared.NextDouble() <= _samplingRate;
}
private Dictionary<string, LogLevel> LoadCategoryLevels()
{
var levels = new Dictionary<string, LogLevel>();
var categoryConfig = _config.GetSection("logging.categories");
if (categoryConfig != null)
{
foreach (var key in categoryConfig.GetKeys())
{
var level = categoryConfig.Get<string>(key);
if (!string.IsNullOrEmpty(level))
{
levels[key] = ParseLogLevel(level);
}
}
}
return levels;
}
private List<string> LoadExcludedPaths()
{
var excluded = new List<string>();
var excludedConfig = _config.GetSection("logging.excluded_paths");
if (excludedConfig != null)
{
foreach (var key in excludedConfig.GetKeys())
{
var path = excludedConfig.Get<string>(key);
if (!string.IsNullOrEmpty(path))
{
excluded.Add(path);
}
}
}
return excluded;
}
private static LogLevel ParseLogLevel(string level)
{
return level.ToLower() switch
{
"trace" => LogLevel.Trace,
"debug" => LogLevel.Debug,
"information" => LogLevel.Information,
"warning" => LogLevel.Warning,
"error" => LogLevel.Error,
"critical" => LogLevel.Critical,
_ => LogLevel.Information
};
}
}
📈 Log Analysis
Log Analytics Service
public class LogAnalyticsService
{
private readonly ILogger<LogAnalyticsService> _logger;
private readonly TSKConfig _config;
private readonly Dictionary<string, LogMetrics> _metrics;
private readonly Timer _analysisTimer;
public LogAnalyticsService(ILogger<LogAnalyticsService> logger, TSKConfig config)
{
_logger = logger;
_config = config;
_metrics = new Dictionary<string, LogMetrics>();
var analysisInterval = TimeSpan.FromMinutes(config.Get<int>("logging.analysis_interval_minutes", 5));
_analysisTimer = new Timer(PerformAnalysis, null, analysisInterval, analysisInterval);
}
public void RecordLogEntry(string category, LogLevel level, string operation, TimeSpan? duration = null)
{
lock (_metrics)
{
if (!_metrics.ContainsKey(category))
{
_metrics[category] = new LogMetrics();
}
var metrics = _metrics[category];
metrics.TotalLogs++;
if (level == LogLevel.Error || level == LogLevel.Critical)
{
metrics.ErrorCount++;
}
if (!string.IsNullOrEmpty(operation))
{
if (!metrics.OperationCounts.ContainsKey(operation))
{
metrics.OperationCounts[operation] = 0;
}
metrics.OperationCounts[operation]++;
}
if (duration.HasValue)
{
metrics.TotalDuration += duration.Value;
metrics.OperationCount++;
if (duration.Value > metrics.MaxDuration)
{
metrics.MaxDuration = duration.Value;
}
if (duration.Value < metrics.MinDuration || metrics.MinDuration == TimeSpan.Zero)
{
metrics.MinDuration = duration.Value;
}
}
}
}
private async void PerformAnalysis(object? state)
{
try
{
var analysis = await GenerateLogAnalysisAsync();
await SendAnalysisToMonitoringAsync(analysis);
_logger.LogInformation("Log analysis completed: {TotalCategories} categories analyzed",
analysis.Categories.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to perform log analysis");
}
}
private async Task<LogAnalysis> GenerateLogAnalysisAsync()
{
var analysis = new LogAnalysis
{
Timestamp = DateTime.UtcNow,
ServiceName = _config.Get<string>("app.name", "unknown"),
Environment = _config.Get<string>("app.environment", "unknown")
};
lock (_metrics)
{
foreach (var kvp in _metrics)
{
var categoryMetrics = kvp.Value;
var categoryAnalysis = new CategoryAnalysis
{
Category = kvp.Key,
TotalLogs = categoryMetrics.TotalLogs,
ErrorCount = categoryMetrics.ErrorCount,
ErrorRate = categoryMetrics.TotalLogs > 0
? (double)categoryMetrics.ErrorCount / categoryMetrics.TotalLogs
: 0,
AverageDuration = categoryMetrics.OperationCount > 0
? categoryMetrics.TotalDuration.TotalMilliseconds / categoryMetrics.OperationCount
: 0,
MaxDuration = categoryMetrics.MaxDuration.TotalMilliseconds,
MinDuration = categoryMetrics.MinDuration.TotalMilliseconds,
TopOperations = categoryMetrics.OperationCounts
.OrderByDescending(x => x.Value)
.Take(10)
.ToDictionary(x => x.Key, x => x.Value)
};
analysis.Categories.Add(categoryAnalysis);
}
}
return analysis;
}
private async Task SendAnalysisToMonitoringAsync(LogAnalysis analysis)
{
var monitoringUrl = _config.Get<string>("logging.monitoring_url");
if (string.IsNullOrEmpty(monitoringUrl))
{
return;
}
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(30);
var json = JsonSerializer.Serialize(analysis);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(monitoringUrl, content);
response.EnsureSuccessStatusCode();
}
public void Dispose()
{
_analysisTimer?.Dispose();
}
}public class LogMetrics
{
public int TotalLogs { get; set; }
public int ErrorCount { get; set; }
public Dictionary<string, int> OperationCounts { get; set; } = new();
public TimeSpan TotalDuration { get; set; }
public int OperationCount { get; set; }
public TimeSpan MaxDuration { get; set; }
public TimeSpan MinDuration { get; set; }
}
public class LogAnalysis
{
public DateTime Timestamp { get; set; }
public string ServiceName { get; set; } = string.Empty;
public string Environment { get; set; } = string.Empty;
public List<CategoryAnalysis> Categories { get; set; } = new();
}
public class CategoryAnalysis
{
public string Category { get; set; } = string.Empty;
public int TotalLogs { get; set; }
public int ErrorCount { get; set; }
public double ErrorRate { get; set; }
public double AverageDuration { get; set; }
public double MaxDuration { get; set; }
public double MinDuration { get; set; }
public Dictionary<string, int> TopOperations { get; set; } = new();
}
🔍 Log Search and Query
Log Query Service
public class LogQueryService
{
private readonly ILogger<LogQueryService> _logger;
private readonly TSKConfig _config;
private readonly HttpClient _httpClient;
public LogQueryService(ILogger<LogQueryService> logger, TSKConfig config)
{
_logger = logger;
_config = config;
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
}
public async Task<List<LogEntry>> SearchLogsAsync(LogSearchCriteria criteria)
{
var searchUrl = _config.Get<string>("logging.search_url");
if (string.IsNullOrEmpty(searchUrl))
{
throw new InvalidOperationException("Log search URL not configured");
}
var queryParams = new List<string>();
if (!string.IsNullOrEmpty(criteria.CorrelationId))
{
queryParams.Add($"correlationId={Uri.EscapeDataString(criteria.CorrelationId)}");
}
if (!string.IsNullOrEmpty(criteria.Level))
{
queryParams.Add($"level={Uri.EscapeDataString(criteria.Level)}");
}
if (!string.IsNullOrEmpty(criteria.Service))
{
queryParams.Add($"service={Uri.EscapeDataString(criteria.Service)}");
}
if (!string.IsNullOrEmpty(criteria.Operation))
{
queryParams.Add($"operation={Uri.EscapeDataString(criteria.Operation)}");
}
if (criteria.StartTime.HasValue)
{
queryParams.Add($"startTime={criteria.StartTime.Value:yyyy-MM-ddTHH:mm:ssZ}");
}
if (criteria.EndTime.HasValue)
{
queryParams.Add($"endTime={criteria.EndTime.Value:yyyy-MM-ddTHH:mm:ssZ}");
}
if (criteria.Limit.HasValue)
{
queryParams.Add($"limit={criteria.Limit.Value}");
}
var url = $"{searchUrl}?{string.Join("&", queryParams)}";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var searchResult = JsonSerializer.Deserialize<LogSearchResult>(content);
return searchResult?.Logs ?? new List<LogEntry>();
}
public async Task<List<LogEntry>> GetLogsByCorrelationIdAsync(string correlationId)
{
var criteria = new LogSearchCriteria
{
CorrelationId = correlationId,
Limit = 100
};
return await SearchLogsAsync(criteria);
}
public async Task<List<LogEntry>> GetErrorLogsAsync(DateTime? startTime = null, DateTime? endTime = null)
{
var criteria = new LogSearchCriteria
{
Level = "Error",
StartTime = startTime,
EndTime = endTime,
Limit = 100
};
return await SearchLogsAsync(criteria);
}
public async Task<LogStatistics> GetLogStatisticsAsync(DateTime startTime, DateTime endTime)
{
var statsUrl = _config.Get<string>("logging.stats_url");
if (string.IsNullOrEmpty(statsUrl))
{
throw new InvalidOperationException("Log statistics URL not configured");
}
var url = $"{statsUrl}?startTime={startTime:yyyy-MM-ddTHH:mm:ssZ}&endTime={endTime:yyyy-MM-ddTHH:mm:ssZ}";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<LogStatistics>(content) ?? new LogStatistics();
}
}public class LogSearchCriteria
{
public string? CorrelationId { get; set; }
public string? Level { get; set; }
public string? Service { get; set; }
public string? Operation { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public int? Limit { get; set; }
}
public class LogSearchResult
{
public List<LogEntry> Logs { get; set; } = new();
public int TotalCount { get; set; }
public bool HasMore { get; set; }
}
public class LogStatistics
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public int TotalLogs { get; set; }
public int ErrorLogs { get; set; }
public int WarningLogs { get; set; }
public int InfoLogs { get; set; }
public Dictionary<string, int> LogsByService { get; set; } = new();
public Dictionary<string, int> LogsByLevel { get; set; } = new();
public double AverageResponseTime { get; set; }
public double MaxResponseTime { get; set; }
}
📝 Summary
This guide covered comprehensive logging patterns for C# TuskLang applications:
- Structured Logging: Configuration and structured logger service with context - Correlation IDs: Middleware and service for request tracing - Log Aggregation: Buffered log aggregation with batch processing - Log Filtering: Category-based filtering and sampling strategies - Log Analysis: Metrics collection and analysis for operational insights - Log Search: Query service for searching and analyzing logs
These logging patterns ensure your C# TuskLang applications have comprehensive observability and debugging capabilities.