From aa32666cb5337c69a0fae5f7e00527a731a32edc Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 30 Aug 2022 09:59:14 -0700 Subject: [PATCH] Add Logs support for ASP.NET Core apps (#1133) * Add OpenTelemetry Logs * Remove commented code * Rename OpenTelemetry.AutoInstrumentation.StartupBootstrapper to OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper. * PR feedback * PR feedback --- OpenTelemetry.AutoInstrumentation.sln | 15 +++ build/nuke/Build.Steps.cs | 9 ++ build/nuke/Projects.cs | 1 + .../Properties/launchSettings.json | 13 ++- .../BootstrapperHostingStartup.cs | 85 ++++++++++++++ ...trumentation.AspNetCoreBootstrapper.csproj | 20 ++++ .../Configuration/ConfigurationKeys.cs | 39 ++++++- .../Configuration/LogExporter.cs | 33 ++++++ .../Configuration/LogSettings.cs | 107 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 1 + 10 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/BootstrapperHostingStartup.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.csproj create mode 100644 src/OpenTelemetry.AutoInstrumentation/Configuration/LogExporter.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation/Configuration/LogSettings.cs diff --git a/OpenTelemetry.AutoInstrumentation.sln b/OpenTelemetry.AutoInstrumentation.sln index e7dbf6f04..135fcf3a8 100644 --- a/OpenTelemetry.AutoInstrumentation.sln +++ b/OpenTelemetry.AutoInstrumentation.sln @@ -135,6 +135,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.StackExchan EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.GrpcNetClient", "test\test-applications\integrations\TestApplication.GrpcNetClient\TestApplication.GrpcNetClient.csproj", "{0605872C-AB2B-4167-9B00-A525090D10BE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper", "src\OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper\OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.csproj", "{C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -571,6 +573,18 @@ Global {0605872C-AB2B-4167-9B00-A525090D10BE}.Release|x64.Build.0 = Release|x64 {0605872C-AB2B-4167-9B00-A525090D10BE}.Release|x86.ActiveCfg = Release|x86 {0605872C-AB2B-4167-9B00-A525090D10BE}.Release|x86.Build.0 = Release|x86 + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|x64.Build.0 = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Debug|x86.Build.0 = Debug|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|Any CPU.Build.0 = Release|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|x64.ActiveCfg = Release|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|x64.Build.0 = Release|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|x86.ActiveCfg = Release|Any CPU + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -617,6 +631,7 @@ Global {E7C2D2CF-C965-449D-A02C-02F7837D0C6D} = {E409ADD3-9574-465C-AB09-4324D205CC7C} {671EB8F0-E164-4E9F-B423-27AF4B59D360} = {E409ADD3-9574-465C-AB09-4324D205CC7C} {0605872C-AB2B-4167-9B00-A525090D10BE} = {E409ADD3-9574-465C-AB09-4324D205CC7C} + {C1AEDAE0-6629-4C88-AB35-AB5B81FD50F6} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F} diff --git a/build/nuke/Build.Steps.cs b/build/nuke/Build.Steps.cs index 3aa20296c..3bbd87f26 100644 --- a/build/nuke/Build.Steps.cs +++ b/build/nuke/Build.Steps.cs @@ -205,6 +205,15 @@ partial class Build .EnableNoRestore() .SetFramework(TargetFramework.NETCOREAPP3_1) .SetOutput(TracerHomeDirectory / TargetFramework.NETCOREAPP3_1)); + + DotNetPublish(s => s + .SetProject(Solution.GetProject(Projects.AutoInstrumentationAspNetCoreBootstrapper)) + .SetConfiguration(BuildConfiguration) + .SetTargetPlatformAnyCPU() + .EnableNoBuild() + .EnableNoRestore() + .SetFramework(TargetFramework.NETCOREAPP3_1) + .SetOutput(TracerHomeDirectory / TargetFramework.NETCOREAPP3_1)); }); Target PublishNativeProfiler => _ => _ diff --git a/build/nuke/Projects.cs b/build/nuke/Projects.cs index d168b26e5..2388bd633 100644 --- a/build/nuke/Projects.cs +++ b/build/nuke/Projects.cs @@ -5,6 +5,7 @@ public static class Projects public const string AutoInstrumentationNative = "OpenTelemetry.AutoInstrumentation.Native"; public const string AutoInstrumentationStartupHook = "OpenTelemetry.AutoInstrumentation.StartupHook"; public const string AutoInstrumentationAdditionalDeps = "OpenTelemetry.AutoInstrumentation.AdditionalDeps"; + public const string AutoInstrumentationAspNetCoreBootstrapper = "OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper"; public static class Tests { diff --git a/examples/AspNetCoreMvc/Properties/launchSettings.json b/examples/AspNetCoreMvc/Properties/launchSettings.json index 1b0d178f1..cbddc743a 100644 --- a/examples/AspNetCoreMvc/Properties/launchSettings.json +++ b/examples/AspNetCoreMvc/Properties/launchSettings.json @@ -19,7 +19,11 @@ "OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED": "true", "OTEL_DOTNET_AUTO_TRACES_PLUGINS": "Examples.AspNetCoreMvc.OtelSdkPlugin, Examples.AspNetCoreMvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "OTEL_SERVICE_NAME": "StartupHook.IISExpress", - "OTEL_TRACES_EXPORTER": "otlp" + "OTEL_TRACES_EXPORTER": "otlp", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper", + "OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED": "true", + "OTEL_DOTNET_AUTO_LOGS_PARSE_STATE_VALUES": "true", + "OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE": "true" }, "use64Bit": true, "nativeDebugging": true @@ -55,7 +59,12 @@ "OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED": "true", "OTEL_DOTNET_AUTO_TRACES_PLUGINS": "Examples.AspNetCoreMvc.OtelSdkPlugin, Examples.AspNetCoreMvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "OTEL_SERVICE_NAME": "StartupHook.Self-hosted", - "OTEL_TRACES_EXPORTER": "otlp" + "OTEL_TRACES_EXPORTER": "otlp", + "OTEL_DOTNET_AUTO_HOME": "$(SolutionDir)bin\\tracer-home", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper", + "OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED": "true", + "OTEL_DOTNET_AUTO_LOGS_PARSE_STATE_VALUES": "true", + "OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE": "true" }, "use64Bit": true, "nativeDebugging": true, diff --git a/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/BootstrapperHostingStartup.cs b/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/BootstrapperHostingStartup.cs new file mode 100644 index 000000000..fb0e626d5 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/BootstrapperHostingStartup.cs @@ -0,0 +1,85 @@ +// +// 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 Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry.AutoInstrumentation.Configuration; +using OpenTelemetry.Logs; + +[assembly: HostingStartup(typeof(OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.BootstrapperHostingStartup))] + +namespace OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper; + +/// +/// Add summary. +/// +public class BootstrapperHostingStartup : IHostingStartup +{ + private readonly LogSettings settings; + + /// + /// Initializes a new instance of the class. + /// + public BootstrapperHostingStartup() + { + settings = LogSettings.FromDefaultSources(); + } + + /// + /// This method gets called by the runtime to lightup ASP.NET Core OpenTelemetry Logs Collection. + /// + /// The . + public void Configure(IWebHostBuilder builder) + { + builder.ConfigureLogging(logging => logging.AddOpenTelemetry(options => + { + if (settings.ConsoleExporterEnabled) + { + options.AddConsoleExporter(); + } + + switch (settings.LogExporter) + { + case LogExporter.Otlp: +#if NETCOREAPP3_1 + if (settings.Http2UnencryptedSupportEnabled) + { + // Adding the OtlpExporter creates a GrpcChannel. + // This switch must be set before creating a GrpcChannel/HttpClient when calling an insecure gRPC service. + // See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + } +#endif + options.AddOtlpExporter(options => + { + if (settings.OtlpExportProtocol.HasValue) + { + options.Protocol = settings.OtlpExportProtocol.Value; + } + }); + break; + case LogExporter.None: + break; + default: + throw new ArgumentOutOfRangeException($"Traces exporter '{settings.LogExporter}' is incorrect"); + } + + options.ParseStateValues = settings.ParseStateValues; + options.IncludeFormattedMessage = settings.IncludeFormattedMessage; + })); + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.csproj b/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.csproj new file mode 100644 index 000000000..3c76405ab --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper/OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/ConfigurationKeys.cs index b29134b4f..88357c759 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configuration/ConfigurationKeys.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/ConfigurationKeys.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using OpenTelemetry.Logs; namespace OpenTelemetry.AutoInstrumentation.Configuration; @@ -51,7 +52,7 @@ public class ConfigurationKeys public const string FlushOnUnhandledException = "OTEL_DOTNET_AUTO_FLUSH_ON_UNHANDLEDEXCEPTION"; /// - /// Configuration keys for trace exporter + /// Configuration keys for traces. /// public static class Traces { @@ -98,7 +99,7 @@ public class ConfigurationKeys } /// - /// Configuration keys for metrics exporter + /// Configuration keys for metrics. /// public static class Metrics { @@ -144,6 +145,40 @@ public class ConfigurationKeys public const string AdditionalSources = "OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES"; } + /// + /// Configuration keys for logs. + /// + public static class Logs + { + /// + /// Configuration key for the logs exporter to be used. + /// Default is "otlp". + /// + public const string Exporter = "OTEL_LOGS_EXPORTER"; + + /// + /// Configuration key for whether the logs console exporter is enabled. + /// + public const string ConsoleExporterEnabled = "OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED"; + + /// + /// Configuration key for whether or not log state should be parsed into + /// on generated s. + /// + public const string ParseStateValues = "OTEL_DOTNET_AUTO_LOGS_PARSE_STATE_VALUES"; + + /// + /// Configuration key for whether or not formatted log message + /// should be included on generated s. + /// + public const string IncludeFormattedMessage = "OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE"; + + /// + /// Configuration key for colon (:) separated list of logs plugins represented by . + /// + public const string ProviderPlugins = "OTEL_DOTNET_AUTO_LOGS_PLUGINS"; + } + /// /// Configuration keys for Sdk /// diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/LogExporter.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/LogExporter.cs new file mode 100644 index 000000000..147868b05 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/LogExporter.cs @@ -0,0 +1,33 @@ +// +// 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. +// + +namespace OpenTelemetry.AutoInstrumentation.Configuration; + +/// +/// Enum representing supported log exporters. +/// +public enum LogExporter +{ + /// + /// None exporter. + /// + None, + + /// + /// OTLP exporter. + /// + Otlp, +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/LogSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/LogSettings.cs new file mode 100644 index 000000000..61bfce8b9 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/LogSettings.cs @@ -0,0 +1,107 @@ +// +// 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.Collections.Generic; + +namespace OpenTelemetry.AutoInstrumentation.Configuration; + +/// +/// Log Settings +/// +public class LogSettings : Settings +{ + /// + /// Initializes a new instance of the class + /// using the specified to initialize values. + /// + /// The to use when retrieving configuration values. + private LogSettings(IConfigurationSource source) + : base(source) + { + LogExporter = ParseLogExporter(source); + ConsoleExporterEnabled = source.GetBool(ConfigurationKeys.Logs.ConsoleExporterEnabled) ?? false; + ParseStateValues = source.GetBool(ConfigurationKeys.Logs.ParseStateValues) ?? false; + IncludeFormattedMessage = source.GetBool(ConfigurationKeys.Logs.IncludeFormattedMessage) ?? false; + + var providerPlugins = source.GetString(ConfigurationKeys.Logs.ProviderPlugins); + if (providerPlugins != null) + { + foreach (var pluginAssemblyQualifiedName in providerPlugins.Split(Constants.ConfigurationValues.DotNetQualifiedNameSeparator)) + { + LogPlugins.Add(pluginAssemblyQualifiedName); + } + } + } + + /// + /// Gets the logs exporter. + /// + public LogExporter LogExporter { get; } + + /// + /// Gets a value indicating whether the ParseStateValues is enabled. + /// + public bool ParseStateValues { get; } + + /// + /// Gets a value indicating whether the IncludeFormattedMessage is enabled. + /// + public bool IncludeFormattedMessage { get; } + + /// + /// Gets a value indicating whether the console exporter is enabled. + /// + public bool ConsoleExporterEnabled { get; } + + /// + /// Gets the list of plugins represented by . + /// + public IList LogPlugins { get; } = new List(); + + internal static LogSettings FromDefaultSources() + { + var configurationSource = new CompositeConfigurationSource + { + new EnvironmentConfigurationSource(), + +#if NETFRAMEWORK + // on .NET Framework only, also read from app.config/web.config + new NameValueConfigurationSource(System.Configuration.ConfigurationManager.AppSettings) +#endif + }; + + return new LogSettings(configurationSource); + } + + private static LogExporter ParseLogExporter(IConfigurationSource source) + { + var logExporterEnvVar = source.GetString(ConfigurationKeys.Logs.Exporter) + ?? Constants.ConfigurationValues.Exporters.Otlp; + + switch (logExporterEnvVar) + { + case null: + case "": + case Constants.ConfigurationValues.Exporters.Otlp: + return LogExporter.Otlp; + case Constants.ConfigurationValues.None: + return LogExporter.None; + default: + throw new FormatException($"Log exporter '{logExporterEnvVar}' is not supported"); + } + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Properties/AssemblyInfo.cs b/src/OpenTelemetry.AutoInstrumentation/Properties/AssemblyInfo.cs index d803ecf4d..203f11c1b 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Properties/AssemblyInfo.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Properties/AssemblyInfo.cs @@ -16,5 +16,6 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper")] [assembly: InternalsVisibleTo("OpenTelemetry.AutoInstrumentation.Tests")] [assembly: InternalsVisibleTo("IntegrationTests")]