Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions OpenTelemetry.Extended.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
<File Path="src/Shared/Guard.cs" />
<File Path="src/Shared/PeriodicExportingMetricReaderHelper.cs" />
<File Path="src/Shared/ResourceSemanticConventions.cs" />
<File Path="src/Shared/SchemaUrls.cs" />
<File Path="src/Shared/SemanticConventions.cs" />
<File Path="src/Shared/SpanAttributeConstants.cs" />
<File Path="src/Shared/StatusHelper.cs" />
Expand Down
1 change: 1 addition & 0 deletions OpenTelemetry.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
<File Path="src/Shared/Guard.cs" />
<File Path="src/Shared/PeriodicExportingMetricReaderHelper.cs" />
<File Path="src/Shared/ResourceSemanticConventions.cs" />
<File Path="src/Shared/SchemaUrls.cs" />
<File Path="src/Shared/SemanticConventions.cs" />
<File Path="src/Shared/SpanAttributeConstants.cs" />
<File Path="src/Shared/StatusHelper.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Added Schema URL to internally created `Resource` instances.
([#7726](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7726))

## 1.18.0

Released 2026-Aug-21
Expand Down
1 change: 1 addition & 0 deletions src/OpenTelemetry/OpenTelemetry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<Compile Include="$(RepoRoot)\src\Shared\DoubleExtensions.cs" Link="Includes\DoubleExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Options\*.cs" Link="Includes\Options\%(Filename).cs" />
<Compile Include="$(RepoRoot)\src\Shared\ResourceSemanticConventions.cs" Link="Includes\ResourceSemanticConventions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SchemaUrls.cs" Link="Includes\SchemaUrls.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Shims\NullableAttributes.cs" Link="Includes\Shims\NullableAttributes.cs" />
<Compile Include="$(RepoRoot)\src\Shared\StopwatchExtensions.cs" Link="Includes\StopwatchExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ThreadSafeRandom.cs" Link="Includes\ThreadSafeRandom.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

using Microsoft.Extensions.Configuration;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Resources;

Expand All @@ -22,10 +23,9 @@ public Resource Detect()

if (this.configuration.TryGetStringValue(EnvVarKey, out var envResourceAttributeValue))
{
resource = new Resource(new Dictionary<string, object>
{
[ResourceSemanticConventions.AttributeServiceName] = envResourceAttributeValue,
});
resource = new(
[new(ResourceSemanticConventions.AttributeServiceName, envResourceAttributeValue)],
SchemaUrls.Get(SemanticConventionsVersion.Current));
}

return resource;
Expand Down
11 changes: 4 additions & 7 deletions src/OpenTelemetry/Resources/ResourceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,9 @@ private static Resource PrepareDefaultResource()
// GetCurrentProcess can throw PlatformNotSupportedException
}

return new Resource(new Dictionary<string, object>
{
[ResourceSemanticConventions.AttributeServiceName] = defaultServiceName,
});
return new Resource(
[new(ResourceSemanticConventions.AttributeServiceName, defaultServiceName)],
SchemaUrls.Get(SemanticConventionsVersion.Current));
}

internal sealed class WrapperResourceDetector : IResourceDetector
Expand All @@ -177,11 +176,9 @@ public ResolvingResourceDetector(Func<IServiceProvider?, IResourceDetector> reso
this.resourceDetectorFactory = resourceDetectorFactory;
}

public void Resolve(IServiceProvider? serviceProvider)
{
public void Resolve(IServiceProvider? serviceProvider) =>
this.resourceDetector = this.resourceDetectorFactory(serviceProvider)
?? throw new InvalidOperationException("ResourceDetector factory did not return a ResourceDetector instance.");
}

