Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
0f1a345216
28 changed files with 509 additions and 444 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Application;
|
||||||
using Autofac.Extensions.DependencyInjection;
|
using Autofac.Extensions.DependencyInjection;
|
||||||
|
|
||||||
var host = Host.CreateDefaultBuilder(args)
|
var host = Host.CreateDefaultBuilder(args)
|
||||||
|
|
@ -6,7 +7,7 @@ var host = Host.CreateDefaultBuilder(args)
|
||||||
{
|
{
|
||||||
webHostBuilder
|
webHostBuilder
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseStartup<PlanTempus.Startup>();
|
.UseStartup<Startup>();
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
using Core.Configurations.JsonConfigProvider;
|
using Core.Configurations.JsonConfigProvider;
|
||||||
using Core.Configurations;
|
using Core.Configurations;
|
||||||
|
|
||||||
namespace PlanTempus
|
namespace Application
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +28,7 @@ namespace PlanTempus
|
||||||
|
|
||||||
services.Configure<Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions>(options =>
|
services.Configure<Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions>(options =>
|
||||||
{
|
{
|
||||||
options.ViewLocationExpanders.Add(new Application.Common.ComponentsViewLocationExpander());
|
options.ViewLocationExpanders.Add(new Common.ComponentsViewLocationExpander());
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using Core.Configurations.SmartConfigProvider;
|
namespace Core.Configurations.SmartConfigProvider;
|
||||||
|
|
||||||
namespace Core.Configurations.SmartConfiguration;
|
|
||||||
public interface IConfigurationRepository
|
public interface IConfigurationRepository
|
||||||
{
|
{
|
||||||
string ConnectionString { get; set; }
|
string ConnectionString { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Core.Configurations.SmartConfiguration;
|
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
|
|
||||||
namespace Core.Configurations.SmartConfigProvider.Repositories;
|
namespace Core.Configurations.SmartConfigProvider.Repositories;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Core.Configurations.SmartConfig
|
namespace Core.Configurations.SmartConfigProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension methods for adding smart configuration providers to IConfigurationBuilder.
|
/// Extension methods for adding smart configuration providers to IConfigurationBuilder.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Core.Configurations.SmartConfig
|
namespace Core.Configurations.SmartConfigProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration options for setting up smart configuration providers.
|
/// Configuration options for setting up smart configuration providers.
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Core.Configurations.SmartConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SmartConfigOptions
|
public class SmartConfigOptions
|
||||||
{
|
{
|
||||||
private SmartConfiguration.IConfigurationRepository _repository;
|
private IConfigurationRepository _repository;
|
||||||
internal string _configKey;
|
internal string _configKey;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Core.Configurations.SmartConfig
|
||||||
public SmartConfigOptions UsePostgres(string configKey)
|
public SmartConfigOptions UsePostgres(string configKey)
|
||||||
{
|
{
|
||||||
_configKey = configKey;
|
_configKey = configKey;
|
||||||
_repository = new Configurations.SmartConfigProvider.Repositories.PostgresConfigurationRepository();
|
_repository = new Repositories.PostgresConfigurationRepository();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -34,12 +34,12 @@ namespace Core.Configurations.SmartConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="repository">The configuration repository to use</param>
|
/// <param name="repository">The configuration repository to use</param>
|
||||||
/// <returns>The configuration options instance for method chaining</returns>
|
/// <returns>The configuration options instance for method chaining</returns>
|
||||||
public SmartConfigOptions UseRepository(SmartConfiguration.IConfigurationRepository repository)
|
public SmartConfigOptions UseRepository(IConfigurationRepository repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal SmartConfiguration.IConfigurationRepository GetRepository() => _repository;
|
internal IConfigurationRepository GetRepository() => _repository;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ using Core.Exceptions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Core.Configurations.SmartConfig
|
namespace Core.Configurations.SmartConfigProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration provider that loads configuration from a smart configuration source (e.g. database).
|
/// Configuration provider that loads configuration from a smart configuration source (e.g. database).
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Core.Configurations.SmartConfig
|
||||||
string _path;
|
string _path;
|
||||||
IConfigurationBuilder _builder;
|
IConfigurationBuilder _builder;
|
||||||
|
|
||||||
Newtonsoft.Json.Linq.JObject _configuration;
|
JObject _configuration;
|
||||||
SmartConfigOptions _smartConfigOptions;
|
SmartConfigOptions _smartConfigOptions;
|
||||||
|
|
||||||
public SmartConfigProvider() { }
|
public SmartConfigProvider() { }
|
||||||
|
|
@ -59,7 +59,7 @@ namespace Core.Configurations.SmartConfig
|
||||||
using (StreamReader file = File.OpenText(_path))
|
using (StreamReader file = File.OpenText(_path))
|
||||||
using (JsonTextReader reader = new JsonTextReader(file))
|
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();
|
_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;
|
return _configuration;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Configurations\AzureAppConfigurationProvider\" />
|
<Folder Include="Configurations\AzureAppConfigurationProvider\" />
|
||||||
<Folder Include="Configurations\PostgresqlConfigurationBuilder\" />
|
<Folder Include="Configurations\PostgresqlConfigurationBuilder\" />
|
||||||
<Folder Include="Logging\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
81
Core/Logging/SeqBackgroundService.cs
Normal file
81
Core/Logging/SeqBackgroundService.cs
Normal file
|
|
@ -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<ITelemetry> _messageChannel;
|
||||||
|
private readonly TelemetryClient _telemetryClient;
|
||||||
|
private readonly SeqLogger _seqLogger;
|
||||||
|
|
||||||
|
public SeqBackgroundService(TelemetryClient telemetryClient,
|
||||||
|
IMessageChannel<ITelemetry> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Core/Logging/SeqConfiguration.cs
Normal file
7
Core/Logging/SeqConfiguration.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using Microsoft.ApplicationInsights.DataContracts;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Core.Logging
|
||||||
|
{
|
||||||
|
public record SeqConfiguration(string IngestionEndpoint, string ApiKey, string Environment);
|
||||||
|
}
|
||||||
29
Core/Logging/SeqHttpClient.cs
Normal file
29
Core/Logging/SeqHttpClient.cs
Normal file
|
|
@ -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<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
Core/Logging/SeqLogger.cs
Normal file
207
Core/Logging/SeqLogger.cs
Normal file
|
|
@ -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<string, object>
|
||||||
|
{
|
||||||
|
{ "@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<string, object>
|
||||||
|
{
|
||||||
|
{ "@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<string, object>
|
||||||
|
{
|
||||||
|
{ "@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<string, object>
|
||||||
|
{
|
||||||
|
{ "@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<string, object>
|
||||||
|
{
|
||||||
|
{ "@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<string, object> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
using Autofac;
|
using Autofac;
|
||||||
|
using Core.Logging;
|
||||||
using Core.Telemetry;
|
using Core.Telemetry;
|
||||||
|
|
||||||
namespace Core.ModuleRegistry
|
namespace Core.ModuleRegistry
|
||||||
{
|
{
|
||||||
public class SeqBackgroundServiceModule : Module
|
public class SeqLoggingModule : Module
|
||||||
{
|
{
|
||||||
protected override void Load(ContainerBuilder builder)
|
protected override void Load(ContainerBuilder builder)
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +16,13 @@ namespace Core.ModuleRegistry
|
||||||
builder.RegisterType<SeqBackgroundService>()
|
builder.RegisterType<SeqBackgroundService>()
|
||||||
.As<Microsoft.Extensions.Hosting.IHostedService>()
|
.As<Microsoft.Extensions.Hosting.IHostedService>()
|
||||||
.SingleInstance();
|
.SingleInstance();
|
||||||
|
|
||||||
|
builder.RegisterType<SeqHttpClient>()
|
||||||
|
.As<SeqHttpClient>()
|
||||||
|
.SingleInstance();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Core.ModuleRegistry
|
||||||
var tmc = Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.CreateDefault();
|
var tmc = Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.CreateDefault();
|
||||||
tmc.ConnectionString = TelemetryConfig.ConnectionString;
|
tmc.ConnectionString = TelemetryConfig.ConnectionString;
|
||||||
tmc.TelemetryChannel.DeveloperMode = true;
|
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;
|
tmc.TelemetryChannel = channel;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Core.CryptoService
|
namespace Core.MultiKeyEncryption
|
||||||
{
|
{
|
||||||
internal class MasterKey
|
internal class MasterKey
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Core.CryptoService
|
namespace Core.MultiKeyEncryption
|
||||||
{
|
{
|
||||||
public class SecureConnectionString
|
public class SecureConnectionString
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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<ITelemetry> _messageChannel;
|
|
||||||
private readonly TelemetryClient _telemetryClient;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public SeqBackgroundService(TelemetryClient telemetryClient,
|
|
||||||
IMessageChannel<ITelemetry> 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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<HttpResponseMessage> 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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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<string, object>
|
|
||||||
{
|
|
||||||
{ "@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<string, object> 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"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,13 +3,13 @@ using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Core.Telemetry
|
namespace Core.Telemetry
|
||||||
{
|
{
|
||||||
public class DebugTelemetryChannel : InMemoryChannel, ITelemetryChannel
|
public class SeqLoggingTelemetryChannel : InMemoryChannel, ITelemetryChannel
|
||||||
{
|
{
|
||||||
private readonly string _filePath;
|
private readonly string _filePath;
|
||||||
public ITelemetryChannel _defaultChannel;
|
public ITelemetryChannel _defaultChannel;
|
||||||
static HttpClient _client = new HttpClient();
|
static HttpClient _client = new HttpClient();
|
||||||
|
|
||||||
static DebugTelemetryChannel()
|
static SeqLoggingTelemetryChannel()
|
||||||
{
|
{
|
||||||
_client = new HttpClient()
|
_client = new HttpClient()
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Core.Telemetry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DebugTelemetryChannel(string filePath)
|
public SeqLoggingTelemetryChannel(string filePath)
|
||||||
{
|
{
|
||||||
_filePath = filePath;
|
_filePath = filePath;
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
namespace Database.Organizations
|
namespace Database.Tenants
|
||||||
{
|
{
|
||||||
internal class InitializeOrganizationData
|
internal class InitializeOrganizationData
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
using Core.Configurations.SmartConfig;
|
|
||||||
using Core.Configurations;
|
using Core.Configurations;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Core.Configurations.JsonConfigProvider;
|
using Core.Configurations.JsonConfigProvider;
|
||||||
using Autofac;
|
using Core.Configurations.SmartConfigProvider;
|
||||||
using System.Data;
|
|
||||||
using Insight.Database;
|
|
||||||
using Npgsql;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
namespace Tests.ConfigurationTests
|
namespace Tests.ConfigurationTests
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using Moq;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Tests;
|
|
||||||
using Core.Configurations.SmartConfig;
|
|
||||||
using Core.Configurations.Common;
|
using Core.Configurations.Common;
|
||||||
|
|
||||||
namespace Tests.ConfigurationTests;
|
namespace Tests.ConfigurationTests;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using Core.Configurations.SmartConfig;
|
using Core.Configurations;
|
||||||
using Core.Configurations;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Core.Configurations.JsonConfigProvider;
|
using Core.Configurations.JsonConfigProvider;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
|
using Core.Configurations.SmartConfigProvider;
|
||||||
|
|
||||||
namespace Tests.ConfigurationTests
|
namespace Tests.ConfigurationTests
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using System.Data;
|
|
||||||
using Insight.Database;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Core.Telemetry;
|
using Core.Telemetry;
|
||||||
using Microsoft.ApplicationInsights;
|
using Microsoft.ApplicationInsights;
|
||||||
using Microsoft.ApplicationInsights.Channel;
|
using Microsoft.ApplicationInsights.Channel;
|
||||||
using Microsoft.ApplicationInsights.DataContracts;
|
using Microsoft.ApplicationInsights.DataContracts;
|
||||||
|
using Core.Logging;
|
||||||
|
|
||||||
namespace Tests
|
namespace Tests.Logging
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class MessageChannelIntegrationTests : TestFixture
|
public class SeqBackgroundServiceTest : TestFixture
|
||||||
{
|
{
|
||||||
private IMessageChannel<ITelemetry> _messageChannel;
|
private IMessageChannel<ITelemetry> _messageChannel;
|
||||||
private SeqBackgroundService _service;
|
private SeqBackgroundService _service;
|
||||||
|
|
@ -22,10 +19,17 @@ namespace Tests
|
||||||
{
|
{
|
||||||
_messageChannel = new MessageChannel();
|
_messageChannel = new MessageChannel();
|
||||||
var telemetryClient = Container.Resolve<TelemetryClient>();
|
var telemetryClient = Container.Resolve<TelemetryClient>();
|
||||||
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();
|
_cts = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Messages_ShouldBeProcessedFromQueue()
|
public async Task Messages_ShouldBeProcessedFromQueue()
|
||||||
132
Tests/Logging/SeqLoggerTests.cs
Normal file
132
Tests/Logging/SeqLoggerTests.cs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,10 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="appconfiguration.dev.json">
|
<None Update="xappconfiguration.dev.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="ConfigurationTests\appconfiguration.dev.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="ConfigurationTests\appconfiguration.dev.json">
|
<None Update="ConfigurationTests\appconfiguration.dev.json">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue