diff --git a/src/Correlate.AspNetCore/AspNetCore/CorrelateOptions.cs b/src/Correlate.AspNetCore/AspNetCore/CorrelateOptions.cs index 43336c3..3c09a48 100644 --- a/src/Correlate.AspNetCore/AspNetCore/CorrelateOptions.cs +++ b/src/Correlate.AspNetCore/AspNetCore/CorrelateOptions.cs @@ -5,7 +5,7 @@ namespace Correlate.AspNetCore; /// /// Options for handling correlation id on incoming requests. /// -public sealed class CorrelateOptions +public sealed class CorrelateOptions: CorrelationManagerOptions { private static readonly string[] DefaultRequestHeaders = { CorrelationHttpHeaders.CorrelationId }; diff --git a/src/Correlate.AspNetCore/DependencyInjection/ServiceCollectionExtensions.cs b/src/Correlate.AspNetCore/DependencyInjection/ServiceCollectionExtensions.cs index ebf374f..08c69bf 100644 --- a/src/Correlate.AspNetCore/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Correlate.AspNetCore/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Correlate.AspNetCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Correlate.DependencyInjection; @@ -16,8 +17,18 @@ public static class ServiceCollectionExtensions /// The so that additional calls can be chained. public static IServiceCollection AddCorrelate(this IServiceCollection services, Action configureOptions) { - return services - .Configure(configureOptions) + services + .Configure(configureOptions); + + services.AddOptions() + .Configure((CorrelationManagerOptions cmo, IOptions co) => + { + cmo.LoggingScopeKey = co.Value.LoggingScopeKey; + }); + + services .AddCorrelate(); + + return services; } } diff --git a/src/Correlate.Core/CorrelationManager.cs b/src/Correlate.Core/CorrelationManager.cs index 8dfa3f8..6bffe69 100644 --- a/src/Correlate.Core/CorrelationManager.cs +++ b/src/Correlate.Core/CorrelationManager.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Correlate; @@ -13,6 +14,7 @@ public class CorrelationManager : IAsyncCorrelationManager, ICorrelationManager, private readonly ICorrelationIdFactory _correlationIdFactory; private readonly DiagnosticListener? _diagnosticListener; private readonly ILogger _logger; + private readonly CorrelationManagerOptions _options = new CorrelationManagerOptions(); /// /// Initializes a new instance of the class. @@ -55,13 +57,35 @@ DiagnosticListener diagnosticListener _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); } + /// + /// Initializes a new instance of the class. + /// + /// The correlation context factory used to create new contexts. + /// The correlation id factory used to generate a new correlation id per context. + /// The correlation context accessor. + /// The logger. + /// The diagnostics listener to run activities on. + /// The configuration options. + public CorrelationManager + ( + ICorrelationContextFactory correlationContextFactory, + ICorrelationIdFactory correlationIdFactory, + ICorrelationContextAccessor correlationContextAccessor, + ILogger logger, + DiagnosticListener diagnosticListener, + IOptions options + ) : this(correlationContextFactory, correlationIdFactory, correlationContextAccessor, logger, diagnosticListener) + { + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + } + /// /// Creates a new activity that can be started and stopped manually. /// /// The correlated activity. public IActivity CreateActivity() { - return new RootActivity(_correlationContextFactory, _logger, _diagnosticListener); + return new RootActivity(_correlationContextFactory, _logger, _diagnosticListener, _options); } /// diff --git a/src/Correlate.Core/CorrelationManagerOptions.cs b/src/Correlate.Core/CorrelationManagerOptions.cs new file mode 100644 index 0000000..eefea83 --- /dev/null +++ b/src/Correlate.Core/CorrelationManagerOptions.cs @@ -0,0 +1,12 @@ +namespace Correlate; + +/// +/// The options class for configuring . +/// +public class CorrelationManagerOptions +{ + /// + /// The scope key that will be used for adding correlation ID to log context. Default is CorrelationId. + /// + public string LoggingScopeKey { get; set; } = CorrelateConstants.CorrelationIdKey; +} \ No newline at end of file diff --git a/src/Correlate.Core/Extensions/LoggerExtensions.cs b/src/Correlate.Core/Extensions/LoggerExtensions.cs index e11631b..66e2443 100644 --- a/src/Correlate.Core/Extensions/LoggerExtensions.cs +++ b/src/Correlate.Core/Extensions/LoggerExtensions.cs @@ -5,17 +5,19 @@ namespace Correlate.Extensions; internal static class LoggerExtensions { - public static IDisposable? BeginCorrelatedScope(this ILogger logger, string correlationId) + public static IDisposable? BeginCorrelatedScope(this ILogger logger, string scopeKey, string correlationId) { - return logger.BeginScope(new CorrelatedLogScope(correlationId)); + return logger.BeginScope(new CorrelatedLogScope(scopeKey, correlationId)); } private sealed class CorrelatedLogScope : IReadOnlyList> { + private readonly string _scopeKey; private readonly string _correlationId; - public CorrelatedLogScope(string correlationId) + public CorrelatedLogScope(string scopeKey, string correlationId) { + _scopeKey = scopeKey; _correlationId = correlationId; } @@ -41,7 +43,7 @@ public KeyValuePair this[int index] { if (index == 0) { - return new KeyValuePair(CorrelateConstants.CorrelationIdKey, _correlationId); + return new KeyValuePair(_scopeKey, _correlationId); } throw new ArgumentOutOfRangeException(nameof(index)); diff --git a/src/Correlate.Core/RootActivity.cs b/src/Correlate.Core/RootActivity.cs index 6eb4d0a..f53f41b 100644 --- a/src/Correlate.Core/RootActivity.cs +++ b/src/Correlate.Core/RootActivity.cs @@ -8,6 +8,7 @@ internal class RootActivity : IActivity { private readonly ICorrelationContextFactory _correlationContextFactory; private readonly DiagnosticListener? _diagnosticListener; + private readonly CorrelationManagerOptions _options; private readonly ILogger _logger; private IDisposable? _logScope; @@ -15,11 +16,13 @@ public RootActivity ( ICorrelationContextFactory correlationContextFactory, ILogger logger, - DiagnosticListener? diagnosticListener) + DiagnosticListener? diagnosticListener, + CorrelationManagerOptions options) { _correlationContextFactory = correlationContextFactory ?? throw new ArgumentNullException(nameof(correlationContextFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _diagnosticListener = diagnosticListener; + _options = options; } /// @@ -49,7 +52,7 @@ public CorrelationContext Start(string correlationId) if (isLoggingEnabled) { - _logScope = _logger.BeginCorrelatedScope(correlationId); + _logScope = _logger.BeginCorrelatedScope(_options.LoggingScopeKey, correlationId); } return context; diff --git a/src/Correlate.DependencyInjection/IServiceCollectionExtensions.cs b/src/Correlate.DependencyInjection/IServiceCollectionExtensions.cs index a5afdbe..b1dc648 100644 --- a/src/Correlate.DependencyInjection/IServiceCollectionExtensions.cs +++ b/src/Correlate.DependencyInjection/IServiceCollectionExtensions.cs @@ -9,6 +9,23 @@ namespace Correlate.DependencyInjection; // ReSharper disable once InconsistentNaming public static class IServiceCollectionExtensions { + /// + /// Adds services required for using correlation. + /// + /// The to add the services to. + /// The callback to customize defaults. + /// The so that additional calls can be chained. + public static IServiceCollection AddCorrelate(this IServiceCollection services, Action configure) + { + services + .AddOptions() + .Configure(configure); + + services.AddCorrelate(); + + return services; + } + /// /// Adds services required for using correlation. /// diff --git a/test/Correlate.AspNetCore.Tests/AspNetCore/CorrelateFeatureTests.cs b/test/Correlate.AspNetCore.Tests/AspNetCore/CorrelateFeatureTests.cs index eaa15a9..7b72de7 100644 --- a/test/Correlate.AspNetCore.Tests/AspNetCore/CorrelateFeatureTests.cs +++ b/test/Correlate.AspNetCore.Tests/AspNetCore/CorrelateFeatureTests.cs @@ -231,6 +231,7 @@ public async Task When_correlating_has_started_it_should_create_logScope_with_co .ContainSingle(le => le.MessageTemplate.Text.StartsWith("Setting response header")) .Which.Properties .Should() + // this tests the {CorrelationId} from log message template in CorrelateFeature.LogRequestHeaderFound, not the one from log scope added by IActivityFactory.CreateActivity .ContainSingle(p => p.Key == expectedLogProperty) .Which.Value .Should() diff --git a/test/Correlate.Core.Tests/CorrelationManagerTests.cs b/test/Correlate.Core.Tests/CorrelationManagerTests.cs index 436a48d..422d007 100644 --- a/test/Correlate.Core.Tests/CorrelationManagerTests.cs +++ b/test/Correlate.Core.Tests/CorrelationManagerTests.cs @@ -1,7 +1,9 @@ using System.Collections; +using System.Diagnostics; using Correlate.Testing; using Correlate.Testing.TestCases; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Serilog; using Serilog.Core; using Serilog.Extensions.Logging; @@ -15,10 +17,12 @@ public class CorrelationManagerTests : IDisposable private readonly CorrelationContextAccessor _correlationContextAccessor; private readonly ICorrelationIdFactory _correlationIdFactoryMock; private readonly ILogger _logger; + private readonly DiagnosticListener _diagnosticListener; + private readonly IOptions _options; private readonly SerilogLoggerProvider _logProvider; private readonly CorrelationManager _sut; - protected CorrelationManagerTests() + protected CorrelationManagerTests(CorrelationManagerOptions options) { _correlationContextAccessor = new CorrelationContextAccessor(); @@ -33,12 +37,16 @@ protected CorrelationManagerTests() _logProvider = new SerilogLoggerProvider(serilogLogger); _logger = new TestLogger(_logProvider.CreateLogger(nameof(CorrelationManager))); + _diagnosticListener = new DiagnosticListener("test"); + _options = Options.Create(options); _sut = new CorrelationManager( new CorrelationContextFactory(_correlationContextAccessor), _correlationIdFactoryMock, _correlationContextAccessor, - _logger + _logger, + _diagnosticListener, + _options ); } @@ -50,6 +58,13 @@ public void Dispose() public class Async : CorrelationManagerTests { + public Async() : base(new() + { + LoggingScopeKey = "ActivityId" + }) + { + } + [Fact] public async Task Given_a_task_should_run_task_inside_correlated_context() { @@ -143,7 +158,7 @@ await _sut.CorrelateAsync(() => var logEvents = TestCorrelator.GetLogEventsFromCurrentContext().ToList(); logEvents.Should() .HaveCount(3) - .And.ContainSingle(ev => ev.MessageTemplate.Text == "Message with correlation id." && ev.Properties.ContainsKey("CorrelationId")); + .And.ContainSingle(ev => ev.MessageTemplate.Text == "Message with correlation id." && ev.Properties.ContainsKey("ActivityId")); } } @@ -361,6 +376,10 @@ await _sut.CorrelateAsync(innerContextId, public class Sync : CorrelationManagerTests { + public Sync() : base(new()) + { + } + [Fact] public void Given_a_action_should_run_action_inside_correlated_context() { @@ -674,7 +693,9 @@ public static IEnumerable NullArgumentTestCases() Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For>() + Substitute.For>(), + Substitute.For("test"), + Options.Create(new CorrelationManagerOptions()) ); static Task CorrelatedTask() diff --git a/test/Correlate.DependencyInjection.Tests/IServiceCollectionExtensionsTests.cs b/test/Correlate.DependencyInjection.Tests/IServiceCollectionExtensionsTests.cs index 06db37b..bfe2605 100644 --- a/test/Correlate.DependencyInjection.Tests/IServiceCollectionExtensionsTests.cs +++ b/test/Correlate.DependencyInjection.Tests/IServiceCollectionExtensionsTests.cs @@ -10,15 +10,20 @@ public class When_adding_correlate_to_container private readonly IServiceCollection _services; private readonly IServiceProvider _sut; - public When_adding_correlate_to_container() + protected When_adding_correlate_to_container(IServiceCollection services) { - _services = new ServiceCollection() - .AddLogging() - .AddCorrelate(); + _services = services; _sut = _services.BuildServiceProvider(); } + public When_adding_correlate_to_container() + : this(new ServiceCollection() + .AddLogging() + .AddCorrelate()) + { + } + [Theory] [ClassData(typeof(ExpectedRegistrations))] public void It_should_resolve(ExpectedRegistration registration) @@ -58,3 +63,13 @@ protected virtual IEnumerable TestCases() } } } + +public class When_adding_correlate_to_container_with_options : When_adding_correlate_to_container +{ + public When_adding_correlate_to_container_with_options() + : base(new ServiceCollection() + .AddLogging() + .AddCorrelate(o => o.LoggingScopeKey = "CustomLoggingScopeKey")) + { + } +} \ No newline at end of file