@@ -158,6 +158,40 @@ public partial class JsonRpcClient
158
158
{
159
159
private int _globalId ;
160
160
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
+
161
195
public JsonRpcClient ( string baseUrl )
162
196
{
163
197
Url = baseUrl ;
@@ -210,6 +244,21 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
210
244
// therefore the latter will be done only in DEBUG mode
211
245
using ( var postStream = new MemoryStream ( ) )
212
246
{
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
213
262
using ( var sw = new StreamWriter ( postStream ) )
214
263
{
215
264
#if DEBUG
@@ -236,37 +285,67 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
236
285
switch ( JsonRpcVersion )
237
286
{
238
287
case JsonRpcVersion . v2 :
288
+ #if ( NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER )
289
+ activity ? . SetTag ( RpcAttributes . AttributeRpcJsonrpcVersion , "2.0" ) ;
290
+ #endif
239
291
#if DEBUG
240
292
string json2 = responseReader . ReadToEnd ( ) ;
241
293
var res2 = JsonConvert . DeserializeObject < JsonResponseV2 < T > > ( json2 , settings ) ;
242
294
#else
243
295
var res2 = ( JsonResponseV2 < T > ) serializer . Deserialize ( responseReader , typeof ( JsonResponseV2 < T > ) ) ;
244
296
#endif
297
+
245
298
if ( res2 . Error != null )
246
299
{
247
300
var descr = new List < string > { res2 . Error . Message } ;
248
301
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
249
307
throw new Failure ( descr ) ;
250
308
}
309
+
310
+ #if ( NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER )
311
+ activity ? . SetStatus ( ActivityStatusCode . Ok ) ;
312
+ #endif
251
313
return res2 . Result ;
252
314
default :
315
+ #if ( NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER )
316
+ activity ? . SetTag ( RpcAttributes . AttributeRpcJsonrpcVersion , "1.0" ) ;
317
+ #endif
253
318
#if DEBUG
254
319
string json1 = responseReader . ReadToEnd ( ) ;
255
320
var res1 = JsonConvert . DeserializeObject < JsonResponseV1 < T > > ( json1 , settings ) ;
256
321
#else
257
322
var res1 = ( JsonResponseV1 < T > ) serializer . Deserialize ( responseReader , typeof ( JsonResponseV1 < T > ) ) ;
258
323
#endif
324
+
259
325
if ( res1 . Error != null )
260
326
{
261
327
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
263
335
throw new Failure ( errorArray ) ;
336
+ }
264
337
}
338
+ #if ( NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER )
339
+ activity ? . SetStatus ( ActivityStatusCode . Ok ) ;
340
+ #endif
265
341
return res1 . Result ;
266
342
}
267
343
}
268
344
}
269
345
}
346
+ #if ( NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER )
347
+ }
348
+ #endif
270
349
}
271
350
}
272
351
@@ -319,6 +398,15 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
319
398
str . Flush ( ) ;
320
399
}
321
400
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
+
322
410
HttpWebResponse webResponse = null ;
323
411
try
324
412
{
@@ -346,6 +434,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
346
434
str . CopyTo ( responseStream ) ;
347
435
responseStream . Flush ( ) ;
348
436
}
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
+
349
447
}
350
448
finally
351
449
{
0 commit comments