diff --git a/src/HotChocolate/Diagnostics/src/Diagnostics/ActivityEnricher.cs b/src/HotChocolate/Diagnostics/src/Diagnostics/ActivityEnricher.cs index b8b13da1db8..4901ab1a26a 100644 --- a/src/HotChocolate/Diagnostics/src/Diagnostics/ActivityEnricher.cs +++ b/src/HotChocolate/Diagnostics/src/Diagnostics/ActivityEnricher.cs @@ -595,23 +595,21 @@ protected virtual void EnrichError(IError error, Activity activity) var tags = new ActivityTagsCollection { new(AttributeExceptionMessage, error.Message), - new(AttributeExceptionType, error.Code) + new(AttributeExceptionType, error.Code ?? "GRAPHQL_ERROR"), }; - if (error.Locations is { Count: > 0, }) + if (error.Path is not null) { - for (var i = 0; i < error.Locations.Count; i++) - { - tags["graphql.error.location.column"] = error.Locations[i].Column; - tags["graphql.error.location.line"] = error.Locations[i].Line; - - activity.AddEvent(new ActivityEvent(AttributeExceptionEventName, default, tags)); - } + tags["graphql.error.path"] = error.Path.ToString(); } - else + + if (error.Locations is { Count: > 0 }) { - activity.AddEvent(new ActivityEvent(AttributeExceptionEventName, default, tags)); + tags["graphql.error.location.column"] = error.Locations[0].Column; + tags["graphql.error.location.line"] = error.Locations[0].Line; } + + activity.AddEvent(new ActivityEvent(AttributeExceptionEventName, default, tags)); } private static ISyntaxNode CreateVariablesNode( diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/QueryInstrumentationTests.cs b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/QueryInstrumentationTests.cs index 4dd4703f29c..43a3c8c038f 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/QueryInstrumentationTests.cs +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/QueryInstrumentationTests.cs @@ -132,7 +132,8 @@ public async Task Validation_error_when_rename_root_is_activated() .ExecuteRequestAsync("{ abc123 }"); // assert - Assert.Equal("CaptureActivities: Begin Validate Document", Activity.Current!.DisplayName); + Assert.Equal("CaptureActivities: Begin Validate Document", + Activity.Current!.DisplayName); } } @@ -353,13 +354,65 @@ public async Task Cause_a_resolver_error_that_deletes_the_whole_result() } } + [Fact] + public async Task Cause_a_resolver_error_that_deletes_the_whole_result_deep() + { + using (CaptureActivities(out var activities)) + { + // arrange & act + await new ServiceCollection() + .AddGraphQL() + .AddInstrumentation(o => + { + o.Scopes = ActivityScopes.All; + o.IncludeDocument = true; + }) + .AddQueryType() + .ExecuteRequestAsync( + """ + query SayHelloOperation { + deep { + deeper { + deeps { + deeper { + causeFatalError + } + } + } + } + } + """); + + // assert +#if NET7_0_OR_GREATER + activities.MatchSnapshot(new SnapshotNameExtension("_NET7")); +#else + activities.MatchSnapshot(); +#endif + } + } + public class SimpleQuery { public string SayHello() => "hello"; public string CauseFatalError() => throw new GraphQLException("fail"); + public Deep Deep() => new Deep(); + public Task DataLoader(CustomDataLoader dataLoader, string key) => dataLoader.LoadAsync(key); } + + public class Deep + { + public Deeper Deeper() => new Deeper(); + + public string CauseFatalError() => throw new GraphQLException("fail"); + } + + public class Deeper + { + public Deep[] Deeps() => [new Deep()]; + } } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result.snap index e021e950f81..d59fa861e4c 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result.snap @@ -137,9 +137,11 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": { - "graphql.error.message": "fail", + "exception.message": "fail", + "exception.type": "GRAPHQL_ERROR", + "graphql.error.path": "/causeFatalError", "graphql.error.location.column": 27, "graphql.error.location.line": 1 } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result__NET7.snap index a7e1374cbef..f50860cdcec 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result__NET7.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result__NET7.snap @@ -137,12 +137,20 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": [ { - "Key": "graphql.error.message", + "Key": "exception.message", "Value": "fail" }, + { + "Key": "exception.type", + "Value": "GRAPHQL_ERROR" + }, + { + "Key": "graphql.error.path", + "Value": "/causeFatalError" + }, { "Key": "graphql.error.location.column", "Value": 27 diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep.snap new file mode 100644 index 00000000000..e8fcadaf5c3 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep.snap @@ -0,0 +1,73 @@ +{ + "activities": [ + { + "OperationName": "ExecuteRequest", + "DisplayName": "Execute Request", + "Status": "Error", + "tags": [ + { + "Key": "graphql.document.id", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.hash", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.body", + "Value": "query SayHelloOperation {\n deep {\n deeper {\n deeps {\n deeper {\n causeFatalError\n }\n }\n }\n }\n}" + }, + { + "Key": "otel.status_code", + "Value": "ERROR" + } + ], + "event": [], + "activities": [ + { + "OperationName": "ParseDocument", + "DisplayName": "Parse Document", + "Status": "Ok", + "tags": [ + { + "Key": "otel.status_code", + "Value": "OK" + } + ], + "event": [] + }, + { + "OperationName": "ValidateDocument", + "DisplayName": "Validate Document", + "Status": "Error", + "tags": [ + { + "Key": "otel.status_code", + "Value": "ERROR" + }, + { + "Key": "graphql.document.id", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.hash", + "Value": "803df9346db185e9dc0b22dd3909aa70" + } + ], + "event": [ + { + "Name": "exception", + "Tags": { + "exception.message": "The field `causeFatalError` does not exist on the type `Deeper`.", + "exception.type": "GRAPHQL_ERROR", + "graphql.error.path": "/deep/deeper/deeps/deeper", + "graphql.error.location.column": 21, + "graphql.error.location.line": 6 + } + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep__NET7.snap new file mode 100644 index 00000000000..df987de347c --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Cause_a_resolver_error_that_deletes_the_whole_result_deep__NET7.snap @@ -0,0 +1,88 @@ +{ + "activities": [ + { + "OperationName": "ExecuteRequest", + "DisplayName": "Execute Request", + "Status": "Error", + "tags": [ + { + "Key": "graphql.document.id", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.hash", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.body", + "Value": "query SayHelloOperation {\n deep {\n deeper {\n deeps {\n deeper {\n causeFatalError\n }\n }\n }\n }\n}" + }, + { + "Key": "otel.status_code", + "Value": "ERROR" + } + ], + "event": [], + "activities": [ + { + "OperationName": "ParseDocument", + "DisplayName": "Parse Document", + "Status": "Ok", + "tags": [ + { + "Key": "otel.status_code", + "Value": "OK" + } + ], + "event": [] + }, + { + "OperationName": "ValidateDocument", + "DisplayName": "Validate Document", + "Status": "Error", + "tags": [ + { + "Key": "otel.status_code", + "Value": "ERROR" + }, + { + "Key": "graphql.document.id", + "Value": "803df9346db185e9dc0b22dd3909aa70" + }, + { + "Key": "graphql.document.hash", + "Value": "803df9346db185e9dc0b22dd3909aa70" + } + ], + "event": [ + { + "Name": "exception", + "Tags": [ + { + "Key": "exception.message", + "Value": "The field `causeFatalError` does not exist on the type `Deeper`." + }, + { + "Key": "exception.type", + "Value": "GRAPHQL_ERROR" + }, + { + "Key": "graphql.error.path", + "Value": "/deep/deeper/deeps/deeper" + }, + { + "Key": "graphql.error.location.column", + "Value": 21 + }, + { + "Key": "graphql.error.location.line", + "Value": 6 + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status.snap index b592ddf4b7c..58598c42d1a 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status.snap @@ -56,9 +56,10 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": { - "graphql.error.message": "The field `sayHello_` does not exist on the type `SimpleQuery`.", + "exception.message": "The field `sayHello_` does not exist on the type `SimpleQuery`.", + "exception.type": "GRAPHQL_ERROR", "graphql.error.location.column": 27, "graphql.error.location.line": 1 } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status__NET7.snap index d08035787a7..c13dbaa8b92 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status__NET7.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/QueryInstrumentationTests.Ensure_that_the_validation_activity_has_an_error_status__NET7.snap @@ -56,12 +56,16 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": [ { - "Key": "graphql.error.message", + "Key": "exception.message", "Value": "The field `sayHello_` does not exist on the type `SimpleQuery`." }, + { + "Key": "exception.type", + "Value": "GRAPHQL_ERROR" + }, { "Key": "graphql.error.location.column", "Value": 27 diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error.snap index 5a2e4f411b6..e1d8c23836e 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error.snap @@ -24,10 +24,10 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": { - "graphql.error.message": "Found a NameStart character `n` (110) following a number, which is disallowed.", - "graphql.error.code": "HC0011", + "exception.message": "Found a NameStart character `n` (110) following a number, which is disallowed.", + "exception.type": "HC0011", "graphql.error.location.column": 37, "graphql.error.location.line": 10 } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error__NET7.snap index 5a6d9f80cdc..e33490a9a8c 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error__NET7.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Http_Post_parser_error__NET7.snap @@ -24,14 +24,14 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": [ { - "Key": "graphql.error.message", + "Key": "exception.message", "Value": "Found a NameStart character `n` (110) following a number, which is disallowed." }, { - "Key": "graphql.error.code", + "Key": "exception.type", "Value": "HC0011" }, { diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated.snap index 75699f922ff..988f5cf7dd5 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated.snap @@ -24,10 +24,10 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": { - "graphql.error.message": "Expected a `Name`-token, but found a `Integer`-token.", - "graphql.error.code": "HC0011", + "exception.message": "Expected a `Name`-token, but found a `Integer`-token.", + "exception.type": "HC0011", "graphql.error.location.column": 21, "graphql.error.location.line": 3 } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated__NET7.snap index 3ae24244d96..b0834861a59 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated__NET7.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Parsing_error_when_rename_root_is_activated__NET7.snap @@ -24,14 +24,14 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": [ { - "Key": "graphql.error.message", + "Key": "exception.message", "Value": "Expected a `Name`-token, but found a `Integer`-token." }, { - "Key": "graphql.error.code", + "Key": "exception.type", "Value": "HC0011" }, { diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated.snap index 5a824009483..29c52863bfc 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated.snap @@ -68,9 +68,10 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": { - "graphql.error.message": "The field `abc` does not exist on the type `Query`.", + "exception.message": "The field `abc` does not exist on the type `Query`.", + "exception.type": "GRAPHQL_ERROR", "graphql.error.location.column": 21, "graphql.error.location.line": 3 } diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated__NET7.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated__NET7.snap index 2dcb421703c..746f536dda4 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated__NET7.snap +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ServerInstrumentationTests.Validation_error_when_rename_root_is_activated__NET7.snap @@ -68,12 +68,16 @@ ], "event": [ { - "Name": "Error", + "Name": "exception", "Tags": [ { - "Key": "graphql.error.message", + "Key": "exception.message", "Value": "The field `abc` does not exist on the type `Query`." }, + { + "Key": "exception.type", + "Value": "GRAPHQL_ERROR" + }, { "Key": "graphql.error.location.column", "Value": 21