Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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-dotnet-contrib.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
<File Path="src/Shared/RedactionHelper.cs" />
<File Path="src/Shared/RequestDataHelper.cs" />
<File Path="src/Shared/ResourceSemanticConventions.cs" />
<File Path="src/Shared/RpcSemanticConventionHelper.cs" />
Comment thread
martincostello marked this conversation as resolved.
Outdated
<File Path="src/Shared/SemanticConventions.cs" />
<File Path="src/Shared/ServerCertificateValidationHandler.cs" />
<File Path="src/Shared/ServerCertificateValidationProvider.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
using static OpenTelemetry.Internal.RpcSemanticConventionHelper;

namespace OpenTelemetry.Instrumentation.AspNetCore;

Expand Down Expand Up @@ -38,6 +39,10 @@ internal AspNetCoreTraceInstrumentationOptions(IConfiguration configuration)
{
this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
}

var rpcSemanticConvention = GetSemanticConventionOptIn(configuration);
this.EmitOldRpcAttributes = rpcSemanticConvention.HasFlag(RpcSemanticConvention.Old);
this.EmitNewRpcAttributes = rpcSemanticConvention.HasFlag(RpcSemanticConvention.New);
Comment thread
martincostello marked this conversation as resolved.
Outdated
}

/// <summary>
Expand Down Expand Up @@ -115,7 +120,7 @@ internal AspNetCoreTraceInstrumentationOptions(IConfiguration configuration)
/// Gets or sets a value indicating whether RPC attributes are added to an Activity when using Grpc.AspNetCore.
/// </summary>
/// <remarks>
/// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md.
/// https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/rpc/rpc-spans.md.
/// </remarks>
internal bool EnableGrpcAspNetCoreSupport { get; set; }

