Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Resources.OperatingSystem] Implement additional osdetector attributes #1983

Merged
merged 31 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
61a1864
Implement additional osdetector attributes
ysolomchenko Jul 29, 2024
0579251
Update CHANGELOG.md
ysolomchenko Jul 31, 2024
631ddfd
Remove warnings
ysolomchenko Aug 1, 2024
3b3abca
Update CHANGELOG.md
ysolomchenko Aug 1, 2024
9589d6b
Update README.md
ysolomchenko Aug 1, 2024
d0d2cb5
fix lint format
ysolomchenko Aug 1, 2024
6c0340f
Add null check to AddAttributeIfNotNullOrEmpty
ysolomchenko Aug 5, 2024
9916df6
Change build id for Windows, move uname for linux
ysolomchenko Aug 5, 2024
1a42f1e
Refactor: reduce process invocations
ysolomchenko Aug 6, 2024
6d54ee2
wp
ysolomchenko Aug 12, 2024
3393bb2
update os.name to get from product name
ysolomchenko Aug 13, 2024
a243e48
Update test
ysolomchenko Aug 20, 2024
461b190
add WaitForExit
ysolomchenko Aug 20, 2024
0594c72
log error when process timeout
ysolomchenko Aug 20, 2024
5ded1ab
remove processes /for mac read info from file
ysolomchenko Aug 21, 2024
968863f
fix test
ysolomchenko Aug 21, 2024
b4ae4c1
fix tests
ysolomchenko Aug 21, 2024
c3f6a7a
ReadAllLines
ysolomchenko Aug 21, 2024
45febc8
refactor
ysolomchenko Aug 22, 2024
638ae53
Merge branch 'main' into add-additional-os-attributes
vishweshbankwar Aug 22, 2024
0387c53
Add parse methods and corresponding tests
ysolomchenko Aug 23, 2024
0bd0f97
Merge branch 'add-additional-os-attributes' of https://github.com/yso…
ysolomchenko Aug 23, 2024
54ced80
Merge branch 'main' into add-additional-os-attributes
ysolomchenko Aug 23, 2024
dcda373
refactor/add samples
ysolomchenko Aug 23, 2024
b11ca52
Merge branch 'add-additional-os-attributes' of https://github.com/yso…
ysolomchenko Aug 23, 2024
4e0ccf9
fix tests/ refactor
ysolomchenko Aug 26, 2024
d85d57e
add ctor for testing
ysolomchenko Aug 27, 2024
958b3a7
refactor
ysolomchenko Aug 28, 2024
9f20fd8
add key-value count check in plist, log warning if mismatch/add fallb…
ysolomchenko Aug 29, 2024
bf11553
rename variables
ysolomchenko Aug 29, 2024
50f5935
Merge branch 'main' into add-additional-os-attributes
Kielek Aug 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/OpenTelemetry.Resources.OperatingSystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

* Implement
`os.build_id`,
`os.description`,
`os.name`,
`os.version` attributes in
`OpenTelemetry.ResourceDetectors.OperatingSystem`.
Kielek marked this conversation as resolved.
Show resolved Hide resolved
([#1983](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1983))

## 0.1.0-alpha.2

Released 2024-Jul-22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#if !NETFRAMEWORK
#if NET
using System.Runtime.InteropServices;
using System.Xml.Linq;
#endif

using static OpenTelemetry.Resources.OperatingSystem.OperatingSystemSemanticConventions;

namespace OpenTelemetry.Resources.OperatingSystem;
Expand All @@ -17,19 +19,53 @@ internal sealed class OperatingSystemDetector : IResourceDetector
/// Detects the resource attributes from the operating system.
/// </summary>
/// <returns>Resource with key-value pairs of resource attributes.</returns>
///
public Resource Detect()
{
var attributes = new List<KeyValuePair<string, object>>();
var osType = GetOSType();

if (osType == null)
{
return Resource.Empty;
}

return new Resource(
[
new(AttributeOperatingSystemType, osType),
]);
attributes.Add(new KeyValuePair<string, object>(AttributeOperatingSystemType, osType));

AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemDescription, GetOSDescription());

switch (osType)
{
case OperatingSystemsValues.Windows:
AddWindowsAttributes(attributes);
break;
#if NET
case OperatingSystemsValues.Linux:
AddLinuxAttributes(attributes);
break;
case OperatingSystemsValues.Darwin:
AddMacOSAttributes(attributes);
break;
#endif
}

return new Resource(attributes);
}

private static void AddAttributeIfNotNullOrEmpty(List<KeyValuePair<string, object>> attributes, string key, object? value)
{
if (value == null)
{
OperatingSystemResourcesEventSource.Log.FailedToValidateValue("The provided value is null");
return;
}

if (value is string strValue && string.IsNullOrEmpty(strValue))
{
OperatingSystemResourcesEventSource.Log.FailedToValidateValue("The provided value string is empty.");
return;
}

attributes.Add(new KeyValuePair<string, object>(key, value!));
}

private static string? GetOSType()
Expand All @@ -55,4 +91,121 @@ public Resource Detect()
}
#endif
}