public Resource Detect()
{
Expand Down
33 changes: 13 additions & 20 deletions src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ public static class ResourceBuilderExtensions
{
private static readonly string InstanceId = Guid.NewGuid().ToString();

private static Resource TelemetryResource { get; } = new Resource(new Dictionary<string, object>
{
[ResourceSemanticConventions.AttributeTelemetrySdkName] = "opentelemetry",
[ResourceSemanticConventions.AttributeTelemetrySdkLanguage] = "dotnet",
[ResourceSemanticConventions.AttributeTelemetrySdkVersion] = Sdk.InformationalVersion,
});
private static Resource TelemetryResource { get; } = new(
[
new(ResourceSemanticConventions.AttributeTelemetrySdkName, "opentelemetry"),
new(ResourceSemanticConventions.AttributeTelemetrySdkLanguage, "dotnet"),
new(ResourceSemanticConventions.AttributeTelemetrySdkVersion, Sdk.InformationalVersion),
],
SchemaUrls.Get(SemanticConventionsVersion.Current));

/// <summary>
/// Adds service information to a <see cref="ResourceBuilder"/>
Expand All @@ -45,41 +46,33 @@ public static ResourceBuilder AddService(
Guard.ThrowIfNull(resourceBuilder);
Guard.ThrowIfNullOrEmpty(serviceName);

var resourceAttributes = new Dictionary<string, object>
var resourceAttributes = new Dictionary<string, object>(4)
{
{ ResourceSemanticConventions.AttributeServiceName, serviceName },
[ResourceSemanticConventions.AttributeServiceName] = serviceName,
};

if (!string.IsNullOrEmpty(serviceNamespace))
if (serviceNamespace is { Length: > 0 })
{
#if NET || NETSTANDARD2_1_OR_GREATER
resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceNamespace, serviceNamespace);
#else
resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceNamespace, serviceNamespace!);
#endif
}

if (!string.IsNullOrEmpty(serviceVersion))
if (serviceVersion is { Length: > 0 })
{
#if NET || NETSTANDARD2_1_OR_GREATER
resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceVersion, serviceVersion);
#else
resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceVersion, serviceVersion!);
#endif
}

if (serviceInstanceId == null && autoGenerateServiceInstanceId)
{
serviceInstanceId = InstanceId;
}

if (serviceInstanceId != null)
if (serviceInstanceId is { Length: > 0 })
Comment thread
martincostello marked this conversation as resolved.
Comment thread
martincostello marked this conversation as resolved.
{
resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceInstance, serviceInstanceId);
}

#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1
return resourceBuilder.AddResource(new Resource(resourceAttributes));
return resourceBuilder.AddResource(new Resource(resourceAttributes, SchemaUrls.Get(SemanticConventionsVersion.Current)));
#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1
}

Expand Down
9 changes: 9 additions & 0 deletions src/OpenTelemetry/SemanticConventionsVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace OpenTelemetry;

internal static class SemanticConventionsVersion
{
internal static readonly Version Current = new(1, 44, 0);
}
10 changes: 10 additions & 0 deletions src/Shared/SchemaUrls.cs

@martincostello martincostello Sep 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace OpenTelemetry.Internal;

internal static class SchemaUrls
{
public static string Get(Version semanticConventionsVersion)
=> $"https://opentelemetry.io/schemas/{semanticConventionsVersion.ToString(3)}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ public void Dispose()

[Fact]
public void OtelServiceNameEnvVar_EnvVarKey()
{
Assert.Equal("OTEL_SERVICE_NAME", OtelServiceNameEnvVarDetector.EnvVarKey);
}
=> Assert.Equal("OTEL_SERVICE_NAME", OtelServiceNameEnvVarDetector.EnvVarKey);

[Fact]
public void OtelServiceNameEnvVar_Null()
Expand All @@ -36,6 +34,7 @@ public void OtelServiceNameEnvVar_Null()

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

[Fact]
Expand All @@ -53,6 +52,7 @@ public void OtelServiceNameEnvVar_WithValue()
// Assert
Assert.NotEqual(Resource.Empty, resource);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, envVarValue), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -71,5 +71,6 @@ public void OtelServiceNameEnvVar_UsingIConfiguration()

Assert.NotEqual(Resource.Empty, resource);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}
}
6 changes: 6 additions & 0 deletions test/OpenTelemetry.Tests/Resources/ResourceBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public void ServiceResource_ServiceName()
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.Single(resource.Attributes, kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName);
Assert.True(Guid.TryParse((string)resource.Attributes.Single(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceInstance).Value, out _));
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -22,6 +23,7 @@ public void ServiceResource_ServiceNameAndInstance()
Assert.Equal(2, resource.Attributes.Count());
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceInstance, "123"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -32,6 +34,7 @@ public void ServiceResource_ServiceNameAndInstanceAndNamespace()
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceInstance, "123"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "my-namespace"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -43,6 +46,7 @@ public void ServiceResource_ServiceNameAndInstanceAndNamespaceAndVersion()
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceInstance, "123"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "my-namespace"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceVersion, "1.2.3"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -51,6 +55,7 @@ public void ServiceResource_AutoGenerateServiceInstanceIdOff()
var resource = ResourceBuilder.CreateEmpty().AddService("my-service", autoGenerateServiceInstanceId: false).Build();
Assert.Single(resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand Down Expand Up @@ -81,5 +86,6 @@ public void ClearTest()
.Build();
Assert.Single(resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}
}
31 changes: 28 additions & 3 deletions test/OpenTelemetry.Tests/Resources/ResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,22 @@ public void ResourceBuilder_Build_PropagatesSchemaUrlFromSingleContributor()
const string SchemaUrl = "https://opentelemetry.io/schemas/1.36.0";

