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