#pragma warning disable CA1416
private static void AddWindowsAttributes(List<KeyValuePair<string, object>> attributes)
{
try
{
var registryKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemBuildId, key.GetValue("CurrentBuild")?.ToString());
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemName, key.GetValue("ProductName")?.ToString());
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemVersion, key.GetValue("CurrentVersion")?.ToString());
}
}
catch (Exception ex)
{
OperatingSystemResourcesEventSource.Log.ResourceAttributesExtractException("Failed to get Windows attributes", ex);
}
}
#pragma warning restore CA1416

#if NET
private static void AddLinuxAttributes(List<KeyValuePair<string, object>> attributes)
{
try
{
var osReleaseContent = File.ReadAllLines("/etc/os-release");
string? buildId = null, name = null, version = null;

foreach (var line in osReleaseContent)
{
if (line.StartsWith("BUILD_ID=", StringComparison.Ordinal))
{
buildId = line.Substring("BUILD_ID=".Length).Trim('"');
}
else if (line.StartsWith("NAME=", StringComparison.Ordinal))
{
name = line.Substring("NAME=".Length).Trim('"');
}
else if (line.StartsWith("VERSION_ID=", StringComparison.Ordinal))
{
version = line.Substring("VERSION_ID=".Length).Trim('"');
}
}

// TODO: fallback for buildId

AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemBuildId, buildId);
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemName, name ?? "Linux");
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemVersion, version);
}
catch (Exception ex)
{
OperatingSystemResourcesEventSource.Log.ResourceAttributesExtractException("Failed to get Linux attributes", ex);
}
}

private static void AddMacOSAttributes(List<KeyValuePair<string, object>> attributes)
{
try
{
var possibleFiles = new[]
{
"/System/Library/CoreServices/SystemVersion.plist",
"/System/Library/CoreServices/ServerVersion.plist",
};

string? plistFilePath = possibleFiles.FirstOrDefault(File.Exists);
if (string.IsNullOrEmpty(plistFilePath))
{
OperatingSystemResourcesEventSource.Log.FailedToFindFile("No suitable plist file found");
return;
}

var properties = new Dictionary<string, string>();
XDocument doc = XDocument.Load(plistFilePath);
var dict = doc.Root?.Element("dict");

if (dict != null)
{
var keys = dict.Elements("key").ToList();
var values = dict.Elements("string").ToList();

if (keys.Count == values.Count)
{
for (int i = 0; i < keys.Count; i++)
{
properties[keys[i].Value] = values[i].Value;
}
}
}

string? name = properties.GetValueOrDefault("ProductName");
string? version = properties.GetValueOrDefault("ProductVersion");
string? buildId = properties.GetValueOrDefault("ProductBuildVersion");

AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemBuildId, buildId);
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemName, name);
AddAttributeIfNotNullOrEmpty(attributes, AttributeOperatingSystemVersion, version);
}
catch (Exception ex)
{
OperatingSystemResourcesEventSource.Log.ResourceAttributesExtractException("Failed to get MacOS attributes", ex);
}
}

#endif

