diff --git a/Application/Program.cs b/Application/Program.cs index 6a96c94..480670b 100644 --- a/Application/Program.cs +++ b/Application/Program.cs @@ -1,3 +1,4 @@ +using Application; using Autofac.Extensions.DependencyInjection; var host = Host.CreateDefaultBuilder(args) @@ -6,7 +7,7 @@ var host = Host.CreateDefaultBuilder(args) { webHostBuilder .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); + .UseStartup(); }) .Build(); diff --git a/Application/Startup.cs b/Application/Startup.cs index 9b0db26..88fbd14 100644 --- a/Application/Startup.cs +++ b/Application/Startup.cs @@ -2,7 +2,7 @@ using Core.Configurations.JsonConfigProvider; using Core.Configurations; -namespace PlanTempus +namespace Application { public class Startup { @@ -28,7 +28,7 @@ namespace PlanTempus services.Configure(options => { - options.ViewLocationExpanders.Add(new Application.Common.ComponentsViewLocationExpander()); + options.ViewLocationExpanders.Add(new Common.ComponentsViewLocationExpander()); }); } diff --git a/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs b/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs index 454ef09..ba040a0 100644 --- a/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs +++ b/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs @@ -1,6 +1,4 @@ -using Core.Configurations.SmartConfigProvider; - -namespace Core.Configurations.SmartConfiguration; +namespace Core.Configurations.SmartConfigProvider; public interface IConfigurationRepository { string ConnectionString { get; set; } diff --git a/Core/Configurations/SmartConfigProvider/Repositories/PostgresConfigurationRepository.cs b/Core/Configurations/SmartConfigProvider/Repositories/PostgresConfigurationRepository.cs index 83aeecf..1535978 100644 --- a/Core/Configurations/SmartConfigProvider/Repositories/PostgresConfigurationRepository.cs +++ b/Core/Configurations/SmartConfigProvider/Repositories/PostgresConfigurationRepository.cs @@ -1,5 +1,4 @@ using System.Data; -using Core.Configurations.SmartConfiguration; using Insight.Database; namespace Core.Configurations.SmartConfigProvider.Repositories; diff --git a/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs b/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs index 781c3b3..5efb867 100644 --- a/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs +++ b/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs @@ -1,4 +1,4 @@ -namespace Core.Configurations.SmartConfig +namespace Core.Configurations.SmartConfigProvider { /// /// Extension methods for adding smart configuration providers to IConfigurationBuilder. diff --git a/Core/Configurations/SmartConfigProvider/SmartConfigOptions.cs b/Core/Configurations/SmartConfigProvider/SmartConfigOptions.cs index 98126bc..a457123 100644 --- a/Core/Configurations/SmartConfigProvider/SmartConfigOptions.cs +++ b/Core/Configurations/SmartConfigProvider/SmartConfigOptions.cs @@ -1,4 +1,4 @@ -namespace Core.Configurations.SmartConfig +namespace Core.Configurations.SmartConfigProvider { /// /// Configuration options for setting up smart configuration providers. @@ -6,7 +6,7 @@ namespace Core.Configurations.SmartConfig /// public class SmartConfigOptions { - private SmartConfiguration.IConfigurationRepository _repository; + private IConfigurationRepository _repository; internal string _configKey; /// @@ -17,7 +17,7 @@ namespace Core.Configurations.SmartConfig public SmartConfigOptions UsePostgres(string configKey) { _configKey = configKey; - _repository = new Configurations.SmartConfigProvider.Repositories.PostgresConfigurationRepository(); + _repository = new Repositories.PostgresConfigurationRepository(); return this; } /// @@ -34,12 +34,12 @@ namespace Core.Configurations.SmartConfig /// /// The configuration repository to use /// The configuration options instance for method chaining - public SmartConfigOptions UseRepository(SmartConfiguration.IConfigurationRepository repository) + public SmartConfigOptions UseRepository(IConfigurationRepository repository) { _repository = repository; return this; } - internal SmartConfiguration.IConfigurationRepository GetRepository() => _repository; + internal IConfigurationRepository GetRepository() => _repository; } } \ No newline at end of file diff --git a/Core/Configurations/SmartConfigProvider/SmartConfigProvider.cs b/Core/Configurations/SmartConfigProvider/SmartConfigProvider.cs index 7f9c655..0e74746 100644 --- a/Core/Configurations/SmartConfigProvider/SmartConfigProvider.cs +++ b/Core/Configurations/SmartConfigProvider/SmartConfigProvider.cs @@ -3,7 +3,7 @@ using Core.Exceptions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Core.Configurations.SmartConfig +namespace Core.Configurations.SmartConfigProvider { /// /// Configuration provider that loads configuration from a smart configuration source (e.g. database). @@ -22,7 +22,7 @@ namespace Core.Configurations.SmartConfig string _path; IConfigurationBuilder _builder; - Newtonsoft.Json.Linq.JObject _configuration; + JObject _configuration; SmartConfigOptions _smartConfigOptions; public SmartConfigProvider() { } @@ -59,7 +59,7 @@ namespace Core.Configurations.SmartConfig using (StreamReader file = File.OpenText(_path)) using (JsonTextReader reader = new JsonTextReader(file)) { - var jsonConfiguration = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.ReadFrom(reader); + var jsonConfiguration = (JObject)JToken.ReadFrom(reader); _connectionString = jsonConfiguration.SelectToken($"ConnectionStrings.{_configKey}")?.ToString(); } @@ -77,7 +77,7 @@ namespace Core.Configurations.SmartConfig } - public Newtonsoft.Json.Linq.JObject Configuration() + public JObject Configuration() { return _configuration; } diff --git a/Core/Core.csproj b/Core/Core.csproj index 5f9b7f3..2cb5851 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -22,7 +22,6 @@ - diff --git a/Core/Logging/SeqBackgroundService.cs b/Core/Logging/SeqBackgroundService.cs new file mode 100644 index 0000000..7f7f33c --- /dev/null +++ b/Core/Logging/SeqBackgroundService.cs @@ -0,0 +1,81 @@ +using Core.Telemetry; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.Extensions.Hosting; + +namespace Core.Logging +{ + public class SeqBackgroundService : BackgroundService + { + private readonly IMessageChannel _messageChannel; + private readonly TelemetryClient _telemetryClient; + private readonly SeqLogger _seqLogger; + + public SeqBackgroundService(TelemetryClient telemetryClient, + IMessageChannel messageChannel, + SeqLogger seqlogger) + { + _telemetryClient = telemetryClient; + _messageChannel = messageChannel; + _seqLogger = seqlogger; + + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + while (!stoppingToken.IsCancellationRequested) + await foreach (var telemetry in _messageChannel.Reader.ReadAllAsync(stoppingToken)) + { + try + { + switch (telemetry) + { + case ExceptionTelemetry et: + await _seqLogger.LogAsync(et); + break; + + case TraceTelemetry et: + await _seqLogger.LogAsync(et); + break; + + case DependencyTelemetry et: + await _seqLogger.LogAsync(et); + break; + + case RequestTelemetry et: + await _seqLogger.LogAsync(et); + break; + + default: + throw new NotSupportedException(telemetry.GetType().Name); + } + } + catch + { + throw; + //_telemetryClient.TrackException(ex); this is disabled for now, we need to think about the channel structure first + } + } + } + catch (Exception ex) + { + if (ex is not OperationCanceledException) + { + _telemetryClient.TrackException(ex); + throw; + } + + _telemetryClient.TrackTrace("Service shutdown started"); + } + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + _messageChannel.Dispose(); + await base.StopAsync(cancellationToken); + } + } +} diff --git a/Core/Logging/SeqConfiguration.cs b/Core/Logging/SeqConfiguration.cs new file mode 100644 index 0000000..7c6afee --- /dev/null +++ b/Core/Logging/SeqConfiguration.cs @@ -0,0 +1,7 @@ +using Microsoft.ApplicationInsights.DataContracts; +using System.Text; + +namespace Core.Logging +{ + public record SeqConfiguration(string IngestionEndpoint, string ApiKey, string Environment); +} \ No newline at end of file diff --git a/Core/Logging/SeqHttpClient.cs b/Core/Logging/SeqHttpClient.cs new file mode 100644 index 0000000..8eb282c --- /dev/null +++ b/Core/Logging/SeqHttpClient.cs @@ -0,0 +1,29 @@ + +namespace Core.Logging +{ + public class SeqHttpClient + { + HttpClient _httpClient; + + public SeqHttpClient(SeqConfiguration seqConfiguration, HttpMessageHandler httpMessageHandler) + { + _httpClient = new HttpClient(httpMessageHandler) + { + BaseAddress = new Uri(seqConfiguration.IngestionEndpoint), + Timeout = TimeSpan.FromSeconds(30) + }; + + _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); + } + + public SeqHttpClient(SeqConfiguration seqConfiguration) : this(seqConfiguration, new HttpClientHandler()) { } + + public async Task SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken) + { + return await _httpClient.SendAsync(httpRequestMessage, cancellationToken); + } + } +} \ No newline at end of file diff --git a/Core/Logging/SeqLogger.cs b/Core/Logging/SeqLogger.cs new file mode 100644 index 0000000..c1a7564 --- /dev/null +++ b/Core/Logging/SeqLogger.cs @@ -0,0 +1,207 @@ +using Microsoft.ApplicationInsights.DataContracts; +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 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 } + }; + + foreach (var prop in trace.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } + + 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 } + }; + + 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 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); + } + + 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 } + }; + + foreach (var prop in dep.Properties) + { + seqEvent.Add(prop.Key, prop.Value); + } + + 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); + } + + 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 + }; + + var result = await _httpClient.SendAsync(requestMessage, cancellationToken); + + result.EnsureSuccessStatusCode(); + } + + 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 FormatSingleException(Exception currentEx, int depth) + { + if (depth > 0) sb.AppendLine("\n--- Inner Exception ---"); + + 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()); + + 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/Core/ModuleRegistry/SeqBackgroundServiceModule.cs b/Core/ModuleRegistry/SeqLoggingModule.cs similarity index 72% rename from Core/ModuleRegistry/SeqBackgroundServiceModule.cs rename to Core/ModuleRegistry/SeqLoggingModule.cs index ad6c77d..679467d 100644 --- a/Core/ModuleRegistry/SeqBackgroundServiceModule.cs +++ b/Core/ModuleRegistry/SeqLoggingModule.cs @@ -1,9 +1,10 @@ using Autofac; +using Core.Logging; using Core.Telemetry; namespace Core.ModuleRegistry { - public class SeqBackgroundServiceModule : Module + public class SeqLoggingModule : Module { protected override void Load(ContainerBuilder builder) { @@ -15,6 +16,13 @@ namespace Core.ModuleRegistry builder.RegisterType() .As() .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + + } } } diff --git a/Core/ModuleRegistry/TelemetryModule.cs b/Core/ModuleRegistry/TelemetryModule.cs index 5be3862..f2d15f8 100644 --- a/Core/ModuleRegistry/TelemetryModule.cs +++ b/Core/ModuleRegistry/TelemetryModule.cs @@ -44,7 +44,7 @@ namespace Core.ModuleRegistry var tmc = Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.CreateDefault(); tmc.ConnectionString = TelemetryConfig.ConnectionString; tmc.TelemetryChannel.DeveloperMode = true; - var channel = new Telemetry.DebugTelemetryChannel("C:\\logs\\telemetry.log"); + var channel = new Telemetry.SeqLoggingTelemetryChannel("C:\\logs\\telemetry.log"); tmc.TelemetryChannel = channel; diff --git a/Core/MultiKeyEncryption/MasterKey.cs b/Core/MultiKeyEncryption/MasterKey.cs index c2e40c1..d21f3a9 100644 --- a/Core/MultiKeyEncryption/MasterKey.cs +++ b/Core/MultiKeyEncryption/MasterKey.cs @@ -1,4 +1,4 @@ -namespace Core.CryptoService +namespace Core.MultiKeyEncryption { internal class MasterKey { diff --git a/Core/MultiKeyEncryption/SecureConnectionString.cs b/Core/MultiKeyEncryption/SecureConnectionString.cs index d746e3c..df4e85e 100644 --- a/Core/MultiKeyEncryption/SecureConnectionString.cs +++ b/Core/MultiKeyEncryption/SecureConnectionString.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -namespace Core.CryptoService +namespace Core.MultiKeyEncryption { public class SecureConnectionString { @@ -22,7 +22,7 @@ namespace Core.CryptoService var encryptedConnString = EncryptWithKey(connectionString, _masterKey); var userKeys = new Dictionary(); - + return new EncryptedData { diff --git a/Core/Telemetry/SeqBackgroundService.cs b/Core/Telemetry/SeqBackgroundService.cs deleted file mode 100644 index b60c8da..0000000 --- a/Core/Telemetry/SeqBackgroundService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Channel; -using Microsoft.Extensions.Hosting; -using System.Net.Http.Headers; -using System.Text; - -namespace Core.Telemetry -{ - public class SeqBackgroundService : BackgroundService - { - private readonly IMessageChannel _messageChannel; - private readonly TelemetryClient _telemetryClient; - private readonly HttpClient _httpClient; - - public SeqBackgroundService(TelemetryClient telemetryClient, - IMessageChannel messageChannel, - HttpClient httpClient) - { - _telemetryClient = telemetryClient; - _messageChannel = messageChannel; - _httpClient = httpClient; - - _httpClient = new HttpClient() - { - BaseAddress = new Uri("http://localhost:5341"), - Timeout = TimeSpan.FromSeconds(30) - }; - - _httpClient.DefaultRequestHeaders.Accept.Clear(); - _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - try - { - while (!stoppingToken.IsCancellationRequested) - await foreach (var message in _messageChannel.Reader.ReadAllAsync(stoppingToken)) - { - - try - { - var eventTelemetry = message as Microsoft.ApplicationInsights.DataContracts.EventTelemetry; - - var level = "Information"; - var seqEvent = new Dictionary - { - { "@t", DateTime.UtcNow.ToString("o") }, - { "@mt", eventTelemetry.Name }, - { "@l", level } // "Information", "Warning", "Error", etc. - }; - - foreach (var prop in eventTelemetry.Context.GlobalProperties) - { - seqEvent.Add(prop.Key, prop.Value); - } - - var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(seqEvent), Encoding.UTF8, "application/vnd.serilog.clef"); - - var key = "4XhWFtY4jJ0NBgohBAFF"; ; - //Gt8hS9ClGNfOCAdswDlW - var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"/ingest/clef?apiKey={key}"); - requestMessage.Content = content; - - var response = await _httpClient.SendAsync(requestMessage, stoppingToken); - - response.EnsureSuccessStatusCode(); - - //if (!response.IsSuccessStatusCode) - //{ - // _telemetryClient.TrackTrace($"HTTP kald fejlede med status {response.StatusCode}", Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Warning); - // continue; - //} - } - catch (Exception ex) - { - //_telemetryClient.TrackException(ex); this is disabled for now, we need to think about the channel structure first - } - } - } - catch (Exception ex) - { - if (ex is not OperationCanceledException) - { - _telemetryClient.TrackException(ex); - throw; - } - - _telemetryClient.TrackTrace("Service shutdown started"); - } - } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - _messageChannel.Dispose(); - await base.StopAsync(cancellationToken); - } - } -} diff --git a/Core/Telemetry/SeqLogger.cs b/Core/Telemetry/SeqLogger.cs deleted file mode 100644 index 4516383..0000000 --- a/Core/Telemetry/SeqLogger.cs +++ /dev/null @@ -1,185 +0,0 @@ -using Microsoft.ApplicationInsights.DataContracts; -using System.Text; -using System.Text.Json; - -namespace Core.Telemetry -{ - public record SeqConfiguration(string IngestionEndpoint, string ApiKey, string Environment); - - - public class SeqHttpClient - { - HttpClient _httpClient; - - public SeqHttpClient(SeqConfiguration seqConfiguration) - { - _httpClient = new HttpClient() - { - BaseAddress = new Uri("http://localhost:5341"), - Timeout = TimeSpan.FromSeconds(30) - }; - - _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); - } - - 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/Core/Telemetry/DebugTelemetryChannel.cs b/Core/Telemetry/SeqLoggingTelemetryChannel.cs similarity index 89% rename from Core/Telemetry/DebugTelemetryChannel.cs rename to Core/Telemetry/SeqLoggingTelemetryChannel.cs index df7a9fa..cd8575d 100644 --- a/Core/Telemetry/DebugTelemetryChannel.cs +++ b/Core/Telemetry/SeqLoggingTelemetryChannel.cs @@ -3,13 +3,13 @@ using System.Net.Http.Headers; namespace Core.Telemetry { - public class DebugTelemetryChannel : InMemoryChannel, ITelemetryChannel + public class SeqLoggingTelemetryChannel : InMemoryChannel, ITelemetryChannel { private readonly string _filePath; public ITelemetryChannel _defaultChannel; static HttpClient _client = new HttpClient(); - static DebugTelemetryChannel() + static SeqLoggingTelemetryChannel() { _client = new HttpClient() { @@ -22,7 +22,7 @@ namespace Core.Telemetry } - public DebugTelemetryChannel(string filePath) + public SeqLoggingTelemetryChannel(string filePath) { _filePath = filePath; } diff --git a/Database/Tenants/InitializeTenantData.cs b/Database/Tenants/InitializeTenantData.cs index 17bb73a..7b607a8 100644 --- a/Database/Tenants/InitializeTenantData.cs +++ b/Database/Tenants/InitializeTenantData.cs @@ -1,7 +1,7 @@ using Insight.Database; using System.Data; -namespace Database.Organizations +namespace Database.Tenants { internal class InitializeOrganizationData { diff --git a/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs index ca6d05d..20497c3 100644 --- a/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs +++ b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs @@ -1,13 +1,8 @@ -using Core.Configurations.SmartConfig; using Core.Configurations; using FluentAssertions; using Newtonsoft.Json.Linq; using Core.Configurations.JsonConfigProvider; -using Autofac; -using System.Data; -using Insight.Database; -using Npgsql; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Core.Configurations.SmartConfigProvider; namespace Tests.ConfigurationTests { diff --git a/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs b/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs index 2793e65..940c282 100644 --- a/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs +++ b/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs @@ -1,7 +1,4 @@ -using Moq; -using Newtonsoft.Json.Linq; -using Tests; -using Core.Configurations.SmartConfig; +using Newtonsoft.Json.Linq; using Core.Configurations.Common; namespace Tests.ConfigurationTests; diff --git a/Tests/ConfigurationTests/SmartConfigProviderTests.cs b/Tests/ConfigurationTests/SmartConfigProviderTests.cs index bc24771..97f735d 100644 --- a/Tests/ConfigurationTests/SmartConfigProviderTests.cs +++ b/Tests/ConfigurationTests/SmartConfigProviderTests.cs @@ -1,11 +1,11 @@ -using Core.Configurations.SmartConfig; -using Core.Configurations; +using Core.Configurations; using FluentAssertions; using Core.Configurations.JsonConfigProvider; using Autofac; using System.Data; using Insight.Database; using Npgsql; +using Core.Configurations.SmartConfigProvider; namespace Tests.ConfigurationTests { diff --git a/Tests/MessageChannelIntegrationTests.cs b/Tests/Logging/SeqBackgroundServiceTest.cs similarity index 83% rename from Tests/MessageChannelIntegrationTests.cs rename to Tests/Logging/SeqBackgroundServiceTest.cs index 4c2ca48..a7edaa8 100644 --- a/Tests/MessageChannelIntegrationTests.cs +++ b/Tests/Logging/SeqBackgroundServiceTest.cs @@ -1,17 +1,14 @@ using Autofac; -using System.Data; -using Insight.Database; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.Extensions.Logging; using Core.Telemetry; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; +using Core.Logging; -namespace Tests +namespace Tests.Logging { [TestClass] - public class MessageChannelIntegrationTests : TestFixture + public class SeqBackgroundServiceTest : TestFixture { private IMessageChannel _messageChannel; private SeqBackgroundService _service; @@ -22,10 +19,17 @@ namespace Tests { _messageChannel = new MessageChannel(); var telemetryClient = Container.Resolve(); - var httpClient = new HttpClient(new TestMessageHandler()); - _service = new SeqBackgroundService(telemetryClient, _messageChannel, httpClient); + + var config = new SeqConfiguration("http://localhost:5341", null, "MSTEST"); + + var httpClient = new SeqHttpClient(config); + var logger = new SeqLogger(httpClient, Environment.MachineName, config); + + + + _service = new SeqBackgroundService(telemetryClient, _messageChannel, logger); _cts = new CancellationTokenSource(); - } + } [TestMethod] public async Task Messages_ShouldBeProcessedFromQueue() diff --git a/Tests/Logging/SeqLoggerTests.cs b/Tests/Logging/SeqLoggerTests.cs new file mode 100644 index 0000000..513909c --- /dev/null +++ b/Tests/Logging/SeqLoggerTests.cs @@ -0,0 +1,132 @@ +using Core.Logging; +using Microsoft.ApplicationInsights.DataContracts; + +namespace Tests.Logging +{ + [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_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); + + + } + [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); + + } + [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); + } + + [TestMethod] + public async Task LogExceptionTelemetry_SendsCorrectData() + { + try + { + int t = 0; + var result = 10 / t; + + } + catch (Exception e) + { + + // Arrange + var exceptionTelemetry = new ExceptionTelemetry(e) + { + Timestamp = DateTimeOffset.UtcNow + }; + exceptionTelemetry.Properties.Add("TestId", _testId); + + // 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); + + // 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); + + // Act + await _logger.LogAsync(requestTelemetry); + } + } +} diff --git a/Tests/TelemetryLogging/SeqLoggerTests.cs b/Tests/TelemetryLogging/SeqLoggerTests.cs deleted file mode 100644 index 8ee1d03..0000000 --- a/Tests/TelemetryLogging/SeqLoggerTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -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); - } - } -} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 0d693a6..ab08f19 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -27,7 +27,10 @@ - + + Always + + Always diff --git a/Tests/appconfiguration.dev.json b/Tests/xappconfiguration.dev.json similarity index 100% rename from Tests/appconfiguration.dev.json rename to Tests/xappconfiguration.dev.json