From 018e14bffa5127722775d2fdb67985925f22576c Mon Sep 17 00:00:00 2001 From: Osric Wilkinson <137781419+osric-ukho@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:22:25 +0000 Subject: [PATCH] A first pass at implementing logging scopes --- .../EventHubLogger.cs | 56 +++++++++++++++---- .../EventHubLoggerTests.cs | 36 +++++++++--- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/UKHO.Logging.EventHubLogProvider/EventHubLogger.cs b/UKHO.Logging.EventHubLogProvider/EventHubLogger.cs index 1d5ce7b..c2ed680 100644 --- a/UKHO.Logging.EventHubLogProvider/EventHubLogger.cs +++ b/UKHO.Logging.EventHubLogProvider/EventHubLogger.cs @@ -17,25 +17,23 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; -using UKHO.Logging.EventHubLogProvider.AzureStorageEventLogging.Interfaces; - namespace UKHO.Logging.EventHubLogProvider { internal class EventHubLogger : ILogger { private const string OriginalFormatPropertyName = "{OriginalFormat}"; + private readonly Action> additionalValuesProvider; + private readonly string categoryName; + private readonly string environment; private readonly IEventHubLog eventHubLog; private readonly LogLevel minimumLogLevel; - private readonly string environment; - private readonly string categoryName; - private readonly string system; - private readonly string service; private readonly string nodeName; - private readonly Action> additionalValuesProvider; + private readonly List scopes; + private readonly string service; + private readonly string system; internal EventHubLogger(IEventHubLog eventHubLog, string categoryName, @@ -48,6 +46,7 @@ internal EventHubLogger(IEventHubLog eventHubLog, system = options.System; service = options.Service; nodeName = options.NodeName; + scopes = new List(); additionalValuesProvider = options.AdditionalValuesProvider; } @@ -73,6 +72,26 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except result["LoggingErrorException"] = e; } + // Allow scopes to override additional values + foreach (var scope in scopes) + { + switch (scope) + { + case KeyValuePair kv: + result[kv.Key] = kv.Value; + break; + case IEnumerable> list: + { + foreach (var kv in list) + { + result[kv.Key] = kv.Value; + } + + break; + } + } + } + var messageTemplate = ""; if (state is IEnumerable> structure) { @@ -130,10 +149,27 @@ public bool IsEnabled(LogLevel logLevel) return logLevel >= minimumLogLevel; } - [ExcludeFromCodeCoverage] // nothing to test public IDisposable BeginScope(TState state) { - return new EmptyDisposable(); + scopes.Add(state); + return new Scope(scopes, state); + } + + private class Scope : IDisposable + { + private readonly List scopes; + private readonly TState state; + + public Scope(List scopes, TState state) + { + this.scopes = scopes; + this.state = state; + } + + public void Dispose() + { + scopes.Remove(state); + } } } } \ No newline at end of file diff --git a/UKHO.Logging.EventHubLogProviderTest/EventHubLoggerTests.cs b/UKHO.Logging.EventHubLogProviderTest/EventHubLoggerTests.cs index 9437a21..7c0a403 100644 --- a/UKHO.Logging.EventHubLogProviderTest/EventHubLoggerTests.cs +++ b/UKHO.Logging.EventHubLogProviderTest/EventHubLoggerTests.cs @@ -19,9 +19,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; + using FakeItEasy; + using Microsoft.Extensions.Logging; + using NUnit.Framework; + using UKHO.Logging.EventHubLogProvider; namespace UKHO.Logging.EventHubLogProviderTest @@ -234,10 +238,10 @@ public void TestLoggerLogsAllTheIndividualParamsForStructuredDataUsingFormattedL var formattedLogEntry = "Message with {Property1} and {Property2} and a escaped C# keyword name {@var}"; var structuredData = new Dictionary { - {"Property1", "Value 1" }, - {"Property2", "Value 2" }, - {"@var", "Var value" }, - {"{OriginalFormat}", formattedLogEntry }, + { "Property1", "Value 1" }, + { "Property2", "Value 2" }, + { "@var", "Var value" }, + { "{OriginalFormat}", formattedLogEntry }, }; eventHubLogger.Log(LogLevel.Information, 123, structuredData, null, (s, e) => string.Join(",", s.Select(kv => $"{kv.Key}:{kv.Value}"))); @@ -259,9 +263,9 @@ public void TestLoggerLogsAllTheIndividualParamsForStructuredDataUsingFormattedL var formattedLogEntry = "Message with {Property1} and {Property1} and a escaped C# keyword name {@var}"; var structuredData = new Dictionary { - {"Property1", new[] { "Value 1", "Value 2" } }, - {"@var", "Var value" }, - {"{OriginalFormat}", formattedLogEntry }, + { "Property1", new[] { "Value 1", "Value 2" } }, + { "@var", "Var value" }, + { "{OriginalFormat}", formattedLogEntry }, }; eventHubLogger.Log(LogLevel.Information, 123, structuredData, null, (s, e) => string.Join(",", s.Select(kv => $"{kv.Key}:{kv.Value}"))); @@ -272,6 +276,24 @@ public void TestLoggerLogsAllTheIndividualParamsForStructuredDataUsingFormattedL Assert.AreEqual("Message with {Property1} and {Property1} and a escaped C# keyword name {@var}", loggedEntry.MessageTemplate); } + [Test] + public void TestLoggerLogsKeyValueScope() + { + LogEntry loggedEntry = null; + A.CallTo(() => fakeEventHubLog.Log(A.Ignored)).Invokes((LogEntry l) => loggedEntry = l); + + var eventHubLogger = CreateTestEventHubLogger(LogLevel.Information, LogLevel.Information, "UKHO.TestClass", fakeEventHubLog); + + var scope = new KeyValuePair("Test Scope Key", "Test Scope Value"); + + using (eventHubLogger.BeginScope(scope)) + { + eventHubLogger.Log(LogLevel.Information, 123, "Simple Message", null, (s, e) => s); + } + + Assert.Contains(scope, loggedEntry.LogProperties); + } + [Test] public void TestLoggerLogsRequiredEnvironmentParameters() {