diff --git a/Core/Telemetry/DebugTelemetryChannel.cs b/Core/Telemetry/DebugTelemetryChannel.cs index 2ebf0ad..df7a9fa 100644 --- a/Core/Telemetry/DebugTelemetryChannel.cs +++ b/Core/Telemetry/DebugTelemetryChannel.cs @@ -28,20 +28,20 @@ namespace Core.Telemetry } public new void Send(ITelemetry telemetry) { - var l = new SeqLogger(_client, "", ""); + //var l = new SeqLogger(_client, "", ""); - l.LogToSeq( - "Bruger {UserId} loggede ind", - "Debug", - new Dictionary { { "UserId", "12345" }, { "Counter", "i++" } } - ); + //l.LogToSeq( + // "Bruger {UserId} loggede ind", + // "Debug", + // new Dictionary { { "UserId", "12345" }, { "Counter", "i++" } } + // ); - if (telemetry is Microsoft.ApplicationInsights.DataContracts.TraceTelemetry trace) - { - var severity = trace.SeverityLevel; - Console.WriteLine($"Trace severity: {severity}, Message: {trace.Message}"); - } + //if (telemetry is Microsoft.ApplicationInsights.DataContracts.TraceTelemetry trace) + //{ + // var severity = trace.SeverityLevel; + // Console.WriteLine($"Trace severity: {severity}, Message: {trace.Message}"); + //} base.Send(telemetry); diff --git a/Core/Telemetry/SeqLogger.cs b/Core/Telemetry/SeqLogger.cs index 6e8eeef..4516383 100644 --- a/Core/Telemetry/SeqLogger.cs +++ b/Core/Telemetry/SeqLogger.cs @@ -1,42 +1,185 @@ -using System.Text; +using Microsoft.ApplicationInsights.DataContracts; +using System.Text; using System.Text.Json; namespace Core.Telemetry { - public class SeqLogger - { - private readonly string _seqUrl; - private readonly string _apiKey; // Optional - HttpClient _httpClient; - public SeqLogger(HttpClient httpClient, string seqUrl, string apiKey = null) - { - _seqUrl = seqUrl; - _apiKey = apiKey; - _httpClient = httpClient; - } + public record SeqConfiguration(string IngestionEndpoint, string ApiKey, string Environment); - public async Task LogToSeq(string message, string level, Dictionary properties) - { - var seqEvent = new Dictionary - { - { "@t", DateTime.UtcNow.ToString("o") }, - { "@mt", message }, - { "@l", level } // "Information", "Warning", "Error", etc. - }; + public class SeqHttpClient + { + HttpClient _httpClient; - foreach (var prop in properties) - { - seqEvent.Add(prop.Key, prop.Value); - } + public SeqHttpClient(SeqConfiguration seqConfiguration) + { + _httpClient = new HttpClient() + { + BaseAddress = new Uri("http://localhost:5341"), + Timeout = TimeSpan.FromSeconds(30) + }; - var content = new StringContent( - JsonSerializer.Serialize(seqEvent), - Encoding.UTF8, - "application/vnd.serilog.clef"); + _httpClient.DefaultRequestHeaders.Accept.Clear(); + _httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + if (seqConfiguration.ApiKey != null) + _httpClient.DefaultRequestHeaders.Add("X-Seq-ApiKey", seqConfiguration.ApiKey); + } - var response = await _httpClient.PostAsync("/ingest/clef?apiKey=Gt8hS9ClGNfOCAdswDlW", content); - response.EnsureSuccessStatusCode(); - } - } + public async Task SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken) + { + return await _httpClient.SendAsync(httpRequestMessage, cancellationToken); + } + } + + + 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 async Task LogTraceTelemetryAsync(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); + } + + await SendToSeqAsync(seqEvent, cancellationToken); + } + + public async Task LogEventTelemetryAsync(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 metric in evt.Metrics) + { + seqEvent.Add($"metric_{metric.Key}", metric.Value); + } + + await SendToSeqAsync(seqEvent, cancellationToken); + } + + public async Task LogExceptionTelemetryAsync(ExceptionTelemetry ex, CancellationToken cancellationToken = default) + { + var seqEvent = new Dictionary + { + { "@t", ex.Timestamp.UtcDateTime.ToString("o") }, + { "@mt", ex.Exception.Message }, + { "@l", "Error" }, + { "Environment", _environmentName }, + { "MachineName", _machineName }, + { "ExceptionType", ex.Exception.GetType().Name }, + { "StackTrace", ex.Exception.StackTrace } + }; + + foreach (var prop in ex.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } + + await SendToSeqAsync(seqEvent, cancellationToken); + } + + public async Task LogDependencyTelemetryAsync(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); + } + + await SendToSeqAsync(seqEvent, cancellationToken); + } + + public async Task LogRequestTelemetryAsync(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); + } + + 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"); + + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/ingest/clef") + { + Content = content + }; + + await _httpClient.SendAsync(requestMessage, cancellationToken); + } + + private string MapSeverityToLevel(SeverityLevel? severity) + { + return severity switch + { + SeverityLevel.Verbose => "Verbose", + SeverityLevel.Information => "Information", + SeverityLevel.Warning => "Warning", + SeverityLevel.Error => "Error", + SeverityLevel.Critical => "Fatal", + _ => "Information" + }; + } + } } diff --git a/Tests/PostgresTests.cs b/Tests/PostgresTests.cs index 956f2af..cbfe0a5 100644 --- a/Tests/PostgresTests.cs +++ b/Tests/PostgresTests.cs @@ -21,34 +21,34 @@ namespace Tests conn.ExecuteSql("SELECT 1 as p"); } - static HttpClient _client = new HttpClient(); - [TestMethod] - public async Task MyTestMethod() - { - _client.BaseAddress = new Uri("http://localhost:5341"); + //static HttpClient _client = new HttpClient(); + //[TestMethod] + //public async Task MyTestMethod() + //{ + // _client.BaseAddress = new Uri("http://localhost:5341"); - var l = new SeqLogger(_client, "", ""); + // var l = new SeqLogger(_client, "", ""); - for (int i = 0; i < 20; i++) - { + // for (int i = 0; i < 20; i++) + // { - await l.LogToSeq( - "Bruger {UserId} loggede ind", - "Debug", - new Dictionary { { "UserId", "12345" }, { "Counter", i++ } } - ); - } - var logger = Container.Resolve(); + // await l.LogToSeq( + // "Bruger {UserId} loggede ind", + // "Debug", + // new Dictionary { { "UserId", "12345" }, { "Counter", i++ } } + // ); + // } + // var logger = Container.Resolve(); - for (int i = 0; i < 5; i++) - { + // for (int i = 0; i < 5; i++) + // { - logger.TrackTrace("Hello 23", Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information); + // logger.TrackTrace("Hello 23", Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information); - } + // } - } + //} [TestMethod] public void TryOrganizationSetupService() diff --git a/Tests/TelemetryLogging/SeqLoggerTests.cs b/Tests/TelemetryLogging/SeqLoggerTests.cs new file mode 100644 index 0000000..8ee1d03 --- /dev/null +++ b/Tests/TelemetryLogging/SeqLoggerTests.cs @@ -0,0 +1,109 @@ +using Core.Telemetry; +using Microsoft.ApplicationInsights.DataContracts; + +namespace Tests.TelemetryLogging +{ + [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); + } + + [TestMethod] + public async Task LogTraceTelemetry_SendsCorrectData() + { + // Arrange + var traceTelemetry = new TraceTelemetry + { + Message = "Test trace message", + SeverityLevel = SeverityLevel.Error, + Timestamp = DateTimeOffset.UtcNow + }; + traceTelemetry.Properties.Add("TestId", _testId); + + // Act + await _logger.LogTraceTelemetryAsync(traceTelemetry); + + // Du kan nu tjekke Seq med følgende query: + // TestId = 'guid-værdi-her' + } + + [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.LogEventTelemetryAsync(eventTelemetry); + } + + [TestMethod] + public async Task LogExceptionTelemetry_SendsCorrectData() + { + // Arrange + var exception = new Exception("Test exception"); + var exceptionTelemetry = new ExceptionTelemetry(exception) + { + Timestamp = DateTimeOffset.UtcNow + }; + exceptionTelemetry.Properties.Add("TestId", _testId); + + // Act + await _logger.LogExceptionTelemetryAsync(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); + + // Act + await _logger.LogDependencyTelemetryAsync(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); + + // Act + await _logger.LogRequestTelemetryAsync(requestTelemetry); + } + } +}