Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public void AddGraphQLErrorEvent(
IError error,
string? operationType = null,
string? operationName = null,
string? schemaCoordinate = null)
string? schemaCoordinate = null,
OperationDocumentInfo? documentInfo = null)
{
var tags = new ActivityTagsCollection
{
Expand Down Expand Up @@ -86,15 +87,8 @@ public void AddGraphQLErrorEvent(
tags[SemanticConventions.GraphQL.Document.Locations] = locations;
}

if (operationType is not null)
{
tags[SemanticConventions.GraphQL.Operation.Type] = operationType;
}

if (!string.IsNullOrEmpty(operationName))
{
tags[SemanticConventions.GraphQL.Operation.Name] = operationName;
}
EnrichOperation(tags, operationType, operationName);
EnrichDocumentInfo(tags, documentInfo);

if (error.Exception is { } exception)
{
Expand All @@ -108,9 +102,10 @@ public void AddGraphQLErrorEvent(

public void AddGraphQLErrorEvent(
Exception exception,
string? operationType = null,
string operationType,
string? operationName = null,
string? schemaCoordinate = null)
string? schemaCoordinate = null,
OperationDocumentInfo? documentInfo = null)
{
var tags = new ActivityTagsCollection
{
Expand All @@ -125,15 +120,8 @@ public void AddGraphQLErrorEvent(
tags[SemanticConventions.GraphQL.Field.SchemaCoordinate] = schemaCoordinate;
}

if (!string.IsNullOrEmpty(operationType))
{
tags[SemanticConventions.GraphQL.Operation.Type] = operationType;
}

if (!string.IsNullOrEmpty(operationName))
{
tags[SemanticConventions.GraphQL.Operation.Name] = operationName;
}
EnrichOperation(tags, operationType, operationName);
EnrichDocumentInfo(tags, documentInfo);

activity.AddEvent(new ActivityEvent("graphql.error", default, tags));
}
Expand Down Expand Up @@ -220,4 +208,44 @@ public void EnrichOperation(OperationType operationType, string? operationName)
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnrichOperation(
ActivityTagsCollection tags,
string? operationType,
string? operationName)
{
if (!string.IsNullOrEmpty(operationType))
{
tags[SemanticConventions.GraphQL.Operation.Type] = operationType;
}

if (!string.IsNullOrEmpty(operationName))
{
tags[SemanticConventions.GraphQL.Operation.Name] = operationName;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnrichDocumentInfo(
ActivityTagsCollection tags,
OperationDocumentInfo? documentInfo)
{
if (documentInfo is null)
{
return;
}

var hash = documentInfo.Hash;

if (!hash.IsEmpty)
{
tags[SemanticConventions.GraphQL.Document.Hash] = $"{hash.AlgorithmName}:{hash.Value}";
}

if (documentInfo is { IsPersisted: true, Id.HasValue: true })
{
tags[SemanticConventions.GraphQL.Document.Id] = documentInfo.Id.Value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ internal static class SemanticConventions
{
public const string ErrorType = "error.type";

public static class Server
{
public const string Address = "server.address";
public const string Port = "server.port";
}

public static class GraphQL
{
public static class Document
Expand Down Expand Up @@ -81,6 +87,7 @@ public static class TypeValues
public const string Resolve = "resolve";
public const string DataLoaderDispatch = "dataloader_dispatch";
public const string DataLoaderBatch = "dataloader_batch";
public const string SubscriptionEvent = "subscription_event";
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using HotChocolate.Execution;
using HotChocolate.Language;
using HotChocolate.Language.Utilities;
Expand Down Expand Up @@ -37,6 +38,8 @@ protected override void OnComplete()
Activity.SetTag(GraphQL.Processing.Type, GraphQL.Processing.TypeValues.Request);
}

EnrichServerAttributes();

string? operationTypeValue = null;
string? operationName = null;
if (TryGetOperationInfo(out var operationType, out operationName))
Expand All @@ -62,7 +65,7 @@ protected override void OnComplete()
Activity.SetTag(GraphQL.Error.Count, result.Errors.Count);
}

EmitErrorEvents(result.Errors, operationTypeValue, operationName);
EmitErrorEvents(result.Errors, operationTypeValue, operationName, documentInfo);
}

if (Context.Result is null or OperationResult { Errors: [_, ..] })
Expand All @@ -86,10 +89,41 @@ protected override void OnComplete()
enricher.EnrichExecuteRequest(Context, Activity);
}

private void EnrichServerAttributes()
{
if (!Context.Features.TryGet<HttpContext>(out var httpContext))
{
return;
}

var request = httpContext.Request;
if (!request.Host.HasValue)
{
return;
}

if (!string.IsNullOrEmpty(request.Host.Host)
&& Activity.GetTagItem(SemanticConventions.Server.Address) is null)
{
Activity.SetTag(SemanticConventions.Server.Address, request.Host.Host);
}

if (request.Host.Port is { } port
&& Activity.GetTagItem(SemanticConventions.Server.Port) is null)
{
var defaultPort = request.IsHttps ? 443 : 80;
if (port != defaultPort)
{
Activity.SetTag(SemanticConventions.Server.Port, port);
}
}
}

private void EmitErrorEvents(
IReadOnlyList<IError> errors,
string? operationType,
string? operationName)
string? operationName,
OperationDocumentInfo documentInfo)
{
var maxEvents = options.MaxErrorEvents;
if (maxEvents <= 0 || errors.Count == 0)
Expand All @@ -100,7 +134,11 @@ private void EmitErrorEvents(
var limit = errors.Count < maxEvents ? errors.Count : maxEvents;
for (var i = 0; i < limit; i++)
{
Activity.AddGraphQLErrorEvent(errors[i], operationType, operationName);
Activity.AddGraphQLErrorEvent(
errors[i],
operationType,
operationName,
documentInfo: documentInfo);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal sealed class SubscriptionEventSpan(Activity activity) : SpanBase(activi
return null;
}

activity.SetTag(GraphQL.Processing.Type, GraphQL.Processing.TypeValues.Execute);
activity.SetTag(GraphQL.Processing.Type, GraphQL.Processing.TypeValues.SubscriptionEvent);
activity.EnrichOperation(OperationType.Subscription, operationName);
activity.EnrichDocumentInfo(context.OperationDocumentInfo);
activity.SetTag(GraphQL.Subscription.Id, subscriptionId.ToString(CultureInfo.InvariantCulture));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ internal sealed class VariableCoercionSpan(

protected override void OnComplete()
{
if (context.VariableValues.Length > 0)
// The pipeline assigns `RequestContext.VariableValues` only when
// coercion completes successfully. A length of zero therefore means
// the coercion threw before the values were committed.
if (context.VariableValues.Length == 0)
{
Activity.SetStatus(ActivityStatusCode.Error);
Activity.SetErrorType(ActivityExtensions.ExecutionErrorType);
}
else if (Activity.Status != ActivityStatusCode.Error)
{
Activity.SetStatus(ActivityStatusCode.Ok);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public sealed class InstrumentationOptions : InstrumentationOptionsBase
/// </summary>
public bool IncludeDataLoaderKeys { get; set; }

/// <summary>
/// Specifies whether the diagnostic listener participates in field resolver
/// instrumentation. When set to <c>false</c>, the GraphQL execution engine
/// skips the resolver instrumentation hook entirely, avoiding any per-field
/// overhead. The default is <c>true</c>.
/// </summary>
public bool EnableResolveFieldValue { get; set; } = true;

internal bool SkipExecuteHttpRequest => (Scopes & ExecuteHttpRequest) != ExecuteHttpRequest;

internal bool SkipParseHttpRequest => (Scopes & ParseHttpRequest) != ParseHttpRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal sealed class ActivityExecutionDiagnosticListener(
private static readonly AsyncLocal<SubscriptionEventSpan?> s_currentSubscriptionEventSpan =
new();

public override bool EnableResolveFieldValue => true;
public override bool EnableResolveFieldValue => options.EnableResolveFieldValue;

public override IDisposable ExecuteRequest(RequestContext context)
{
Expand Down Expand Up @@ -268,7 +268,8 @@ public override void ResolverError(IMiddlewareContext context, IError error)
operationType: SemanticConventions.GraphQL.Operation.TypeValues[
context.Operation.Definition.Operation],
operationName: context.Operation.Name,
schemaCoordinate: context.Selection.Field.Coordinate.ToString());
schemaCoordinate: context.Selection.Field.Coordinate.ToString(),
documentInfo: context.Features.Get<OperationDocumentInfo>());
}
}

Expand Down Expand Up @@ -352,7 +353,14 @@ public override void DocumentNotFoundInStorage(RequestContext context, Operation
{
if (context.Features.TryGet<ExecuteRequestSpan>(out var span))
{
span.Activity.AddEvent(new(nameof(DocumentNotFoundInStorage)));
var tags = new ActivityTagsCollection();

if (documentId.HasValue)
{
tags[SemanticConventions.GraphQL.Document.Id] = documentId.Value;
}

span.Activity.AddEvent(new ActivityEvent(nameof(DocumentNotFoundInStorage), default, tags));
Comment thread
tobias-tengler marked this conversation as resolved.
enricher.EnrichDocumentNotFoundInStorage(context, documentId, span.Activity);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using HotChocolate.Execution;
using HotChocolate.Features;
using HotChocolate.Language;
using HotChocolate.PersistedOperations;
using HotChocolate.Resolvers;
Expand Down Expand Up @@ -456,6 +458,29 @@ public async Task DataLoader_BatchExecution_With_Keys_RecordsBatchSpan()
}
}

[Fact]
public async Task VariableCoercion_FailingScalar_RecordsErrorOnCoercionSpan()
{
using (CaptureActivities(out var activities))
{
// arrange & act
await new ServiceCollection()
.AddGraphQL()
.AddInstrumentation(o => o.Scopes = ActivityScopes.All)
.AddQueryType<MoodQuery>()
.AddType<MoodScalarType>()
.ExecuteRequestAsync(
OperationRequestBuilder.New()
.SetDocument("query($mood: Mood!) { greetMood(mood: $mood) }")
.SetVariableValues(
new Dictionary<string, object?> { { "mood", "happy" } })
.Build());

// assert
activities.MatchSnapshot();
}
}

[Fact]
public async Task VariableCoercion_WithAllScopes_RecordsCoercionSpan()
{
Expand Down Expand Up @@ -640,6 +665,28 @@ public IEnumerable<FailingItem> FailingItems(int count)
=> dataLoader.LoadAsync(key);
}

public sealed class MoodScalarType : StringType
{
public MoodScalarType()
: base("Mood")
{
}

protected override string OnCoerceInputLiteral(StringValueNode valueLiteral)
=> throw new FormatException(
$"'{valueLiteral.Value}' is not a recognized mood.");

protected override string OnCoerceInputValue(JsonElement inputValue, IFeatureProvider context)
=> throw new FormatException(
$"'{inputValue.GetString()}' is not a recognized mood.");
}

public class MoodQuery
{
public string GreetMood([GraphQLType<MoodScalarType>] string mood)
=> $"Greetings, {mood}!";
}

public class FailingItem(int index)
{
public int Index { get; } = index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
{
"Key": "graphql.operation.name",
"Value": "SayHelloOperation"
},
{
"Key": "graphql.document.hash",
"Value": "md5:851fb754d9ba6b5cc5a55ebcbea2621d"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
"event": [
{
"Name": "DocumentNotFoundInStorage",
"Tags": []
"Tags": [
{
"Key": "graphql.document.id",
"Value": "a8c5e2f1d3b4a6e7c9d0f1a2b3c4d5e6"
}
]
},
{
"Name": "graphql.error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
"column": 27
}
]
},
{
"Key": "graphql.document.hash",
"Value": "md5:bb1d246465341a97bdc727d6cd8ead5c"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
{
"Key": "graphql.operation.type",
"Value": "query"
},
{
"Key": "graphql.document.hash",
"Value": "md5:b8c29c6e4a2bbe7edee752c4f7cb0f7a"
}
]
}
Expand Down
Loading
Loading