Table of Contents

Observability

Traces and metrics

Every RPC emits one client span and one rpc.client.duration measurement, in seconds, following the OpenTelemetry gRPC conventions, with the attributes rpc.system, rpc.service, rpc.method, and rpc.grpc.status_code. A retried call is a single span with its final outcome. Nothing is recorded or allocated until a listener subscribes, and request payloads, metadata, and credentials are never recorded.

The source and the meter are both named OpenShell.Sdk, available as OpenShellTelemetry.ActivitySourceName and OpenShellTelemetry.MeterName. With the OpenTelemetry .NET SDK, subscribe by name: tracing.AddSource(OpenShellTelemetry.ActivitySourceName) and metrics.AddMeter(OpenShellTelemetry.MeterName).

Without OpenTelemetry, the .NET listeners receive the same data:

/// <summary>Prints every OpenShell RPC span and duration measurement, without OpenTelemetry.</summary>
internal static IDisposable ListenToOpenShell()
{
    var activities = new ActivityListener
    {
        ShouldListenTo = source => source.Name == OpenShellTelemetry.ActivitySourceName,
        Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
        ActivityStopped = activity => Console.WriteLine(
            $"{activity.DisplayName} {activity.GetTagItem("rpc.grpc.status_code")} {activity.Duration.TotalMilliseconds:F1} ms"),
    };
    ActivitySource.AddActivityListener(activities);

    var meters = new MeterListener
    {
        InstrumentPublished = (instrument, listener) =>
        {
            if (instrument.Meter.Name == OpenShellTelemetry.MeterName)
            {
                listener.EnableMeasurementEvents(instrument);
            }
        },
    };
    meters.SetMeasurementEventCallback<double>((instrument, seconds, tags, _) =>
        Console.WriteLine($"{instrument.Name}: {seconds:F3} s"));
    meters.Start();

    return new Listeners(activities, meters);
}

private sealed class Listeners(ActivityListener activities, MeterListener meters) : IDisposable
{
    public void Dispose()
    {
        activities.Dispose();
        meters.Dispose();
    }
}

Logging

Set OpenShellClientOptions.LoggerFactory to receive the SDK's own log events: retries, watch reconnects, the gRPC channel, and edge tunnel activity. The dependency injection package supplies the container's logger factory automatically, and GatewayConnectOptions takes one too.

using var loggerFactory = LoggerFactory.Create(logging => logging.AddConsole().SetMinimumLevel(LogLevel.Information));

// Retries and watch reconnects log under "OpenShell.Sdk", the edge tunnel under "OpenShell.Edge.EdgeTunnelProxy",
// and the gRPC channel under "Grpc.Net.Client".
var client = new OpenShellClient(new OpenShellClientOptions
{
    Address = new Uri("https://gateway.example.com"),
    LoggerFactory = loggerFactory,
});

A standalone EdgeTunnelProxy logs through EdgeTunnelOptions.Logger. Sandbox waits and token refresh do not log.