Skip to content

Commit 9d2cf5e

Browse files
edwintorokKonstantina Chremmou
authored andcommitted
CP-44752: propagate System.Diagnostics tracing information using W3C traceparent header.
Signed-off-by: Edwin Török <[email protected]>
1 parent 59a95c3 commit 9d2cf5e

File tree

1 file changed

+144
-41
lines changed

1 file changed

+144
-41
lines changed

ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs

Lines changed: 144 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using System.IO;
3333
using System.Net;
3434
#if (NET8_0_OR_GREATER)
35+
using System.Diagnostics;
3536
using System.Linq;
3637
using System.Net.Http;
3738
using System.Net.Http.Headers;
@@ -71,6 +72,8 @@ public static JsonRequest Create(JsonRpcVersion jsonRpcVersion, int id, string m
7172
}
7273
}
7374

75+
public abstract string JsonRPC { get;}
76+
7477
/// <summary>
7578
/// Unique call id. Can be null in JSON_RPC v2.0, but xapi disallows it.
7679
/// </summary>
@@ -101,6 +104,9 @@ public JsonRequestV1(int id, string method, JToken parameters)
101104
: base(id, method, parameters)
102105
{
103106
}
107+
108+
[JsonIgnore]
109+
public override string JsonRPC => "1.0";
104110
}
105111

106112
internal class JsonRequestV2 : JsonRequest
@@ -111,7 +117,7 @@ public JsonRequestV2(int id, string method, JToken parameters)
111117
}
112118

113119
[JsonProperty("jsonrpc", Required = Required.Always)]
114-
public string JsonRPC => "2.0";
120+
public override string JsonRPC => "2.0";
115121
}
116122

117123

@@ -158,6 +164,42 @@ public partial class JsonRpcClient
158164
{
159165
private int _globalId;
160166

167+
#if (NET8_0_OR_GREATER)
168+
private static readonly Type ClassType = typeof(JsonRpcClient);
169+
private static readonly System.Reflection.AssemblyName ClassAssemblyName = ClassType?.Assembly?.GetName();
170+
private static readonly ActivitySource source = new ActivitySource(ClassAssemblyName.Name + "." + ClassType?.FullName, ClassAssemblyName.Version?.ToString());
171+
172+
// Follow naming conventions from OpenTelemetry.SemanticConventions
173+
// Not yet on NuGet though:
174+
// dotnet add package OpenTelemetry.SemanticConventions
175+
private static class RpcAttributes
176+
{
177+
public const string AttributeRpcMethod = "rpc.method";
178+
public const string AttributeRpcSystem = "rpc.system";
179+
public const string AttributeRpcService = "rpc.service";
180+
public const string AttributeRpcJsonrpcErrorCode = "rpc.jsonrpc.error_code";
181+
public const string AttributeRpcJsonrpcErrorMessage = "rpc.jsonrpc.error_message";
182+
public const string AttributeRpcJsonrpcRequestId = "rpc.jsonrpc.request_id";
183+
public const string AttributeRpcJsonrpcVersion = "rpc.jsonrpc.version";
184+
public const string AttributeRpcMessageType = "rpc.message.type";
185+
186+
public static class RpcMessageTypeValues
187+
{
188+
public const string Sent = "SENT";
189+
public const string Received = "RECEIVED";
190+
}
191+
}
192+
193+
private static class ServerAttributes
194+
{
195+
public const string AttributeServerAddress = "server.address";
196+
}
197+
198+
// not part of the SemanticConventions package
199+
private const string ValueJsonRpc = "jsonrpc";
200+
private const string EventRpcMessage = "rpc.message";
201+
#endif
202+
161203
public JsonRpcClient(string baseUrl)
162204
{
163205
Url = baseUrl;
@@ -216,63 +258,98 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
216258
// therefore the latter will be done only in DEBUG mode
217259
using (var postStream = new MemoryStream())
218260
{
219-
using (var sw = new StreamWriter(postStream))
261+
#if (NET8_0_OR_GREATER)
262+
// the semantic convention is $package.$service/$method
263+
using (Activity activity = source.CreateActivity("XenAPI/" + callName, ActivityKind.Client))
220264
{
265+
activity?.Start();
266+
// Set the fields described in the OpenTelemetry Semantic Conventions:
267+
// https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
268+
// https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
269+
activity?.SetTag(RpcAttributes.AttributeRpcSystem, ValueJsonRpc);
270+
activity?.SetTag(ServerAttributes.AttributeServerAddress, new Uri(Url).Host);
271+
activity?.SetTag(RpcAttributes.AttributeRpcMethod, callName);
272+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcRequestId, id.ToString());
273+
#endif
274+
using (var sw = new StreamWriter(postStream))
275+
{
221276
#if DEBUG
222-
var settings = CreateSettings(serializer.Converters);
223-
string jsonReq = JsonConvert.SerializeObject(request, settings);
224-
if (RequestEvent != null)
225-
RequestEvent(jsonReq);
226-
sw.Write(jsonReq);
277+
var settings = CreateSettings(serializer.Converters);
278+
string jsonReq = JsonConvert.SerializeObject(request, settings);
279+
if (RequestEvent != null)
280+
RequestEvent(jsonReq);
281+
sw.Write(jsonReq);
227282
#else
228-
if (RequestEvent != null)
229-
RequestEvent(callName);
230-
serializer.Serialize(sw, request);
283+
if (RequestEvent != null)
284+
RequestEvent(callName);
285+
serializer.Serialize(sw, request);
231286
#endif
232-
sw.Flush();
233-
postStream.Seek(0, SeekOrigin.Begin);
287+
sw.Flush();
288+
postStream.Seek(0, SeekOrigin.Begin);
234289

235-
using (var responseStream = new MemoryStream())
236-
{
237-
PerformPostRequest(postStream, responseStream);
238-
responseStream.Position = 0;
239-
240-
using (var responseReader = new StreamReader(responseStream))
290+
using (var responseStream = new MemoryStream())
241291
{
242-
switch (JsonRpcVersion)
292+
PerformPostRequest(postStream, responseStream);
293+
responseStream.Position = 0;
294+
295+
using (var responseReader = new StreamReader(responseStream))
243296
{
244-
case JsonRpcVersion.v2:
297+
#if (NET8_0_OR_GREATER)
298+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, request.JsonRPC);
299+
#endif
300+
switch (JsonRpcVersion)
301+
{
302+
case JsonRpcVersion.v2:
245303
#if DEBUG
246-
string json2 = responseReader.ReadToEnd();
247-
var res2 = JsonConvert.DeserializeObject<JsonResponseV2<T>>(json2, settings);
304+
string json2 = responseReader.ReadToEnd();
305+
var res2 = JsonConvert.DeserializeObject<JsonResponseV2<T>>(json2, settings);
248306
#else
249-
var res2 = (JsonResponseV2<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV2<T>));
307+
var res2 = (JsonResponseV2<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV2<T>));
250308
#endif
251-
if (res2.Error != null)
252-
{
253-
var descr = new List<string> { res2.Error.Message };
254-
descr.AddRange(res2.Error.Data.ToObject<string[]>());
255-
throw new Failure(descr);
256-
}
257-
return res2.Result;
258-
default:
309+
if (res2.Error != null)
310+
{
311+
var descr = new List<string> { res2.Error.Message };
312+
descr.AddRange(res2.Error.Data.ToObject<string[]>());
313+
#if (NET8_0_OR_GREATER)
314+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorCode, res2.Error.Code);
315+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, descr);
316+
activity?.SetStatus(ActivityStatusCode.Error);
317+
#endif
318+
throw new Failure(descr);
319+
}
320+
#if (NET8_0_OR_GREATER)
321+
activity?.SetStatus(ActivityStatusCode.Ok);
322+
#endif
323+
return res2.Result;
324+
default:
259325
#if DEBUG
260-
string json1 = responseReader.ReadToEnd();
261-
var res1 = JsonConvert.DeserializeObject<JsonResponseV1<T>>(json1, settings);
326+
string json1 = responseReader.ReadToEnd();
327+
var res1 = JsonConvert.DeserializeObject<JsonResponseV1<T>>(json1, settings);
262328
#else
263-
var res1 = (JsonResponseV1<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV1<T>));
329+
var res1 = (JsonResponseV1<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV1<T>));
264330
#endif
265-
if (res1.Error != null)
266-
{
267-
var errorArray = res1.Error.ToObject<string[]>();
331+
var errorArray = res1.Error?.ToObject<string[]>();
268332
if (errorArray != null)
333+
{
334+
#if (NET8_0_OR_GREATER)
335+
activity?.SetStatus(ActivityStatusCode.Error);
336+
// we can't be sure whether we'll have a Code here
337+
// the exact format of an error object is not specified in JSONRPC v1
338+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, errorArray.ToString());
339+
#endif
269340
throw new Failure(errorArray);
270-
}
271-
return res1.Result;
341+
}
342+
#if (NET8_0_OR_GREATER)
343+
activity?.SetStatus(ActivityStatusCode.Ok);
344+
#endif
345+
return res1.Result;
346+
}
272347
}
273348
}
274349
}
350+
#if (NET8_0_OR_GREATER)
275351
}
352+
#endif
276353
}
277354
}
278355

