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

Enhance distributed tracing support #7647

Merged
merged 1 commit into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions src/Orleans.CodeGenerator/InvokableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public static (ClassDeclarationSyntax, GeneratedInvokerDescription) Generate(
GenerateGetArgumentCount(libraryTypes, method),
GenerateGetMethodName(libraryTypes, method),
GenerateGetInterfaceName(libraryTypes, method),
GenerateGetActivityName(libraryTypes, method),
GenerateGetInterfaceType(libraryTypes, method),
GenerateGetInterfaceTypeArguments(libraryTypes, method),
GenerateGetMethodTypeArguments(libraryTypes, method),
Expand Down Expand Up @@ -410,6 +411,28 @@ private static MemberDeclarationSyntax GenerateGetArgumentCount(
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));

private static MemberDeclarationSyntax GenerateGetActivityName(
LibraryTypes libraryTypes,
MethodDescription methodDescription)
{
// This property is intended to contain a value suitable for use as an OpenTelemetry Span Name for RPC calls.
// Therefore, the interface name and method name components must not include periods or slashes.
// In order to avoid that, we omit the namespace from the interface name.
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md

var interfaceName = methodDescription.Method.ContainingType.ToDisplayName(methodDescription.TypeParameterSubstitutions, includeGlobalSpecifier: false, includeNamespace: false);
var methodName = methodDescription.Method.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var activityName = $"{interfaceName}/{methodName}";
return PropertyDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), "ActivityName")
.WithExpressionBody(
ArrowExpressionClause(
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(activityName))))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}

private static MemberDeclarationSyntax GenerateGetMethodName(
LibraryTypes libraryTypes,
MethodDescription methodDescription) =>
Expand Down
18 changes: 9 additions & 9 deletions src/Orleans.CodeGenerator/SyntaxGeneration/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ public static TypeSyntax ToTypeSyntax(this ITypeSymbol typeSymbol, Dictionary<IT
return result;
}

public static string ToDisplayName(this ITypeSymbol typeSymbol, Dictionary<ITypeParameterSymbol, string> substitutions, bool includeGlobalSpecifier = true)
public static string ToDisplayName(this ITypeSymbol typeSymbol, Dictionary<ITypeParameterSymbol, string> substitutions, bool includeGlobalSpecifier = true, bool includeNamespace = true)
{
if (typeSymbol.SpecialType == SpecialType.System_Void)
{
return "void";
}

var result = new StringBuilder();
ToTypeSyntaxInner(typeSymbol, substitutions, result, includeGlobalSpecifier);
ToTypeSyntaxInner(typeSymbol, substitutions, result, includeGlobalSpecifier, includeNamespace);
return result.ToString();
}

Expand Down Expand Up @@ -92,15 +92,15 @@ public static string ToDisplayName(this IAssemblySymbol assemblySymbol)
return result;
}

