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

Update semantic conventions for grpc client #4658

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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

* Updated [Semantic
Conventions](https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md)
to v1.21.0. This library can emit either old, new, or both attributes. Users
can control which attributes are emitted by setting the environment variable
OTEL_SEMCONV_STABILITY_OPT_IN.
[#4658](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4658)

## 1.5.0-beta.1

Released 2023-Jun-05
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Instrumentation.Http;
using OpenTelemetry.Trace;
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;

namespace OpenTelemetry.Instrumentation.GrpcNetClient.Implementation
{
Expand All @@ -33,13 +34,19 @@ internal sealed class GrpcClientDiagnosticListener : ListenerHandler
private const string OnStopEvent = "Grpc.Net.Client.GrpcOut.Stop";

private readonly GrpcClientInstrumentationOptions options;
private readonly bool emitOldAttributes;
private readonly bool emitNewAttributes;
private readonly PropertyFetcher<HttpRequestMessage> startRequestFetcher = new("Request");
private readonly PropertyFetcher<HttpResponseMessage> stopRequestFetcher = new("Response");

public GrpcClientDiagnosticListener(GrpcClientInstrumentationOptions options)
: base("Grpc.Net.Client")
{
this.options = options;

this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);

this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}

public override void OnEventWritten(string name, object payload)
Expand Down Expand Up @@ -135,16 +142,34 @@ public void OnStartActivity(Activity activity, object payload)
}

var uriHostNameType = Uri.CheckHostName(request.RequestUri.Host);
if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
if (this.emitOldAttributes)
{
activity.SetTag(SemanticConventions.AttributeNetPeerIp, request.RequestUri.Host);
if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
{
activity.SetTag(SemanticConventions.AttributeNetPeerIp, request.RequestUri.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host);
}

activity.SetTag(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port);
}
else

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
if (this.emitNewAttributes)
{
activity.SetTag(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host);
}
if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
{
activity.SetTag(SemanticConventions.AttributeServerSocketAddress, request.RequestUri.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
}

activity.SetTag(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port);
activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
}

try
{
Expand Down
200 changes: 199 additions & 1 deletion test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Greet;
using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenTelemetry.Context.Propagation;
Expand Down Expand Up @@ -48,7 +49,7 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho
var uri = new Uri($"{baseAddress}:1234");
var uriHostNameType = Uri.CheckHostName(uri.Host);

var httpClient = ClientTestHelpers.CreateTestClient(async request =>
using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
Expand Down Expand Up @@ -123,6 +124,203 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho
}
}

[Theory]
[InlineData("http://localhost")]
[InlineData("http://localhost", false)]
[InlineData("http://127.0.0.1")]
[InlineData("http://127.0.0.1", false)]
[InlineData("http://[::1]")]
[InlineData("http://[::1]", false)]
public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool shouldEnrich = true)
{
KeyValuePair<string, string>[] config = new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("OTEL_SEMCONV_STABILITY_OPT_IN", "http") };
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(config)
.Build();

bool enrichWithHttpRequestMessageCalled = false;
bool enrichWithHttpResponseMessageCalled = false;

var uri = new Uri($"{baseAddress}:1234");
var uriHostNameType = Uri.CheckHostName(uri.Host);

using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
response.TrailingHeaders().Add("grpc-message", "value");
return response;
});

var processor = new Mock<BaseProcessor<Activity>>();

using var parent = new Activity("parent")
.SetIdFormat(ActivityIdFormat.W3C)
.Start();

using (Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOnSampler())
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
.AddGrpcClientInstrumentation(options =>
{
if (shouldEnrich)
{
options.EnrichWithHttpRequestMessage = (activity, httpRequestMessage) => { enrichWithHttpRequestMessageCalled = true; };
options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; };
}
})
.AddProcessor(processor.Object)
.Build())
{
var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions
{
HttpClient = httpClient,
});
var client = new Greeter.GreeterClient(channel);
var rs = client.SayHello(new HelloRequest());
}

Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
var activity = (Activity)processor.Invocations[2].Arguments[0];

ValidateGrpcActivity(activity);
Assert.Equal(parent.TraceId, activity.Context.TraceId);
Assert.Equal(parent.SpanId, activity.ParentSpanId);
Assert.NotEqual(parent.SpanId, activity.Context.SpanId);
Assert.NotEqual(default, activity.Context.SpanId);

Assert.Equal($"greet.Greeter/SayHello", activity.DisplayName);
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));

if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
{
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeServerAddress));
}
else
{
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeServerAddress));
}

Assert.Equal(uri.Port, activity.GetTagValue(SemanticConventions.AttributeServerPort));
Assert.Equal(Status.Unset, activity.GetStatus());

// Tags added by the library then removed from the instrumentation
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));

if (shouldEnrich)
{
Assert.True(enrichWithHttpRequestMessageCalled);
Assert.True(enrichWithHttpResponseMessageCalled);
}
}

[Theory]
[InlineData("http://localhost")]
[InlineData("http://localhost", false)]
[InlineData("http://127.0.0.1")]
[InlineData("http://127.0.0.1", false)]
[InlineData("http://[::1]")]
[InlineData("http://[::1]", false)]
public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, bool shouldEnrich = true)
{
KeyValuePair<string, string>[] config = new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") };
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(config)
.Build();

bool enrichWithHttpRequestMessageCalled = false;
bool enrichWithHttpResponseMessageCalled = false;

var uri = new Uri($"{baseAddress}:1234");
var uriHostNameType = Uri.CheckHostName(uri.Host);

using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
response.TrailingHeaders().Add("grpc-message", "value");
return response;
});

var processor = new Mock<BaseProcessor<Activity>>();

using var parent = new Activity("parent")
.SetIdFormat(ActivityIdFormat.W3C)
.Start();

using (Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOnSampler())
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
.AddGrpcClientInstrumentation(options =>
{
if (shouldEnrich)
{
options.EnrichWithHttpRequestMessage = (activity, httpRequestMessage) => { enrichWithHttpRequestMessageCalled = true; };
options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; };
}
})
.AddProcessor(processor.Object)
.Build())
{
var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions
{
HttpClient = httpClient,
});
var client = new Greeter.GreeterClient(channel);
var rs = client.SayHello(new HelloRequest());
}

Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
var activity = (Activity)processor.Invocations[2].Arguments[0];

ValidateGrpcActivity(activity);
Assert.Equal(parent.TraceId, activity.Context.TraceId);
Assert.Equal(parent.SpanId, activity.ParentSpanId);
Assert.NotEqual(parent.SpanId, activity.Context.SpanId);
Assert.NotEqual(default, activity.Context.SpanId);

Assert.Equal($"greet.Greeter/SayHello", activity.DisplayName);
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));

if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
{
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeNetPeerIp));
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeServerAddress));
}
else
{
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp));
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
Assert.Equal(uri.Host, activity.GetTagValue(SemanticConventions.AttributeServerAddress));
}

Assert.Equal(uri.Port, activity.GetTagValue(SemanticConventions.AttributeServerPort));
Assert.Equal(uri.Port, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
Assert.Equal(Status.Unset, activity.GetStatus());

// Tags added by the library then removed from the instrumentation
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));

if (shouldEnrich)
{
Assert.True(enrichWithHttpRequestMessageCalled);
Assert.True(enrichWithHttpResponseMessageCalled);
}
}

#if NET6_0_OR_GREATER
[Theory]
[InlineData(true)]
Expand Down