Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CP-44752: propagate System.Diagnostics tracing information using W3C traceparent header #5929

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ doc:
sdk:
dune build --profile=$(PROFILE) @sdkgen xapi-sdk.install @ocaml/sdk-gen/install

# workaround for no .resx generation, just for compilation testing
sdksanity: sdk
cd _build/install/default/share/csharp/src && dotnet add package Newtonsoft.Json && dotnet build -f netstandard2.0
cd _build/install/default/share/csharp && dotnet test XenServerTest -p:DefineConstants=BUILD_FOR_TEST

.PHONY: sdk-build-c

sdk-build-c: sdk
Expand Down
121 changes: 120 additions & 1 deletion ocaml/sdk-gen/csharp/autogen/src/JsonRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@

using System;
using System.Collections.Generic;
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
using System.Diagnostics;
#endif
using System.IO;
using System.Net;
using System.Net.Security;
Expand Down Expand Up @@ -155,6 +158,41 @@ public partial class JsonRpcClient
{
private int _globalId;

#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
private static readonly Type ClassType = typeof(JsonRpcClient);
private static readonly System.Reflection.AssemblyName ClassAssemblyName= ClassType?.Assembly?.GetName();
private static readonly ActivitySource source = new ActivitySource(ClassAssemblyName.Name + "." + ClassType?.FullName, ClassAssemblyName.Version?.ToString());

// Follow naming conventions from OpenTelemetry.SemanticConventions
// Not yet on NuGet though:
// dotnet add package OpenTelemetry.SemanticConventions
private static class RpcAttributes {
public const string AttributeRpcMethod = "rpc.method";
public const string AttributeRpcSystem = "rpc.system";
public const string AttributeRpcService = "rpc.service";
public const string AttributeRpcJsonrpcErrorCode = "rpc.jsonrpc.error_code";
public const string AttributeRpcJsonrpcErrorMessage = "rpc.jsonrpc.error_message";
public const string AttributeRpcJsonrpcRequestId = "rpc.jsonrpc.request_id";
public const string AttributeRpcJsonrpcVersion = "rpc.jsonrpc.version";

public const string AttributeRpcMessageType = "rpc.message.type";
public static class RpcMessageTypeValues
{
public const string Sent = "SENT";

public const string Received = "RECEIVED";
}
}

private static class ServerAttributes {
public const string AttributeServerAddress = "server.address";
}

// not part of the SemanticConventions package
private const string ValueJsonRpc = "jsonrpc";
private const string EventRpcMessage = "rpc.message";
#endif

public JsonRpcClient(string baseUrl)
{
Url = baseUrl;
Expand Down Expand Up @@ -207,6 +245,21 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
// therefore the latter will be done only in DEBUG mode
using (var postStream = new MemoryStream())
{
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
// the semantic convention is $package.$service/$method
using (Activity activity = source.CreateActivity("XenAPI/" + callName, ActivityKind.Client))
{
// .NET 5 would use W3C format for the header by default but we build for .Net 4.x still
activity?.SetIdFormat(ActivityIdFormat.W3C);
activity?.Start();
// Set the fields described in the OpenTelemetry Semantic Conventions:
// https://web.archive.org/web/20250119181511/https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
// https://web.archive.org/web/20241113162246/https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
activity?.SetTag(RpcAttributes.AttributeRpcSystem, ValueJsonRpc);
activity?.SetTag(ServerAttributes.AttributeServerAddress, new Uri(Url).Host);
activity?.SetTag(RpcAttributes.AttributeRpcMethod, callName);
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcRequestId, id.ToString());
#endif
using (var sw = new StreamWriter(postStream))
{
#if DEBUG
Expand All @@ -233,37 +286,67 @@ protected virtual T Rpc<T>(string callName, JToken parameters, JsonSerializer se
switch (JsonRpcVersion)
{
case JsonRpcVersion.v2:
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "2.0");
#endif
#if DEBUG
string json2 = responseReader.ReadToEnd();
var res2 = JsonConvert.DeserializeObject<JsonResponseV2<T>>(json2, settings);
#else
var res2 = (JsonResponseV2<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV2<T>));
#endif

if (res2.Error != null)
{
var descr = new List<string> { res2.Error.Message };
descr.AddRange(res2.Error.Data.ToObject<string[]>());
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorCode, res2.Error.Code);
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, descr);
activity?.SetStatus(ActivityStatusCode.Error);
#endif
throw new Failure(descr);
}

#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetStatus(ActivityStatusCode.Ok);
#endif
return res2.Result;
default:
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcVersion, "1.0");
#endif
#if DEBUG
string json1 = responseReader.ReadToEnd();
var res1 = JsonConvert.DeserializeObject<JsonResponseV1<T>>(json1, settings);
#else
var res1 = (JsonResponseV1<T>)serializer.Deserialize(responseReader, typeof(JsonResponseV1<T>));
#endif

if (res1.Error != null)
{
var errorArray = res1.Error.ToObject<string[]>();
if (errorArray != null)
if (errorArray != null) {
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetStatus(ActivityStatusCode.Error);
// we can't be sure whether we'll have a Code here
// the exact format of an error object is not specified in JSONRPC v1
activity?.SetTag(RpcAttributes.AttributeRpcJsonrpcErrorMessage, errorArray.ToString());
#endif
throw new Failure(errorArray);
}
}
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
activity?.SetStatus(ActivityStatusCode.Ok);
#endif
return res1.Result;
}
}
}
}
#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
}
#endif
}
}