private static string GetOSDescription()
{
#if NET
return RuntimeInformation.OSDescription;
#else
return Environment.OSVersion.ToString();
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Tracing;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Resources.OperatingSystem;

[EventSource(Name = "OpenTelemetry-Resources-OperatingSystem")]
internal sealed class OperatingSystemResourcesEventSource : EventSource
{
public static OperatingSystemResourcesEventSource Log = new();

private const int EventIdFailedToExtractAttributes = 1;
private const int EventIdFailedToValidateValue = 2;
private const int EventIdFailedToFindFile = 3;

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

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

[Event(EventIdFailedToValidateValue, Message = "Failed to validate value. Details: '{0}'", Level = EventLevel.Warning)]
public void FailedToValidateValue(string error)
{
this.WriteEvent(EventIdFailedToValidateValue, error);
}

[Event(EventIdFailedToFindFile, Message = "Process timeout occurred: '{0}'", Level = EventLevel.Warning)]
public void FailedToFindFile(string error)
{
this.WriteEvent(EventIdFailedToFindFile, error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ namespace OpenTelemetry.Resources.OperatingSystem;
internal static class OperatingSystemSemanticConventions
{
public const string AttributeOperatingSystemType = "os.type";
public const string AttributeOperatingSystemBuildId = "os.build_id";
public const string AttributeOperatingSystemDescription = "os.description";
public const string AttributeOperatingSystemName = "os.name";
public const string AttributeOperatingSystemVersion = "os.version";

public static class OperatingSystemsValues
{
Expand Down
3 changes: 2 additions & 1 deletion src/OpenTelemetry.Resources.OperatingSystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ using var loggerFactory = LoggerFactory.Create(builder =>
The resource detectors will record the following metadata based on where
your application is running:

- **OperatingSystemDetector**: `os.type`.
- **OperatingSystemDetector**: `os.type`, `os.build_id`, `os.description`,
`os.name`, `os.version`.

## References

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,44 @@ public class OperatingSystemDetectorTests
public void TestOperatingSystemAttributes()
{
var resource = ResourceBuilder.CreateEmpty().AddOperatingSystemDetector().Build();

var resourceAttributes = resource.Attributes.ToDictionary(x => x.Key, x => (string)x.Value);

string expectedPlatform;
string expectedDescription;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
expectedPlatform = OperatingSystemSemanticConventions.OperatingSystemsValues.Windows;
expectedDescription = "Windows";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
expectedPlatform = OperatingSystemSemanticConventions.OperatingSystemsValues.Linux;
expectedDescription = "Linux";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
expectedPlatform = OperatingSystemSemanticConventions.OperatingSystemsValues.Darwin;
expectedDescription = "Darwin";
}
else
{
throw new PlatformNotSupportedException("Unknown platform");
}

Assert.Single(resourceAttributes);
Assert.Contains(OperatingSystemSemanticConventions.AttributeOperatingSystemDescription, resourceAttributes.Keys);
Assert.Contains(OperatingSystemSemanticConventions.AttributeOperatingSystemName, resourceAttributes.Keys);
Assert.Contains(OperatingSystemSemanticConventions.AttributeOperatingSystemType, resourceAttributes.Keys);
Assert.Contains(OperatingSystemSemanticConventions.AttributeOperatingSystemVersion, resourceAttributes.Keys);

Assert.True(resourceAttributes.ContainsKey(OperatingSystemSemanticConventions.AttributeOperatingSystemType));
// Not checking on Linux because the description may vary depending on the distribution.
if (expectedDescription != "Linux")
{
Assert.Contains(OperatingSystemSemanticConventions.AttributeOperatingSystemBuildId, resourceAttributes.Keys);
Assert.Contains(expectedDescription, resourceAttributes[OperatingSystemSemanticConventions.AttributeOperatingSystemDescription]);
Assert.Equal(5, resourceAttributes.Count);
}

Assert.Equal(resourceAttributes[OperatingSystemSemanticConventions.AttributeOperatingSystemType], expectedPlatform);
Assert.Equal(expectedPlatform, resourceAttributes[OperatingSystemSemanticConventions.AttributeOperatingSystemType]);
}
}
Loading