diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 3970850f7e..49c6e5d819 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -24,6 +24,10 @@ `readonly` ([#3065](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3065)) +* [Bug fix] OpenTelemetryLoggerProvider is now unaffected by changes to + OpenTelemetryLoggerOptions after the LoggerFactory is built. + ([#3055](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3055)) + ## 1.2.0-rc3 Released 2022-Mar-04 diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 574dfbefb5..9fc41beac9 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -46,21 +46,20 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } - var processor = this.provider.Processor; + var provider = this.provider; + var processor = provider.Processor; if (processor != null) { - var options = this.provider.Options; - var record = new LogRecord( - options.IncludeScopes ? this.ScopeProvider : null, + provider.IncludeScopes ? this.ScopeProvider : null, DateTime.UtcNow, this.categoryName, logLevel, eventId, - options.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null, - options.ParseStateValues ? null : state, + provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null, + provider.ParseStateValues ? null : state, exception, - options.ParseStateValues ? this.ParseState(state) : null); + provider.ParseStateValues ? this.ParseState(state) : null); processor.OnEnd(record); diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index c79fa57270..372c3d04ed 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -25,7 +25,9 @@ namespace OpenTelemetry.Logs [ProviderAlias("OpenTelemetry")] public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISupportExternalScope { - internal readonly OpenTelemetryLoggerOptions Options; + internal readonly bool IncludeScopes; + internal readonly bool IncludeFormattedMessage; + internal readonly bool ParseStateValues; internal BaseProcessor Processor; internal Resource Resource; private readonly Hashtable loggers = new(); @@ -48,7 +50,10 @@ internal OpenTelemetryLoggerProvider(OpenTelemetryLoggerOptions options) { Guard.ThrowIfNull(options); - this.Options = options; + this.IncludeScopes = options.IncludeScopes; + this.IncludeFormattedMessage = options.IncludeFormattedMessage; + this.ParseStateValues = options.ParseStateValues; + this.Resource = options.ResourceBuilder.Build(); foreach (var processor in options.Processors) diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index 135681cf80..5b2052b2dd 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -30,38 +30,16 @@ namespace OpenTelemetry.Logs.Tests { - public sealed class LogRecordTest : IDisposable + public sealed class LogRecordTest { - private readonly ILogger logger; - private readonly List exportedItems = new(); - private readonly ILoggerFactory loggerFactory; - private readonly BaseExportProcessor processor; - private readonly BaseExporter exporter; - private OpenTelemetryLoggerOptions options; - - public LogRecordTest() - { - this.exporter = new InMemoryExporter(this.exportedItems); - this.processor = new TestLogRecordProcessor(this.exporter); - this.loggerFactory = LoggerFactory.Create(builder => - { - builder.AddOpenTelemetry(options => - { - this.options = options; - options - .AddProcessor(this.processor); - }); - builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); - }); - - this.logger = this.loggerFactory.CreateLogger(); - } - [Fact] public void CheckCateogryNameForLog() { - this.logger.LogInformation("Log"); - var categoryName = this.exportedItems[0].CategoryName; + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + + logger.LogInformation("Log"); + var categoryName = exportedItems[0].CategoryName; Assert.Equal(typeof(LogRecordTest).FullName, categoryName); } @@ -75,19 +53,25 @@ public void CheckCateogryNameForLog() [InlineData(LogLevel.Critical)] public void CheckLogLevel(LogLevel logLevel) { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var message = $"Log {logLevel}"; - this.logger.Log(logLevel, message); + logger.Log(logLevel, message); - var logLevelRecorded = this.exportedItems[0].LogLevel; + var logLevelRecorded = exportedItems[0].LogLevel; Assert.Equal(logLevel, logLevelRecorded); } [Fact] public void CheckStateForUnstructuredLog() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var message = "Hello, World!"; - this.logger.LogInformation(message); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation(message); + var state = exportedItems[0].State as IReadOnlyList>; // state only has {OriginalFormat} Assert.Equal(1, state.Count); @@ -98,9 +82,12 @@ public void CheckStateForUnstructuredLog() [Fact] public void CheckStateForUnstructuredLogWithStringInterpolation() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var message = $"Hello from potato {0.99}."; - this.logger.LogInformation(message); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation(message); + var state = exportedItems[0].State as IReadOnlyList>; // state only has {OriginalFormat} Assert.Equal(1, state.Count); @@ -111,9 +98,12 @@ public void CheckStateForUnstructuredLogWithStringInterpolation() [Fact] public void CheckStateForStructuredLogWithTemplate() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var message = "Hello from {name} {price}."; - this.logger.LogInformation(message, "tomato", 2.99); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation(message, "tomato", 2.99); + var state = exportedItems[0].State as IReadOnlyList>; // state has name, price and {OriginalFormat} Assert.Equal(3, state.Count); @@ -136,9 +126,12 @@ public void CheckStateForStructuredLogWithTemplate() [Fact] public void CheckStateForStructuredLogWithStrongType() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var food = new Food { Name = "artichoke", Price = 3.99 }; - this.logger.LogInformation("{food}", food); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation("{food}", food); + var state = exportedItems[0].State as IReadOnlyList>; // state has food and {OriginalFormat} Assert.Equal(2, state.Count); @@ -160,9 +153,12 @@ public void CheckStateForStructuredLogWithStrongType() [Fact] public void CheckStateForStructuredLogWithAnonymousType() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var anonymousType = new { Name = "pumpkin", Price = 5.99 }; - this.logger.LogInformation("{food}", anonymousType); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation("{food}", anonymousType); + var state = exportedItems[0].State as IReadOnlyList>; // state has food and {OriginalFormat} Assert.Equal(2, state.Count); @@ -184,13 +180,16 @@ public void CheckStateForStructuredLogWithAnonymousType() [Fact] public void CheckStateForStrucutredLogWithGeneralType() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var food = new Dictionary { ["Name"] = "truffle", ["Price"] = 299.99, }; - this.logger.LogInformation("{food}", food); - var state = this.exportedItems[0].State as IReadOnlyList>; + logger.LogInformation("{food}", food); + var state = exportedItems[0].State as IReadOnlyList>; // state only has food and {OriginalFormat} Assert.Equal(2, state.Count); @@ -220,18 +219,21 @@ public void CheckStateForStrucutredLogWithGeneralType() [Fact] public void CheckStateForExceptionLogged() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var exceptionMessage = "Exception Message"; var exception = new Exception(exceptionMessage); var message = "Exception Occurred"; - this.logger.LogInformation(exception, message); + logger.LogInformation(exception, message); - var state = this.exportedItems[0].State; + var state = exportedItems[0].State; var itemCount = state.GetType().GetProperty("Count").GetValue(state); // state only has {OriginalFormat} Assert.Equal(1, itemCount); - var loggedException = this.exportedItems[0].Exception; + var loggedException = exportedItems[0].Exception; Assert.NotNull(loggedException); Assert.Equal(exceptionMessage, loggedException.Message); @@ -241,8 +243,11 @@ public void CheckStateForExceptionLogged() [Fact] public void CheckTraceIdForLogWithinDroppedActivity() { - this.logger.LogInformation("Log within a dropped activity"); - var logRecord = this.exportedItems[0]; + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + + logger.LogInformation("Log within a dropped activity"); + var logRecord = exportedItems[0]; Assert.Null(Activity.Current); Assert.Equal(default, logRecord.TraceId); @@ -253,6 +258,9 @@ public void CheckTraceIdForLogWithinDroppedActivity() [Fact] public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var sampler = new RecordOnlySampler(); var exportedActivityList = new List(); var activitySourceName = "LogRecordTest"; @@ -265,8 +273,8 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly() using var activity = activitySource.StartActivity("Activity"); - this.logger.LogInformation("Log within activity marked as RecordOnly"); - var logRecord = this.exportedItems[0]; + logger.LogInformation("Log within activity marked as RecordOnly"); + var logRecord = exportedItems[0]; var currentActivity = Activity.Current; Assert.NotNull(Activity.Current); @@ -278,6 +286,9 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly() [Fact] public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + var logger = loggerFactory.CreateLogger(); + var sampler = new AlwaysOnSampler(); var exportedActivityList = new List(); var activitySourceName = "LogRecordTest"; @@ -290,8 +301,8 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() using var activity = activitySource.StartActivity("Activity"); - this.logger.LogInformation("Log within activity marked as RecordAndSample"); - var logRecord = this.exportedItems[0]; + logger.LogInformation("Log within activity marked as RecordAndSample"); + var logRecord = exportedItems[0]; var currentActivity = Activity.Current; Assert.NotNull(Activity.Current); @@ -301,315 +312,326 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() } [Fact] - public void IncludeFormattedMessageTest() + public void VerifyIncludeFormattedMessage_False() { - this.logger.LogInformation("OpenTelemetry!"); - var logRecord = this.exportedItems[0]; + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = false); + var logger = loggerFactory.CreateLogger(); + + logger.LogInformation("OpenTelemetry!"); + var logRecord = exportedItems[0]; Assert.Null(logRecord.FormattedMessage); + } - this.options.IncludeFormattedMessage = true; - try - { - this.logger.LogInformation("OpenTelemetry!"); - logRecord = this.exportedItems[1]; - Assert.Equal("OpenTelemetry!", logRecord.FormattedMessage); + [Fact] + public void VerifyIncludeFormattedMessage_True() + { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); + var logger = loggerFactory.CreateLogger(); - this.logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); - logRecord = this.exportedItems[2]; - Assert.Equal("OpenTelemetry Hello World!", logRecord.FormattedMessage); - } - finally - { - this.options.IncludeFormattedMessage = false; - } + logger.LogInformation("OpenTelemetry!"); + var logRecord = exportedItems[0]; + Assert.Equal("OpenTelemetry!", logRecord.FormattedMessage); + + logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); + logRecord = exportedItems[1]; + Assert.Equal("OpenTelemetry Hello World!", logRecord.FormattedMessage); } [Fact] public void IncludeFormattedMessageTestWhenFormatterNull() { - this.logger.Log(LogLevel.Information, default, "Hello World!", null, null); - var logRecord = this.exportedItems[0]; + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); + var logger = loggerFactory.CreateLogger(); + + logger.Log(LogLevel.Information, default, "Hello World!", null, null); + var logRecord = exportedItems[0]; Assert.Null(logRecord.FormattedMessage); - this.options.IncludeFormattedMessage = true; - try - { - // Pass null as formatter function - this.logger.Log(LogLevel.Information, default, "Hello World!", null, null); - logRecord = this.exportedItems[1]; - Assert.Null(logRecord.FormattedMessage); - - var expectedFormattedMessage = "formatted message"; - this.logger.Log(LogLevel.Information, default, "Hello World!", null, (state, ex) => expectedFormattedMessage); - logRecord = this.exportedItems[2]; - Assert.Equal(expectedFormattedMessage, logRecord.FormattedMessage); - } - finally - { - this.options.IncludeFormattedMessage = false; - } + // Pass null as formatter function + logger.Log(LogLevel.Information, default, "Hello World!", null, null); + logRecord = exportedItems[1]; + Assert.Null(logRecord.FormattedMessage); + + var expectedFormattedMessage = "formatted message"; + logger.Log(LogLevel.Information, default, "Hello World!", null, (state, ex) => expectedFormattedMessage); + logRecord = exportedItems[2]; + Assert.Equal(expectedFormattedMessage, logRecord.FormattedMessage); } [Fact] - public void IncludeScopesTest() + public void VerifyIncludeScopes_False() { - using var scope = this.logger.BeginScope("string_scope"); + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = false); + var logger = loggerFactory.CreateLogger(); - this.logger.LogInformation("OpenTelemetry!"); - var logRecord = this.exportedItems[0]; + using var scope = logger.BeginScope("string_scope"); + + logger.LogInformation("OpenTelemetry!"); + var logRecord = exportedItems[0]; List scopes = new List(); logRecord.ForEachScope((scope, state) => scopes.Add(scope.Scope), null); Assert.Empty(scopes); + } - this.options.IncludeScopes = true; - try - { - this.logger.LogInformation("OpenTelemetry!"); - logRecord = this.exportedItems[1]; + [Fact] + public void VerifyIncludeScopes_True() + { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = true); + var logger = loggerFactory.CreateLogger(); - int reachedDepth = -1; - logRecord.ForEachScope( - (scope, state) => - { - reachedDepth++; - scopes.Add(scope.Scope); - foreach (KeyValuePair item in scope) - { - Assert.Equal(string.Empty, item.Key); - Assert.Equal("string_scope", item.Value); - } - }, - null); - Assert.Single(scopes); - Assert.Equal(0, reachedDepth); - Assert.Equal("string_scope", scopes[0]); + using var scope = logger.BeginScope("string_scope"); - scopes.Clear(); + logger.LogInformation("OpenTelemetry!"); + var logRecord = exportedItems[0]; - List> expectedScope2 = new List> + List scopes = new List(); + + logger.LogInformation("OpenTelemetry!"); + logRecord = exportedItems[1]; + + int reachedDepth = -1; + logRecord.ForEachScope( + (scope, state) => { - new KeyValuePair("item1", "value1"), - new KeyValuePair("item2", "value2"), - }; - using var scope2 = this.logger.BeginScope(expectedScope2); + reachedDepth++; + scopes.Add(scope.Scope); + foreach (KeyValuePair item in scope) + { + Assert.Equal(string.Empty, item.Key); + Assert.Equal("string_scope", item.Value); + } + }, + null); + Assert.Single(scopes); + Assert.Equal(0, reachedDepth); + Assert.Equal("string_scope", scopes[0]); + + scopes.Clear(); + + List> expectedScope2 = new List> + { + new KeyValuePair("item1", "value1"), + new KeyValuePair("item2", "value2"), + }; + using var scope2 = logger.BeginScope(expectedScope2); - this.logger.LogInformation("OpenTelemetry!"); - logRecord = this.exportedItems[2]; + logger.LogInformation("OpenTelemetry!"); + logRecord = exportedItems[2]; - reachedDepth = -1; - logRecord.ForEachScope( - (scope, state) => + reachedDepth = -1; + logRecord.ForEachScope( + (scope, state) => + { + scopes.Add(scope.Scope); + if (reachedDepth++ == 1) { - scopes.Add(scope.Scope); - if (reachedDepth++ == 1) + foreach (KeyValuePair item in scope) { - foreach (KeyValuePair item in scope) - { - Assert.Contains(item, expectedScope2); - } + Assert.Contains(item, expectedScope2); } - }, - null); - Assert.Equal(2, scopes.Count); - Assert.Equal(1, reachedDepth); - Assert.Equal("string_scope", scopes[0]); - Assert.Same(expectedScope2, scopes[1]); + } + }, + null); + Assert.Equal(2, scopes.Count); + Assert.Equal(1, reachedDepth); + Assert.Equal("string_scope", scopes[0]); + Assert.Same(expectedScope2, scopes[1]); - scopes.Clear(); + scopes.Clear(); - KeyValuePair[] expectedScope3 = new KeyValuePair[] - { - new KeyValuePair("item3", "value3"), - new KeyValuePair("item4", "value4"), - }; - using var scope3 = this.logger.BeginScope(expectedScope3); + KeyValuePair[] expectedScope3 = new KeyValuePair[] + { + new KeyValuePair("item3", "value3"), + new KeyValuePair("item4", "value4"), + }; + using var scope3 = logger.BeginScope(expectedScope3); - this.logger.LogInformation("OpenTelemetry!"); - logRecord = this.exportedItems[3]; + logger.LogInformation("OpenTelemetry!"); + logRecord = exportedItems[3]; - reachedDepth = -1; - logRecord.ForEachScope( - (scope, state) => + reachedDepth = -1; + logRecord.ForEachScope( + (scope, state) => + { + scopes.Add(scope.Scope); + if (reachedDepth++ == 2) { - scopes.Add(scope.Scope); - if (reachedDepth++ == 2) + foreach (KeyValuePair item in scope) { - foreach (KeyValuePair item in scope) - { - Assert.Contains(item, expectedScope3); - } + Assert.Contains(item, expectedScope3); } - }, - null); - Assert.Equal(3, scopes.Count); - Assert.Equal(2, reachedDepth); - Assert.Equal("string_scope", scopes[0]); - Assert.Same(expectedScope2, scopes[1]); - Assert.Same(expectedScope3, scopes[2]); - } - finally - { - this.options.IncludeScopes = false; - } + } + }, + null); + Assert.Equal(3, scopes.Count); + Assert.Equal(2, reachedDepth); + Assert.Equal("string_scope", scopes[0]); + Assert.Same(expectedScope2, scopes[1]); + Assert.Same(expectedScope3, scopes[2]); } [Fact] - public void ParseStateValuesUsingStandardExtensionsTest() + public void VerifyParseStateValues_False_UsingStandardExtensions() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = false); + var logger = loggerFactory.CreateLogger(); + // Tests state parsing with standard extensions. - this.logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); - var logRecord = this.exportedItems[0]; + logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); + var logRecord = exportedItems[0]; Assert.NotNull(logRecord.State); Assert.Null(logRecord.StateValues); + } - this.options.ParseStateValues = true; - try - { - var complex = new { Property = "Value" }; + [Fact] + public void VerifyParseStateValues_True_UsingStandardExtensions() + { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); - this.logger.LogInformation("{Product} {Year} {Complex}!", "OpenTelemetry", 2021, complex); - logRecord = this.exportedItems[1]; + // Tests state parsing with standard extensions. - Assert.Null(logRecord.State); - Assert.NotNull(logRecord.StateValues); - Assert.Equal(4, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); - Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); - Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year} {Complex}!"), logRecord.StateValues[3]); + logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); + var logRecord = exportedItems[0]; - KeyValuePair actualComplex = logRecord.StateValues[2]; - Assert.Equal("Complex", actualComplex.Key); - Assert.Same(complex, actualComplex.Value); - } - finally - { - this.options.ParseStateValues = false; - } + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(3, logRecord.StateValues.Count); + Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); + Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year}!"), logRecord.StateValues[2]); + + var complex = new { Property = "Value" }; + + logger.LogInformation("{Product} {Year} {Complex}!", "OpenTelemetry", 2021, complex); + logRecord = exportedItems[1]; + + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(4, logRecord.StateValues.Count); + Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); + Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year} {Complex}!"), logRecord.StateValues[3]); + + KeyValuePair actualComplex = logRecord.StateValues[2]; + Assert.Equal("Complex", actualComplex.Key); + Assert.Same(complex, actualComplex.Value); } [Fact] public void ParseStateValuesUsingStructTest() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); + // Tests struct IReadOnlyList> parse path. - this.options.ParseStateValues = true; - try - { - this.logger.Log( - LogLevel.Information, - 0, - new StructState(new KeyValuePair("Key1", "Value1")), - null, - (s, e) => "OpenTelemetry!"); - var logRecord = this.exportedItems[0]; - - Assert.Null(logRecord.State); - Assert.NotNull(logRecord.StateValues); - Assert.Equal(1, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); - } - finally - { - this.options.ParseStateValues = false; - } + logger.Log( + LogLevel.Information, + 0, + new StructState(new KeyValuePair("Key1", "Value1")), + null, + (s, e) => "OpenTelemetry!"); + var logRecord = exportedItems[0]; + + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(1, logRecord.StateValues.Count); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] public void ParseStateValuesUsingListTest() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); + // Tests ref IReadOnlyList> parse path. - this.options.ParseStateValues = true; - try - { - this.logger.Log( - LogLevel.Information, - 0, - new List> { new KeyValuePair("Key1", "Value1") }, - null, - (s, e) => "OpenTelemetry!"); - var logRecord = this.exportedItems[0]; - - Assert.Null(logRecord.State); - Assert.NotNull(logRecord.StateValues); - Assert.Equal(1, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); - } - finally - { - this.options.ParseStateValues = false; - } + logger.Log( + LogLevel.Information, + 0, + new List> { new KeyValuePair("Key1", "Value1") }, + null, + (s, e) => "OpenTelemetry!"); + var logRecord = exportedItems[0]; + + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(1, logRecord.StateValues.Count); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] public void ParseStateValuesUsingIEnumerableTest() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); + // Tests IEnumerable> parse path. - this.options.ParseStateValues = true; - try - { - this.logger.Log( - LogLevel.Information, - 0, - new ListState(new KeyValuePair("Key1", "Value1")), - null, - (s, e) => "OpenTelemetry!"); - var logRecord = this.exportedItems[0]; - - Assert.Null(logRecord.State); - Assert.NotNull(logRecord.StateValues); - Assert.Equal(1, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); - } - finally - { - this.options.ParseStateValues = false; - } + logger.Log( + LogLevel.Information, + 0, + new ListState(new KeyValuePair("Key1", "Value1")), + null, + (s, e) => "OpenTelemetry!"); + var logRecord = exportedItems[0]; + + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(1, logRecord.StateValues.Count); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] public void ParseStateValuesUsingCustomTest() { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); + // Tests unknown state parse path. - this.options.ParseStateValues = true; - try + CustomState state = new CustomState { - CustomState state = new CustomState - { - Property = "Value", - }; + Property = "Value", + }; - this.logger.Log( - LogLevel.Information, - 0, - state, - null, - (s, e) => "OpenTelemetry!"); - var logRecord = this.exportedItems[0]; + logger.Log( + LogLevel.Information, + 0, + state, + null, + (s, e) => "OpenTelemetry!"); + var logRecord = exportedItems[0]; - Assert.Null(logRecord.State); - Assert.NotNull(logRecord.StateValues); - Assert.Equal(1, logRecord.StateValues.Count); + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(1, logRecord.StateValues.Count); - KeyValuePair actualState = logRecord.StateValues[0]; + KeyValuePair actualState = logRecord.StateValues[0]; - Assert.Equal(string.Empty, actualState.Key); - Assert.Same(state, actualState.Value); - } - finally - { - this.options.ParseStateValues = false; - } + Assert.Equal(string.Empty, actualState.Key); + Assert.Same(state, actualState.Value); } - public void Dispose() + private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action configure = null) { - this.loggerFactory?.Dispose(); + exportedItems = new List(); + var exporter = new InMemoryExporter(exportedItems); + var processor = new TestLogRecordProcessor(exporter); + return LoggerFactory.Create(builder => + { + builder.AddOpenTelemetry(options => + { + configure?.Invoke(options); + options.AddProcessor(processor); + }); + builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); + }); } internal struct Food diff --git a/test/OpenTelemetry.Tests/Logs/LoggerOptionsTest.cs b/test/OpenTelemetry.Tests/Logs/LoggerOptionsTest.cs new file mode 100644 index 0000000000..14ccaf92db --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LoggerOptionsTest.cs @@ -0,0 +1,54 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#if !NET461 + +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LoggerOptionsTest + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void VerifyOptionsCannotBeChangedAfterInit(bool initialValue) + { + var options = new OpenTelemetryLoggerOptions + { + IncludeFormattedMessage = initialValue, + IncludeScopes = initialValue, + ParseStateValues = initialValue, + }; + var provider = new OpenTelemetryLoggerProvider(options); + + // Verify initial set + Assert.Equal(initialValue, provider.IncludeFormattedMessage); + Assert.Equal(initialValue, provider.IncludeScopes); + Assert.Equal(initialValue, provider.ParseStateValues); + + // Attempt to change value + options.IncludeFormattedMessage = !initialValue; + options.IncludeScopes = !initialValue; + options.ParseStateValues = !initialValue; + + // Verify processor is unchanged + Assert.Equal(initialValue, provider.IncludeFormattedMessage); + Assert.Equal(initialValue, provider.IncludeScopes); + Assert.Equal(initialValue, provider.ParseStateValues); + } + } +} +#endif