Expand Down Expand Up @@ -293,12 +376,38 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
webRequest.Headers.Add(header.Key, header.Value);
}

#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
// propagate W3C traceparent and tracestate
// HttpClient would do this automatically on .NET 5,
// and .NET 6 would provide even more control over this: https://blog.ladeak.net/posts/opentelemetry-net6-httpclient
// the caller must ensure that the activity is in W3C format (by inheritance or direct setting)
var activity = Activity.Current;
if (activity != null && activity.IdFormat == ActivityIdFormat.W3C)
{
webRequest.Headers.Add("traceparent", activity.Id);
var state = activity.TraceStateString;
if (state?.Length > 0)
{
webRequest.Headers.Add("tracestate", state);
}
}
#endif

using (var str = webRequest.GetRequestStream())
{
postStream.CopyTo(str);
str.Flush();
}

#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
if (activity != null) {
var tags = new ActivityTagsCollection{
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Sent }
};
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
}
#endif

HttpWebResponse webResponse = null;
try
{
Expand Down Expand Up @@ -326,6 +435,16 @@ protected virtual void PerformPostRequest(Stream postStream, Stream responseStre
str.CopyTo(responseStream);
responseStream.Flush();
}

#if (NET462_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
if (activity != null) {
var tags = new ActivityTagsCollection{
{ RpcAttributes.AttributeRpcMessageType, RpcAttributes.RpcMessageTypeValues.Received }
};
activity.AddEvent(new ActivityEvent(EventRpcMessage, DateTimeOffset.Now, tags));
}
#endif

}
finally
{
Expand Down
4 changes: 4 additions & 0 deletions ocaml/sdk-gen/csharp/autogen/src/XenServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageIcon>packageIcon.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README-NuGet.md</PackageReadmeFile>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\LICENSE" Pack="true" PackagePath="LICENSE" />
Expand All @@ -30,6 +31,9 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="FriendlyErrorNames.Designer.cs">
<DesignTime>True</DesignTime>
Expand Down
84 changes: 84 additions & 0 deletions ocaml/sdk-gen/csharp/autogen/src/packages.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"version": 1,
"dependencies": {
".NETFramework,Version=v4.5": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.2, )",
"resolved": "1.0.2",
"contentHash": "5/cSEVld+px/CuRrbohO/djfg6++eR6zGpy88MgqloXvkj//WXWpFZyu/OpkXPN0u5m+dN/EVwLNYFUxD4h2+A==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net45": "1.0.2"
}
},
"Newtonsoft.Json": {
"type": "Direct",
"requested": "[13.0.3, )",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net45": {
"type": "Transitive",
"resolved": "1.0.2",
"contentHash": "Nm14pRmqB+4u2JEMdtngnbDcJidTmswMxOJ992TpTwiwcUTERxLlHwwSh0HiUoRjS0TO0sozsiB0h6FHjCUdEA=="
}
},
".NETStandard,Version=v2.0": {
"NETStandard.Library": {
"type": "Direct",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Newtonsoft.Json": {
"type": "Direct",
"requested": "[13.0.3, )",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.Diagnostics.DiagnosticSource": {
"type": "Direct",
"requested": "[8.0.1, )",
"resolved": "8.0.1",
"contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
}
}
}
}
Loading