@@ -294,7 +371,7 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
294371
Proxy = WebProxy
295372
};
296373

297-
httpHandler.ServerCertificateCustomValidationCallback = ServerCertificateValidationCallback;
374+
httpHandler.ServerCertificateCustomValidationCallback = ServerCertificateValidationCallback;
298375

299376
httpClient = new HttpClient(httpHandler) { Timeout = TimeSpan.FromMilliseconds(Timeout) };
300377

@@ -313,6 +390,26 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
313390
requestMessage.Headers.Add(header.Key, header.Value);
314391
}
315392

393+
// propagate W3C traceparent and tracestate
394+
// HttpClient would do this automatically on .NET 5,
395+
// and .NET 6 would provide even more control over this: https://blog.ladeak.net/posts/opentelemetry-net6-httpclient
396+
// the caller must ensure that the activity is in W3C format (by inheritance or direct setting)
397+
var activity = Activity.Current;
398+
if (activity != null)
399+
{
400+
if (activity.IdFormat == ActivityIdFormat.W3C)
401+
{
402+
requestMessage.Headers.Add("traceparent", activity.Id);
403+
var state = activity.TraceStateString;
404+
405+
if (state?.Length > 0)
406+
requestMessage.Headers.Add("tracestate", state);
407+
}
408+
409+
var tags = new ActivityTagsCollection { { RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Sent } };
410+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
411+
}
412+
316413
responseMessage = httpClient.SendAsync(requestMessage).Result;
317414
responseMessage.EnsureSuccessStatusCode();
318415

@@ -321,10 +418,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
321418
responseStream.Flush();
322419

323420
ResponseHeaders = responseMessage.Headers.ToDictionary(header => header.Key, header => string.Join(",", header.Value));
421+
422+
if (activity != null)
423+
{
424+
var tags = new ActivityTagsCollection { { RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Received } };
425+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
426+
}
324427
}
325428
finally
326429
{
327-
RequestHeaders = null;
430+
RequestHeaders = null;
328431
responseMessage?.Dispose();
329432
requestMessage?.Dispose();
330433
httpClient?.Dispose();

0 commit comments

Comments
 (0)