private static void ToTypeSyntaxInner(ITypeSymbol typeSymbol, Dictionary<ITypeParameterSymbol, string> substitutions, StringBuilder res, bool includeGlobalSpecifier = true)
private static void ToTypeSyntaxInner(ITypeSymbol typeSymbol, Dictionary<ITypeParameterSymbol, string> substitutions, StringBuilder res, bool includeGlobalSpecifier = true, bool includeNamespace = true)
{
switch (typeSymbol)
{
case IDynamicTypeSymbol:
res.Append("dynamic");
break;
case IArrayTypeSymbol a:
ToTypeSyntaxInner(a.ElementType, substitutions, res, includeGlobalSpecifier);
ToTypeSyntaxInner(a.ElementType, substitutions, res, includeGlobalSpecifier, includeNamespace);
res.Append('[');
if (a.Rank > 1)
{
Expand All @@ -120,21 +120,21 @@ private static void ToTypeSyntaxInner(ITypeSymbol typeSymbol, Dictionary<ITypePa
}
break;
case INamedTypeSymbol n:
OnNamedTypeSymbol(n, substitutions, res, includeGlobalSpecifier);
OnNamedTypeSymbol(n, substitutions, res, includeGlobalSpecifier, includeNamespace);
break;
default:
throw new NotSupportedException($"Symbols of type {typeSymbol?.GetType().ToString() ?? "null"} are not supported");
}

static void OnNamedTypeSymbol(INamedTypeSymbol symbol, Dictionary<ITypeParameterSymbol, string> substitutions, StringBuilder res, bool includeGlobalSpecifier)
static void OnNamedTypeSymbol(INamedTypeSymbol symbol, Dictionary<ITypeParameterSymbol, string> substitutions, StringBuilder res, bool includeGlobalSpecifier, bool includeNamespace)
{
switch (symbol.ContainingSymbol)
{
case INamespaceSymbol ns:
case INamespaceSymbol ns when includeNamespace:
AddFullNamespace(ns, res, includeGlobalSpecifier);
break;
case INamedTypeSymbol containingType:
OnNamedTypeSymbol(containingType, substitutions, res, includeGlobalSpecifier);
OnNamedTypeSymbol(containingType, substitutions, res, includeGlobalSpecifier, includeNamespace);
res.Append('.');
break;
}
Expand All @@ -151,7 +151,7 @@ static void OnNamedTypeSymbol(INamedTypeSymbol symbol, Dictionary<ITypeParameter
res.Append(',');
}

ToTypeSyntaxInner(typeParameter, substitutions, res, includeGlobalSpecifier);
ToTypeSyntaxInner(typeParameter, substitutions, res, includeGlobalSpecifier, includeNamespace);
first = false;
}
res.Append('>');
Expand Down
3 changes: 3 additions & 0 deletions src/Orleans.Core.Abstractions/Runtime/GrainReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ public abstract void SetTarget<TTargetHolder>(TTargetHolder holder)
/// <inheritdoc/>
public abstract string InterfaceName { get; }

/// <inheritdoc/>
public abstract string ActivityName { get; }

/// <inheritdoc/>
public abstract Type InterfaceType { get; }

Expand Down
58 changes: 33 additions & 25 deletions src/Orleans.Core/Diagnostics/ActivityPropagationGrainCallFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@ internal abstract class ActivityPropagationGrainCallFilter
protected const string TraceParentHeaderName = "traceparent";
protected const string TraceStateHeaderName = "tracestate";

internal const string ActivitySourceName = "orleans.runtime.graincall";
internal const string ActivityNameIn = "Orleans.Runtime.GrainCall.In";
internal const string ActivityNameOut = "Orleans.Runtime.GrainCall.Out";
internal const string RpcSystem = "orleans";
internal const string ActivitySourceName = "Microsoft.Orleans";

protected static readonly ActivitySource activitySource = new ActivitySource(ActivitySourceName);
protected static readonly ActivitySource Source = new(ActivitySourceName);

protected static async Task Process(IGrainCallContext context, Activity activity)
{
if (activity is not null)
{
// rpc attributes from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md
activity.SetTag("rpc.service", context.InterfaceMethod?.DeclaringType?.FullName);
activity.SetTag("rpc.method", context.InterfaceMethod?.Name);
activity.SetTag("net.peer.name", context.Grain?.ToString());
activity.SetTag("rpc.system", "orleans");
activity.SetTag("rpc.system", RpcSystem);
activity.SetTag("rpc.service", context.InterfaceName);
activity.SetTag("rpc.method", context.MethodName);

if (activity.IsAllDataRequested)
{
// Custom attributes
activity.SetTag("rpc.orleans.target_id", context.TargetId.ToString());
if (context.SourceId is GrainId sourceId)
{
activity.SetTag("rpc.orleans.source_id", sourceId.ToString());
}
}
}

try
Expand All @@ -49,6 +57,7 @@ protected static async Task Process(IGrainCallContext context, Activity activity
activity.SetTag("exception.escaped", true);
activity.SetTag("status", "Error");
}

throw;
}
finally
Expand All @@ -63,78 +72,80 @@ protected static async Task Process(IGrainCallContext context, Activity activity
/// </summary>
internal class ActivityPropagationOutgoingGrainCallFilter : ActivityPropagationGrainCallFilter, IOutgoingGrainCallFilter
{
private readonly DistributedContextPropagator propagator;
private readonly DistributedContextPropagator _propagator;

/// <summary>
/// Initializes a new instance of the <see cref="ActivityPropagationOutgoingGrainCallFilter"/> class.
/// </summary>
/// <param name="propagator">The context propagator.</param>
public ActivityPropagationOutgoingGrainCallFilter(DistributedContextPropagator propagator)
{
this.propagator = propagator;
_propagator = propagator;
}

/// <inheritdoc />
public Task Invoke(IOutgoingGrainCallContext context)
{
var activity = activitySource.StartActivity(ActivityNameOut, ActivityKind.Client);
var activity = Source.StartActivity(context.Request.ActivityName, ActivityKind.Client);

if (activity != null)
if (activity is not null)
{
propagator.Inject(activity, null, static (carrier, key, value) =>
_propagator.Inject(activity, null, static (carrier, key, value) =>
{
RequestContext.Set(key, value);
});
}

return Process(context, activity);
}

}

/// <summary>
/// Populates distributed context information from incoming requests.
/// </summary>
internal class ActivityPropagationIncomingGrainCallFilter : ActivityPropagationGrainCallFilter, IIncomingGrainCallFilter
{
private readonly DistributedContextPropagator propagator;
private readonly DistributedContextPropagator _propagator;

/// <summary>
/// Initializes a new instance of the <see cref="ActivityPropagationIncomingGrainCallFilter"/> class.
/// </summary>
/// <param name="propagator">The context propagator.</param>
public ActivityPropagationIncomingGrainCallFilter(DistributedContextPropagator propagator)
{
this.propagator = propagator;
_propagator = propagator;
}

/// <inheritdoc />
public Task Invoke(IIncomingGrainCallContext context)
{
Activity activity = default;

propagator.ExtractTraceIdAndState(null,
_propagator.ExtractTraceIdAndState(null,
static (object carrier, string fieldName, out string fieldValue, out IEnumerable<string> fieldValues) =>
{
fieldValues = default;
fieldValue = RequestContext.Get(fieldName) as string;
},
out var traceParent,
out var traceState);

if (!string.IsNullOrEmpty(traceParent))
{
activity = activitySource.CreateActivity(ActivityNameIn, ActivityKind.Server, traceParent);
activity = Source.CreateActivity(context.Request.ActivityName, ActivityKind.Server, traceParent);

if (activity is not null)
{
if (!string.IsNullOrEmpty(traceState))
{
activity.TraceStateString = traceState;
}
var baggage = propagator.ExtractBaggage(null, static (object carrier, string fieldName, out string fieldValue, out IEnumerable<string> fieldValues) =>

var baggage = _propagator.ExtractBaggage(null, static (object carrier, string fieldName, out string fieldValue, out IEnumerable<string> fieldValues) =>
{
fieldValues = default;
fieldValue = RequestContext.Get(fieldName) as string;
});

if (baggage is not null)
{
foreach (var baggageItem in baggage)
Expand All @@ -146,13 +157,10 @@ public Task Invoke(IIncomingGrainCallContext context)
}
else
{
activity = activitySource.CreateActivity(ActivityNameIn, ActivityKind.Server);
activity = Source.CreateActivity(context.Request.ActivityName, ActivityKind.Server);
}

if (activity is not null)
{
activity.Start();
}
activity?.Start();
return Process(context, activity);
}
}
Expand Down
1 change: 0 additions & 1 deletion src/Orleans.Core/Runtime/OutgoingCallInvoker.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Orleans.CodeGeneration;
using Orleans.Serialization.Invocation;
Expand Down
5 changes: 5 additions & 0 deletions src/Orleans.Serialization/Invocation/IInvokable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public interface IInvokable : IDisposable
/// </summary>
string InterfaceName { get; }

/// <summary>
/// Gets the activity name, which refers to both the interface name and method name.
/// </summary>
string ActivityName { get; }

/// <summary>
/// Gets the method info object, which may be <see langword="null"/>.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions test/Orleans.Serialization.UnitTests/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private static async ValueTask<Response> CompleteInvokeAsync(ValueTask resultTas
public abstract string MethodName { get; }
public abstract Type[] MethodTypeArguments { get; }
public abstract string InterfaceName { get; }
public abstract string ActivityName { get; }
public abstract Type InterfaceType { get; }
public abstract Type[] InterfaceTypeArguments { get; }
public abstract Type[] ParameterTypes { get; }
Expand Down Expand Up @@ -108,6 +109,7 @@ private static async ValueTask<Response> CompleteInvokeAsync(ValueTask<TResult>
public abstract string MethodName { get; }
public abstract Type[] MethodTypeArguments { get; }
public abstract string InterfaceName { get; }
public abstract string ActivityName { get; }
public abstract Type InterfaceType { get; }
public abstract Type[] InterfaceTypeArguments { get; }
public abstract Type[] ParameterTypes { get; }
Expand Down Expand Up @@ -163,6 +165,7 @@ private static async ValueTask<Response> CompleteInvokeAsync(Task<TResult> resul
public abstract string MethodName { get; }
public abstract Type[] MethodTypeArguments { get; }
public abstract string InterfaceName { get; }
public abstract string ActivityName { get; }
public abstract Type InterfaceType { get; }
public abstract Type[] InterfaceTypeArguments { get; }
public abstract Type[] ParameterTypes { get; }
Expand Down Expand Up @@ -219,6 +222,7 @@ private static async ValueTask<Response> CompleteInvokeAsync(Task resultTask)
public abstract string MethodName { get; }
public abstract Type[] MethodTypeArguments { get; }
public abstract string InterfaceName { get; }
public abstract string ActivityName { get; }
public abstract Type InterfaceType { get; }
public abstract Type[] InterfaceTypeArguments { get; }
public abstract Type[] ParameterTypes { get; }
Expand Down Expand Up @@ -254,6 +258,7 @@ public ValueTask<Response> Invoke()
public abstract string MethodName { get; }
public abstract Type[] MethodTypeArguments { get; }
public abstract string InterfaceName { get; }
public abstract string ActivityName { get; }
public abstract Type InterfaceType { get; }
public abstract Type[] InterfaceTypeArguments { get; }
public abstract Type[] ParameterTypes { get; }
Expand Down
Loading