Compare commits

...

2 Commits

Author SHA1 Message Date
Jedd Morgan 70acc06f37 feat(otel): Change sig for remote (#460)
.NET Build and Publish / build (push) Has been cancelled
* Remote changes

* Fix tests

* Fix tests

* ditto
2026-03-16 15:32:33 +00:00
Jedd Morgan a2c99a537a feat(otel): Add proper support for remote spans (#458)
.NET Build and Publish / build (push) Has been cancelled
* Add proper support for remote spans

* Fix tests
2026-03-12 12:52:10 +00:00
8 changed files with 58 additions and 29 deletions
@@ -5,6 +5,7 @@ public interface ISdkActivity : IDisposable
void SetTag(string key, object? value);
void RecordException(Exception e);
string TraceId { get; }
string SpanId { get; }
void SetStatus(SdkActivityStatusCode code);
void InjectHeaders(Action<string, string> header);
@@ -1,12 +1,20 @@
using System.Runtime.CompilerServices;
using Speckle.Connectors.Logging;
namespace Speckle.Sdk.Logging;
public interface ISdkActivityFactory : IDisposable
{
/// <param name="name"></param>
/// <param name="source"></param>
/// <param name="parentId">Only need to set if the parent is coming from an external source (e.g.to trace between client and server)</param>
/// <returns></returns>
ISdkActivity? Start(string? name = default, [CallerMemberName] string source = "", string? parentId = null);
ISdkActivity? Start(
string? name = null,
SdkActivityKind kind = SdkActivityKind.Internal,
[CallerMemberName] string source = ""
);
ISdkActivity? StartRemote(
string traceContext,
SdkActivityKind kind,
string? name = null,
[CallerMemberName] string source = ""
);
}
@@ -0,0 +1,30 @@
namespace Speckle.Connectors.Logging;
public enum SdkActivityKind
{
/// <summary>
/// Default value.
/// Indicates that the Activity represents an internal operation within an application, as opposed to an operations with remote parents or children.
/// </summary>
Internal = 0,
/// <summary>
/// Server activity represents request incoming from external component.
/// </summary>
Server = 1,
/// <summary>
/// Client activity represents outgoing request to the external component.
/// </summary>
Client = 2,
/// <summary>
/// Producer activity represents output provided to external components.
/// </summary>
Producer = 3,
/// <summary>
/// Consumer activity represents output received from an external component.
/// </summary>
Consumer = 4,
}
@@ -1,8 +1,12 @@
namespace Speckle.Sdk.Logging;
using Speckle.Connectors.Logging;
namespace Speckle.Sdk.Logging;
public sealed class NullActivityFactory : ISdkActivityFactory
{
public void Dispose() { }
public ISdkActivity? Start(string? name = default, string source = "", string? parentId = null) => null;
public ISdkActivity? Start(string? name, SdkActivityKind kind, string source) => null;
public ISdkActivity? StartRemote(string traceContext, SdkActivityKind kind, string? name, string source) => null;
}
@@ -52,7 +52,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "DownloadObjects", null)).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "DownloadObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -91,7 +91,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "DownloadSingleObject", null)).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "DownloadSingleObject")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -132,7 +132,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "HasObjects", null)).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "HasObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -171,7 +171,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "UploadObjects", null)).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "UploadObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -1,8 +0,0 @@
{
"Type": "AggregateException",
"InnerException": {
"Data": {},
"Message": "FORBIDDEN: Your auth token does not have the required scope: workspace:read.",
"Type": "SpeckleGraphQLForbiddenException"
}
}
@@ -1,8 +0,0 @@
{
"Type": "AggregateException",
"InnerException": {
"Data": {},
"Message": "FORBIDDEN: Your auth token does not have the required scope: workspace:read.",
"Type": "SpeckleGraphQLForbiddenException"
}
}
@@ -25,13 +25,15 @@ public class WorkspaceResourceTests
public async Task TestGetWorkspace()
{
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.Get("non-existent-id"));
await Verify(ex);
Assert.Single(ex.InnerExceptions);
Assert.All(ex.InnerExceptions, item => Assert.IsType<SpeckleGraphQLForbiddenException>(item));
}
[Fact]
public async Task TestGetProjects()
{
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.GetProjects("non-existent-id"));
await Verify(ex);
Assert.Single(ex.InnerExceptions);
Assert.All(ex.InnerExceptions, item => Assert.IsType<SpeckleGraphQLForbiddenException>(item));
}
}