diff --git a/Core/Logging/SeqBackgroundService.cs b/Core/Logging/SeqBackgroundService.cs index 7f7f33c..6d21380 100644 --- a/Core/Logging/SeqBackgroundService.cs +++ b/Core/Logging/SeqBackgroundService.cs @@ -49,7 +49,12 @@ namespace Core.Logging await _seqLogger.LogAsync(et); break; - default: + case EventTelemetry et: + await _seqLogger.LogAsync(et); + break; + + + default: throw new NotSupportedException(telemetry.GetType().Name); } } diff --git a/Core/Logging/SeqLogger.cs b/Core/Logging/SeqLogger.cs index cf3868a..ec5578a 100644 --- a/Core/Logging/SeqLogger.cs +++ b/Core/Logging/SeqLogger.cs @@ -3,205 +3,246 @@ using System.Text; namespace Core.Logging { - public class SeqLogger - { - private readonly SeqHttpClient _httpClient; - private readonly string _environmentName; - private readonly string _machineName; - private readonly SeqConfiguration _configuration; + public class SeqLogger + { + private readonly SeqHttpClient _httpClient; + private readonly string _environmentName; + private readonly string _machineName; + private readonly SeqConfiguration _configuration; - public SeqLogger(SeqHttpClient httpClient, string environmentName, SeqConfiguration configuration) - { - _httpClient = httpClient; - _environmentName = configuration.Environment; - _machineName = Environment.MachineName; - } + public SeqLogger(SeqHttpClient httpClient, string environmentName, SeqConfiguration configuration) + { + _httpClient = httpClient; + _environmentName = configuration.Environment; + _machineName = Environment.MachineName; + } - public async Task LogAsync(TraceTelemetry trace, CancellationToken cancellationToken = default) - { - var seqEvent = new Dictionary - { - { "@t", trace.Timestamp.UtcDateTime.ToString("o") }, - { "@mt", trace.Message }, - { "@l", MapSeverityToLevel(trace.SeverityLevel) }, - { "Environment", _environmentName }, - { "MachineName", _machineName } - }; + public async Task LogAsync(TraceTelemetry trace, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", trace.Timestamp.UtcDateTime.ToString("o") }, + { "@mt", trace.Message }, + { "@l", MapSeverityToLevel(trace.SeverityLevel) }, + { "Environment", _environmentName }, + { "MachineName", _machineName } + }; - foreach (var prop in trace.Properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + foreach (var prop in trace.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - await SendToSeqAsync(seqEvent, cancellationToken); - } + await SendToSeqAsync(seqEvent, cancellationToken); + } - public async Task LogAsync(EventTelemetry evt, CancellationToken cancellationToken = default) - { - var seqEvent = new Dictionary - { - { "@t", evt.Timestamp.UtcDateTime.ToString("o") }, - { "@mt", evt.Name }, - { "@l", "Information" }, - { "Environment", _environmentName }, - { "MachineName", _machineName } - }; + public async Task LogAsync(EventTelemetry evt, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", evt.Timestamp.UtcDateTime.ToString("o") }, + { "@mt", evt.Name }, + { "@l", "Information" }, + { "Environment", _environmentName }, + { "MachineName", _machineName } + }; - foreach (var prop in evt.Properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + foreach (var prop in evt.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - foreach (var metric in evt.Metrics) - { - seqEvent.Add($"metric_{metric.Key}", metric.Value); - } + foreach (var metric in evt.Metrics) + { + seqEvent.Add($"metric_{metric.Key}", metric.Value); + } - await SendToSeqAsync(seqEvent, cancellationToken); - } + await SendToSeqAsync(seqEvent, cancellationToken); + } - public async Task LogAsync(ExceptionTelemetry ex, CancellationToken cancellationToken = default) - { - var seqEvent = new Dictionary - { - { "@t", ex.Timestamp.UtcDateTime.ToString("o") }, - { "@mt", ex.Exception.Message }, - { "@l", "Error" }, - { "@x", FormatExceptionForSeq(ex.Exception) }, - { "Environment", _environmentName }, - { "MachineName", _machineName }, - { "ExceptionType", ex.Exception.GetType().Name }, - }; + public async Task LogAsync(ExceptionTelemetry ex, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", ex.Timestamp.UtcDateTime.ToString("o") }, + { "@mt", ex.Exception.Message }, + { "@l", "Error" }, + { "@x", FormatExceptionForSeq(ex.Exception) }, + { "Environment", _environmentName }, + { "MachineName", _machineName }, + { "ExceptionType", ex.Exception.GetType().Name }, + }; - foreach (var prop in ex.Properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + foreach (var prop in ex.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - await SendToSeqAsync(seqEvent, cancellationToken); - } + await SendToSeqAsync(seqEvent, cancellationToken); + } - public async Task LogAsync(DependencyTelemetry dep, CancellationToken cancellationToken = default) - { - var seqEvent = new Dictionary - { - { "@t", dep.Timestamp.UtcDateTime.ToString("o") }, - { "@mt", $"Dependency: {dep.Name}" }, - { "@l", dep.Success??true ? "Information" : "Error" }, - { "Environment", _environmentName }, - { "MachineName", _machineName }, - { "DependencyType", dep.Type }, - { "Target", dep.Target }, - { "Duration", dep.Duration.TotalMilliseconds } - }; + public async Task LogAsync(DependencyTelemetry dep, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", dep.Timestamp.UtcDateTime.ToString("o") }, + { "@mt", $"Dependency: {dep.Name}" }, + { "@l", dep.Success??true ? "Information" : "Error" }, + { "Environment", _environmentName }, + { "MachineName", _machineName }, + { "DependencyType", dep.Type }, + { "Target", dep.Target }, + { "Duration", dep.Duration.TotalMilliseconds } + }; - foreach (var prop in dep.Properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + foreach (var prop in dep.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - await SendToSeqAsync(seqEvent, cancellationToken); - } + await SendToSeqAsync(seqEvent, cancellationToken); + } - public async Task LogAsync(RequestTelemetry req, CancellationToken cancellationToken = default) - { - var seqEvent = new Dictionary - { - { "@t", req.Timestamp.UtcDateTime.ToString("o") }, - { "@mt", $"Request: {req.Name}" }, - { "@l", req.Success??true ? "Information" : "Error" }, - { "Environment", _environmentName }, - { "MachineName", _machineName }, - { "Url", req.Url }, - { "ResponseCode", req.ResponseCode }, - { "Duration", req.Duration.TotalMilliseconds } - }; - - foreach (var prop in req.Properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + public async Task LogAsync(RequestTelemetry req, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", req.Timestamp.UtcDateTime.ToString("o") }, + { "@mt",$"{req.Properties["httpMethod"]} {req.Name}" }, + { "@l", req.Success??true ? "Information" : "Error" }, + { "Environment", _environmentName }, + { "MachineName", _machineName }, + { "Url", req.Url }, + { "ResponseCode", req.ResponseCode }, + { "Application", "sadasd" }, + { "RequestMethod", req.Properties["httpMethod"] }, + { "RequestId", req.Id }, + { "ItemTypeFlag", req.ItemTypeFlag.ToString() }, + { "@sp", "12"}, + { "@tr", "23344"}, + { "@sk","Server" }, + { "@st", req.Timestamp.UtcDateTime.Subtract(req.Duration).ToString("o") } + }; + req.Properties.Remove("httpMethod"); - await SendToSeqAsync(seqEvent, cancellationToken); - } + //we should add a property with name { "Application", "<...>" } other the Seq Span is not looking ok + foreach (var prop in req.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - private async Task SendToSeqAsync(Dictionary seqEvent, CancellationToken cancellationToken) - { - var content = new StringContent( - Newtonsoft.Json.JsonConvert.SerializeObject(seqEvent), - Encoding.UTF8, - "application/vnd.serilog.clef"); + await SendToSeqAsync(seqEvent, cancellationToken); + } + public async Task LogAsync(Microsoft.ApplicationInsights.Extensibility.IOperationHolder operationHolder, CancellationToken cancellationToken = default) + { + var req = operationHolder.Telemetry; + + var seqEvent = new Dictionary + { - var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/ingest/clef") - { - Content = content - }; + { "@t", req.Timestamp.UtcDateTime.ToString("o") }, + { "@mt",$"{req.Properties["httpMethod"]} {req.Name}" }, + { "@l", req.Success??true ? "Information" : "Error" }, + { "Environment", _environmentName }, + { "MachineName", _machineName }, + { "Url", req.Url }, + { "ResponseCode", req.ResponseCode }, + { "Application", "sadasd" }, + { "RequestMethod", req.Properties["httpMethod"] }, + { "RequestId", req.Id }, + { "ItemTypeFlag", req.ItemTypeFlag.ToString() }, + { "@sp", "12"}, + { "@tr", "23344"}, + { "@sk","Server" }, + { "@st", req.Timestamp.UtcDateTime.Subtract(req.Duration).ToString("o") } + }; + req.Properties.Remove("httpMethod"); - var result = await _httpClient.SendAsync(requestMessage, cancellationToken); + //we should add a property with name { "Application", "<...>" } other the Seq Span is not looking ok + foreach (var prop in req.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } - result.EnsureSuccessStatusCode(); - } + await SendToSeqAsync(seqEvent, cancellationToken); + } + private async Task SendToSeqAsync(Dictionary seqEvent, CancellationToken cancellationToken) + { + var content = new StringContent( + Newtonsoft.Json.JsonConvert.SerializeObject(seqEvent), + Encoding.UTF8, + "application/vnd.serilog.clef"); - private string MapSeverityToLevel(SeverityLevel? severity) - { - return severity switch - { - SeverityLevel.Verbose => "Verbose", - SeverityLevel.Information => "Information", - SeverityLevel.Warning => "Warning", - SeverityLevel.Error => "Error", - SeverityLevel.Critical => "Fatal", - _ => "Information" - }; - } - private string FormatExceptionForSeq(Exception ex) - { - var sb = new StringBuilder(); - var exceptionCount = 0; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/ingest/clef") + { + Content = content + }; - void FormatSingleException(Exception currentEx, int depth) - { - if (depth > 0) sb.AppendLine("\n--- Inner Exception ---"); + var result = await _httpClient.SendAsync(requestMessage, cancellationToken); - sb.AppendLine($"Exception Type: {currentEx.GetType().FullName}"); - sb.AppendLine($"Message: {currentEx.Message}"); - sb.AppendLine($"Source: {currentEx.Source}"); - sb.AppendLine($"HResult: 0x{currentEx.HResult:X8}"); - sb.AppendLine("Stack Trace:"); - sb.AppendLine(currentEx.StackTrace?.Trim()); + result.EnsureSuccessStatusCode(); + } - if (currentEx.Data.Count > 0) - { - sb.AppendLine("Additional Data:"); - foreach (var key in currentEx.Data.Keys) - { - sb.AppendLine($" {key}: {currentEx.Data[key]}"); - } - } - } + private string MapSeverityToLevel(SeverityLevel? severity) + { + return severity switch + { + SeverityLevel.Verbose => "Verbose", + SeverityLevel.Information => "Information", + SeverityLevel.Warning => "Warning", + SeverityLevel.Error => "Error", + SeverityLevel.Critical => "Fatal", + _ => "Information" + }; + } + private string FormatExceptionForSeq(Exception ex) + { + var sb = new StringBuilder(); + var exceptionCount = 0; - void RecurseExceptions(Exception currentEx, int depth = 0) - { - if (currentEx is AggregateException aggEx) - { - foreach (var inner in aggEx.InnerExceptions) - { - RecurseExceptions(inner, depth); - depth++; - } - } - else if (currentEx.InnerException != null) - { - RecurseExceptions(currentEx.InnerException, depth + 1); - } + void FormatSingleException(Exception currentEx, int depth) + { + if (depth > 0) sb.AppendLine("\n--- Inner Exception ---"); - FormatSingleException(currentEx, depth); - exceptionCount++; - } + sb.AppendLine($"Exception Type: {currentEx.GetType().FullName}"); + sb.AppendLine($"Message: {currentEx.Message}"); + sb.AppendLine($"Source: {currentEx.Source}"); + sb.AppendLine($"HResult: 0x{currentEx.HResult:X8}"); + sb.AppendLine("Stack Trace:"); + sb.AppendLine(currentEx.StackTrace?.Trim()); - RecurseExceptions(ex); - sb.Insert(0, $"EXCEPTION CHAIN ({exceptionCount} exceptions):\n"); - return sb.ToString(); - } - } + if (currentEx.Data.Count > 0) + { + sb.AppendLine("Additional Data:"); + foreach (var key in currentEx.Data.Keys) + { + sb.AppendLine($" {key}: {currentEx.Data[key]}"); + } + } + } + + void RecurseExceptions(Exception currentEx, int depth = 0) + { + if (currentEx is AggregateException aggEx) + { + foreach (var inner in aggEx.InnerExceptions) + { + RecurseExceptions(inner, depth); + depth++; + } + } + else if (currentEx.InnerException != null) + { + RecurseExceptions(currentEx.InnerException, depth + 1); + } + + FormatSingleException(currentEx, depth); + exceptionCount++; + } + + RecurseExceptions(ex); + sb.Insert(0, $"EXCEPTION CHAIN ({exceptionCount} exceptions):\n"); + return sb.ToString(); + } + } } diff --git a/Tests/Logging/SeqBackgroundServiceTest.cs b/Tests/Logging/SeqBackgroundServiceTest.cs index a7edaa8..dd6da41 100644 --- a/Tests/Logging/SeqBackgroundServiceTest.cs +++ b/Tests/Logging/SeqBackgroundServiceTest.cs @@ -40,8 +40,15 @@ namespace Tests.Logging for (int i = 0; i < 5; i++) { - var eventTelemetry = new EventTelemetry("SomeEvent"); - await _messageChannel.Writer.WriteAsync(eventTelemetry); + var eventTelemetry = new EventTelemetry + { + Name = "Test Event", + Timestamp = DateTimeOffset.UtcNow + }; + eventTelemetry.Properties.Add("TestId", Guid.NewGuid().ToString()); + eventTelemetry.Metrics.Add("TestMetric", 42.0); + + await _messageChannel.Writer.WriteAsync(eventTelemetry); } // wait for processing diff --git a/Tests/Logging/SeqLoggerTests.cs b/Tests/Logging/SeqLoggerTests.cs index 513909c..e010fe3 100644 --- a/Tests/Logging/SeqLoggerTests.cs +++ b/Tests/Logging/SeqLoggerTests.cs @@ -1,132 +1,146 @@ -using Core.Logging; +using Autofac; +using Core.Logging; +using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; namespace Tests.Logging { - [TestClass] - public class SeqLoggerTests : TestFixture - { - private SeqLogger _logger; - private SeqHttpClient _httpClient; - private readonly string _testId; + [TestClass] + public class SeqLoggerTests : TestFixture + { + private SeqLogger _logger; + private SeqHttpClient _httpClient; + private readonly string _testId; - public SeqLoggerTests() - { - _testId = Guid.NewGuid().ToString(); - var config = new SeqConfiguration("http://localhost:5341", null, "MSTEST"); - _httpClient = new SeqHttpClient(config); - _logger = new SeqLogger(_httpClient, Environment.MachineName, config); - } + public SeqLoggerTests() + { + _testId = Guid.NewGuid().ToString(); + var config = new SeqConfiguration("http://localhost:5341", null, "MSTEST"); + _httpClient = new SeqHttpClient(config); + _logger = new SeqLogger(_httpClient, Environment.MachineName, config); + } - [TestMethod] - public async Task LogTraceTelemetry_SendsCorrectDataWithErrorLevel() - { - // Arrange - var traceTelemetry = new TraceTelemetry - { - Message = "Test trace error message", - SeverityLevel = SeverityLevel.Error, - Timestamp = DateTimeOffset.UtcNow - }; - traceTelemetry.Properties.Add("TestId", _testId); + [TestMethod] + public async Task LogTraceTelemetry_SendsCorrectDataWithErrorLevel() + { + // Arrange + var traceTelemetry = new TraceTelemetry + { + Message = "Test trace error message", + SeverityLevel = SeverityLevel.Error, + Timestamp = DateTimeOffset.UtcNow + }; + traceTelemetry.Properties.Add("TestId", _testId); - // Act - await _logger.LogAsync(traceTelemetry); + // Act + await _logger.LogAsync(traceTelemetry); - } - [TestMethod] - public async Task LogTraceTelemetry_SendsCorrectDataWithWarningLevel() - { - // Arrange - var traceTelemetry = new TraceTelemetry - { - Message = "Test trace warning message", - SeverityLevel = SeverityLevel.Warning, - Timestamp = DateTimeOffset.UtcNow - }; - traceTelemetry.Properties.Add("TestId", _testId); + } + [TestMethod] + public async Task LogTraceTelemetry_SendsCorrectDataWithWarningLevel() + { + // Arrange + var traceTelemetry = new TraceTelemetry + { + Message = "Test trace warning message", + SeverityLevel = SeverityLevel.Warning, + Timestamp = DateTimeOffset.UtcNow + }; + traceTelemetry.Properties.Add("TestId", _testId); - // Act - await _logger.LogAsync(traceTelemetry); + // Act + await _logger.LogAsync(traceTelemetry); - } - [TestMethod] - public async Task LogEventTelemetry_SendsCorrectData() - { - // Arrange - var eventTelemetry = new EventTelemetry - { - Name = "Test Event", - Timestamp = DateTimeOffset.UtcNow - }; - eventTelemetry.Properties.Add("TestId", _testId); - eventTelemetry.Metrics.Add("TestMetric", 42.0); + } + [TestMethod] + public async Task LogEventTelemetry_SendsCorrectData() + { + // Arrange + var eventTelemetry = new EventTelemetry + { + Name = "Test Event", + Timestamp = DateTimeOffset.UtcNow + }; + eventTelemetry.Properties.Add("TestId", _testId); + eventTelemetry.Metrics.Add("TestMetric", 42.0); - // Act - await _logger.LogAsync(eventTelemetry); - } + // Act + await _logger.LogAsync(eventTelemetry); + } - [TestMethod] - public async Task LogExceptionTelemetry_SendsCorrectData() - { - try - { - int t = 0; - var result = 10 / t; + [TestMethod] + public async Task LogExceptionTelemetry_SendsCorrectData() + { + try + { + int t = 0; + var result = 10 / t; - } - catch (Exception e) - { + } + catch (Exception e) + { - // Arrange - var exceptionTelemetry = new ExceptionTelemetry(e) - { - Timestamp = DateTimeOffset.UtcNow - }; - exceptionTelemetry.Properties.Add("TestId", _testId); + // Arrange + var exceptionTelemetry = new ExceptionTelemetry(e) + { + Timestamp = DateTimeOffset.UtcNow + }; + exceptionTelemetry.Properties.Add("TestId", _testId); - // Act - await _logger.LogAsync(exceptionTelemetry); - } - } + // Act + await _logger.LogAsync(exceptionTelemetry); + } + } - [TestMethod] - public async Task LogDependencyTelemetry_SendsCorrectData() - { - // Arrange - var dependencyTelemetry = new DependencyTelemetry - { - Name = "SQL Query", - Type = "SQL", - Target = "TestDB", - Success = true, - Duration = TimeSpan.FromMilliseconds(100), - Timestamp = DateTimeOffset.UtcNow - }; - dependencyTelemetry.Properties.Add("TestId", _testId); + [TestMethod] + public async Task LogDependencyTelemetry_SendsCorrectData() + { + // Arrange + var dependencyTelemetry = new DependencyTelemetry + { + Name = "SQL Query", + Type = "SQL", + Target = "TestDB", + Success = true, + Duration = TimeSpan.FromMilliseconds(100), + Timestamp = DateTimeOffset.UtcNow + }; + dependencyTelemetry.Properties.Add("TestId", _testId); - // Act - await _logger.LogAsync(dependencyTelemetry); - } + // Act + await _logger.LogAsync(dependencyTelemetry); + } - [TestMethod] - public async Task LogRequestTelemetry_SendsCorrectData() - { - // Arrange - var requestTelemetry = new RequestTelemetry - { - Name = "GET /api/test", - Success = true, - ResponseCode = "200", - Duration = TimeSpan.FromMilliseconds(50), - Url = new Uri("http://test.com/api/test"), - Timestamp = DateTimeOffset.UtcNow - }; - requestTelemetry.Properties.Add("TestId", _testId); + [TestMethod] + public async Task LogRequestTelemetry_SendsCorrectData() + { + var telemetryClient = Container.Resolve(); - // Act - await _logger.LogAsync(requestTelemetry); - } - } + var operationId = "op123"; + + using (Microsoft.ApplicationInsights.Extensibility.IOperationHolder operation = telemetryClient.StartOperation("GET /api/parent")) + { + + using (var child = telemetryClient.StartOperation("api/test")) + // Arrange + { + + //child.Telemetry.Name = "/api/test"; + child.Telemetry.Success = true; + child.Telemetry.ResponseCode = "200"; + child.Telemetry.Duration = TimeSpan.FromMilliseconds(50); + child.Telemetry.Url = new Uri("http://test.com/api/test"); + child.Telemetry.Timestamp = DateTimeOffset.UtcNow; + + child.Telemetry.Properties.Add("httpMethod", HttpMethod.Get.ToString()); + child.Telemetry.Properties.Add("TestId", _testId); + + await _logger.LogAsync(child); + }; + + } + // Act + } + } }