Skip to content

Commit f6c2641

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 f6c2641

File tree

1 file changed

+100
-1
lines changed

1 file changed

+100
-1
lines changed

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

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

161+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
162+
private static readonly Type ClassType = typeof(JsonRpcClient);
163+
private static readonly System.Reflection.AssemblyName ClassAssemblyName= ClassType?.Assembly?.GetName();
164+
private static readonly ActivitySource source = new ActivitySource(ClassAssemblyName.Name + "." + ClassType?.FullName, ClassAssemblyName.Version?.ToString());
165+
166+
// Follow naming conventions from OpenTelemetry.SemanticConventions
167+
// Not yet on NuGet though:
168+
// dotnet add package OpenTelemetry.SemanticConventions
169+
private static class RpcAttributes {
170+
public const string AttributeRpcMethod = "rpc.method";
171+
public const string AttributeRpcSystem = "rpc.system";
172+
public const string AttributeRpcService = "rpc.service";
173+
public const string AttributeRpcJsonrpcErrorCode = "rpc.jsonrpc.error_code";
174+
public const string AttributeRpcJsonrpcErrorMessage = "rpc.jsonrpc.error_message";
175+
public const string AttributeRpcJsonrpcRequestId = "rpc.jsonrpc.request_id";
176+
public const string AttributeRpcJsonrpcVersion = "rpc.jsonrpc.version";
177+
178+
public const string AttributeRpcMessageType = "rpc.message.type";
179+
public static class RpcMessageTypeValues
180+
{
181+
public const string Sent = "SENT";
182+
183+
public const string Received = "RECEIVED";
184+
}
185+
}
186+
187+
private static class ServerAttributes {
188+
public const string AttributeServerAddress = "server.address";
189+
}
190+
191+
// not part of the SemanticConventions package
192+
private const string ValueJsonRpc = "jsonrpc";
193+
private const string EventRpcMessage = "rpc.message";
194+
#endif
195+
161196
public JsonRpcClient(string baseUrl)
162197
{
163198
Url = baseUrl;
@@ -210,6 +245,21 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
210245
// therefore the latter will be done only in DEBUG mode
211246
using (var postStream = new MemoryStream())
212247
{
248+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
249+
// the semantic convention is $package.$service/$method
250+
using (Activity activity = source.CreateActivity("XenAPI/" + callName, ActivityKind.Client))
251+
{
252+
// .NET 5 would use W3C format for the header by default but we build for .Net 4.x still
253+
activity?.SetIdFormat(ActivityIdFormat.W3C);
254+
activity?.Start();
255+
// Set the fields described in the OpenTelemetry Semantic Conventions:
256+
// https://web.archive.org/web/20250119181511/https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
257+
// https://web.archive.org/web/20241113162246/https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
258+
activity?.SetTag(RpcAttributes.AttributeRpcSystem, ValueJsonRpc);
259+
activity?.SetTag(ServerAttributes.AttributeServerAddress, new Uri(Url).Host);
260+
activity?.SetTag(RpcAttributes.AttributeRpcMethod, callName);
261+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcRequestId, id.ToString());
262+
#endif
213263
using (var sw = new StreamWriter(postStream))
214264
{
215265
#if DEBUG
@@ -236,37 +286,67 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
236286
switch (JsonRpcVersion)
237287
{
238288
case JsonRpcVersion.v2:
289+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
290+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "2.0");
291+
#endif
239292
#if DEBUG
240293
string json2 = responseReader.ReadToEnd();
241294
var res2 = JsonConvert.DeserializeObject<JsonResponseV2<T>>(json2, settings);
242295
#else
243296
var res2 = (JsonResponseV2<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV2<T>));
244297
#endif
298+
245299
if (res2.Error != null)
246300
{
247301
var descr = new List<string> { res2.Error.Message };
248302
descr.AddRange(res2.Error.Data.ToObject<string[]>());
303+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
304+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorCode, res2.Error.Code);
305+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, descr);
306+
activity?.SetStatus(ActivityStatusCode.Error);
307+
#endif
249308
throw new Failure(descr);
250309
}
310+
311+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
312+
activity?.SetStatus(ActivityStatusCode.Ok);
313+
#endif
251314
return res2.Result;
252315
default:
316+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
317+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "1.0");
318+
#endif
253319
#if DEBUG
254320
string json1 = responseReader.ReadToEnd();
255321
var res1 = JsonConvert.DeserializeObject<JsonResponseV1<T>>(json1, settings);
256322
#else
257323
var res1 = (JsonResponseV1<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV1<T>));
258324
#endif
325+
259326
if (res1.Error != null)
260327
{
261328
var errorArray = res1.Error.ToObject<string[]>();
262-
if (errorArray != null)
329+
if (errorArray != null) {
330+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
331+
activity?.SetStatus(ActivityStatusCode.Error);
332+
// we can't be sure whether we'll have a Code here
333+
// the exact format of an error object is not specified in JSONRPC v1
334+
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, errorArray.ToString());
335+
#endif
263336
throw new Failure(errorArray);
337+
}
264338
}
339+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
340+
activity?.SetStatus(ActivityStatusCode.Ok);
341+
#endif
265342
return res1.Result;
266343
}
267344
}
268345
}
269346
}
347+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
348+
}
349+
#endif
270350
}
271351
}
272352

@@ -319,6 +399,15 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
319399
str.Flush();
320400
}
321401

402+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
403+
if (activity != null) {
404+
var tags = new ActivityTagsCollection{
405+
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Sent }
406+
};
407+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
408+
}
409+
#endif
410+
322411
HttpWebResponse webResponse = null;
323412
try
324413
{
@@ -346,6 +435,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
346435
str.CopyTo(responseStream);
347436
responseStream.Flush();
348437
}
438+
439+
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
440+
if (activity != null) {
441+
var tags = new ActivityTagsCollection{
442+
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Received }
443+
};
444+
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
445+
}
446+
#endif
447+
349448
}
350449
finally
351450
{

0 commit comments

Comments
 (0)