diff --git a/CHANGELOG.md b/CHANGELOG.md index e482180ab4..7c3f6c2dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h - Support for [ASP.NET Core 10 metrics](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0). - Support for ASP.NET Core 10 Blazor traces from `Microsoft.AspNetCore.Components` - and `"Microsoft.AspNetCore.Components.Server.Circuits`. + and `Microsoft.AspNetCore.Components.Server.Circuits`. - Experimental support for file-based configuration. - Experimental support for configuration based instrumentation. - IL rewrite for SqlCommand on .NET Framework to ensure `CommandText` is @@ -102,6 +102,10 @@ and runtime, then loads the correct version of dependency assemblies. See [#4269](https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/issues/4269) for details. - Fixed rule engine check for .NET 9 to reflect longer support for [STS channel](https://devblogs.microsoft.com/dotnet/dotnet-sts-releases-supported-for-24-months/). +- Fix bug in signal specific OTLP exporter variables: `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, + `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` and `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`. + See [#4593](https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/issues/4593) + for details. ## [1.13.0-beta.1](https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/tag/v1.13.0-beta.1) diff --git a/src/OpenTelemetry.AutoInstrumentation/AutoInstrumentationEventSource.cs b/src/OpenTelemetry.AutoInstrumentation/AutoInstrumentationEventSource.cs index 63e2c135e4..4268d4933e 100644 --- a/src/OpenTelemetry.AutoInstrumentation/AutoInstrumentationEventSource.cs +++ b/src/OpenTelemetry.AutoInstrumentation/AutoInstrumentationEventSource.cs @@ -40,7 +40,7 @@ public void Information(string message) WriteEvent(4, message); } - /// Logs as Warning level message. + /// Logs as Verbose level message. /// Message to log. [Event(5, Message = "{0}", Level = EventLevel.Verbose)] public void Verbose(string message) diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs index 9d5be2854a..1ed8dd216a 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; +using OpenTelemetry.AutoInstrumentation.Logging; using OpenTelemetry.Exporter; namespace OpenTelemetry.AutoInstrumentation.Configurations.Otlp; @@ -12,6 +13,8 @@ namespace OpenTelemetry.AutoInstrumentation.Configurations.Otlp; /// internal class OtlpSettings { + private static readonly IOtelLogger Logger = OtelLogging.GetLogger(); + public OtlpSettings(OtlpSignalType signalType, Configuration configuration) { Protocol = GetExporterOtlpProtocol(signalType, configuration); @@ -156,18 +159,37 @@ private static Uri GetOtlpHttpEndpoint(string? endpoint, OtlpSignalType signalTy private static OtlpExportProtocol? GetExporterOtlpProtocol(OtlpSignalType signalType, Configuration configuration) { - // the default in SDK is grpc. http/protobuf should be default for our purposes + // http/protobuf should be default for our purposes. Always set a value to avoid relying on SDK, because the default in SDK is grpc. var priorityVar = OtlpSpecConfigDefinitions.GetProtocolEnvVar(signalType); - var exporterOtlpProtocol = configuration.GetString(priorityVar) ?? - configuration.GetString(OtlpSpecConfigDefinitions.DefaultProtocolEnvVarName); + var defaultVar = OtlpSpecConfigDefinitions.DefaultProtocolEnvVarName; + string? usedEnvVarName = priorityVar; + var exporterOtlpProtocol = configuration.GetString(priorityVar); + if (exporterOtlpProtocol == null) + { + exporterOtlpProtocol = configuration.GetString(defaultVar); + usedEnvVarName = defaultVar; + } - if (string.IsNullOrEmpty(exporterOtlpProtocol)) + if (!string.IsNullOrEmpty(exporterOtlpProtocol)) { - // override settings only for http/protobuf - return OtlpExportProtocol.HttpProtobuf; + switch (exporterOtlpProtocol) + { + case "grpc": +#if NETFRAMEWORK + Logger.Warning($"OTLP protocol 'grpc' is not supported on .NET Framework in environment variable '{usedEnvVarName}'. Changing to 'http/protobuf' instead."); + return OtlpExportProtocol.HttpProtobuf; +#else + return OtlpExportProtocol.Grpc; +#endif + case "http/protobuf": + return OtlpExportProtocol.HttpProtobuf; + default: + Logger.Warning($"Invalid OTLP protocol value '{exporterOtlpProtocol}' in environment variable '{usedEnvVarName}'. Supported values are 'grpc' and 'http/protobuf'. Defaulting to 'http/protobuf'."); + return OtlpExportProtocol.HttpProtobuf; + } } - // null value here means that it will be handled by OTEL .NET SDK - return null; + // In case of absent value, it will fall back to default value + return OtlpExportProtocol.HttpProtobuf; } } diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs index e9f2e2f330..f226d3213f 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Reflection; using OpenTelemetry.AutoInstrumentation.Configurations; using OpenTelemetry.Exporter; using Xunit; @@ -379,9 +380,13 @@ internal void IncludeFormattedMessage_DependsOnCorrespondingEnvVariable(string i [Theory] [InlineData("", OtlpExportProtocol.HttpProtobuf)] [InlineData(null, OtlpExportProtocol.HttpProtobuf)] - [InlineData("http/protobuf", null)] - [InlineData("grpc", null)] - [InlineData("nonExistingProtocol", null)] + [InlineData("http/protobuf", OtlpExportProtocol.HttpProtobuf)] +#if NETFRAMEWORK + [InlineData("grpc", OtlpExportProtocol.HttpProtobuf)] +#else + [InlineData("grpc", OtlpExportProtocol.Grpc)] +#endif + [InlineData("nonExistingProtocol", OtlpExportProtocol.HttpProtobuf)] internal void OtlpExportProtocol_DependsOnCorrespondingEnvVariable(string? otlpProtocol, OtlpExportProtocol? expectedOtlpExportProtocol) { Environment.SetEnvironmentVariable(AutoOtlpDefinitions.DefaultProtocolEnvVarName, otlpProtocol); @@ -393,6 +398,78 @@ internal void OtlpExportProtocol_DependsOnCorrespondingEnvVariable(string? otlpP Assert.Equal(expectedOtlpExportProtocol, settings.OtlpSettings.Protocol); } + [Theory] + [InlineData("http/protobuf", "1", "key1=value1,key2=value2", OtlpExportProtocol.HttpProtobuf, 1)] +#if NETFRAMEWORK + [InlineData("grpc", "15000", "a=b,c=d", OtlpExportProtocol.HttpProtobuf, 15000)] +#else + [InlineData("grpc", "15000", "a=b,c=d", OtlpExportProtocol.Grpc, 15000)] +#endif + [InlineData("invalid", "42", "x=y", OtlpExportProtocol.HttpProtobuf, 42)] + internal void OtlpExportProtocol_CheckPriorityEnvIsSet_Traces(string? protocol, string timeout, string headers, OtlpExportProtocol? expectedProtocol, int expectedTimeout) + { + var unexpectedProtocolForDistraction = expectedProtocol == OtlpExportProtocol.HttpProtobuf ? "grpc" : "http/protobuf"; + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.DefaultProtocolEnvVarName, unexpectedProtocolForDistraction); // set a different default to verify priority + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.TracesProtocolEnvVarName, protocol); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.TracesTimeoutEnvVarName, timeout); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.TracesHeadersEnvVarName, headers); + + var settings = Settings.FromDefaultSources(false).OtlpSettings; + + Assert.NotNull(settings); + Assert.Equal(expectedProtocol, settings.Protocol); + Assert.Equal(expectedTimeout, settings.TimeoutMilliseconds); + Assert.Equal(headers, settings.Headers); + } + + [Theory] + [InlineData("http/protobuf", "1", "key1=value1,key2=value2", OtlpExportProtocol.HttpProtobuf, 1)] +#if NETFRAMEWORK + [InlineData("grpc", "25000", "m=n", OtlpExportProtocol.HttpProtobuf, 25000)] +#else + [InlineData("grpc", "25000", "m=n", OtlpExportProtocol.Grpc, 25000)] +#endif + [InlineData("invalid", "100", "a=b", OtlpExportProtocol.HttpProtobuf, 100)] + internal void OtlpExportProtocol_CheckPriorityEnvIsSet_Metrics(string? protocol, string timeout, string headers, OtlpExportProtocol? expectedProtocol, int expectedTimeout) + { + var unexpectedProtocolForDistraction = expectedProtocol == OtlpExportProtocol.HttpProtobuf ? "grpc" : "http/protobuf"; + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.DefaultProtocolEnvVarName, unexpectedProtocolForDistraction); // set a different default to verify priority + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.MetricsProtocolEnvVarName, protocol); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.MetricsTimeoutEnvVarName, timeout); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.MetricsHeadersEnvVarName, headers); + + var settings = Settings.FromDefaultSources(false).OtlpSettings; + + Assert.NotNull(settings); + Assert.Equal(expectedProtocol, settings.Protocol); + Assert.Equal(expectedTimeout, settings.TimeoutMilliseconds); + Assert.Equal(headers, settings.Headers); + } + + [Theory] + [InlineData("http/protobuf", "1", "key1=value1,key2=value2", OtlpExportProtocol.HttpProtobuf, 1)] +#if NETFRAMEWORK + [InlineData("grpc", "5000", "l=m", OtlpExportProtocol.HttpProtobuf, 5000)] +#else + [InlineData("grpc", "5000", "l=m", OtlpExportProtocol.Grpc, 5000)] +#endif + [InlineData("invalid", "77", "z=zz", OtlpExportProtocol.HttpProtobuf, 77)] + internal void OtlpExportProtocol_CheckPriorityEnvIsSet_Logs(string? protocol, string timeout, string headers, OtlpExportProtocol? expectedProtocol, int expectedTimeout) + { + var unexpectedProtocolForDistraction = expectedProtocol == OtlpExportProtocol.HttpProtobuf ? "grpc" : "http/protobuf"; + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.DefaultProtocolEnvVarName, unexpectedProtocolForDistraction); // set a different default to verify priority + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.LogsProtocolEnvVarName, protocol); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.LogsTimeoutEnvVarName, timeout); + Environment.SetEnvironmentVariable(AutoOtlpDefinitions.LogsHeadersEnvVarName, headers); + + var settings = Settings.FromDefaultSources(false).OtlpSettings; + + Assert.NotNull(settings); + Assert.Equal(expectedProtocol, settings.Protocol); + Assert.Equal(expectedTimeout, settings.TimeoutMilliseconds); + Assert.Equal(headers, settings.Headers); + } + [Theory] [InlineData("true", true)] [InlineData("false", false)] @@ -477,5 +554,20 @@ private static void ClearEnvVars() Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders, null); Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders, null); Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.OracleMdaSetDbStatementForText, null); + + // Cleanup OTLP env vars + foreach (var envVar in GetAllOtlpEnvVarNames()) + { + Environment.SetEnvironmentVariable(envVar, null); + } + } + + private static IEnumerable GetAllOtlpEnvVarNames() + { + return typeof(AutoOtlpDefinitions) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => f.FieldType == typeof(string)) + .Select(f => (string)f.GetValue(null)!) + .ToList() ?? Enumerable.Empty(); } }