From 82b65bbe9a33465749ef84744a0b4bc111802030 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sat, 13 Mar 2021 23:21:46 -0800 Subject: [PATCH 1/6] Moved ActivityEventAttachingLogProcessor to OpenTelemetry.Extensions. --- OpenTelemetry.sln | 7 + build/CodeAnalysis.props | 21 +++ build/Common.prod.props | 2 +- build/Common.props | 8 +- .../Controllers/WeatherForecastController.cs | 21 ++- .../AspNetCore/Examples.AspNetCore.csproj | 1 + examples/AspNetCore/Program.cs | 19 ++ examples/AspNetCore/appsettings.json | 1 + .../.publicApi/net452/PublicAPI.Shipped.txt | 1 + .../.publicApi/net452/PublicAPI.Unshipped.txt | 1 + .../.publicApi/net461/PublicAPI.Shipped.txt | 1 + .../.publicApi/net461/PublicAPI.Unshipped.txt | 9 + .../.publicApi/net5.0/PublicAPI.Shipped.txt | 1 + .../.publicApi/net5.0/PublicAPI.Unshipped.txt | 9 + .../netstandard2.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 9 + src/OpenTelemetry.Extensions/AssemblyInfo.cs | 19 ++ src/OpenTelemetry.Extensions/CHANGELOG.md | 4 + .../OpenTelemetryExtensionsEventSource.cs | 45 +++++ .../ActivityEventAttachingLogProcessor.cs | 114 +++++++++++ ...tivityEventAttachingLogProcessorOptions.cs | 40 ++++ .../Logs/DefaultLogStateConverter.cs | 178 ++++++++++++++++++ .../Logs/OpenTelemetryLoggingExtensions.cs | 55 ++++++ .../OpenTelemetry.Extensions.csproj | 18 ++ src/OpenTelemetry.Extensions/README.md | 4 + 25 files changed, 580 insertions(+), 9 deletions(-) create mode 100644 build/CodeAnalysis.props create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions/AssemblyInfo.cs create mode 100644 src/OpenTelemetry.Extensions/CHANGELOG.md create mode 100644 src/OpenTelemetry.Extensions/Internal/OpenTelemetryExtensionsEventSource.cs create mode 100644 src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs create mode 100644 src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs create mode 100644 src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs create mode 100644 src/OpenTelemetry.Extensions/Logs/OpenTelemetryLoggingExtensions.cs create mode 100644 src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj create mode 100644 src/OpenTelemetry.Extensions/README.md diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index c4cd9ca811f..8c51c9d9801 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E-03FA-4FFF-89A5-C51F107623FD}" ProjectSection(SolutionItems) = preProject + build\CodeAnalysis.props = build\CodeAnalysis.props build\Common.nonprod.props = build\Common.nonprod.props build\Common.prod.props = build\Common.prod.props build\Common.props = build\Common.props @@ -199,6 +200,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.5.0", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exception-handling", "docs\trace\exception-handling\exception-handling.csproj", "{08D29501-F0A3-468F-B18D-BD1821A72383}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions", "src\OpenTelemetry.Extensions\OpenTelemetry.Extensions.csproj", "{7BD1BED9-4CC1-46BE-9152-FF58CD70E08B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -393,6 +396,10 @@ Global {08D29501-F0A3-468F-B18D-BD1821A72383}.Debug|Any CPU.Build.0 = Debug|Any CPU {08D29501-F0A3-468F-B18D-BD1821A72383}.Release|Any CPU.ActiveCfg = Release|Any CPU {08D29501-F0A3-468F-B18D-BD1821A72383}.Release|Any CPU.Build.0 = Release|Any CPU + {7BD1BED9-4CC1-46BE-9152-FF58CD70E08B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BD1BED9-4CC1-46BE-9152-FF58CD70E08B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BD1BED9-4CC1-46BE-9152-FF58CD70E08B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BD1BED9-4CC1-46BE-9152-FF58CD70E08B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/CodeAnalysis.props b/build/CodeAnalysis.props new file mode 100644 index 00000000000..330635b1c4b --- /dev/null +++ b/build/CodeAnalysis.props @@ -0,0 +1,21 @@ + + + enable + + + + AllEnabledByDefault + latest + + + + + $(NoWarn);nullable + + + + + all + + + diff --git a/build/Common.prod.props b/build/Common.prod.props index 2d77e1a1871..65d65d8e139 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -6,7 +6,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + All diff --git a/build/Common.props b/build/Common.props index 2bc3c531f61..2521e0a4ba1 100644 --- a/build/Common.props +++ b/build/Common.props @@ -1,6 +1,6 @@ - 8.0 + 9.0 true $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) $(MSBuildThisFileDirectory)debug.snk @@ -29,7 +29,8 @@ [2.1.1,6.0) [2.1.1,6.0) [1.0.7,2.0) - [3.3.1] + [3.3.2] + [5.0.3] [16.9.1] [2.1.0,6.0) [2.1.0,6.0) @@ -57,9 +58,6 @@ All - diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 33090f6de7a..43552a70e50 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -20,6 +20,7 @@ using System.Net.Http; using Examples.AspNetCore.Models; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Examples.AspNetCore.Controllers { @@ -32,7 +33,14 @@ public class WeatherForecastController : ControllerBase "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; - private static HttpClient httpClient = new HttpClient(); + private static readonly HttpClient HttpClient = new HttpClient(); + + private readonly ILogger logger; + + public WeatherForecastController(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } [HttpGet] public IEnumerable Get() @@ -40,15 +48,22 @@ public IEnumerable Get() // Making an http call here to serve as an example of // how dependency calls will be captured and treated // automatically as child of incoming request. - var res = httpClient.GetStringAsync("http://google.com").Result; + var res = HttpClient.GetStringAsync("http://google.com").Result; var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast + var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)], }) .ToArray(); + + this.logger.LogInformation( + "WeatherForecasts generated {count}: {forecasts}", + forecast.Length, + new { forecast }); + + return forecast; } } } diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index e067ca3eba6..bc4e85a684d 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -19,6 +19,7 @@ + diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index dbcce6603b2..9ef2a55c418 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -15,7 +15,9 @@ // using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Examples.AspNetCore { @@ -31,6 +33,23 @@ public static IHostBuilder CreateHostBuilder(string[] args) => .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); + }) + .ConfigureLogging((context, builder) => + { + builder.ClearProviders(); + builder.AddConsole(); + + var attachLogsToActivity = context.Configuration.GetValue("AttachLogsToActivity"); + if (attachLogsToActivity) + { + builder.AddOpenTelemetry(options => + { + options.IncludeScopes = true; + options.ParseStateValues = true; + options.IncludeFormattedMessage = true; + options.AddActivityEventAttachingLogProcessor(); + }); + } }); } } diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index fad736148cb..c6e1719d88c 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -7,6 +7,7 @@ } }, "AllowedHosts": "*", + "AttachLogsToActivity": true, "UseExporter": "console", "Jaeger": { "ServiceName": "jaeger-test", diff --git a/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net452/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..4fafd3cc483 --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +#nullable enable +Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ActivityEventAttachingLogProcessorOptions() -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.set -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.get -> System.Action>!>! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.set -> void +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddActivityEventAttachingLogProcessor(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! diff --git a/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..4fafd3cc483 --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/net5.0/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +#nullable enable +Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ActivityEventAttachingLogProcessorOptions() -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.set -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.get -> System.Action>!>! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.set -> void +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddActivityEventAttachingLogProcessor(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! diff --git a/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..4fafd3cc483 --- /dev/null +++ b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +#nullable enable +Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ActivityEventAttachingLogProcessorOptions() -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.set -> void +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.get -> System.Action>!>! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.set -> void +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddActivityEventAttachingLogProcessor(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! diff --git a/src/OpenTelemetry.Extensions/AssemblyInfo.cs b/src/OpenTelemetry.Extensions/AssemblyInfo.cs new file mode 100644 index 00000000000..11bfd5a2025 --- /dev/null +++ b/src/OpenTelemetry.Extensions/AssemblyInfo.cs @@ -0,0 +1,19 @@ +// +// 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. +// + +using System; + +[assembly: CLSCompliant(false)] diff --git a/src/OpenTelemetry.Extensions/CHANGELOG.md b/src/OpenTelemetry.Extensions/CHANGELOG.md new file mode 100644 index 00000000000..617d979ab29 --- /dev/null +++ b/src/OpenTelemetry.Extensions/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +## Unreleased + diff --git a/src/OpenTelemetry.Extensions/Internal/OpenTelemetryExtensionsEventSource.cs b/src/OpenTelemetry.Extensions/Internal/OpenTelemetryExtensionsEventSource.cs new file mode 100644 index 00000000000..7e3784bc42e --- /dev/null +++ b/src/OpenTelemetry.Extensions/Internal/OpenTelemetryExtensionsEventSource.cs @@ -0,0 +1,45 @@ +// +// 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. +// + +using System; +using System.Diagnostics.Tracing; + +namespace OpenTelemetry.Internal +{ + /// + /// EventSource implementation for OpenTelemetry SDK extensions implementation. + /// + [EventSource(Name = "OpenTelemetry-Extensions")] + internal class OpenTelemetryExtensionsEventSource : EventSource + { + public static OpenTelemetryExtensionsEventSource Log = new OpenTelemetryExtensionsEventSource(); + + [NonEvent] + public void LogProcessorException(string @event, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.LogProcessorException(@event, ex.ToInvariantString()); + } + } + + [Event(1, Message = "Unknown error in LogProcessor event '{0}': '{1}'.", Level = EventLevel.Error)] + public void LogProcessorException(string @event, string exception) + { + this.WriteEvent(1, @event, exception); + } + } +} diff --git a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs new file mode 100644 index 00000000000..fe93e9352f8 --- /dev/null +++ b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs @@ -0,0 +1,114 @@ +// +// 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_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER +using System; +using System.Diagnostics; +using OpenTelemetry.Internal; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Logs +{ + internal class ActivityEventAttachingLogProcessor : BaseProcessor + { + private static readonly Action ProcessScopeRef = ProcessScope; + + private readonly ActivityEventAttachingLogProcessorOptions options; + + public ActivityEventAttachingLogProcessor(ActivityEventAttachingLogProcessorOptions options) + { + this.options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public override void OnEnd(LogRecord data) + { + Activity? activity = Activity.Current; + + if (activity?.IsAllDataRequested == true) + { + var tags = new ActivityTagsCollection + { + { nameof(data.CategoryName), data.CategoryName }, + { nameof(data.LogLevel), data.LogLevel }, + }; + + if (data.EventId != 0) + { + tags[nameof(data.EventId)] = data.EventId; + } + + var activityEvent = new ActivityEvent("log", data.Timestamp, tags); + + data.ForEachScope(ProcessScopeRef, new State(tags, this)); + + if (data.StateValues != null) + { + try + { + this.options.StateConverter?.Invoke(tags, data.StateValues); + } + catch (Exception ex) + { + OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing state of type [{data.State.GetType().FullName}]", ex); + } + } + + if (!string.IsNullOrEmpty(data.FormattedMessage)) + { + tags["FormattedMessage"] = data.FormattedMessage; + } + + activity.AddEvent(activityEvent); + + if (data.Exception != null) + { + activity.RecordException(data.Exception); + } + } + } + + private static void ProcessScope(object scope, State state) + { + if (scope != null) + { + try + { + state.Processor.options.ScopeConverter?.Invoke(state.Tags, state.Index++, scope); + } + catch (Exception ex) + { + OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing scope of type [{scope.GetType().FullName}]", ex); + } + } + } + + private class State + { + public State(ActivityTagsCollection tags, ActivityEventAttachingLogProcessor processor) + { + this.Tags = tags; + this.Processor = processor; + } + + public ActivityTagsCollection Tags { get; } + + public ActivityEventAttachingLogProcessor Processor { get; } + + public int Index { get; set; } + } + } +} +#endif diff --git a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs new file mode 100644 index 00000000000..0d0cf9caf0c --- /dev/null +++ b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs @@ -0,0 +1,40 @@ +// +// 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_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenTelemetry.Logs +{ + /// + /// Stores options for the . + /// + public class ActivityEventAttachingLogProcessorOptions + { + /// + /// Gets or sets the callback action used to convert log state to tags. + /// + public Action>> StateConverter { get; set; } = DefaultLogStateConverter.ConvertState; + + /// + /// Gets or sets the callback action used to convert log scopes to tags. + /// + public Action ScopeConverter { get; set; } = DefaultLogStateConverter.ConvertScope; + } +} +#endif diff --git a/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs new file mode 100644 index 00000000000..eae7d9dbaeb --- /dev/null +++ b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs @@ -0,0 +1,178 @@ +// +// 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_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Emit; + +namespace OpenTelemetry.Logs +{ + internal static class DefaultLogStateConverter + { + private static readonly ConcurrentDictionary> TypePropertyCache = new ConcurrentDictionary>(); + + public static void ConvertState(ActivityTagsCollection tags, IReadOnlyList> state) + { + for (int i = 0; i < state.Count; i++) + { + KeyValuePair item = state[i]; + + if (!string.IsNullOrEmpty(item.Key)) + { + ConvertState(tags, $"state.{item.Key}", item.Value); + } + else + { + ConvertState(tags, $"state", item.Value); + } + } + } + + public static void ConvertScope(ActivityTagsCollection tags, int index, object scope) + { + ConvertState(tags, $"scope[{index}]", scope); + } + + private static void ConvertState(ActivityTagsCollection tags, string keyPrefix, object state) + { + if (state is IReadOnlyList> stateList) + { + for (int i = 0; i < stateList.Count; i++) + { + KeyValuePair item = stateList[i]; + + ConvertState(tags, $"{keyPrefix}.{item.Key}", item.Value); + } + } + else if (state is IEnumerable> stateValues) + { + foreach (KeyValuePair item in stateValues) + { + ConvertState(tags, $"{keyPrefix}.{item.Key}", item.Value); + } + } + else if (state != null) + { + Type type = state.GetType(); + if (type.IsValueType || type == typeof(string)) + { + if (keyPrefix == "state.{OriginalFormat}") + { + keyPrefix = "Format"; + } + + tags[keyPrefix] = state; + } + else if (state is IEnumerable enumerable) + { + int index = 0; + foreach (object stateItem in enumerable) + { + ConvertState(tags, $"{keyPrefix}[{index++}]", stateItem); + } + } + else + { + AddObjectToTags(tags, keyPrefix, state, type); + } + } + } + + private static void AddObjectToTags(ActivityTagsCollection tags, string keyPrefix, object item, Type itemType) + { + if (!TypePropertyCache.TryGetValue(itemType, out List? propertyGetters)) + { + PropertyInfo[] properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + propertyGetters = new List(properties.Length); + + foreach (PropertyInfo propertyInfo in properties) + { + if (propertyInfo.CanRead) + { + propertyGetters.Add(new PropertyGetter(itemType, propertyInfo)); + } + } + + TypePropertyCache.TryAdd(itemType, propertyGetters); + } + + foreach (PropertyGetter propertyGetter in propertyGetters) + { + object propertyValue = propertyGetter.GetPropertyFunc(item); + ConvertState(tags, $"{keyPrefix}.{propertyGetter.PropertyName}", propertyValue); + } + } + + private class PropertyGetter + { + public PropertyGetter(Type type, PropertyInfo propertyInfo) + { + this.PropertyName = propertyInfo.Name; + + this.GetPropertyFunc = BuildGetPropertyFunc(propertyInfo, type); + } + + public string PropertyName { get; } + + public Func GetPropertyFunc { get; } + + private static Func BuildGetPropertyFunc(PropertyInfo propertyInfo, Type runtimePropertyType) + { + MethodInfo realMethod = propertyInfo.GetMethod!; + + Type declaringType = propertyInfo.DeclaringType!; + + Type declaredPropertyType = propertyInfo.PropertyType; + + DynamicMethod dynamicMethod = new DynamicMethod( + nameof(PropertyGetter), + typeof(object), + new[] { typeof(object) }, + typeof(PropertyGetter).Module, + skipVisibility: true); + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + + if (declaringType.IsValueType) + { + generator.Emit(OpCodes.Unbox, declaringType); + generator.Emit(OpCodes.Call, realMethod); + } + else + { + generator.Emit(OpCodes.Castclass, declaringType); + generator.Emit(OpCodes.Callvirt, realMethod); + } + + if (declaredPropertyType != runtimePropertyType && declaredPropertyType.IsValueType) + { + generator.Emit(OpCodes.Box, declaredPropertyType); + } + + generator.Emit(OpCodes.Ret); + + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + } + } + } +} +#endif diff --git a/src/OpenTelemetry.Extensions/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry.Extensions/Logs/OpenTelemetryLoggingExtensions.cs new file mode 100644 index 00000000000..82f468b5340 --- /dev/null +++ b/src/OpenTelemetry.Extensions/Logs/OpenTelemetryLoggingExtensions.cs @@ -0,0 +1,55 @@ +// +// 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_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER +using System; +using System.Diagnostics; +using OpenTelemetry.Logs; + +namespace Microsoft.Extensions.Logging +{ + /// + /// Contains OpenTelemetry logging SDK extensions. + /// + public static class OpenTelemetryLoggingExtensions + { + /// + /// Adds a LogRecord Processor to the OpenTelemetry ILoggingBuilder + /// which converts messages into s on the + /// currently active . + /// + /// options to use. + /// . + /// The instance of to chain the calls. + /// is null. + public static OpenTelemetryLoggerOptions AddActivityEventAttachingLogProcessor( + this OpenTelemetryLoggerOptions loggerOptions, + Action? configure = null) + { + if (loggerOptions == null) + { + throw new ArgumentNullException(nameof(loggerOptions)); + } + + var options = new ActivityEventAttachingLogProcessorOptions(); + configure?.Invoke(options); +#pragma warning disable CA2000 // Dispose objects before losing scope + return loggerOptions.AddProcessor(new ActivityEventAttachingLogProcessor(options)); +#pragma warning restore CA2000 // Dispose objects before losing scope + } + } +} +#endif diff --git a/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj new file mode 100644 index 00000000000..ada9b195bfe --- /dev/null +++ b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj @@ -0,0 +1,18 @@ + + + + + net452;net461;netstandard2.0;net5.0 + OpenTelemetry .NET SDK Extensions + extensions- + + + + + + + + + + + diff --git a/src/OpenTelemetry.Extensions/README.md b/src/OpenTelemetry.Extensions/README.md new file mode 100644 index 00000000000..568234293ad --- /dev/null +++ b/src/OpenTelemetry.Extensions/README.md @@ -0,0 +1,4 @@ +# OpenTelemetry .NET SDK Extensions + +Contains useful extensions to the OpenTelemetry .NET SDK that are not part of +the official OpenTelemetry specification. From ccb5815a4081ac61fa6e7288b102c71593898a30 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 18 Mar 2021 21:33:24 -0700 Subject: [PATCH 2/6] Lint. --- src/OpenTelemetry.Extensions/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenTelemetry.Extensions/CHANGELOG.md b/src/OpenTelemetry.Extensions/CHANGELOG.md index 617d979ab29..1512c421622 100644 --- a/src/OpenTelemetry.Extensions/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions/CHANGELOG.md @@ -1,4 +1,3 @@ # Changelog ## Unreleased - From abdbac2d78757c8fd35e5f9c0339ae0c76d9e92b Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 18 Mar 2021 21:51:10 -0700 Subject: [PATCH 3/6] Code review. --- .../Controllers/WeatherForecastController.cs | 2 +- examples/AspNetCore/Models/WeatherForecast.cs | 2 +- .../Logs/DefaultLogStateConverter.cs | 85 +------------------ 3 files changed, 3 insertions(+), 86 deletions(-) diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 43552a70e50..55edd3e27b6 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -61,7 +61,7 @@ public IEnumerable Get() this.logger.LogInformation( "WeatherForecasts generated {count}: {forecasts}", forecast.Length, - new { forecast }); + forecast); return forecast; } diff --git a/examples/AspNetCore/Models/WeatherForecast.cs b/examples/AspNetCore/Models/WeatherForecast.cs index a614da07a73..50fd32f163f 100644 --- a/examples/AspNetCore/Models/WeatherForecast.cs +++ b/examples/AspNetCore/Models/WeatherForecast.cs @@ -18,7 +18,7 @@ namespace Examples.AspNetCore.Models { - public class WeatherForecast + public record WeatherForecast { public DateTime Date { get; set; } diff --git a/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs index eae7d9dbaeb..909af27e913 100644 --- a/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs +++ b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs @@ -17,18 +17,13 @@ #if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Reflection; -using System.Reflection.Emit; namespace OpenTelemetry.Logs { internal static class DefaultLogStateConverter { - private static readonly ConcurrentDictionary> TypePropertyCache = new ConcurrentDictionary>(); - public static void ConvertState(ActivityTagsCollection tags, IReadOnlyList> state) { for (int i = 0; i < state.Count; i++) @@ -91,86 +86,8 @@ private static void ConvertState(ActivityTagsCollection tags, string keyPrefix, } else { - AddObjectToTags(tags, keyPrefix, state, type); - } - } - } - - private static void AddObjectToTags(ActivityTagsCollection tags, string keyPrefix, object item, Type itemType) - { - if (!TypePropertyCache.TryGetValue(itemType, out List? propertyGetters)) - { - PropertyInfo[] properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - propertyGetters = new List(properties.Length); - - foreach (PropertyInfo propertyInfo in properties) - { - if (propertyInfo.CanRead) - { - propertyGetters.Add(new PropertyGetter(itemType, propertyInfo)); - } + tags[keyPrefix] = state.ToString(); } - - TypePropertyCache.TryAdd(itemType, propertyGetters); - } - - foreach (PropertyGetter propertyGetter in propertyGetters) - { - object propertyValue = propertyGetter.GetPropertyFunc(item); - ConvertState(tags, $"{keyPrefix}.{propertyGetter.PropertyName}", propertyValue); - } - } - - private class PropertyGetter - { - public PropertyGetter(Type type, PropertyInfo propertyInfo) - { - this.PropertyName = propertyInfo.Name; - - this.GetPropertyFunc = BuildGetPropertyFunc(propertyInfo, type); - } - - public string PropertyName { get; } - - public Func GetPropertyFunc { get; } - - private static Func BuildGetPropertyFunc(PropertyInfo propertyInfo, Type runtimePropertyType) - { - MethodInfo realMethod = propertyInfo.GetMethod!; - - Type declaringType = propertyInfo.DeclaringType!; - - Type declaredPropertyType = propertyInfo.PropertyType; - - DynamicMethod dynamicMethod = new DynamicMethod( - nameof(PropertyGetter), - typeof(object), - new[] { typeof(object) }, - typeof(PropertyGetter).Module, - skipVisibility: true); - ILGenerator generator = dynamicMethod.GetILGenerator(); - - generator.Emit(OpCodes.Ldarg_0); - - if (declaringType.IsValueType) - { - generator.Emit(OpCodes.Unbox, declaringType); - generator.Emit(OpCodes.Call, realMethod); - } - else - { - generator.Emit(OpCodes.Castclass, declaringType); - generator.Emit(OpCodes.Callvirt, realMethod); - } - - if (declaredPropertyType != runtimePropertyType && declaredPropertyType.IsValueType) - { - generator.Emit(OpCodes.Box, declaredPropertyType); - } - - generator.Emit(OpCodes.Ret); - - return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } } } From 693e07501ec5f5b1d0c4ae0ece515e239f57976d Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 18 Mar 2021 22:02:39 -0700 Subject: [PATCH 4/6] Project file tweak. --- src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj index ada9b195bfe..b036ee35e66 100644 --- a/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj +++ b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj @@ -2,7 +2,8 @@ - net452;net461;netstandard2.0;net5.0 + net452;net461;netstandard2.0 + $(TargetFrameworks);net5.0 OpenTelemetry .NET SDK Extensions extensions- From 8c36e40e6859f1aebfba32c14a81f07e326ec747 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 6 May 2021 14:11:20 -0700 Subject: [PATCH 5/6] Updates for new log scope API. Simplified state & scope default parsers. --- examples/AspNetCore/Program.cs | 2 + examples/AspNetCore/appsettings.json | 2 +- .../.publicApi/net461/PublicAPI.Unshipped.txt | 2 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 2 +- .../ActivityEventAttachingLogProcessor.cs | 19 +++--- ...tivityEventAttachingLogProcessorOptions.cs | 2 +- .../Logs/DefaultLogStateConverter.cs | 67 ++++++------------- 7 files changed, 34 insertions(+), 62 deletions(-) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 5055b765979..f23939bddd6 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -50,7 +50,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) => options.IncludeFormattedMessage = true; options.AddConsoleExporter(); if (context.Configuration.GetValue("AttachLogsToActivity")) + { options.AddActivityEventAttachingLogProcessor(); + } }); } }); diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index e034aa3a2db..56412444a5d 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -7,9 +7,9 @@ } }, "AllowedHosts": "*", - "AttachLogsToActivity": true, "UseExporter": "console", "UseLogging": true, + "AttachLogsToActivity": true, "Jaeger": { "ServiceName": "jaeger-test", "AgentHost": "localhost", diff --git a/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt index 4fafd3cc483..d8ad7cbaa56 100644 --- a/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions/.publicApi/net461/PublicAPI.Unshipped.txt @@ -2,7 +2,7 @@ Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ActivityEventAttachingLogProcessorOptions() -> void -OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.set -> void OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.get -> System.Action>!>! OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.set -> void diff --git a/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 4fafd3cc483..d8ad7cbaa56 100644 --- a/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,7 +2,7 @@ Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ActivityEventAttachingLogProcessorOptions() -> void -OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! +OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.get -> System.Action! OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.ScopeConverter.set -> void OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.get -> System.Action>!>! OpenTelemetry.Logs.ActivityEventAttachingLogProcessorOptions.StateConverter.set -> void diff --git a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs index fe93e9352f8..93b2f625f9d 100644 --- a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs +++ b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessor.cs @@ -24,7 +24,7 @@ namespace OpenTelemetry.Logs { internal class ActivityEventAttachingLogProcessor : BaseProcessor { - private static readonly Action ProcessScopeRef = ProcessScope; + private static readonly Action ProcessScopeRef = ProcessScope; private readonly ActivityEventAttachingLogProcessorOptions options; @@ -80,18 +80,15 @@ public override void OnEnd(LogRecord data) } } - private static void ProcessScope(object scope, State state) + private static void ProcessScope(LogRecordScope scope, State state) { - if (scope != null) + try { - try - { - state.Processor.options.ScopeConverter?.Invoke(state.Tags, state.Index++, scope); - } - catch (Exception ex) - { - OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing scope of type [{scope.GetType().FullName}]", ex); - } + state.Processor.options.ScopeConverter?.Invoke(state.Tags, state.Index++, scope); + } + catch (Exception ex) + { + OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing scope of type [{scope.GetType().FullName}]", ex); } } diff --git a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs index 0d0cf9caf0c..a5f98cf5b1b 100644 --- a/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs +++ b/src/OpenTelemetry.Extensions/Logs/ActivityEventAttachingLogProcessorOptions.cs @@ -34,7 +34,7 @@ public class ActivityEventAttachingLogProcessorOptions /// /// Gets or sets the callback action used to convert log scopes to tags. /// - public Action ScopeConverter { get; set; } = DefaultLogStateConverter.ConvertScope; + public Action ScopeConverter { get; set; } = DefaultLogStateConverter.ConvertScope; } } #endif diff --git a/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs index 909af27e913..8bae055c83d 100644 --- a/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs +++ b/src/OpenTelemetry.Extensions/Logs/DefaultLogStateConverter.cs @@ -15,8 +15,6 @@ // #if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER -using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -28,66 +26,41 @@ public static void ConvertState(ActivityTagsCollection tags, IReadOnlyList item = state[i]; + KeyValuePair stateItem = state[i]; - if (!string.IsNullOrEmpty(item.Key)) + object value = stateItem.Value; + if (value != null) { - ConvertState(tags, $"state.{item.Key}", item.Value); - } - else - { - ConvertState(tags, $"state", item.Value); + if (string.IsNullOrEmpty(stateItem.Key)) + { + tags["state"] = value; + } + else + { + tags[$"state.{stateItem.Key}"] = value; + } } } } - public static void ConvertScope(ActivityTagsCollection tags, int index, object scope) + public static void ConvertScope(ActivityTagsCollection tags, int index, LogRecordScope scope) { - ConvertState(tags, $"scope[{index}]", scope); - } - - private static void ConvertState(ActivityTagsCollection tags, string keyPrefix, object state) - { - if (state is IReadOnlyList> stateList) - { - for (int i = 0; i < stateList.Count; i++) - { - KeyValuePair item = stateList[i]; + string prefix = $"scope[{index}]"; - ConvertState(tags, $"{keyPrefix}.{item.Key}", item.Value); - } - } - else if (state is IEnumerable> stateValues) - { - foreach (KeyValuePair item in stateValues) - { - ConvertState(tags, $"{keyPrefix}.{item.Key}", item.Value); - } - } - else if (state != null) + foreach (KeyValuePair scopeItem in scope) { - Type type = state.GetType(); - if (type.IsValueType || type == typeof(string)) + object value = scopeItem.Value; + if (value != null) { - if (keyPrefix == "state.{OriginalFormat}") + if (string.IsNullOrEmpty(scopeItem.Key)) { - keyPrefix = "Format"; + tags[prefix] = value; } - - tags[keyPrefix] = state; - } - else if (state is IEnumerable enumerable) - { - int index = 0; - foreach (object stateItem in enumerable) + else { - ConvertState(tags, $"{keyPrefix}[{index++}]", stateItem); + tags[$"{prefix}.{scopeItem.Key}"] = value; } } - else - { - tags[keyPrefix] = state.ToString(); - } } } } From 3bbace48baa93ded6c1201ac7531be40c103064e Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 6 May 2021 14:21:14 -0700 Subject: [PATCH 6/6] Logs section in settings. --- examples/AspNetCore/Program.cs | 12 +++++++----- examples/AspNetCore/appsettings.json | 9 +++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index f23939bddd6..2c3a9c13954 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -40,16 +40,18 @@ public static IHostBuilder CreateHostBuilder(string[] args) => builder.ClearProviders(); builder.AddConsole(); - var useLogging = context.Configuration.GetValue("UseLogging"); + var loggingConfiguration = context.Configuration.GetSection("Logs"); + + var useLogging = loggingConfiguration.GetValue("Enabled"); if (useLogging) { builder.AddOpenTelemetry(options => { - options.IncludeScopes = true; - options.ParseStateValues = true; - options.IncludeFormattedMessage = true; + options.IncludeScopes = loggingConfiguration.GetValue("IncludeScopes"); + options.ParseStateValues = loggingConfiguration.GetValue("ParseStateValues"); + options.IncludeFormattedMessage = loggingConfiguration.GetValue("IncludeFormattedMessage"); options.AddConsoleExporter(); - if (context.Configuration.GetValue("AttachLogsToActivity")) + if (loggingConfiguration.GetValue("AttachLogsToActivity")) { options.AddActivityEventAttachingLogProcessor(); } diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index 56412444a5d..9c07a7ec2f6 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -8,8 +8,13 @@ }, "AllowedHosts": "*", "UseExporter": "console", - "UseLogging": true, - "AttachLogsToActivity": true, + "Logs": { + "Enabled": true, + "IncludeScopes": true, + "ParseStateValues": true, + "IncludeFormattedMessage": true, + "AttachLogsToActivity": true + }, "Jaeger": { "ServiceName": "jaeger-test", "AgentHost": "localhost",