Skip to content

Commit

Permalink
Merge pull request Cysharp#422 from Cysharp/feat/opentelemetry_100rc11
Browse files Browse the repository at this point in the history
feat: publish OpenTelemetry support to NuGet listed latest version.
  • Loading branch information
mayuki authored Jun 15, 2021
2 parents 440d434 + 04ef25a commit 59b4af3
Show file tree
Hide file tree
Showing 18 changed files with 674 additions and 544 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,7 @@ I believe that this can be easily and effectively applied to sending a large num

## Experimentals
### OpenTelemetry
MagicOnion.OpenTelemetry is implementation of [open\-telemetry/opentelemetry\-dotnet: OpenTelemetry \.NET SDK](https://github.com/open-telemetry/opentelemetry-dotnet), so you can use any OpenTelemetry exporter, like [Prometheus](https://prometheus.io/), [StackDriver](https://cloud.google.com/stackdriver/pricing), [Zipkin](https://zipkin.io/) and others.
MagicOnion.OpenTelemetry is implementation of [open\-telemetry/opentelemetry\-dotnet: OpenTelemetry \.NET SDK](https://github.com/open-telemetry/opentelemetry-dotnet), so you can use any OpenTelemetry exporter, like [Jaeger](https://www.jaegertracing.io/), [Zipkin](https://zipkin.io/), [StackDriver](https://cloud.google.com/stackdriver) and others.
See details at [MagicOnion.Server.OpenTelemetry](src/MagicOnion.Server.OpenTelemetry)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Reflection;

namespace MagicOnion.Server.OpenTelemetry.Internal
{
internal static class MagicOnionInstrumentation
{
/// <summary>
/// The assembly name.
/// </summary>
internal static readonly AssemblyName AssemblyName = typeof(MagicOnionInstrumentation).Assembly.GetName();

/// <summary>
/// The activity source name.
/// </summary>
internal static readonly string ActivitySourceName = AssemblyName.Name;

/// <summary>
/// The version.
/// </summary>
internal static readonly Version Version = AssemblyName.Version;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Text;

namespace MagicOnion.Server.OpenTelemetry
namespace MagicOnion.Server.OpenTelemetry.Internal
{
public static class MagicOnionTelemetry
internal static class MagicOnionTelemetryConstants
{
public const string ServiceContextItemKeyTrace = ".TraceContext";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using Grpc.Core;

namespace MagicOnion.Server.OpenTelemetry
namespace MagicOnion.Server.OpenTelemetry.Internal
{
public static class OpenTelemetryHelper
internal static class OpenTelemetryHelper
{
/// <summary>
/// Convert gRPC StatusCode to OpenTelemetry Status.
/// </summary>
/// <remarks>spec: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#set-status</remarks>
/// <param name="code"></param>
/// <returns></returns>
public static global::OpenTelemetry.Trace.Status GrpcToOpenTelemetryStatus(StatusCode code)
internal static global::OpenTelemetry.Trace.Status GrpcToOpenTelemetryStatus(StatusCode code)
{
return code switch
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;
using Grpc.Core;
using System.Collections.Generic;
using System.Linq;
using OpenTelemetry;

// ReSharper disable once CheckNamespace

namespace MagicOnion.Server.OpenTelemetry.Internal
{
internal static class PropagatorExtensions
{
/// <summary>
/// Injects the context into a carrier
/// </summary>
/// <param name="propagator"></param>
/// <param name="context"></param>
/// <param name="carrier"></param>
public static void Inject(this TextMapPropagator propagator, PropagationContext context, CallOptions carrier)
{
static void SetMetadata(Metadata metadata, string key, string value) => metadata.Add(new Metadata.Entry(key, value));
propagator.Inject(context, carrier.Headers, SetMetadata);
}

/// <summary>
/// Extract the context from a carrier
/// </summary>
/// <param name="propagator"></param>
/// <param name="activityContext"></param>
/// <param name="carrier"></param>
/// <returns></returns>
public static PropagationContext Extract(this TextMapPropagator propagator, ActivityContext? activityContext, Metadata carrier)
{
static IEnumerable<string> GetMetadata(Metadata metadata, string key)
{
for (var i = 0; i < metadata.Count; i++)
{
var entry = metadata[i];
if (entry.Key.Equals(key))
{
return new string[1] { entry.Value };
}
}

return Enumerable.Empty<string>();
}
return propagator.Extract(new PropagationContext(activityContext ?? default, Baggage.Current), carrier, GetMetadata);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace MagicOnion.Server.OpenTelemetry.Internal
{
/// <summary>
/// OpenTelemetry Tag Keys
/// </summary>
internal static class SemanticConventions
{
// tag spec: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#grpc
public const string AttributeServiceName = "service.name";
public const string AttributeException = "exception";

public const string AttributeHttpHost = "http.host";
public const string AttributeHttpUrl = "http.url";
public const string AttributeHttpUserAgent = "http.user_agent";

public const string AttributeRpcGrpcMethod = "rpc.grpc.method";
public const string AttributeRpcGrpcStatusCode = "rpc.grpc.status_code";
public const string AttributeRpcGrpcStatusDetail = "rpc.grpc.status_detail";
public const string AttributeRpcSystem = "rpc.system";
public const string AttributeRpcService = "rpc.service";
public const string AttributeRpcMethod = "rpc.method";

public const string AttributeMessageId = "message.id";
public const string AttributeMessageCompressedSize = "message.compressed_size";
public const string AttributeMessageUncompressedSize = "message.uncompressed_size";

public const string AttributeMagicOnionPeerName = "magiconion.peer.ip";
public const string AttributeMagicOnionAuthEnabled = "magiconion.auth.enabled";
public const string AttributeMagicOnionAuthPeerAuthenticated = "magiconion.auth.peer_authenticated";
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
Expand All @@ -18,19 +18,19 @@
<PackageId>MagicOnion.Server.OpenTelemetry</PackageId>
<Description>Telemetry Extensions of MagicOnion.</Description>
<PackageTags>$(PackageTags);OpenTelemetry</PackageTags>
<!-- Match to OpenTelemetry-dotnet beta version -->
<VersionSuffix>beta-080.1</VersionSuffix>
<!-- Match to OpenTelemetry-dotnet rc version. 1.1.0-beta2 -> beta2-1.0.0 -->
<VersionSuffix>beta2-1.1.0</VersionSuffix>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
<PackageReference Include="OpenTelemetry" Version="1.0.0-rc1.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc1.1" />
<PackageReference Include="OpenTelemetry" Version="1.1.0-beta2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MagicOnion.Client\MagicOnion.Client.csproj" />
<ProjectReference Include="..\MagicOnion.Server\MagicOnion.Server.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace MagicOnion.Server.OpenTelemetry
/// <summary>
/// ActivitySource for MagicOnion OpenTelemetry
/// </summary>
/// <remarks>Avoid directly register ActivitySource to Singleton for easier identification.</remarks>
public class MagicOnionActivitySources
{
private readonly ActivitySource activitySource;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Grpc.Core;
using MagicOnion.Client;
using MagicOnion.Server.OpenTelemetry.Internal;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace MagicOnion.Server.OpenTelemetry
{
// note: move package to MagicOnion.Client.OpenTelemetry?
/// <summary>
/// Collect OpenTelemetry Tracer with Client filter (Unary).
/// </summary>
public class MagicOnionOpenTelemetryClientFilter : IClientFilter
{
readonly ActivitySource source;
readonly MagicOnionOpenTelemetryOptions options;

public MagicOnionOpenTelemetryClientFilter(ActivitySource activitySource, MagicOnionOpenTelemetryOptions options)
{
this.source = activitySource;
this.options = options;
}

public async ValueTask<ResponseContext> SendAsync(RequestContext context, Func<RequestContext, ValueTask<ResponseContext>> next)
{
var rpcService = context.MethodPath.Split('/')[0];
using var rpcScope = new ClientRpcScope(rpcService, context.MethodPath, context, source, options);
rpcScope.SetTags(options.TracingTags);

try
{
var response = await next(context);

rpcScope.Complete();
return response;
}
catch (Exception ex)
{
rpcScope.CompleteWithException(ex);
throw;
}
finally
{
rpcScope.RestoreParentActivity();
}
}
}

internal class ClientRpcScope : RpcScope
{
public ClientRpcScope(string rpcService, string rpcMethod, RequestContext context, ActivitySource source, MagicOnionOpenTelemetryOptions options)
: base(rpcService, rpcMethod, options.ServiceName)
{
// capture the current activity
this.ParentActivity = Activity.Current;

if (!source.HasListeners())
return;

var rpcActivity = source.StartActivity(
context.MethodPath.TrimStart('/'),
ActivityKind.Client,
ParentActivity == default ? default : ParentActivity.Context);

if (rpcActivity == null)
return;

var callOptions = context.CallOptions;
if (callOptions.Headers == null)
{
callOptions = callOptions.WithHeaders(new Metadata());
}

SetActivity(rpcActivity);

Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(rpcActivity.Context, Baggage.Current), callOptions);
}

/// <summary>
/// Restores the parent activity.
/// </summary>
public void RestoreParentActivity()
{
Activity.Current = this.ParentActivity;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
using System;
using System.Reflection;
using OpenTelemetry.Metrics;
using OpenTelemetry.Metrics.Export;
using System.Collections.Generic;

namespace MagicOnion.Server.OpenTelemetry
{
/// <summary>
/// OpenTelemetry Options to inject Application Information
/// </summary>
public class MagicOnionOpenTelemetryOptions
{
/// <summary>
/// Metrics Exporter Endpoint. Default Prometheus endpoint.
/// ServiceName for Tracer. Especially Zipkin use service.name tag to identify service name.
/// </summary>
public string MetricsExporterEndpoint { get; set; } = "http://127.0.0.1:9184/metrics/";
/// <summary>
/// Metrics Exporter Hosting Endpoint.
/// </summary>
public string MetricsExporterHostingEndpoint { get; set; } = "http://+:9184/metrics/";
/// <summary>
/// Tracer ServiceName use as ActivitySource
/// </summary>
public string MagicOnionActivityName { get; set; } = Assembly.GetEntryAssembly().GetName().Name;
}
/// <remarks>input to tag `service.name`</remarks>
public string ServiceName { get; set; }

public class MagicOnionOpenTelemetryMeterFactoryOption
{
/// <summary>
/// OpenTelemetry MetricsProcessor. default is <see cref="UngroupedBatcher"/>
/// Expose RpsScope to the ServiceContext.Items. RpsScope key begin with .TraceContext
/// </summary>
public MetricProcessor MetricProcessor { get; set; } = new UngroupedBatcher();
/// <summary>
/// OpenTelemetry MetricsExporter Implementation to use.
/// </summary>
public MetricExporter MetricExporter { get; set; }
/// <summary>
/// OpenTelemetry Metric Push Interval.
/// </summary>
public TimeSpan MetricPushInterval { get; set; } = TimeSpan.FromSeconds(10);
public bool ExposeRpcScope { get; set; } = true;

/// <summary>
/// MagicOnionLogger to collect OpenTelemetry metrics.
/// Application specific OpenTelemetry Tracing tags
/// </summary>
public Func<MeterProvider, IMagicOnionLogger> MeterLogger { get; set; }
public Dictionary<string, string> TracingTags { get; set; } = new Dictionary<string, string>();
}
}
Loading

0 comments on commit 59b4af3

Please sign in to comment.