// // 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 !NETFRAMEWORK using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Logs.Tests { public sealed class LogRecordTest { private enum Field { FormattedMessage, State, StateValues, } [Fact] public void CheckCategoryNameForLog() { 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); } [Theory] [InlineData(LogLevel.Trace)] [InlineData(LogLevel.Debug)] [InlineData(LogLevel.Information)] [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] public void CheckLogLevel(LogLevel logLevel) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); const string message = "Log {logLevel}"; logger.Log(logLevel, message, 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(); const string message = "Hello, World!"; logger.LogInformation(message); var state = exportedItems[0].State as IReadOnlyList>; // state only has {OriginalFormat} Assert.Equal(1, state.Count); Assert.Equal(message, state.ToString()); } [Fact] [SuppressMessage("CA2254", "CA2254", Justification = "While you shouldn't use interpolation in a log message, this test verifies things work with it anyway.")] public void CheckStateForUnstructuredLogWithStringInterpolation() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); var message = $"Hello from potato {0.99}."; logger.LogInformation(message); var state = exportedItems[0].State as IReadOnlyList>; // state only has {OriginalFormat} Assert.Equal(1, state.Count); Assert.Equal(message, state.ToString()); } [Fact] public void CheckStateForStructuredLogWithTemplate() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); const string message = "Hello from {name} {price}."; logger.LogInformation(message, "tomato", 2.99); var state = exportedItems[0].State as IReadOnlyList>; // state has name, price and {OriginalFormat} Assert.Equal(3, state.Count); // Check if state has name Assert.Contains(state, item => item.Key == "name"); Assert.Equal("tomato", state.First(item => item.Key == "name").Value); // Check if state has price Assert.Contains(state, item => item.Key == "price"); Assert.Equal(2.99, state.First(item => item.Key == "price").Value); // Check if state has OriginalFormat Assert.Contains(state, item => item.Key == "{OriginalFormat}"); Assert.Equal(message, state.First(item => item.Key == "{OriginalFormat}").Value); Assert.Equal($"Hello from tomato 2.99.", state.ToString()); } [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 }; logger.LogInformation("{food}", food); var state = exportedItems[0].State as IReadOnlyList>; // state has food and {OriginalFormat} Assert.Equal(2, state.Count); // Check if state has food Assert.Contains(state, item => item.Key == "food"); var foodParameter = (Food)state.First(item => item.Key == "food").Value; Assert.Equal(food.Name, foodParameter.Name); Assert.Equal(food.Price, foodParameter.Price); // Check if state has OriginalFormat Assert.Contains(state, item => item.Key == "{OriginalFormat}"); Assert.Equal("{food}", state.First(item => item.Key == "{OriginalFormat}").Value); Assert.Equal(food.ToString(), state.ToString()); } [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 }; logger.LogInformation("{food}", anonymousType); var state = exportedItems[0].State as IReadOnlyList>; // state has food and {OriginalFormat} Assert.Equal(2, state.Count); // Check if state has food Assert.Contains(state, item => item.Key == "food"); var foodParameter = state.First(item => item.Key == "food").Value as dynamic; Assert.Equal(anonymousType.Name, foodParameter.Name); Assert.Equal(anonymousType.Price, foodParameter.Price); // Check if state has OriginalFormat Assert.Contains(state, item => item.Key == "{OriginalFormat}"); Assert.Equal("{food}", state.First(item => item.Key == "{OriginalFormat}").Value); Assert.Equal(anonymousType.ToString(), state.ToString()); } [Fact] public void CheckStateForStructuredLogWithGeneralType() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); var food = new Dictionary { ["Name"] = "truffle", ["Price"] = 299.99, }; logger.LogInformation("{food}", food); var state = exportedItems[0].State as IReadOnlyList>; // state only has food and {OriginalFormat} Assert.Equal(2, state.Count); // Check if state has food Assert.Contains(state, item => item.Key == "food"); var foodParameter = state.First(item => item.Key == "food").Value as Dictionary; Assert.True(food.Count == foodParameter.Count && !food.Except(foodParameter).Any()); // Check if state has OriginalFormat Assert.Contains(state, item => item.Key == "{OriginalFormat}"); Assert.Equal("{food}", state.First(item => item.Key == "{OriginalFormat}").Value); var prevCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; try { Assert.Equal("[Name, truffle], [Price, 299.99]", state.ToString()); } finally { CultureInfo.CurrentCulture = prevCulture; } } [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); const string message = "Exception Occurred"; logger.LogInformation(exception, message); var state = exportedItems[0].State; var itemCount = state.GetType().GetProperty("Count").GetValue(state); // state only has {OriginalFormat} Assert.Equal(1, itemCount); var loggedException = exportedItems[0].Exception; Assert.NotNull(loggedException); Assert.Equal(exceptionMessage, loggedException.Message); Assert.Equal(message, state.ToString()); } [Fact] public void CheckStateCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); logger.LogInformation("This does not matter."); var logRecord = exportedItems[0]; logRecord.State = "newState"; var expectedState = "newState"; Assert.Equal(expectedState, logRecord.State); } [Fact] public void CheckStateValuesCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); var logger = loggerFactory.CreateLogger(); logger.Log( LogLevel.Information, 0, new List> { new KeyValuePair("Key1", "Value1") }, null, (s, e) => "OpenTelemetry!"); var logRecord = exportedItems[0]; var expectedStateValues = new List> { new KeyValuePair("Key2", "Value2") }; logRecord.StateValues = expectedStateValues; Assert.Equal(expectedStateValues, logRecord.StateValues); } [Fact] public void CheckFormattedMessageCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); var logger = loggerFactory.CreateLogger(); logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); var logRecord = exportedItems[0]; var expectedFormattedMessage = "OpenTelemetry Good Night!"; logRecord.FormattedMessage = expectedFormattedMessage; Assert.Equal(expectedFormattedMessage, logRecord.FormattedMessage); } [Fact] public void CheckStateCanBeSetByProcessor() { var exportedItems = new List(); var exporter = new InMemoryExporter(exportedItems); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { options.AddProcessor(new RedactionProcessor(Field.State)); options.AddInMemoryExporter(exportedItems); }); }); var logger = loggerFactory.CreateLogger(); logger.LogInformation($"This does not matter."); var state = exportedItems[0].State as IReadOnlyList>; Assert.Equal("newStateKey", state[0].Key.ToString()); Assert.Equal("newStateValue", state[0].Value.ToString()); } [Fact] public void CheckStateValuesCanBeSetByProcessor() { var exportedItems = new List(); var exporter = new InMemoryExporter(exportedItems); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { options.AddProcessor(new RedactionProcessor(Field.StateValues)); options.AddInMemoryExporter(exportedItems); options.ParseStateValues = true; }); }); var logger = loggerFactory.CreateLogger(); logger.LogInformation("This does not matter."); var stateValue = exportedItems[0]; Assert.Equal(new KeyValuePair("newStateValueKey", "newStateValueValue"), stateValue.StateValues[0]); } [Fact] public void CheckFormattedMessageCanBeSetByProcessor() { var exportedItems = new List(); var exporter = new InMemoryExporter(exportedItems); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { options.AddProcessor(new RedactionProcessor(Field.FormattedMessage)); options.AddInMemoryExporter(exportedItems); options.IncludeFormattedMessage = true; }); }); var logger = loggerFactory.CreateLogger(); logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); var item = exportedItems[0]; Assert.Equal("OpenTelemetry Good Night!", item.FormattedMessage); } [Fact] public void CheckTraceIdForLogWithinDroppedActivity() { 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); Assert.Equal(default, logRecord.SpanId); Assert.Equal(default, logRecord.TraceFlags); } [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"; using var activitySource = new ActivitySource(activitySourceName); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) .SetSampler(sampler) .AddInMemoryExporter(exportedActivityList) .Build(); using var activity = activitySource.StartActivity("Activity"); logger.LogInformation("Log within activity marked as RecordOnly"); var logRecord = exportedItems[0]; var currentActivity = Activity.Current; Assert.NotNull(Activity.Current); Assert.Equal(currentActivity.TraceId, logRecord.TraceId); Assert.Equal(currentActivity.SpanId, logRecord.SpanId); Assert.Equal(currentActivity.ActivityTraceFlags, logRecord.TraceFlags); } [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"; using var activitySource = new ActivitySource(activitySourceName); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) .SetSampler(sampler) .AddInMemoryExporter(exportedActivityList) .Build(); using var activity = activitySource.StartActivity("Activity"); logger.LogInformation("Log within activity marked as RecordAndSample"); var logRecord = exportedItems[0]; var currentActivity = Activity.Current; Assert.NotNull(Activity.Current); Assert.Equal(currentActivity.TraceId, logRecord.TraceId); Assert.Equal(currentActivity.SpanId, logRecord.SpanId); Assert.Equal(currentActivity.ActivityTraceFlags, logRecord.TraceFlags); } [Fact] public void VerifyIncludeFormattedMessage_False() { 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); } [Fact] public void VerifyIncludeFormattedMessage_True() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); var logger = loggerFactory.CreateLogger(); 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() { 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); // 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 VerifyIncludeScopes_False() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = false); var logger = loggerFactory.CreateLogger(); 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); } [Fact] public void VerifyIncludeScopes_True() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = true); var logger = loggerFactory.CreateLogger(); using var scope = logger.BeginScope("string_scope"); logger.LogInformation("OpenTelemetry!"); var logRecord = exportedItems[0]; List scopes = new List(); logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[1]; 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]); scopes.Clear(); List> expectedScope2 = new List> { new KeyValuePair("item1", "value1"), new KeyValuePair("item2", "value2"), }; using var scope2 = logger.BeginScope(expectedScope2); logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[2]; reachedDepth = -1; logRecord.ForEachScope( (scope, state) => { scopes.Add(scope.Scope); if (reachedDepth++ == 1) { foreach (KeyValuePair item in scope) { 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]); scopes.Clear(); KeyValuePair[] expectedScope3 = new KeyValuePair[] { new KeyValuePair("item3", "value3"), new KeyValuePair("item4", "value4"), }; using var scope3 = logger.BeginScope(expectedScope3); logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[3]; reachedDepth = -1; logRecord.ForEachScope( (scope, state) => { scopes.Add(scope.Scope); if (reachedDepth++ == 2) { foreach (KeyValuePair item in scope) { 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]); } [Fact] 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. logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); var logRecord = exportedItems[0]; Assert.NotNull(logRecord.State); Assert.Null(logRecord.StateValues); } [Fact] public void VerifyParseStateValues_True_UsingStandardExtensions() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); var logger = loggerFactory.CreateLogger(); // Tests state parsing with standard extensions. logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); var logRecord = exportedItems[0]; 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. 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. 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. 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. CustomState state = new CustomState { Property = "Value", }; 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); KeyValuePair actualState = logRecord.StateValues[0]; Assert.Equal(string.Empty, actualState.Key); Assert.Same(state, actualState.Value); } [Fact] public void DisposingStateTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); var logger = loggerFactory.CreateLogger(); DisposingState state = new DisposingState("Hello world"); logger.Log( LogLevel.Information, 0, state, null, (s, e) => "OpenTelemetry!"); var logRecord = exportedItems[0]; state.Dispose(); Assert.Null(logRecord.State); Assert.NotNull(logRecord.StateValues); Assert.Equal(1, logRecord.StateValues.Count); KeyValuePair actualState = logRecord.StateValues[0]; Assert.Same("Value", actualState.Key); Assert.Same("Hello world", actualState.Value); } [Theory] [InlineData(true)] [InlineData(false)] public void ReusedLogRecordScopeTest(bool buffer) { var processor = new ScopeProcessor(buffer); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { options.IncludeScopes = true; options.AddProcessor(processor); }); }); var logger = loggerFactory.CreateLogger("TestLogger"); using (var scope1 = logger.BeginScope("scope1")) { logger.LogInformation("message1"); } using (var scope2 = logger.BeginScope("scope2")) { logger.LogInformation("message2"); } Assert.Equal(2, processor.Scopes.Count); Assert.Equal("scope1", processor.Scopes[0]); Assert.Equal("scope2", processor.Scopes[1]); } private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action configure = null) { var items = exportedItems = new List(); return LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { configure?.Invoke(options); options.AddInMemoryExporter(items); }); builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); }); } internal struct Food { public string Name { get; set; } public double Price { get; set; } } private struct StructState : IReadOnlyList> { private readonly List> list; public StructState(params KeyValuePair[] items) { this.list = new List>(items); } public int Count => this.list.Count; public KeyValuePair this[int index] => this.list[index]; public IEnumerator> GetEnumerator() { return this.list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.list.GetEnumerator(); } } internal sealed class DisposingState : IReadOnlyList>, IDisposable { private string value; private bool disposed; public DisposingState(string value) { this.Value = value; } public int Count => 1; public string Value { get { if (this.disposed) { throw new ObjectDisposedException(nameof(DisposingState)); } return this.value; } private set => this.value = value; } public KeyValuePair this[int index] => index switch { 0 => new KeyValuePair(nameof(this.Value), this.Value), _ => throw new IndexOutOfRangeException(nameof(index)), }; public void Dispose() { this.disposed = true; } public IEnumerator> GetEnumerator() { for (var i = 0; i < this.Count; i++) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } private class RedactionProcessor : BaseProcessor { private readonly Field fieldToUpdate; public RedactionProcessor(Field fieldToUpdate) { this.fieldToUpdate = fieldToUpdate; } public override void OnEnd(LogRecord logRecord) { if (this.fieldToUpdate == Field.State) { logRecord.State = new List> { new KeyValuePair("newStateKey", "newStateValue") }; } else if (this.fieldToUpdate == Field.StateValues) { logRecord.StateValues = new List> { new KeyValuePair("newStateValueKey", "newStateValueValue") }; } else { logRecord.FormattedMessage = "OpenTelemetry Good Night!"; } } } private class ListState : IEnumerable> { private readonly List> list; public ListState(params KeyValuePair[] items) { this.list = new List>(items); } public IEnumerator> GetEnumerator() { return this.list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.list.GetEnumerator(); } } private class CustomState { public string Property { get; set; } } private class ScopeProcessor : BaseProcessor { private readonly bool buffer; public ScopeProcessor(bool buffer) { this.buffer = buffer; } public List Scopes { get; } = new(); public override void OnEnd(LogRecord data) { data.ForEachScope( (scope, state) => { this.Scopes.Add(scope.Scope); }, null); if (this.buffer) { data.Buffer(); } } } } } #endif