-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy pathGrpcLegacyClientCommon.cs
258 lines (220 loc) · 11.3 KB
/
GrpcLegacyClientCommon.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// <copyright file="GrpcLegacyClientCommon.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable
using System;
using System.Runtime.CompilerServices;
using Datadog.Trace.ClrProfiler.AutoInstrumentation.Grpc.GrpcLegacy.Client.DuckTypes;
using Datadog.Trace.Configuration;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Logging;
using Datadog.Trace.Propagators;
using Datadog.Trace.Util.Http;
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Grpc.GrpcLegacy.Client
{
internal static class GrpcLegacyClientCommon
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(GrpcLegacyClientCommon));
private static readonly ConditionalWeakTable<object, string> ChannelToHostMap = new();
public static Scope? CreateClientSpan(
Tracer tracer,
in CallInvocationDetailsStruct callInvocationDetails,
in StatusStruct receivedStatus)
{
var requestMetadata = callInvocationDetails.Options.Headers;
if (requestMetadata is null)
{
// We're in an undefined state, and can't reliably create the spans so bail out
Log.Error("Unable to create GRPC client spans - RequestMetadata object was null");
return null;
}
Scope? scope = null;
try
{
// try extracting all the details we need
var requestMetadataWrapper = new MetadataHeadersCollection(requestMetadata);
var existingContext = tracer.TracerManager.SpanContextPropagator.Extract(requestMetadataWrapper);
var existingSpanContext = existingContext.SpanContext;
// If this operation creates the trace, then we will need to re-apply the sampling priority
bool setSamplingPriority = existingSpanContext?.SamplingPriority != null && tracer.ActiveScope == null;
// grab the temporary values we stored in the metadata
ExtractTemporaryHeaders(
requestMetadata,
existingSpanContext?.TraceId128 ?? default,
out var methodKind,
out var methodName,
out var grpcService,
out var startTime,
out var parentContext);
var clientSchema = tracer.CurrentTraceSettings.Schema.Client;
var operationName = clientSchema.GetOperationNameForProtocol("grpc");
var serviceName = clientSchema.GetServiceName(component: "grpc-client");
var tags = clientSchema.CreateGrpcClientTags();
var methodFullName = callInvocationDetails.Method;
tags.Host = GetNormalizedHost(callInvocationDetails.Channel.Instance!, callInvocationDetails.Channel.Target);
GrpcCommon.AddGrpcTags(tags, tracer, methodKind, name: methodName, path: methodFullName, serviceName: grpcService);
var span = tracer.StartSpan(
operationName,
parent: parentContext,
tags: tags,
spanId: existingSpanContext?.SpanId ?? 0,
traceId: existingSpanContext?.TraceId128 ?? default,
serviceName: serviceName,
startTime: startTime);
span.Type = SpanTypes.Grpc;
span.ResourceName = methodFullName;
span.SetHeaderTags(requestMetadataWrapper, tracer.Settings.GrpcTags, GrpcCommon.RequestMetadataTagPrefix);
scope = tracer.ActivateSpan(span);
if (setSamplingPriority && existingSpanContext?.SamplingPriority is { } samplingPriority)
{
span.Context.TraceContext?.SetSamplingPriority(samplingPriority);
}
GrpcCommon.RecordFinalStatus(span, receivedStatus.StatusCode, receivedStatus.Detail, receivedStatus.DebugException);
tracer.TracerManager.Telemetry.IntegrationGeneratedSpan(IntegrationId.Grpc);
}
catch (Exception ex)
{
Log.Error(ex, "Error creating client span for GRPC call");
}
return scope;
}
public static void InjectHeaders<TMethod, TCallOptions>(Tracer tracer, TMethod method, ref TCallOptions callOptionsInstance)
where TMethod : IMethod
{
if (!tracer.Settings.IsIntegrationEnabled(IntegrationId.Grpc) || callOptionsInstance is null)
{
return;
}
try
{
var span = CreateInactiveSpan(tracer, method.FullName);
IMetadata proxy;
var callOptions = callOptionsInstance.DuckCast<ICallOptions>();
if (callOptions.Headers?.IsReadOnly == true)
{
proxy = callOptions.Headers;
}
else
{
var metadata = CachedMetadataHelper<TCallOptions>.CreateMetadata();
proxy = metadata.DuckCast<IMetadata>();
if (callOptions.Headers is { Count: > 0 })
{
// copy everything from the old one into the new one
foreach (var entry in callOptions.Headers)
{
proxy.Add(entry!);
}
}
// Replace the existing callOptions with a new instance (with the headers set)
callOptionsInstance = (TCallOptions)callOptions.WithHeaders(metadata);
}
// Save _everything_ we need in the metadata (we'll remove the cruft before serializing it)
var collection = new MetadataHeadersCollection(proxy);
// Add the headers that we need to recreate the span later
AddTemporaryHeaders(
proxy,
grpcType: method.GrpcType,
methodName: method.Name,
serviceName: method.ServiceName,
startTime: span.StartTime,
parentContext: span.Context.Parent);
// Add the propagation headers
var context = new PropagationContext(span.Context, Baggage.Current);
tracer.TracerManager.SpanContextPropagator.Inject(context, collection);
}
catch (Exception ex)
{
Log.Error(ex, "Error creating inactive client span for GRPC call");
}
}
internal static string GetNormalizedHost(object channelInstance, string target)
{
if (ChannelToHostMap.TryGetValue(channelInstance, out var normalizedHost))
{
return normalizedHost;
}
var host = target.IndexOf(':') switch
{
-1 => target,
var index => target.Substring(startIndex: 0, length: index),
};
normalizedHost = HttpRequestUtils.GetNormalizedHost(host);
#if NETCOREAPP3_1_OR_GREATER
ChannelToHostMap.AddOrUpdate(channelInstance, normalizedHost);
#else
ChannelToHostMap.GetValue(channelInstance, x => normalizedHost);
#endif
return normalizedHost;
}
private static void AddTemporaryHeaders(IMetadata metadata, int grpcType, string? methodName, string? serviceName, DateTimeOffset startTime, ISpanContext? parentContext)
{
metadata.Add(TemporaryHeaders.MethodKind, GrpcCommon.GetGrpcMethodKind(grpcType));
if (methodName is not null)
{
metadata.Add(TemporaryHeaders.MethodName, methodName);
}
if (serviceName is not null)
{
metadata.Add(TemporaryHeaders.Service, serviceName);
}
metadata.Add(TemporaryHeaders.StartTime, startTime.ToUnixTimeMilliseconds().ToString());
if (parentContext is not null)
{
metadata.Add(TemporaryHeaders.ParentId, parentContext.SpanId.ToString());
var parentService = parentContext is SpanContext s ? s.ServiceName : parentContext.ServiceName;
if (parentService is not null)
{
metadata.Add(TemporaryHeaders.ParentService, parentService);
}
}
}
private static void ExtractTemporaryHeaders(
IMetadata metadata,
TraceId traceId,
out string methodKind,
out string? methodName,
out string? serviceName,
out DateTimeOffset startTime,
out ISpanContext? parentContext)
{
// These should always be available
var deserializedMethodKind = metadata.Get(TemporaryHeaders.MethodKind)?.DuckCast<MetadataEntryStruct>().Value;
if (deserializedMethodKind is null)
{
// Shouldn't ever happen, but play it safe
Log.Warning("Temporary GRPC header x-datadog-temp-kind not found - assuming Unary request");
}
methodKind = deserializedMethodKind ?? GrpcMethodKinds.Unary;
methodName = metadata.Get(TemporaryHeaders.MethodName)?.DuckCast<MetadataEntryStruct>().Value;
serviceName = metadata.Get(TemporaryHeaders.Service)?.DuckCast<MetadataEntryStruct>().Value;
var unixTimeMilliseconds = metadata.Get(TemporaryHeaders.StartTime)?.DuckCast<MetadataEntryStruct>().Value;
startTime = long.TryParse(unixTimeMilliseconds, out var milliseconds)
? DateTimeOffset.FromUnixTimeMilliseconds(milliseconds)
: DateTimeOffset.UtcNow;
parentContext = null;
if (traceId != TraceId.Zero)
{
var parentIdString = metadata.Get(TemporaryHeaders.ParentId)?.DuckCast<MetadataEntryStruct>().Value;
var parentService = metadata.Get(TemporaryHeaders.ParentService)?.DuckCast<MetadataEntryStruct>().Value;
if (!string.IsNullOrEmpty(parentService) && ulong.TryParse(parentIdString, out var parentId))
{
parentContext = new ReadOnlySpanContext(traceId, parentId, parentService);
}
}
}
private static Span CreateInactiveSpan(Tracer tracer, string? methodFullName)
{
var operationName = tracer.CurrentTraceSettings.Schema.Client.GetOperationNameForProtocol("grpc");
var serviceName = tracer.CurrentTraceSettings.Schema.Client.GetServiceName(component: "grpc-client");
var tags = tracer.CurrentTraceSettings.Schema.Client.CreateGrpcClientTags();
tags.SetAnalyticsSampleRate(IntegrationId.Grpc, tracer.Settings, enabledWithGlobalSetting: false);
tracer.CurrentTraceSettings.Schema.RemapPeerService(tags);
var span = tracer.StartSpan(operationName, tags, serviceName: serviceName, addToTraceContext: false);
span.Type = SpanTypes.Grpc;
span.ResourceName = methodFullName;
return span;
}
}
}