// A detector contributes a Schema URL; other contributors leave it empty.
var resource = ResourceBuilder.CreateDefault()
var resource = ResourceBuilder.CreateEmpty()
.AddAttributes([new KeyValuePair<string, object>($"{KeyName}0", $"{ValueName}0")])
.AddAttributes([new KeyValuePair<string, object>(KeyName, ValueName)], SchemaUrl)
.Build();

Assert.Equal(SchemaUrl, resource.SchemaUrl);
}

[Fact]
public void ResourceBuilder_Build_DefaultContributorsShareSchemaUrlWithoutConflict()
{
var resource = ResourceBuilder.CreateDefault().Build();

Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
Comment thread
martincostello marked this conversation as resolved.
}

[Fact]
public void GetResourceWithTelemetrySDKAttributes()
{
Expand All @@ -541,6 +550,7 @@ public void GetResourceWithTelemetrySDKAttributes()
Assert.Equal(4, attributes.Count());
ValidateDefaultAttributes(attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -554,6 +564,7 @@ public void GetResourceWithDefaultAttributes_EmptyResource()
Assert.Equal(4, attributes.Count());
ValidateDefaultAttributes(attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -568,6 +579,7 @@ public void GetResourceWithDefaultAttributes_ResourceWithAttrs()
ValidateAttributes(attributes, 0, 1);
ValidateDefaultAttributes(attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -585,6 +597,7 @@ public void GetResourceWithDefaultAttributes_WithResourceEnvVar()
Assert.Contains(new KeyValuePair<string, object>("EVKey1", "EVVal1"), attributes);
Assert.Contains(new KeyValuePair<string, object>("EVKey2", "EVVal2"), attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -600,6 +613,7 @@ public void EnvironmentVariableDetectors_DoNotDuplicateAttributes()
Assert.Contains(new KeyValuePair<string, object>("EVKey1", "EVVal1"), attributes);
Assert.Contains(new KeyValuePair<string, object>("EVKey2", "EVVal2"), attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -615,6 +629,7 @@ public void GetResource_WithServiceEnvVar()
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "some-service"), attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -631,6 +646,7 @@ public void GetResource_WithServiceNameSetWithTwoEnvVars()
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "from-service-name"), attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -647,6 +663,7 @@ public void GetResource_WithServiceNameSetWithTwoEnvVarsAndCode()
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "from-code"), attributes);
ValidateTelemetrySdkAttributes(attributes);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand All @@ -673,6 +690,8 @@ public void ResourceBuilder_AddDetector_Test()
var resource = builder.Build();

Assert.True(factoryExecuted);
Assert.NotNull(resource);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

[Fact]
Expand Down Expand Up @@ -709,17 +728,23 @@ public void ResourceBuilder_AddDetectorInternal_Test()
return new NoopResourceDetector();
});

builder.Build();
var resource = builder.Build();

Assert.True(validTestRun);
Assert.NotNull(resource);
Assert.StartsWith("https://opentelemetry.io/schemas/", resource.SchemaUrl, StringComparison.Ordinal);
}

internal static void ValidateTelemetrySdkAttributes(IEnumerable<KeyValuePair<string, object>> attributes)
{
Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.name", "opentelemetry"), attributes);
Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.language", "dotnet"), attributes);

var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version", StringComparison.Ordinal));
Assert.Single(versionAttribute);
var version = Assert.Single(versionAttribute);
var versionString = Assert.IsType<string>(version.Value);

Assert.NotEmpty(versionString);
}

internal static void ValidateDefaultAttributes(IEnumerable<KeyValuePair<string, object>> attributes)
Expand Down
Loading