Skip to content

Commit d7468f6

Browse files
committed
CP-44752: SDK(C#): Conditional activity source for JsonRPC
Doesn't exist on .Net45. Only creates these activity sources if a listener has been created by the caller, otherwise `activity` will be `null`, and the code would be a no-op by default. A listener is created by OpenTelemetry instrumentation for example. Signed-off-by: Edwin Török <[email protected]>
1 parent 921dae5 commit d7468f6

File tree

1 file changed

+99
-1
lines changed

1 file changed

+99
-1
lines changed

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

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,40 @@ public partial class JsonRpcClient
158158
{
159159
private int _globalId;
160160

161+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
162+
private static readonly System.Reflection.AssemblyName AssemblyName = typeof(JsonRpcClient).Assembly?.GetName();
163+
private static ActivitySource source = new ActivitySource(AssemblyName?.FullName, AssemblyName?.Version?.ToString());
164+
165+
// Follow naming conventions from OpenTelemetry.SemanticConventions
166+
// Not yet on NuGet though:
167+
// dotnet add package OpenTelemetry.SemanticConventions
168+
private static class RpcAttributes {
169+
public const string AttributeRpcMethod = "rpc.method";
170+
public const string AttributeRpcSystem = "rpc.system";
171+
public const string AttributeRpcService = "rpc.service";
172+
public const string AttributeRpcJsonrpcErrorCode = "rpc.jsonrpc.error_code";
173+
public const string AttributeRpcJsonrpcErrorMessage = "rpc.jsonrpc.error_message";
174+
public const string AttributeRpcJsonrpcRequestId = "rpc.jsonrpc.request_id";
175+
public const string AttributeRpcJsonrpcVersion = "rpc.jsonrpc.version";
176+
177+
public const string AttributeRpcMessageType = "rpc.message.type";
178+
public static class RpcMessageTypeValues
179+
{
180+
public const string Sent = "SENT";
181+
182+
public const string Received = "RECEIVED";
183+
}
184+
}
185+
186+
private static class ServerAttributes {
187+
public const string AttributeServerAddress = "server.address";
188+
}
189+
190+
// not part of the SemanticConventions package
191+
private const string ValueJsonRpc = "jsonrpc";
192+
private const string EventRpcMessage = "rpc.message";
193+
#endif
194+
161195
public JsonRpcClient(string baseUrl)
162196
{
163197
Url = baseUrl;
@@ -210,6 +244,21 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
210244
// therefore the latter will be done only in DEBUG mode
211245
using (var postStream = new MemoryStream())
212246
{
247+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
248+
// the semantic convention is $package.$service/$method
249+
using (Activity activity = source.CreateActivity("XenAPI/" + callName, ActivityKind.Client))
250+
{
251+
// .NET 5 would use W3C format for the header by default but we build for .Net 4.x still
252+
activity?.SetIdFormat(ActivityIdFormat.W3C);
253+
activity?.Start();
254+
// Set the fields described in the OpenTelemetry Semantic Conventions:
255+
// https://web.archive.org/web/20250119181511/https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
256+
// https://web.archive.org/web/20241113162246/https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
257+
activity?.SetTag(RpcAttributes.AttributeRpcSystem, ValueJsonRpc);
258+
activity?.SetTag(ServerAttributes.AttributeServerAddress, new Uri(Url).Host);
259+
activity?.SetTag(RpcAttributes.AttributeRpcMethod, callName);
260+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcRequestId, id.ToString());
261+
#endif
213262
using (var sw = new StreamWriter(postStream))
214263
{
215264
#if DEBUG
@@ -236,37 +285,67 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
236285
switch (JsonRpcVersion)
237286
{
238287
case JsonRpcVersion.v2:
288+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
289+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "2.0");
290+
#endif
239291
#if DEBUG
240292
string json2 = responseReader.ReadToEnd();
241293
var res2 = JsonConvert.DeserializeObject<JsonResponseV2<T>>(json2, settings);
242294
#else
243295
var res2 = (JsonResponseV2<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV2<T>));
244296
#endif
297+
245298
if (res2.Error != null)
246299
{
247300
var descr = new List<string> { res2.Error.Message };
248301
descr.AddRange(res2.Error.Data.ToObject<string[]>());
302+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
303+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorCode, res2.Error.Code);
304+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, descr);
305+
activity?.SetStatus(ActivityStatusCode.Error);
306+
#endif
249307
throw new Failure(descr);
250308
}
309+
310+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
311+
activity?.SetStatus(ActivityStatusCode.Ok);
312+
#endif
251313
return res2.Result;
252314
default:
315+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
316+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "1.0");
317+
#endif
253318
#if DEBUG
254319
string json1 = responseReader.ReadToEnd();
255320
var res1 = JsonConvert.DeserializeObject<JsonResponseV1<T>>(json1, settings);
256321
#else
257322
var res1 = (JsonResponseV1<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV1<T>));
258323
#endif
324+
259325
if (res1.Error != null)
260326
{
261327
var errorArray = res1.Error.ToObject<string[]>();
262-
if (errorArray != null)
328+
if (errorArray != null) {
329+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
330+
activity?.SetStatus(ActivityStatusCode.Error);
331+
// we can't be sure whether we'll have a Code here
332+
// the exact format of an error object is not specified in JSONRPC v1
333+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, errorArray.ToString());
334+
#endif
263335
throw new Failure(errorArray);
336+
}
264337
}
338+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
339+
activity?.SetStatus(ActivityStatusCode.Ok);
340+
#endif
265341
return res1.Result;
266342
}
267343
}
268344
}
269345
}
346+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
347+
}
348+
#endif
270349
}
271350
}
272351

@@ -319,6 +398,15 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
319398
str.Flush();
320399
}
321400

401+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
402+
if (activity != null) {
403+
var tags = new ActivityTagsCollection{
404+
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Sent }
405+
};
406+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
407+
}
408+
#endif
409+
322410
HttpWebResponse webResponse = null;
323411
try
324412
{
@@ -346,6 +434,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
346434
str.CopyTo(responseStream);
347435
responseStream.Flush();
348436
}
437+
438+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
439+
if (activity != null) {
440+
var tags = new ActivityTagsCollection{
441+
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Received }
442+
};
443+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
444+
}
445+
#endif
446+
349447
}
350448
finally
351449
{

0 commit comments

Comments
 (0)