diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index d42846d6996..c464714f603 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* The `OtlpExporterOptions` defaults can be overridden using + `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS` and `OTEL_EXPORTER_OTLP_TIMEOUT` + envionmental variables as defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md). + ([#2188](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2188)) + ## 1.2.0-alpha1 Released 2021-Jul-23 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs index 27d4ec3de9e..1824c82fb69 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs @@ -16,6 +16,7 @@ using System; using System.Diagnostics.Tracing; +using System.Security; using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation @@ -25,6 +26,15 @@ internal class OpenTelemetryProtocolExporterEventSource : EventSource { public static readonly OpenTelemetryProtocolExporterEventSource Log = new OpenTelemetryProtocolExporterEventSource(); + [NonEvent] + public void MissingPermissionsToReadEnvironmentVariable(SecurityException ex) + { + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.MissingPermissionsToReadEnvironmentVariable(ex.ToInvariantString()); + } + } + [NonEvent] public void FailedToConvertToProtoDefinitionError(Exception ex) { @@ -81,5 +91,17 @@ public void CouldNotTranslateMetric(string className, string methodName) { this.WriteEvent(5, className, methodName); } + + [Event(6, Message = "Failed to parse environment variable: '{0}', value: '{1}'.", Level = EventLevel.Warning)] + public void FailedToParseEnvironmentVariable(string name, string value) + { + this.WriteEvent(6, name, value); + } + + [Event(7, Message = "Missing permissions to read environment variable: '{0}'", Level = EventLevel.Warning)] + public void MissingPermissionsToReadEnvironmentVariable(string exception) + { + this.WriteEvent(7, exception); + } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 7102bab456c..f112e76a773 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -16,6 +16,8 @@ using System; using System.Diagnostics; +using System.Security; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; namespace OpenTelemetry.Exporter { @@ -24,6 +26,57 @@ namespace OpenTelemetry.Exporter /// public class OtlpExporterOptions { + internal const string EndpointEnvVarName = "OTEL_EXPORTER_OTLP_ENDPOINT"; + internal const string HeadersEnvVarName = "OTEL_EXPORTER_OTLP_HEADERS"; + internal const string TimeoutEnvVarName = "OTEL_EXPORTER_OTLP_TIMEOUT"; + + /// + /// Initializes a new instance of the class. + /// + public OtlpExporterOptions() + { + try + { + string endpointEnvVar = Environment.GetEnvironmentVariable(EndpointEnvVarName); + if (!string.IsNullOrEmpty(endpointEnvVar)) + { + if (Uri.TryCreate(endpointEnvVar, UriKind.Absolute, out var endpoint)) + { + this.Endpoint = endpoint; + } + else + { + OpenTelemetryProtocolExporterEventSource.Log.FailedToParseEnvironmentVariable(EndpointEnvVarName, endpointEnvVar); + } + } + + string headersEnvVar = Environment.GetEnvironmentVariable(HeadersEnvVarName); + if (!string.IsNullOrEmpty(headersEnvVar)) + { + this.Headers = headersEnvVar; + } + + string timeoutEnvVar = Environment.GetEnvironmentVariable(TimeoutEnvVarName); + if (!string.IsNullOrEmpty(timeoutEnvVar)) + { + if (int.TryParse(timeoutEnvVar, out var timeout)) + { + this.TimeoutMilliseconds = timeout; + } + else + { + OpenTelemetryProtocolExporterEventSource.Log.FailedToParseEnvironmentVariable(TimeoutEnvVarName, timeoutEnvVar); + } + } + } + catch (SecurityException ex) + { + // The caller does not have the required permission to + // retrieve the value of an environment variable from the current process. + OpenTelemetryProtocolExporterEventSource.Log.MissingPermissionsToReadEnvironmentVariable(ex); + } + } + /// /// Gets or sets the target to which the exporter is going to send traces. /// Must be a valid Uri with scheme (http) and host, and diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs new file mode 100644 index 00000000000..d7a698a0d6c --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs @@ -0,0 +1,112 @@ +// +// 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 Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests +{ + public class OtlpExporterOptionsTests : IDisposable + { + public OtlpExporterOptionsTests() + { + ClearEnvVars(); + } + + public void Dispose() + { + ClearEnvVars(); + } + + [Fact] + public void OtlpExporterOptions_Defaults() + { + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://localhost:4317"), options.Endpoint); + Assert.Null(options.Headers); + Assert.Equal(10000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_EnvironmentVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://test:8888"), options.Endpoint); + Assert.Equal("A=2,B=3", options.Headers); + Assert.Equal(2000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_InvalidEndpointVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "invalid"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://localhost:4317"), options.Endpoint); // use default + } + + [Fact] + public void OtlpExporterOptions_InvalidTimeoutVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "invalid"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(10000, options.TimeoutMilliseconds); // use default + } + + [Fact] + public void OtlpExporterOptions_SetterOverridesEnvironmentVariable() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000"); + + var options = new OtlpExporterOptions + { + Endpoint = new Uri("http://localhost:200"), + Headers = "C=3", + TimeoutMilliseconds = 40000, + }; + + Assert.Equal(new Uri("http://localhost:200"), options.Endpoint); + Assert.Equal("C=3", options.Headers); + Assert.Equal(40000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_EnvironmentVariableNames() + { + Assert.Equal("OTEL_EXPORTER_OTLP_ENDPOINT", OtlpExporterOptions.EndpointEnvVarName); + Assert.Equal("OTEL_EXPORTER_OTLP_HEADERS", OtlpExporterOptions.HeadersEnvVarName); + Assert.Equal("OTEL_EXPORTER_OTLP_TIMEOUT", OtlpExporterOptions.TimeoutEnvVarName); + } + + private static void ClearEnvVars() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, null); + } + } +}