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