Expand All @@ -128,4 +133,14 @@ internal AspNetCoreTraceInstrumentationOptions(IConfiguration configuration)
/// The redaction can be disabled by setting this property to <see langword="true" />.
/// </remarks>
internal bool DisableUrlQueryRedaction { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the old RPC attributes should be emitted.
/// </summary>
internal bool EmitOldRpcAttributes { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the new RPC attributes should be emitted.
/// </summary>
internal bool EmitNewRpcAttributes { get; set; }
}
5 changes: 5 additions & 0 deletions src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
* Fix enrich methods being called multiple times.
([#4015](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4015))

* Add support for version [1.41.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/rpc/README.md)
of the Semantic Conventions for RPC/gRPC when the `OTEL_SEMCONV_STABILITY_OPT_IN`
environment variable is set to `rpc` or `rpc/dup`.
([#4370](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4370))

## 1.15.2

Released 2026-Apr-21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,14 @@ public void OnStopActivity(Activity activity, object? payload)

if (!string.IsNullOrEmpty(grpcMethod))
{
AddGrpcAttributes(activity, grpcMethod!, context, grpcStatusCode, hasGrpcStatusCode);
AddGrpcAttributes(
activity,
grpcMethod!,
context,
grpcStatusCode,
hasGrpcStatusCode,
this.options.EmitOldRpcAttributes,
this.options.EmitNewRpcAttributes);
}
}

Expand Down Expand Up @@ -382,7 +389,14 @@ static bool TryFetchException(object? payload, [NotNullWhen(true)] out Exception
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context, int grpcStatusCode, bool validStatusCode)
private static void AddGrpcAttributes(
Activity activity,
string grpcMethod,
HttpContext context,
int grpcStatusCode,
bool validStatusCode,
bool emitOldRpcAttributes,
bool emitNewRpcAttributes)
{
var details = GrpcMethodCache.Get(grpcMethod);

Expand All @@ -391,16 +405,41 @@ private static void AddGrpcAttributes(Activity activity, string grpcMethod, Http
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#span-name
activity.DisplayName = details.DisplayName;

activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);
// See the specs for old and new semantic conventions.
// https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/rpc/rpc-spans.md
// https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/rpc/rpc-spans.md

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/rpc/rpc-spans.md

if (context.Connection.RemoteIpAddress != null)
if (emitOldRpcAttributes)
{
activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);

if (context.Connection.RemoteIpAddress != null)
{
activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
}

activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
}

activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
if (emitNewRpcAttributes)
{
activity.SetTag(SemanticConventions.AttributeRpcSystemName, GrpcTagHelper.RpcSystemGrpc);

if (context.Request.Host.HasValue)
{
var uriHostNameType = Uri.CheckHostName(context.Request.Host.Host);

if (uriHostNameType is UriHostNameType.IPv4 or UriHostNameType.IPv6)
{
activity.SetTag(SemanticConventions.AttributeNetworkPeerAddress, context.Request.Host.Host);

if (context.Request.Host.Port is { } port)
{
activity.SetTag(SemanticConventions.AttributeNetworkPeerPort, port);
}
}
}
Comment thread
martincostello marked this conversation as resolved.
Outdated
}

if (validStatusCode)
{
Expand All @@ -409,20 +448,31 @@ private static void AddGrpcAttributes(Activity activity, string grpcMethod, Http

if (details.IsParsed)
{
activity.SetTag(SemanticConventions.AttributeRpcService, details.RpcService);
if (emitOldRpcAttributes)
{
activity.SetTag(SemanticConventions.AttributeRpcService, details.RpcService);
}

activity.SetTag(SemanticConventions.AttributeRpcMethod, details.RpcMethod);

// Remove the grpc.method tag added by the gRPC .NET library
// See https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/non-normative/compatibility/grpc.md#attribute-mapping
activity.SetTag(GrpcTagHelper.GrpcMethodTagName, null);

// Remove the grpc.status_code tag added by the gRPC .NET library
activity.SetTag(GrpcTagHelper.GrpcStatusTagName, null);
activity.SetTag(GrpcTagHelper.GrpcStatusCodeTagName, null);
activity.SetTag(GrpcTagHelper.GrpcTargetTagName, null);
}

if (validStatusCode)
if (validStatusCode)
{
if (emitOldRpcAttributes)
{
// setting rpc.grpc.status_code
activity.SetTag(SemanticConventions.AttributeRpcGrpcStatusCode, grpcStatusCode);
}

if (emitNewRpcAttributes)
{
activity.SetTag(SemanticConventions.AttributeRpcResponseStatusCode, grpcStatusCode);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Compile Include="$(RepoRoot)\src\Shared\PropertyFetcher.cs" Link="Includes\PropertyFetcher.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RequestDataHelper.cs" Link="Includes\RequestDataHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RpcSemanticConventionHelper.cs" Link="Includes\RpcSemanticConventionHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SemanticConventions.cs" Link="Includes\SemanticConventions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SpanHelper.cs" Link="Includes\SpanHelper.cs" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenTelemetry.Instrumentation.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ appBuilder.Services.AddOpenTelemetry()
```

Semantic conventions for RPC are still
[experimental](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/rpc)
[experimental](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/rpc#semantic-conventions-for-rpc)
and hence the instrumentation only offers it as an experimental feature.

## Troubleshooting
Expand Down
5 changes: 4 additions & 1 deletion src/Shared/GrpcTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ internal static class GrpcTagHelper

// The Grpc.Net.Client library adds its own tags to the activity.
// These tags are used to source the tags added by the OpenTelemetry instrumentation.
// See https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/non-normative/compatibility/grpc.md#attribute-mapping
public const string GrpcMethodTagName = "grpc.method";
public const string GrpcStatusTagName = "grpc.status";
public const string GrpcStatusCodeTagName = "grpc.status_code";
public const string GrpcTargetTagName = "grpc.target";

public static string? GetGrpcMethodFromActivity(Activity activity)
=> activity.GetTagValue(GrpcMethodTagName) as string;
Expand Down Expand Up @@ -91,7 +94,7 @@ public static ActivityStatusCode ResolveSpanStatusForGrpcStatusCodeOnClient(int

/// <summary>
/// Helper method that populates span properties from RPC status code according
/// to https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/grpc.md#server.
/// to https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/rpc/grpc.md.
/// This method is for server spans where only specific status codes are considered errors:
/// UNKNOWN, DEADLINE_EXCEEDED, UNIMPLEMENTED, INTERNAL, UNAVAILABLE, and DATA_LOSS.
/// </summary>
Expand Down
84 changes: 84 additions & 0 deletions src/Shared/RpcSemanticConventionHelper.cs
Comment thread
martincostello marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;

namespace OpenTelemetry.Internal;

/// <summary>
/// Helper class for RPC Semantic Conventions.
/// </summary>
/// <remarks>
/// Due to a breaking change in the semantic conventions, affected instrumentation libraries
/// must inspect an environment variable to determine which attributes to emit.
/// This is expected to be removed when the instrumentation libraries reach Stable.
/// <see href="https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/rpc/rpc-spans.md"/>.
/// <see href="https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/rpc/rpc-spans.md"/>.
/// </remarks>
internal static class RpcSemanticConventionHelper
{
internal const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN";
internal static readonly char[] Separator = [',', ' '];

Comment thread
martincostello marked this conversation as resolved.
Outdated
[Flags]
internal enum RpcSemanticConvention
{
/// <summary>
/// Instructs an instrumentation library to emit the old experimental RPC attributes.
/// </summary>
Old = 0x1,

/// <summary>
/// Instructs an instrumentation library to emit the new, v1.23.0 RPC attributes.
/// </summary>
New = 0x2,

/// <summary>
/// Instructs an instrumentation library to emit both the old and new attributes.
/// </summary>
Dupe = Old | New,
}

public static RpcSemanticConvention GetSemanticConventionOptIn(IConfiguration configuration)
{
if (TryGetConfiguredValues(configuration, out var values))
{
if (values.Contains("rpc/dup"))
{
return RpcSemanticConvention.Dupe;
}
else if (values.Contains("rpc"))
{
return RpcSemanticConvention.New;
}
}

return RpcSemanticConvention.Old;
}

private static bool TryGetConfiguredValues(IConfiguration configuration, [NotNullWhen(true)] out HashSet<string>? values)
{
try
{
var stringValue = configuration[SemanticConventionOptInKeyName];

if (string.IsNullOrWhiteSpace(stringValue))
{
values = null;
return false;
}

#pragma warning disable IDE0370 // Suppression is unnecessary
var stringValues = stringValue!.Split(separator: Separator, options: StringSplitOptions.RemoveEmptyEntries);
#pragma warning restore IDE0370 // Suppression is unnecessary
values = new HashSet<string>(stringValues, StringComparer.OrdinalIgnoreCase);
return true;
}
catch
{
values = null;
return false;
}
}
}
Loading