Skip to content

Commit

Permalink
Add netstandard2.0 target to Grpc.Net.Client instrumentation (#3105)
Browse files Browse the repository at this point in the history
  • Loading branch information
alanwest committed May 6, 2022
1 parent e8659f7 commit 95500ff
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, string, object>
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions.GrpcClientInstrumentationOptions() -> void
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions.SuppressDownstreamInstrumentation.get -> bool
OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions.SuppressDownstreamInstrumentation.set -> void
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddGrpcClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientInstrumentationOptions> configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Add `netstandard2.0` target enabling the Grpc.Net.Client instrumentation to
be consumed by .NET Framework applications.
([#3105](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3105))

## 1.0.0-rc9.3

Released 2022-Apr-15
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
<TargetFrameworks>netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks>
<Description>gRPC for .NET client instrumentation for OpenTelemetry .NET</Description>
<PackageTags>$(PackageTags);distributed-tracing</PackageTags>
<IncludeDiagnosticSourceInstrumentationHelpers>true</IncludeDiagnosticSourceInstrumentationHelpers>
Expand Down
2 changes: 2 additions & 0 deletions test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

#if !NETFRAMEWORK
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -101,3 +102,4 @@ public void Configure(IApplicationBuilder app)
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// <copyright file="ClientTestHelpers.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>

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Net.Compression;

namespace OpenTelemetry.Instrumentation.Grpc.Tests.GrpcTestHelpers
{
internal static class ClientTestHelpers
{
public static HttpClient CreateTestClient(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendAsync, Uri baseAddress = null)
{
var handler = TestHttpMessageHandler.Create(sendAsync);
var httpClient = new HttpClient(handler);
httpClient.BaseAddress = baseAddress ?? new Uri("https://localhost");

return httpClient;
}

public static Task<StreamContent> CreateResponseContent<TResponse>(TResponse response, ICompressionProvider compressionProvider = null)
where TResponse : IMessage<TResponse>
{
return CreateResponseContentCore(new[] { response }, compressionProvider);
}

public static async Task WriteResponseAsync<TResponse>(Stream ms, TResponse response, ICompressionProvider compressionProvider)
where TResponse : IMessage<TResponse>
{
var compress = false;

byte[] data;
if (compressionProvider != null)
{
compress = true;

var output = new MemoryStream();
var compressionStream = compressionProvider.CreateCompressionStream(output, System.IO.Compression.CompressionLevel.Fastest);
var compressedData = response.ToByteArray();

compressionStream.Write(compressedData, 0, compressedData.Length);
compressionStream.Flush();
compressionStream.Dispose();
data = output.ToArray();
}
else
{
data = response.ToByteArray();
}

await ResponseUtils.WriteHeaderAsync(ms, data.Length, compress, CancellationToken.None);
#if NET5_0_OR_GREATER
await ms.WriteAsync(data);
#else
await ms.WriteAsync(data, 0, data.Length);
#endif
}

private static async Task<StreamContent> CreateResponseContentCore<TResponse>(TResponse[] responses, ICompressionProvider compressionProvider)
where TResponse : IMessage<TResponse>
{
var ms = new MemoryStream();
foreach (var response in responses)
{
await WriteResponseAsync(ms, response, compressionProvider);
}

ms.Seek(0, SeekOrigin.Begin);
var streamContent = new StreamContent(ms);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/grpc");
return streamContent;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// <copyright file="ResponseUtils.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>

using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace OpenTelemetry.Instrumentation.Grpc.Tests.GrpcTestHelpers
{
internal static class ResponseUtils
{
internal const string MessageEncodingHeader = "grpc-encoding";
internal const string IdentityGrpcEncoding = "identity";
internal const string StatusTrailer = "grpc-status";
internal static readonly MediaTypeHeaderValue GrpcContentTypeHeaderValue = new MediaTypeHeaderValue("application/grpc");
internal static readonly Version ProtocolVersion = new Version(2, 0);
private const int MessageDelimiterSize = 4; // how many bytes it takes to encode "Message-Length"
private const int HeaderSize = MessageDelimiterSize + 1; // message length + compression flag

public static HttpResponseMessage CreateResponse(
HttpStatusCode statusCode,
HttpContent payload,
global::Grpc.Core.StatusCode? grpcStatusCode = global::Grpc.Core.StatusCode.OK)
{
payload.Headers.ContentType = GrpcContentTypeHeaderValue;

var message = new HttpResponseMessage(statusCode)
{
Content = payload,
Version = ProtocolVersion,
};

message.RequestMessage = new HttpRequestMessage();
#if NETFRAMEWORK
message.RequestMessage.Properties[TrailingHeadersHelpers.ResponseTrailersKey] = new ResponseTrailers();
#endif
message.Headers.Add(MessageEncodingHeader, IdentityGrpcEncoding);

if (grpcStatusCode != null)
{
message.TrailingHeaders().Add(StatusTrailer, grpcStatusCode.Value.ToString("D"));
}

return message;
}

public static Task WriteHeaderAsync(Stream stream, int length, bool compress, CancellationToken cancellationToken)
{
var headerData = new byte[HeaderSize];

// Compression flag
headerData[0] = compress ? (byte)1 : (byte)0;

// Message length
EncodeMessageLength(length, headerData.AsSpan(1));

return stream.WriteAsync(headerData, 0, headerData.Length, cancellationToken);
}

private static void EncodeMessageLength(int messageLength, Span<byte> destination)
{
Debug.Assert(destination.Length >= MessageDelimiterSize, "Buffer too small to encode message length.");

BinaryPrimitives.WriteUInt32BigEndian(destination, (uint)messageLength);
}

#if NETFRAMEWORK
private class ResponseTrailers : HttpHeaders
{
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// <copyright file="TestHttpMessageHandler.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>

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace OpenTelemetry.Instrumentation.Grpc.Tests.GrpcTestHelpers
{
public class TestHttpMessageHandler : HttpMessageHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsync;

public TestHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsync)
{
this.sendAsync = sendAsync;
}

public static TestHttpMessageHandler Create(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendAsync)
{
var tcs = new TaskCompletionSource<HttpResponseMessage>(TaskCreationOptions.RunContinuationsAsynchronously);

return new TestHttpMessageHandler(async (request, cancellationToken) =>
{
using var registration = cancellationToken.Register(() => tcs.TrySetCanceled());
var result = await Task.WhenAny(sendAsync(request), tcs.Task);
return await result;
});
}

public static TestHttpMessageHandler Create(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsync)
{
return new TestHttpMessageHandler(sendAsync);
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return this.sendAsync(request, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// <copyright file="TrailingHeadersHelpers.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>

using System.Net.Http;
using System.Net.Http.Headers;

namespace OpenTelemetry.Instrumentation.Grpc.Tests.GrpcTestHelpers
{
internal static class TrailingHeadersHelpers
{
public static readonly string ResponseTrailersKey = "__ResponseTrailers";

public static HttpHeaders TrailingHeaders(this HttpResponseMessage responseMessage)
{
#if !NETFRAMEWORK
return responseMessage.TrailingHeaders;
#else
if (responseMessage.RequestMessage.Properties.TryGetValue(ResponseTrailersKey, out var headers) &&
headers is HttpHeaders httpHeaders)
{
return httpHeaders;
}

// App targets .NET Standard 2.0 and the handler hasn't set trailers
// in RequestMessage.Properties with known key. Return empty collection.
// Client call will likely fail because it is unable to get a grpc-status.
return ResponseTrailers.Empty;
#endif
}

#if NETFRAMEWORK
public static void EnsureTrailingHeaders(this HttpResponseMessage responseMessage)
{
if (!responseMessage.RequestMessage.Properties.ContainsKey(ResponseTrailersKey))
{
responseMessage.RequestMessage.Properties[ResponseTrailersKey] = new ResponseTrailers();
}
}

private class ResponseTrailers : HttpHeaders
{
public static readonly ResponseTrailers Empty = new ResponseTrailers();
}
#endif
}
}
Loading

0 comments on commit 95500ff

Please sign in to comment.