< Summary

Information
Class: NGql.Core.Observability.NGqlActivity
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Observability/NGqlActivity.cs
Line coverage
100%
Covered lines: 32
Uncovered lines: 0
Coverable lines: 32
Total lines: 208
Line coverage: 100%
Branch coverage
100%
Covered branches: 28
Total branches: 28
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
StartQuery(...)100%11100%
StartField(...)100%11100%
StartPooling(...)100%11100%
WithQueryTags(...)100%11100%
WithFieldTags(...)100%11100%
WithPoolingTags(...)100%11100%
WithTag(...)100%22100%
WithStatus(...)100%22100%
WithException(...)100%88100%
AddEvent(...)100%44100%
AddEvent(...)100%44100%
Dispose()100%22100%
get_IsRecording()100%22100%
get_Id()100%22100%
get_TraceId()100%22100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Observability/NGqlActivity.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Runtime.CompilerServices;
 3
 4namespace NGql.Core.Observability;
 5
 6/// <summary>
 7/// Helper class for creating scoped activities with automatic disposal and error handling.
 8/// Provides convenient RAII pattern for OpenTelemetry-compatible distributed tracing.
 9/// </summary>
 10internal readonly ref struct NGqlActivity
 11{
 12    private readonly Activity? _activity;
 13
 14    private NGqlActivity(Activity? activity)
 15    {
 1099516        _activity = activity;
 1099517    }
 18
 19    /// <summary>
 20    /// Creates a scoped activity for query building operations
 21    /// </summary>
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23    public static NGqlActivity StartQuery(string operationName)
 24    {
 27625        var activity = NGqlTelemetry.StartQueryBuildingActivity(operationName);
 27626        return new NGqlActivity(activity);
 27    }
 28
 29    /// <summary>
 30    /// Creates a scoped activity for field operations
 31    /// </summary>
 32    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 33    public static NGqlActivity StartField(string operationName)
 34    {
 3035        var activity = NGqlTelemetry.StartFieldActivity(operationName);
 3036        return new NGqlActivity(activity);
 37    }
 38
 39    /// <summary>
 40    /// Creates a scoped activity for pooling operations
 41    /// </summary>
 42    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 43    public static NGqlActivity StartPooling(string poolType, string operation)
 44    {
 1068945        var activity = NGqlTelemetry.StartPoolingActivity(poolType, operation);
 1068946        return new NGqlActivity(activity);
 47    }
 48
 49    /// <summary>
 50    /// Adds query-related tags to the activity
 51    /// </summary>
 52    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 53    public readonly NGqlActivity WithQueryTags(string? queryName, int fieldCount)
 54    {
 3055        NGqlTelemetry.TagQueryActivity(_activity, queryName, fieldCount);
 3056        return this;
 57    }
 58
 59    /// <summary>
 60    /// Adds field-related tags to the activity
 61    /// </summary>
 62    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 63    public readonly NGqlActivity WithFieldTags(string fieldPath, bool hasArguments, bool hasMetadata)
 64    {
 3065        NGqlTelemetry.TagFieldActivity(_activity, fieldPath, hasArguments, hasMetadata);
 3066        return this;
 67    }
 68
 69    /// <summary>
 70    /// Adds pooling-related tags to the activity
 71    /// </summary>
 72    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 73    public readonly NGqlActivity WithPoolingTags(string poolType, string cacheHit, int poolSize)
 74    {
 1067475        NGqlTelemetry.TagPoolingActivity(_activity, poolType, cacheHit, poolSize);
 1067476        return this;
 77    }
 78
 79    /// <summary>
 80    /// Adds a custom tag to the activity
 81    /// </summary>
 82    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 83    public readonly NGqlActivity WithTag(string key, object? value)
 84    {
 26785        _activity?.SetTag(key, value);
 26786        return this;
 87    }
 88
 89    /// <summary>
 90    /// Adds a custom status to the activity
 91    /// </summary>
 92    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 93    public readonly NGqlActivity WithStatus(ActivityStatusCode statusCode, string? description = null)
 94    {
 16595        _activity?.SetStatus(statusCode, description);
 16596        return this;
 97    }
 98
 99    /// <summary>
 100    /// Records an exception in the activity. Each call site uses the null-conditional operator
 101    /// so an inactive activity (no listener registered) is a single null-test branch that's
 102    /// always reachable in tests.
 103    /// </summary>
 104    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 105    public readonly NGqlActivity WithException(Exception exception)
 106    {
 39107        _activity?.SetStatus(ActivityStatusCode.Error, exception.Message);
 39108        _activity?.SetTag("exception.type", exception.GetType().Name);
 39109        _activity?.SetTag("exception.message", exception.Message);
 39110        _activity?.SetTag("exception.stacktrace", exception.StackTrace);
 39111        return this;
 112    }
 113
 114    /// <summary>
 115    /// Adds an event to the activity (equivalent to OpenTelemetry spans events)
 116    /// </summary>
 117    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118    public readonly NGqlActivity AddEvent(string name, DateTimeOffset? timestamp = null)
 119    {
 369120        _activity?.AddEvent(new ActivityEvent(name, timestamp ?? DateTimeOffset.UtcNow));
 369121        return this;
 122    }
 123
 124    /// <summary>
 125    /// Adds an event with tags to the activity
 126    /// </summary>
 127    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 128    public readonly NGqlActivity AddEvent(string name, ActivityTagsCollection tags, DateTimeOffset? timestamp = null)
 129    {
 27130        _activity?.AddEvent(new ActivityEvent(name, timestamp ?? DateTimeOffset.UtcNow, tags));
 27131        return this;
 132    }
 133
 134    /// <summary>
 135    /// Automatically disposes the activity when leaving scope
 136    /// </summary>
 137    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 138    public readonly void Dispose()
 139    {
 140        // Activity disposal is automatic when it goes out of scope
 141        // This explicit disposal ensures proper cleanup for our ref struct
 10995142        _activity?.Dispose();
 18143    }
 144
 145    /// <summary>
 146    /// Gets whether the activity is active and being recorded
 147    /// </summary>
 198148    public readonly bool IsRecording => _activity?.IsAllDataRequested == true;
 149
 150    /// <summary>
 151    /// Gets the activity ID for correlation (equivalent to OpenTelemetry span ID)
 152    /// </summary>
 18153    public readonly string? Id => _activity?.Id;
 154
 155    /// <summary>
 156    /// Gets the trace ID for distributed tracing correlation
 157    /// </summary>
 18158    public readonly string? TraceId => _activity?.TraceId.ToString();
 159}
 160
 161/// <summary>
 162/// Extension methods for adding NGql-specific observability to existing code
 163/// </summary>
 164internal static class ActivityExtensions
 165{
 166    /// <summary>
 167    /// Safely executes an action with automatic error handling and activity recording
 168    /// </summary>
 169    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 170    public static T WithObservability<T>(this NGqlActivity activity, Func<T> action, string operationName)
 171    {
 172        try
 173        {
 174            activity.AddEvent($"{operationName}.start");
 175            var result = action();
 176            activity.WithStatus(ActivityStatusCode.Ok);
 177            activity.AddEvent($"{operationName}.complete");
 178            return result;
 179        }
 180        catch (Exception ex)
 181        {
 182            activity.WithException(ex);
 183            activity.AddEvent($"{operationName}.error");
 184            throw;
 185        }
 186    }
 187
 188    /// <summary>
 189    /// Safely executes an action with automatic error handling and activity recording
 190    /// </summary>
 191    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 192    public static void WithObservability(this NGqlActivity activity, Action action, string operationName)
 193    {
 194        try
 195        {
 196            activity.AddEvent($"{operationName}.start");
 197            action();
 198            activity.WithStatus(ActivityStatusCode.Ok);
 199            activity.AddEvent($"{operationName}.complete");
 200        }
 201        catch (Exception ex)
 202        {
 203            activity.WithException(ex);
 204            activity.AddEvent($"{operationName}.error");
 205            throw;
 206        }
 207    }
 208}