252 lines
12 KiB
C#
252 lines
12 KiB
C#
// <copyright file="GrpcTests.server.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>
|
|
|
|
#if NET6_0_OR_GREATER
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Threading;
|
|
using Greet;
|
|
using Grpc.Core;
|
|
using Grpc.Net.Client;
|
|
using Moq;
|
|
using OpenTelemetry.Context.Propagation;
|
|
using OpenTelemetry.Instrumentation.Grpc.Services.Tests;
|
|
using OpenTelemetry.Instrumentation.GrpcNetClient;
|
|
using OpenTelemetry.Trace;
|
|
using Xunit;
|
|
using Status = OpenTelemetry.Trace.Status;
|
|
|
|
namespace OpenTelemetry.Instrumentation.Grpc.Tests
|
|
{
|
|
public partial class GrpcTests : IDisposable
|
|
{
|
|
private const string OperationNameHttpRequestIn = "Microsoft.AspNetCore.Hosting.HttpRequestIn";
|
|
private const string OperationNameGrpcOut = "Grpc.Net.Client.GrpcOut";
|
|
private const string OperationNameHttpOut = "System.Net.Http.HttpRequestOut";
|
|
|
|
private readonly GrpcServer<GreeterService> server;
|
|
|
|
public GrpcTests()
|
|
{
|
|
this.server = new GrpcServer<GreeterService>();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes(bool? enableGrpcAspNetCoreSupport)
|
|
{
|
|
var exportedItems = new List<Activity>();
|
|
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder();
|
|
|
|
if (enableGrpcAspNetCoreSupport.HasValue)
|
|
{
|
|
tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
|
|
{
|
|
options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
tracerProviderBuilder.AddAspNetCoreInstrumentation();
|
|
}
|
|
|
|
using var tracerProvider = tracerProviderBuilder
|
|
.AddInMemoryExporter(exportedItems)
|
|
.Build();
|
|
|
|
var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
|
|
var uri = new Uri($"http://localhost:{this.server.Port}");
|
|
|
|
using var channel = GrpcChannel.ForAddress(uri);
|
|
var client = new Greeter.GreeterClient(channel);
|
|
var returnMsg = client.SayHello(new HelloRequest()).Message;
|
|
Assert.False(string.IsNullOrEmpty(returnMsg));
|
|
|
|
WaitForExporterToReceiveItems(exportedItems, 1);
|
|
Assert.Single(exportedItems);
|
|
var activity = exportedItems[0];
|
|
|
|
Assert.Equal(ActivityKind.Server, activity.Kind);
|
|
|
|
if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
|
|
{
|
|
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
|
|
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
|
|
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
|
|
Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
|
|
Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
|
|
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
|
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
|
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
|
|
}
|
|
else
|
|
{
|
|
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
|
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
|
}
|
|
|
|
Assert.Equal(Status.Unset, activity.GetStatus());
|
|
|
|
// The following are http.* attributes that are also included on the span for the gRPC invocation.
|
|
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
|
Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
|
|
Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
|
Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
|
Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
|
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
|
|
}
|
|
|
|
#if NET6_0_OR_GREATER
|
|
[Theory(Skip = "Skipping for .NET 6 and higher due to bug #3023")]
|
|
#endif
|
|
[InlineData(null)]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributesWhenItCreatesNewActivity(bool? enableGrpcAspNetCoreSupport)
|
|
{
|
|
try
|
|
{
|
|
// B3Propagator along with the headers passed to the client.SayHello ensure that the instrumentation creates a sibling activity
|
|
Sdk.SetDefaultTextMapPropagator(new Extensions.Propagators.B3Propagator());
|
|
var exportedItems = new List<Activity>();
|
|
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder();
|
|
|
|
if (enableGrpcAspNetCoreSupport.HasValue)
|
|
{
|
|
tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
|
|
{
|
|
options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
tracerProviderBuilder.AddAspNetCoreInstrumentation();
|
|
}
|
|
|
|
using var tracerProvider = tracerProviderBuilder
|
|
.AddInMemoryExporter(exportedItems)
|
|
.Build();
|
|
|
|
var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
|
|
var uri = new Uri($"http://localhost:{this.server.Port}");
|
|
|
|
using var channel = GrpcChannel.ForAddress(uri);
|
|
var client = new Greeter.GreeterClient(channel);
|
|
var headers = new Metadata
|
|
{
|
|
{ "traceparent", "00-120dc44db5b736468afb112197b0dbd3-5dfbdf27ec544544-01" },
|
|
{ "x-b3-traceid", "120dc44db5b736468afb112197b0dbd3" },
|
|
{ "x-b3-spanid", "b0966f651b9e0126" },
|
|
{ "x-b3-sampled", "1" },
|
|
};
|
|
client.SayHello(new HelloRequest(), headers);
|
|
|
|
WaitForExporterToReceiveItems(exportedItems, 1);
|
|
Assert.Single(exportedItems);
|
|
var activity = exportedItems[0];
|
|
|
|
Assert.Equal(ActivityKind.Server, activity.Kind);
|
|
|
|
if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
|
|
{
|
|
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
|
|
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
|
|
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
|
|
Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
|
|
Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
|
|
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
|
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
|
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
|
|
}
|
|
else
|
|
{
|
|
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
|
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
|
}
|
|
|
|
Assert.Equal(Status.Unset, activity.GetStatus());
|
|
|
|
// The following are http.* attributes that are also included on the span for the gRPC invocation.
|
|
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
|
Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
|
|
Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
|
Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
|
Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
|
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
|
|
}
|
|
finally
|
|
{
|
|
// Set the SDK to use the default propagator for other unit tests
|
|
Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(new TextMapPropagator[]
|
|
{
|
|
new TraceContextPropagator(),
|
|
new BaggagePropagator(),
|
|
}));
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
this.server.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private static void WaitForExporterToReceiveItems(List<Activity> itemsReceived, int itemCount)
|
|
{
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
Assert.True(SpinWait.SpinUntil(
|
|
() =>
|
|
{
|
|
Thread.Sleep(10);
|
|
return itemsReceived.Count >= itemCount;
|
|
},
|
|
TimeSpan.FromSeconds(1)));
|
|
}
|
|
|
|
private static void WaitForProcessorInvocations(Mock<BaseProcessor<Activity>> spanProcessor, int invocationCount)
|
|
{
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
Assert.True(SpinWait.SpinUntil(
|
|
() =>
|
|
{
|
|
Thread.Sleep(10);
|
|
return spanProcessor.Invocations.Count >= invocationCount;
|
|
},
|
|
TimeSpan.FromSeconds(1)));
|
|
}
|
|
|
|
private static Activity GetActivityFromProcessorInvocation(Mock<BaseProcessor<Activity>> processor, string methodName, string activityOperationName)
|
|
{
|
|
return processor.Invocations
|
|
.FirstOrDefault(invo =>
|
|
{
|
|
return invo.Method.Name == methodName
|
|
&& (invo.Arguments[0] as Activity)?.OperationName == activityOperationName;
|
|
})?.Arguments[0] as Activity;
|
|
}
|
|
}
|
|
}
|
|
#endif
|