Skip to content

Commit

Permalink
open-telemetry#172 Resource detectors for Docker
Browse files Browse the repository at this point in the history
- Added Resource detectors for Docker environment to extract container.id
- Added Unit Tests
  • Loading branch information
swetharavichandrancisco committed Feb 14, 2022
1 parent 9373eb5 commit 207200f
Show file tree
Hide file tree
Showing 13 changed files with 542 additions and 0 deletions.
14 changes: 14 additions & 0 deletions opentelemetry-dotnet-contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Owin", "examples\o
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Instrumentation.Owin.Tests", "test\OpenTelemetry.Contrib.Instrumentation.Owin.Tests\OpenTelemetry.Contrib.Instrumentation.Owin.Tests.csproj", "{D52558C8-B7BF-4F59-A0FA-9AA629E68012}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Contrib.Extensions.Docker", "src\OpenTelemetry.Contrib.Extensions.Docker\OpenTelemetry.Contrib.Extensions.Docker.csproj", "{498A6808-C0DF-441F-A764-51A3BC4B8FC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Contrib.Extensions.Docker.Tests", "test\OpenTelemetry.Contrib.Extensions.Docker.Tests\OpenTelemetry.Contrib.Extensions.Docker.Tests.csproj", "{FB41E19E-2682-4D07-BA59-FD5205AFA71E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -306,6 +310,14 @@ Global
{D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Release|Any CPU.Build.0 = Release|Any CPU
{498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Release|Any CPU.Build.0 = Release|Any CPU
{FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -353,6 +365,8 @@ Global
{8D11A34C-D0EF-4DE1-8230-32168E67044D} = {B75EE478-97F7-4E9F-9A5A-DB3D0988EDEA}
{6B3AA3F2-89A7-433F-918A-1E5E6AAF8423} = {8D11A34C-D0EF-4DE1-8230-32168E67044D}
{D52558C8-B7BF-4F59-A0FA-9AA629E68012} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
{498A6808-C0DF-441F-A764-51A3BC4B8FC5} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
{FB41E19E-2682-4D07-BA59-FD5205AFA71E} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66}
Expand Down
23 changes: 23 additions & 0 deletions src/OpenTelemetry.Contrib.Extensions.Docker/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <copyright file="AssemblyInfo.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.Runtime.CompilerServices;

#if SIGNED
[assembly: InternalsVisibleTo("OpenTelemetry.Contrib.Extensions.Docker.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")]
#else
[assembly: InternalsVisibleTo("OpenTelemetry.Contrib.Extensions.Docker.Tests")]
#endif
63 changes: 63 additions & 0 deletions src/OpenTelemetry.Contrib.Extensions.Docker/DockerEventSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// <copyright file="DockerEventSource.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.Diagnostics.Tracing;
using System.Globalization;
using System.Threading;

namespace OpenTelemetry.Contrib.Extensions.Docker
{
[EventSource(Name = "OpenTelemetry-Docker")]
internal class DockerEventSource : EventSource
{
public static DockerEventSource Log = new DockerEventSource();

[NonEvent]
public void ResourceAttributesExtractException(string format, Exception ex)
{
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1)))
{
this.FailedToExtractResourceAttributes(format, ToInvariantString(ex));
}
}

[Event(3, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Warning)]
public void FailedToExtractResourceAttributes(string format, string exception)
{
this.WriteEvent(3, format, exception);
}

/// <summary>
/// Returns a culture-independent string representation of the given <paramref name="exception"/> object,
/// appropriate for diagnostics tracing.
/// </summary>
private static string ToInvariantString(Exception exception)
{
var originalUICulture = Thread.CurrentThread.CurrentUICulture;

try
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
return exception.ToString();
}
finally
{
Thread.CurrentThread.CurrentUICulture = originalUICulture;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
<Description>OpenTelemetry extensions for Docker</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.1.0" />
</ItemGroup>
</Project>
26 changes: 26 additions & 0 deletions src/OpenTelemetry.Contrib.Extensions.Docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

### Docker Resource Detectors

You can configure Docker resource detector to
the `TracerProvider` with the following example below.

```csharp
using OpenTelemetry;
using OpenTelemetry.Contrib.Extensions.Docker;

var tracerProvider = Sdk.CreateTracerProviderBuilder()
// other configurations
.SetResourceBuilder(ResourceBuilder
.CreateDefault()
.AddDockerDetector())
.Build();
```

The resource detectors will record the following metadata based on where
your application is running:

- **DockerResourceDetector**: container id.

## References

- [OpenTelemetry Project](https://opentelemetry.io/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// <copyright file="DockerResourceDetector.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 System.IO;
using OpenTelemetry.Contrib.Extensions.Docker.Utils;

namespace OpenTelemetry.Contrib.Extensions.Docker.Resources
{
/// <summary>
/// Resource detector for application running in Docker environment.
/// </summary>
public class DockerResourceDetector : IResourceDetector
{
private const string FILEPATH = "/proc/self/cgroup";

/// <summary>
/// Detects the resource attributes from Docker.
/// </summary>
/// <returns>List of key-value pairs of resource attributes.</returns>
public IEnumerable<KeyValuePair<string, object>> Detect()
{
string containerId = this.ExtractContainerId(FILEPATH);

if (string.IsNullOrEmpty(containerId))
{
return null;
}

return this.BuildResourceAttributes(containerId);
}

/// <summary>
/// Builds the resource attributes from Container Id.
/// </summary>
/// <param name="containerId">Container Id</param>
/// <returns>List of key-value pairs of resource attributes.</returns>
internal List<KeyValuePair<string, object>> BuildResourceAttributes(string containerId)
{
var resourceAttributes = new List<KeyValuePair<string, object>>()
{
new KeyValuePair<string, object>(DockerSemanticConventions.AttributeContainerID, containerId),
};

return resourceAttributes;
}

/// <summary>
/// Extracts Container Id from path.
/// </summary>
/// <param name="path">cgroup path.</param>
/// <returns>Container Id, Null if not found or exception being thrown.</returns>
internal string ExtractContainerId(string path)
{
try
{
if (!File.Exists(path))
{
return null;
}

foreach (string line in File.ReadLines(path))
{
string containerId = (!string.IsNullOrEmpty(line)) ? this.GetIdFromLine(line) : null;
if (!string.IsNullOrEmpty(containerId))
{
return containerId;
}
}
}
catch (Exception ex)
{
DockerEventSource.Log.ResourceAttributesExtractException($"{nameof(DockerResourceDetector)} : Failed to extract Container id from path", ex);
}

return null;
}

/// <summary>
/// Gets the Container Id from the line after removing the prefix and suffix.
/// </summary>
/// <param name="line">line read from cgroup file.</param>
/// <returns>Container Id.</returns>
internal string GetIdFromLine(string line)
{
// This cgroup output line should have the container id in it
int lastSlashIndex = line.LastIndexOf('/');
if (lastSlashIndex < 0)
{
return null;
}

string lastSection = line.Substring(lastSlashIndex + 1);
int startIndex = lastSection.LastIndexOf('-');
int endIndex = lastSection.LastIndexOf('.');

string containerId = this.RemovePrefixAndSuffixIfneeded(lastSection, startIndex, endIndex);

if (string.IsNullOrEmpty(containerId) || !EncodingUtils.IsValidHexString(containerId))
{
return null;
}

return containerId;
}

private string RemovePrefixAndSuffixIfneeded(string input, int startIndex, int endIndex)
{
startIndex = (startIndex == -1) ? 0 : startIndex + 1;

if (endIndex == -1)
{
endIndex = input.Length;
}

return input.Substring(startIndex, endIndex - startIndex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <copyright file="DockerSemanticConventions.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.Contrib.Extensions.Docker.Resources
{
internal static class DockerSemanticConventions
{
public const string AttributeContainerID = "container.id";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// <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>

using System.Collections.Generic;

namespace OpenTelemetry.Contrib.Extensions.Docker.Resources
{
/// <summary>
/// Resource detector interface.
/// Mocking https://github.com/open-telemetry/opentelemetry-dotnet/blob/6b7f2dd77cf9d37260a853fcc95f7b77e296065d/src/OpenTelemetry/Resources/IResourceDetector.cs.
/// </summary>
public interface IResourceDetector
{
/// <summary>
/// Called to get key-value pairs of attribute from detector.
/// </summary>
/// <returns>List of key-value pairs of resource attributes.</returns>
IEnumerable<KeyValuePair<string, object>> Detect();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// <copyright file="ResourceBuilderExtensions.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 OpenTelemetry.Contrib.Extensions.Docker.Resources;

namespace OpenTelemetry.Resources
{
/// <summary>
/// Extension class for ResourceBuilder.
/// </summary>
public static class ResourceBuilderExtensions
{
/// <summary>
/// Add Docker resource detector to ResourceBuilder.
/// </summary>
/// <param name="resourceBuilder"><see cref="ResourceBuilder"/> being configured.</param>
/// <returns>The instance of <see cref="ResourceBuilder"/> to chain the calls.</returns>
public static ResourceBuilder AddDockerDetector(this ResourceBuilder resourceBuilder)
{
var resourceDetector = new DockerResourceDetector();
var resourceAttributes = resourceDetector.Detect();

if (resourceAttributes != null)
{
resourceBuilder.AddAttributes(resourceAttributes);
}

return resourceBuilder;
}
}
}
Loading

0 comments on commit 207200f

Please sign in to comment.