diff --git a/Core/Logging/SeqBackgroundService.cs b/Core/Logging/SeqBackgroundService.cs index d7d8f3b..07e8392 100644 --- a/Core/Logging/SeqBackgroundService.cs +++ b/Core/Logging/SeqBackgroundService.cs @@ -19,6 +19,9 @@ namespace PlanTempus.Core.Logging _telemetryClient = telemetryClient; _messageChannel = messageChannel; _seqLogger = seqlogger; + + _telemetryClient.TrackTrace("SeqBackgroundService started"); + } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -72,12 +75,12 @@ namespace PlanTempus.Core.Logging throw; } - _telemetryClient.TrackTrace("Service shutdown started"); } } public override async Task StopAsync(CancellationToken cancellationToken) { + _telemetryClient.TrackTrace("StopAsync called: Service shutdown started"); _messageChannel.Dispose(); await base.StopAsync(cancellationToken); } diff --git a/Core/ModuleRegistry/SeqLoggingModule.cs b/Core/ModuleRegistry/SeqLoggingModule.cs index 5e52188..fae509c 100644 --- a/Core/ModuleRegistry/SeqLoggingModule.cs +++ b/Core/ModuleRegistry/SeqLoggingModule.cs @@ -1,32 +1,31 @@ using Autofac; using PlanTempus.Core.Logging; -using PlanTempus.Core.Telemetry; namespace PlanTempus.Core.ModuleRegistry { - public class SeqLoggingModule : Module - { - public required SeqConfiguration SeqConfiguration { get; set; } + public class SeqLoggingModule : Module + { + public required SeqConfiguration SeqConfiguration { get; set; } - protected override void Load(ContainerBuilder builder) - { + protected override void Load(ContainerBuilder builder) + { - //builder.RegisterType() - // .As>() - // .SingleInstance(); + //builder.RegisterType() + // .As>() + // .SingleInstance(); - builder.RegisterType() - .As() - .SingleInstance(); + builder.RegisterType() + //.As() + .SingleInstance(); - builder.RegisterGeneric(typeof(SeqLogger<>)); + builder.RegisterGeneric(typeof(SeqLogger<>)); - builder.RegisterInstance(SeqConfiguration); + builder.RegisterInstance(SeqConfiguration); - builder.RegisterType() - .As() - .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); - } - } + } + } } diff --git a/Core/ModuleRegistry/TelemetryModule.cs b/Core/ModuleRegistry/TelemetryModule.cs index 059182a..b8849f4 100644 --- a/Core/ModuleRegistry/TelemetryModule.cs +++ b/Core/ModuleRegistry/TelemetryModule.cs @@ -15,6 +15,14 @@ namespace PlanTempus.Core.ModuleRegistry configuration.ConnectionString = TelemetryConfig.ConnectionString; configuration.TelemetryChannel.DeveloperMode = true; + var client = new Microsoft.ApplicationInsights.TelemetryClient(configuration); + client.Context.GlobalProperties["Application"] = GetType().Namespace.Split('.')[0]; + client.Context.GlobalProperties["MachineName"] = Environment.MachineName; + client.Context.GlobalProperties["Version"] = Environment.Version.ToString(); + client.Context.GlobalProperties["ProcessorCount"] = Environment.ProcessorCount.ToString(); + + builder.Register(c => client).InstancePerLifetimeScope(); + if (TelemetryConfig.UseSeqLoggingTelemetryChannel) { var messageChannel = new Telemetry.MessageChannel(); @@ -23,21 +31,13 @@ namespace PlanTempus.Core.ModuleRegistry .As>() .SingleInstance(); - configuration.TelemetryChannel = new Telemetry.SeqLoggingTelemetryChannel(messageChannel); + configuration.TelemetryChannel = new Telemetry.SeqTelemetryChannel(messageChannel, client); } var telemetryProcessorChain = new Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryProcessorChainBuilder(configuration); telemetryProcessorChain.Use(next => new Telemetry.Enrichers.EnrichWithMetaTelemetry(next)); telemetryProcessorChain.Build(); - - var client = new Microsoft.ApplicationInsights.TelemetryClient(configuration); - client.Context.GlobalProperties["Application"] = GetType().Namespace.Split('.')[0]; - client.Context.GlobalProperties["MachineName"] = Environment.MachineName; - client.Context.GlobalProperties["Version"] = Environment.Version.ToString(); - client.Context.GlobalProperties["ProcessorCount"] = Environment.ProcessorCount.ToString(); - - builder.Register(c => client).InstancePerLifetimeScope(); } } diff --git a/Core/Telemetry/SeqLoggingTelemetryChannel.cs b/Core/Telemetry/SeqLoggingTelemetryChannel.cs deleted file mode 100644 index 5a262a6..0000000 --- a/Core/Telemetry/SeqLoggingTelemetryChannel.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.ApplicationInsights.Channel; - -namespace PlanTempus.Core.Telemetry -{ - public class SeqLoggingTelemetryChannel : InMemoryChannel, ITelemetryChannel - { - private readonly IMessageChannel _messageChannel; - - public SeqLoggingTelemetryChannel(IMessageChannel messageChannel) - { - _messageChannel = messageChannel; - } - public new void Send(ITelemetry telemetry) - { - var writeTask = _messageChannel.Writer.WriteAsync(telemetry).AsTask(); - writeTask.ContinueWith(t => - { - if (t.Exception != null) - { - throw t.Exception; - } - }, TaskContinuationOptions.OnlyOnFaulted); - - base.Send(telemetry); - } - } -} diff --git a/Core/Telemetry/SeqTelemetryChannel.cs b/Core/Telemetry/SeqTelemetryChannel.cs new file mode 100644 index 0000000..f65e260 --- /dev/null +++ b/Core/Telemetry/SeqTelemetryChannel.cs @@ -0,0 +1,37 @@ +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Channel; + +namespace PlanTempus.Core.Telemetry +{ + public class SeqTelemetryChannel : InMemoryChannel, ITelemetryChannel + { + private readonly IMessageChannel _messageChannel; + private readonly TelemetryClient _telemetryClient; + + public SeqTelemetryChannel(IMessageChannel messageChannel, TelemetryClient telemetryClient) + { + _messageChannel = messageChannel; + _telemetryClient = telemetryClient; + } + public new void Send(ITelemetry telemetry) + { + if (telemetry.Context.GlobalProperties["OmitSeqTelemetryChannel"] != "true") + try + { + var writeTask = _messageChannel.Writer.WriteAsync(telemetry).AsTask(); + writeTask.ContinueWith(t => + { + if (t.Exception != null) + { + throw t.Exception; + } + }, TaskContinuationOptions.OnlyOnFaulted); + } + catch (Exception e) + { + _telemetryClient.TrackException(e, new Dictionary { { "OmitSeqTelemetryChannel", "true" } }); + } + base.Send(telemetry); + } + } +} diff --git a/Tests/Logging/SeqBackgroundServiceTest.cs b/Tests/Logging/SeqBackgroundServiceTest.cs index 22f5772..29e8da1 100644 --- a/Tests/Logging/SeqBackgroundServiceTest.cs +++ b/Tests/Logging/SeqBackgroundServiceTest.cs @@ -32,9 +32,7 @@ namespace PlanTempus.Tests.Logging [TestMethod] public async Task Messages_ShouldBeProcessedFromQueue() { - var processedMessages = new List(); - - var serviceTask = _service.StartAsync(_cts.Token); + await _service.StartAsync(_cts.Token); for (int i = 0; i < 5; i++) { diff --git a/Tests/Logging/SeqLoggerTests.cs b/Tests/Logging/SeqLoggerTests.cs index 23f3893..0842851 100644 --- a/Tests/Logging/SeqLoggerTests.cs +++ b/Tests/Logging/SeqLoggerTests.cs @@ -112,6 +112,10 @@ namespace PlanTempus.Tests.Logging await _logger.LogAsync(dependencyTelemetry); } + /// + /// This is for scope test in SeqLogger. It is not testing anything related to the TelemetryChannel which logs to Seq. + /// + /// [TestMethod] public async Task LogRequestTelemetryInOperationHolderWithParentChild_SendsCorrectData() { diff --git a/Tests/Logging/SeqTelemetryChannelTest.cs b/Tests/Logging/SeqTelemetryChannelTest.cs new file mode 100644 index 0000000..5ff67e0 --- /dev/null +++ b/Tests/Logging/SeqTelemetryChannelTest.cs @@ -0,0 +1,70 @@ +using Autofac; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using PlanTempus.Core.Logging; +using PlanTempus.Core.Telemetry; + +namespace PlanTempus.Tests.Logging +{ + [TestClass] + public class SeqTelemetryChannelTest : TestFixture + { + private IMessageChannel _messageChannel; + TelemetryClient _telemetryClient; + private SeqBackgroundService _service; + private CancellationTokenSource _cts; + + [TestInitialize] + public void SetupThis() + { + //it is important to use the same MessageChannel as the BackgroundService uses + //we know that IMessageChannel _messageChannel; is registered via Autofac and manually injected into SeqBackgroundService + //so we can get it by calling the Autofac Container in this test. + + _messageChannel = Container.Resolve>(); + _service = Container.Resolve(); + _telemetryClient = Container.Resolve(); + + _cts = new CancellationTokenSource(); + } + + [TestMethod] + public async Task Messages_ShouldBeProcessedFromQueue() + { + await _service.StartAsync(_cts.Token); + + for (int i = 0; i < 5; i++) + { + var eventTelemetry = new EventTelemetry + { + Name = "Test Event 3", + Timestamp = DateTimeOffset.UtcNow + }; + + eventTelemetry.Properties.Add("TestId", Guid.NewGuid().ToString()); + eventTelemetry.Metrics.Add("TestMetric", 42.0); + + //we don't write to the _messageChannel.Writer.WriteAsync(eventTelemetry);, but the TelemetryClient which is configured to use SeqTelemetryChannel + _telemetryClient.TrackEvent(eventTelemetry); + } + + // wait for processing + await Task.Delay(5000); + + //_cts.Cancel(); + await _service.StopAsync(CancellationToken.None); + + + bool hasMoreMessages = await _messageChannel.Reader.WaitToReadAsync(); + Assert.IsFalse(hasMoreMessages, "Queue should be empty after 5 seconds"); + } + + + [TestCleanup] + public void Cleanup() + { + _cts?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Tests/TestFixture.cs b/Tests/TestFixture.cs index af76a76..7d6b103 100644 --- a/Tests/TestFixture.cs +++ b/Tests/TestFixture.cs @@ -8,84 +8,89 @@ using PlanTempus.Core.Configurations.JsonConfigProvider; using PlanTempus.Core.ModuleRegistry; namespace PlanTempus.Tests { - /// - /// Act as base class for tests. Avoids duplication of test setup code - /// - [TestClass] - public abstract partial class TestFixture - { - private readonly string _configurationFilePath; + /// + /// Act as base class for tests. Avoids duplication of test setup code + /// + [TestClass] + public abstract partial class TestFixture + { + private readonly string _configurationFilePath; - protected IContainer Container { get; private set; } - protected ContainerBuilder ContainerBuilder { get; private set; } + protected IContainer Container { get; private set; } + protected ContainerBuilder ContainerBuilder { get; private set; } - public virtual IConfigurationRoot Configuration() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile($"{_configurationFilePath}appconfiguration.dev.json") - .Build(); + public virtual IConfigurationRoot Configuration() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile($"{_configurationFilePath}appconfiguration.dev.json") + .Build(); - return configuration; - } + return configuration; + } - protected TestFixture() : this(null) { } - public TestFixture(string configurationFilePath) - { - if (configurationFilePath is not null) - _configurationFilePath = configurationFilePath?.TrimEnd('/') + "/"; + protected TestFixture() : this(null) { } + public TestFixture(string configurationFilePath) + { + if (configurationFilePath is not null) + _configurationFilePath = configurationFilePath?.TrimEnd('/') + "/"; - CreateContainerBuilder(); - Container = ContainerBuilder.Build(); - } - protected virtual void CreateContainerBuilder() - { - IConfigurationRoot configuration = Configuration(); + CreateContainerBuilder(); + Container = ContainerBuilder.Build(); + } + protected virtual void CreateContainerBuilder() + { + IConfigurationRoot configuration = Configuration(); - //var logger = new LoggerConfiguration() - // .MinimumLevel.Verbose() - // .MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning) - // .MinimumLevel.Override("System", Serilog.Events.LogEventLevel.Error) - // .WriteTo.Seq("http://localhost:5341", apiKey: "Gt8hS9ClGNfOCAdswDlW") - // .WriteTo.ApplicationInsights(configuration.Get("ApplicationInsights:ConnectionString"), - // TelemetryConverter.Traces) - // .Enrich.FromLogContext() - // .Enrich.WithMachineName() - // .CreateLogger(); + //var logger = new LoggerConfiguration() + // .MinimumLevel.Verbose() + // .MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning) + // .MinimumLevel.Override("System", Serilog.Events.LogEventLevel.Error) + // .WriteTo.Seq("http://localhost:5341", apiKey: "Gt8hS9ClGNfOCAdswDlW") + // .WriteTo.ApplicationInsights(configuration.Get("ApplicationInsights:ConnectionString"), + // TelemetryConverter.Traces) + // .Enrich.FromLogContext() + // .Enrich.WithMachineName() + // .CreateLogger(); - var builder = new ContainerBuilder(); + var builder = new ContainerBuilder(); - builder.RegisterGeneric(typeof(Logger<>)) - .As(typeof(ILogger<>)) - .SingleInstance(); + builder.RegisterGeneric(typeof(Logger<>)) + .As(typeof(ILogger<>)) + .SingleInstance(); - builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule - { - ConnectionString = configuration.GetConnectionString("DefaultConnection") - }); + builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule + { + ConnectionString = configuration.GetConnectionString("DefaultConnection") + }); - builder.RegisterModule(new TelemetryModule - { - TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() - }); + builder.RegisterModule(new TelemetryModule + { + TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() + }); + builder.RegisterModule(new SeqLoggingModule + { + SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject() + + }); - ContainerBuilder = builder; - } + ContainerBuilder = builder; + } - [TestCleanup] - public void CleanUp() - { - Trace.Flush(); - var telemetryClient = Container.Resolve(); - telemetryClient.Flush(); + [TestCleanup] + public void CleanUp() + { + Trace.Flush(); + var telemetryClient = Container.Resolve(); + telemetryClient.Flush(); - if (Container != null) - { - Container.Dispose(); - Container = null; - } - } + if (Container != null) + { + Container.Dispose(); + Container = null; + } + } - } + } } \ No newline at end of file