Skip to content

Commit

Permalink
Adding support for default resource creation with environment variable (
Browse files Browse the repository at this point in the history
#1436)

* Adding default and envVar resource. Initial commit

* Adding Create method for resource creation with default resource and relevant unit tests.

* Adding isNullOrEmpty check to env var value to remove redundant condition.

* TracerProviderBuilder now calls Resource.Create to get the default resource with EnvVar attributes as well

* The default reource now includes the attributes from env var as well. Refactoring unit tests for the same.

* Adding ResourceDetector interface and public methods to add attributes from default and env var resources

* Missed out new resource files. Adding

* adding some more tests

* Adding missing import

* Updating public api list

* Refactoring method name for new methods

* Making IResourceDetector and corresponding impl internal until spec is official about detectors

* Refactoring return

* Was using wrong Sdk in test. Fixed.

* Adding error handling to OtelEnvResourceDetector

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
  • Loading branch information
srprash and cijothomas committed Nov 12, 2020
1 parent cb7f421 commit 4ab6da0
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 12 deletions.
6 changes: 6 additions & 0 deletions src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ public void SelfDiagnosticsFileCreateException(string logDirectory, string excep
this.WriteEvent(26, logDirectory, exception);
}

[Event(27, Message = "Failed to create resource from ResourceDetector: '{0}' due to '{1}'.", Level = EventLevel.Warning)]
public void ResourceDetectorFailed(string resourceDetector, string issue)
{
this.WriteEvent(27, resourceDetector, issue);
}

#if DEBUG
public class OpenTelemetryEventListener : EventListener
{
Expand Down
30 changes: 30 additions & 0 deletions src/OpenTelemetry/Resources/IResourceDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// <copyright file="IResourceDetector.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

namespace OpenTelemetry.Resources
{
/// <summary>
/// An interface for Resource detectors.
/// </summary>
internal interface IResourceDetector
{
/// <summary>
/// Called to get a resource with attributes from detector.
/// </summary>
/// <returns>An instance of <see cref="Resource"/>.</returns>
Resource Detect();
}
}
69 changes: 69 additions & 0 deletions src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// <copyright file="OtelEnvResourceDetector.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System;
using System.Collections.Generic;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Resources
{
internal class OtelEnvResourceDetector : IResourceDetector
{
private const string OTelResourceEnvVarKey = "OTEL_RESOURCE_ATTRIBUTES";
private const char AttributeListSplitter = ',';
private const char AttributeKeyValueSplitter = '=';

public Resource Detect()
{
var resource = Resource.Empty;

try
{
string envResourceAttributeValue = Environment.GetEnvironmentVariable(OTelResourceEnvVarKey);
if (!string.IsNullOrEmpty(envResourceAttributeValue))
{
var attributes = ParseResourceAttributes(envResourceAttributeValue);
resource = new Resource(attributes);
}
}
catch (Exception ex)
{
OpenTelemetrySdkEventSource.Log.ResourceDetectorFailed("OtelEnvResourceDetector", ex.Message);
}

return resource;
}

private static IEnumerable<KeyValuePair<string, object>> ParseResourceAttributes(string resourceAttributes)
{
var attributes = new List<KeyValuePair<string, object>>();

string[] rawAttributes = resourceAttributes.Split(AttributeListSplitter);
foreach (string rawKeyValuePair in rawAttributes)
{
string[] keyValuePair = rawKeyValuePair.Split(AttributeKeyValueSplitter);
if (keyValuePair.Length != 2)
{
continue;
}

attributes.Add(new KeyValuePair<string, object>(keyValuePair[0].Trim(), keyValuePair[1].Trim()));
}

return attributes;
}
}
}
38 changes: 38 additions & 0 deletions src/OpenTelemetry/Resources/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

using System;
using System.Collections.Generic;
using System.Linq;
using OpenTelemetry.Internal;
Expand All @@ -30,6 +31,11 @@ public class Resource
public const string ServiceNamespaceKey = "service.namespace";
public const string ServiceInstanceIdKey = "service.instance.id";
public const string ServiceVersionKey = "service.version";
private const string TelemetrySdkNameKey = "telemetry.sdk.name";
private const string TelemetrySdkLanguageKey = "telemetry.sdk.language";
private const string TelemetrySdkVersionKey = "telemetry.sdk.version";

private static readonly Version Version = typeof(Resource).Assembly.GetName().Version;

// this implementation follows https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/sdk.md

Expand Down Expand Up @@ -60,6 +66,13 @@ public Resource(IEnumerable<KeyValuePair<string, object>> attributes)
/// </summary>
public IEnumerable<KeyValuePair<string, object>> Attributes { get; }

private static Resource TelemetryResource { get; } = new Resource(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(TelemetrySdkNameKey, "opentelemetry"),
new KeyValuePair<string, object>(TelemetrySdkLanguageKey, "dotnet"),
new KeyValuePair<string, object>(TelemetrySdkVersionKey, Version.ToString()),
});

/// <summary>
/// Returns a new, merged <see cref="Resource"/> by merging the current <see cref="Resource"/> with the.
/// <code>other</code> <see cref="Resource"/>. In case of a collision the current <see cref="Resource"/> takes precedence.
Expand Down Expand Up @@ -92,6 +105,31 @@ public Resource Merge(Resource other)
return new Resource(newAttributes);
}

