diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 4945b0eaaff..48f07ef1d72 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -22,6 +22,9 @@ Notes](../../RELEASENOTES.md). * Improve performance and reduce memory consumption for metrics histograms. ([#6715](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6715)) +* Decode `value` in OTEL_RESOURCE_ATTRIBUTES environment variable. + ([#6737](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6737)) + ## 1.14.0 Released 2025-Nov-12 diff --git a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs index 468b37ca1a9..15440aa4831 100644 --- a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs +++ b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Net; using Microsoft.Extensions.Configuration; namespace OpenTelemetry.Resources; @@ -9,7 +10,7 @@ internal sealed class OtelEnvResourceDetector : IResourceDetector { public const string EnvVarKey = "OTEL_RESOURCE_ATTRIBUTES"; private const char AttributeListSplitter = ','; - private const char AttributeKeyValueSplitter = '='; + private static readonly char[] AttributeKeyValueSplitter = ['=']; private readonly IConfiguration configuration; @@ -38,13 +39,14 @@ private static List> ParseResourceAttributes(string string[] rawAttributes = resourceAttributes.Split(AttributeListSplitter); foreach (string rawKeyValuePair in rawAttributes) { - string[] keyValuePair = rawKeyValuePair.Split(AttributeKeyValueSplitter); + string[] keyValuePair = rawKeyValuePair.Split(AttributeKeyValueSplitter, 2); if (keyValuePair.Length != 2) { continue; } - attributes.Add(new KeyValuePair(keyValuePair[0].Trim(), keyValuePair[1].Trim())); + var value = WebUtility.UrlDecode(keyValuePair[1].Trim()); + attributes.Add(new KeyValuePair(keyValuePair[0].Trim(), value)); } return attributes; diff --git a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs index 2f2c337d67e..c47bc6ea517 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs @@ -68,6 +68,27 @@ public void OtelEnvResource_WithEnvVar_2() Assert.Contains(new KeyValuePair("Key2", "Val2"), resource.Attributes); } + [Theory] + [InlineData("Key1=Val1%20With%20Spaces", "Key1", "Val1 With Spaces")] + [InlineData(" query= select%20*%20from%20foo ", "query", "select * from foo")] + [InlineData("raw=100%25%", "raw", "100%%")] + [InlineData("bad=%G1value", "bad", "%G1value")] + [InlineData("a=%2C%3B%3D", "a", ",;=")] + [InlineData("url=https://x.com?a=1", "url", "https://x.com?a=1")] + public void OtelEnvResource_WithEnvVar_Decoding(string envVarValue, string key, string value) + { + // Arrange + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, envVarValue); + var resource = new OtelEnvResourceDetector( + new ConfigurationBuilder().AddEnvironmentVariables().Build()) + .Detect(); + + // Assert + Assert.NotEqual(Resource.Empty, resource); + Assert.Single(resource.Attributes); + Assert.Contains(new KeyValuePair(key, value), resource.Attributes); + } + [Fact] public void OtelEnvResource_UsingIConfiguration() {