@@ -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