/// <summary>
/// Returns a new <see cref="Resource"/> with added attributes from telemetry sdk and the <see cref="OtelEnvResourceDetector"/>.
/// </summary>
/// <returns><see cref="Resource"/>.</returns>
internal Resource GetResourceWithDefaultAttributes()
{
return this.Merge(TelemetryResource).Merge(new OtelEnvResourceDetector().Detect());
}

/// <summary>
/// Returns a new <see cref="Resource"/> with added attributes from resource detectors in the order of the list.
/// </summary>
/// <param name="detectors">A list of <see cref="IResourceDetector"/>.</param>
/// <returns><see cref="Resource"/>.</returns>
internal Resource GetResourceFromDetectors(List<IResourceDetector> detectors)
{
var resource = this;
foreach (IResourceDetector detector in detectors)
{
resource = resource.Merge(detector.Detect());
}

return resource;
}

private static KeyValuePair<string, object> SanitizeAttribute(KeyValuePair<string, object> attribute)
{
string sanitizedKey = null;
Expand Down
4 changes: 2 additions & 2 deletions src/OpenTelemetry/Resources/Resources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static Resource CreateServiceResource(string serviceName, string serviceI
if (serviceName == null)
{
OpenTelemetrySdkEventSource.Log.InvalidArgument("Create service resource", "serviceName", "is null");
return Resource.Empty;
return Resource.Empty.GetResourceWithDefaultAttributes();
}

var attributes = new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>(Resource.ServiceNameKey, serviceName), };
Expand All @@ -58,7 +58,7 @@ public static Resource CreateServiceResource(string serviceName, string serviceI
attributes.Add(new KeyValuePair<string, object>(Resource.ServiceVersionKey, serviceVersion));
}

return new Resource(attributes);
return new Resource(attributes).GetResourceWithDefaultAttributes();
}
}
}
2 changes: 1 addition & 1 deletion src/OpenTelemetry/Trace/TracerProviderBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class TracerProviderBuilder

private readonly List<BaseProcessor<Activity>> processors = new List<BaseProcessor<Activity>>();
private readonly List<string> sources = new List<string>();
private Resource resource = Resource.Empty;
private Resource resource = Resource.Empty.GetResourceWithDefaultAttributes();
private Sampler sampler = new ParentBasedSampler(new AlwaysOnSampler());

internal TracerProviderBuilder()
Expand Down
74 changes: 74 additions & 0 deletions test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// <copyright file="OtelEnvResourceDetectorTest.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System;
using System.Collections.Generic;
using Xunit;

namespace OpenTelemetry.Resources.Tests
{
public class OtelEnvResourceDetectorTest : IDisposable
{
private const string OtelEnvVarKey = "OTEL_RESOURCE_ATTRIBUTES";

public OtelEnvResourceDetectorTest()
{
Environment.SetEnvironmentVariable(OtelEnvVarKey, null);
}

[Fact]
public void OtelEnvResource_NullEnvVar()
{
// Arrange
var resource = new OtelEnvResourceDetector().Detect();

// Assert
Assert.Equal(Resource.Empty, resource);
}

[Fact]
public void OtelEnvResource_WithEnvVar_1()
{
// Arrange
var envVarValue = "Key1=Val1,Key2=Val2";
Environment.SetEnvironmentVariable(OtelEnvVarKey, envVarValue);
var resource = new OtelEnvResourceDetector().Detect();

// Assert
Assert.NotEqual(Resource.Empty, resource);
Assert.Contains(new KeyValuePair<string, object>("Key1", "Val1"), resource.Attributes);
}

[Fact]
public void OtelEnvResource_WithEnvVar_2()
{
// Arrange
var envVarValue = "Key1,Key2=Val2";
Environment.SetEnvironmentVariable(OtelEnvVarKey, envVarValue);
var resource = new OtelEnvResourceDetector().Detect();

// Assert
Assert.NotEqual(Resource.Empty, resource);
Assert.Single(resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("Key2", "Val2"), resource.Attributes);
}

public void Dispose()
{
Environment.SetEnvironmentVariable(OtelEnvVarKey, null);
}
}
}
Loading

0 comments on commit 4ab6da0

Please sign in to comment.