From 5f2663dc08fb9952f629f6cca53c42762ac351d7 Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 7 Aug 2025 21:24:39 +0200 Subject: [PATCH 1/7] Adds initial otel spec documents --- .vscode/settings.json | 12 ++++ spec/registry.yaml | 89 ++++++++++++++++++++++++ spec/spans.yml | 153 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 spec/registry.yaml create mode 100644 spec/spans.yml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0cee69e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.rulers": [ + 80 + ], + "yaml.schemas": { + "https://raw.githubusercontent.com/open-telemetry/weaver/v0.16.1/schemas/semconv.schema.json": [ + "spec/**/*.yaml" + ] + }, + "json.schemaDownload.enable": true, + "markdown.extension.toc.levels": "2..6" +} \ No newline at end of file diff --git a/spec/registry.yaml b/spec/registry.yaml new file mode 100644 index 0000000..14b9bf6 --- /dev/null +++ b/spec/registry.yaml @@ -0,0 +1,89 @@ +groups: + - id: registry.graphql + type: attribute_group + display_name: GraphQL Attributes + brief: "This document defines attributes for GraphQL operations and resolvers." + attributes: + - id: graphql.operation.name + brief: "The name of the operation being executed." + type: string + stability: development + examples: ["FindBookById", "GetUserProfile"] + note: > + This represents the operation name as specified in the GraphQL query document. + When the operation name is not provided, this attribute SHOULD be omitted. + - id: graphql.operation.type + brief: "The type of the operation being executed." + stability: development + type: + members: + - id: query + value: "query" + brief: "GraphQL query operation" + stability: development + - id: mutation + value: "mutation" + brief: "GraphQL mutation operation" + stability: development + - id: subscription + value: "subscription" + brief: "GraphQL subscription operation" + stability: development + examples: ["query", "mutation", "subscription"] + - id: graphql.operation.hash + brief: "The hash of the operation document." + type: string + stability: development + examples: + [ + "sha256:400483f38c08e8a3d3b972409c9dfb8e4a326e1b1940864932acd9f873d8664c", + ] + note: > + The hash algorithm used SHOULD be specified as part of the value (e.g., "sha256:..."). + This can be used for monitoring operation distribution and caching strategies. + - id: graphql.operation.id + brief: > + The document identifier for persisted operations. + type: string + stability: development + examples: ["aa3e37c1bf54708e93f12c137afba004"] + note: > + This is a hash or identifier of the document provided by the user to identify + persisted operations. This attribute SHOULD only be set when using persisted operations. + - id: graphql.selection.name + brief: "The name of the selection that is being resolved. Either the field name or an alias." + type: string + stability: development + examples: ["newAddress", "bookTitle"] + note: > + If the field has an alias, this SHOULD be the alias name. Otherwise, it SHOULD be the field name. + - id: graphql.selection.path + brief: "The path of the selection that is being resolved." + type: string + stability: development + examples: ["/persons/0/address", "person[0].address"] + note: > + The path represents the location of the field being resolved within the result structure. + Path format may vary between implementations but SHOULD be consistent within an implementation. + - id: graphql.selection.field.name + brief: "The name of the field that is being resolved." + type: string + stability: development + examples: ["address", "name", "id"] + note: > + This is always the actual field name as defined in the schema, not an alias. + - id: graphql.selection.field.parent_type + brief: "The type that declares the field that is being resolved." + type: string + stability: development + examples: ["Person", "Query", "Mutation"] + note: > + This is the GraphQL type name that contains the field definition. + - id: graphql.selection.field.coordinate + brief: "The coordinate of the field that is being resolved." + type: string + stability: development + examples: ["Person.address", "Query.findBookById"] + note: > + The coordinate follows the format "{ParentType}.{fieldName}" and uniquely + identifies the field within the GraphQL schema. diff --git a/spec/spans.yml b/spec/spans.yml new file mode 100644 index 0000000..a6233f3 --- /dev/null +++ b/spec/spans.yml @@ -0,0 +1,153 @@ +groups: + - id: span.graphql.server.operation + type: span + stability: development + span_kind: server + brief: > + This span represents an incoming GraphQL operation on a server implementation. + note: | + **Span name** SHOULD be of the format `{graphql.operation.type}` provided + `graphql.operation.type` is available. If `graphql.operation.type` is not available, + the span SHOULD be named `GraphQL Operation`. + + For persisted operations with a specified operation name, instrumentations MAY provide + a configuration option to enable a more descriptive span name following + `{graphql.operation.type} {graphql.operation.name}` format when + `graphql.operation.name` is available and the operation is successfully identified + in the document. + + > **Warning** + > The `graphql.operation.name` value is provided by the client and can have high + > cardinality. Using it in the GraphQL server span name is NOT RECOMMENDED for + > ad-hoc operations without careful consideration of cardinality implications. + > For persisted operations, the cardinality is bounded and using the operation + > name in the span name is more acceptable. + > + > Implementations MUST NOT include the operation name in the span name when + > the operation was not found or could not be identified in the document. + > This prevents potential security issues and ensures span names remain meaningful. + attributes: + - ref: graphql.operation.type + requirement_level: required + - ref: graphql.operation.name + requirement_level: + conditionally_required: If available and not empty. + - ref: graphql.operation.hash + requirement_level: recommended + - ref: graphql.operation.id + requirement_level: + conditionally_required: If using persisted operations. + - ref: user_agent.original + requirement_level: recommended + + - id: span.graphql.server.document.parsing + type: span + stability: development + span_kind: internal + brief: > + This span represents the time spent parsing a GraphQL document. + note: | + **Span name** SHOULD be `GraphQL Document Parsing`. + + This span covers the parsing phase of GraphQL request processing, + where the document string is parsed into an abstract syntax tree (AST). + attributes: + - ref: graphql.operation.type + requirement_level: required + - ref: graphql.operation.name + requirement_level: + conditionally_required: If available and not empty. + - ref: graphql.operation.hash + requirement_level: recommended + - ref: graphql.operation.id + requirement_level: + conditionally_required: If using persisted operations. + + - id: span.graphql.server.document.validation + type: span + stability: development + span_kind: internal + brief: > + This span represents the time spent validating a GraphQL document. + note: | + **Span name** SHOULD be `GraphQL Document Validation`. + + This span covers the validation phase of GraphQL request processing, + where the document AST is validated against the GraphQL schema. + attributes: + - ref: graphql.operation.type + requirement_level: required + - ref: graphql.operation.name + requirement_level: + conditionally_required: If available and not empty. + - ref: graphql.operation.hash + requirement_level: recommended + - ref: graphql.operation.id + requirement_level: + conditionally_required: If using persisted operations. + + - id: span.graphql.server.document.variable_coercion + type: span + stability: development + span_kind: internal + brief: > + This span represents the time spent coercing variables for a GraphQL request. + note: | + **Span name** SHOULD be `GraphQL Variable Coercion`. + + This span covers the variable coercion phase of GraphQL request processing, + where input variables are coerced and validated according to their types. + attributes: + - ref: graphql.operation.type + requirement_level: required + - ref: graphql.operation.name + requirement_level: + conditionally_required: If available and not empty. + - ref: graphql.operation.hash + requirement_level: recommended + - ref: graphql.operation.id + requirement_level: + conditionally_required: If using persisted operations. + + - id: span.graphql.server.operation.execution + type: span + stability: development + span_kind: internal + brief: > + This span represents the execution phase of a GraphQL operation. + note: | + **Span name** SHOULD be `GraphQL Operation Execution`. + + This span covers the whole execution part of a GraphQL request, + including field resolution and result formatting. + attributes: + - ref: graphql.operation.type + requirement_level: required + - ref: graphql.operation.name + requirement_level: recommended + - ref: graphql.operation.hash + requirement_level: recommended + + - id: span.graphql.server.resolver.execution + type: span + stability: development + span_kind: internal + brief: > + This span represents the execution of a GraphQL field resolver. + note: | + **Span name** SHOULD be `{graphql.selection.field.coordinate}`. + + This span covers the execution of an individual field resolver, + including both synchronous and asynchronous resolvers. + The span ends when the resolver result is available. + attributes: + - ref: graphql.selection.name + requirement_level: recommended + - ref: graphql.selection.path + requirement_level: recommended + - ref: graphql.selection.field.name + requirement_level: required + - ref: graphql.selection.field.parent_type + requirement_level: required + - ref: graphql.selection.field.coordinate + requirement_level: required From a3130be9c3a6342ade289de215f06b45951cbd79 Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 7 Aug 2025 22:01:49 +0200 Subject: [PATCH 2/7] cleanup --- spec/registry.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/registry.yaml b/spec/registry.yaml index 14b9bf6..c3a97cc 100644 --- a/spec/registry.yaml +++ b/spec/registry.yaml @@ -64,7 +64,6 @@ groups: examples: ["/persons/0/address", "person[0].address"] note: > The path represents the location of the field being resolved within the result structure. - Path format may vary between implementations but SHOULD be consistent within an implementation. - id: graphql.selection.field.name brief: "The name of the field that is being resolved." type: string From 9d5df68b79ace66a475fe717c276e9e7ec05965c Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 7 Aug 2025 22:20:19 +0200 Subject: [PATCH 3/7] cleanup --- spec/spans.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spans.yml b/spec/spans.yml index a6233f3..9b19278 100644 --- a/spec/spans.yml +++ b/spec/spans.yml @@ -50,7 +50,7 @@ groups: **Span name** SHOULD be `GraphQL Document Parsing`. This span covers the parsing phase of GraphQL request processing, - where the document string is parsed into an abstract syntax tree (AST). + where the document string is parsed into an abstract syntax tree. attributes: - ref: graphql.operation.type requirement_level: required From fbe883d28df0e959d4e4c0e2c428a0faa9c589ae Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 7 Aug 2025 22:38:27 +0200 Subject: [PATCH 4/7] improve resolver execution --- spec/spans.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/spans.yml b/spec/spans.yml index 9b19278..1ea4315 100644 --- a/spec/spans.yml +++ b/spec/spans.yml @@ -50,7 +50,7 @@ groups: **Span name** SHOULD be `GraphQL Document Parsing`. This span covers the parsing phase of GraphQL request processing, - where the document string is parsed into an abstract syntax tree. + where the document string is parsed into an abstract syntax tree (AST). attributes: - ref: graphql.operation.type requirement_level: required @@ -140,6 +140,27 @@ groups: This span covers the execution of an individual field resolver, including both synchronous and asynchronous resolvers. The span ends when the resolver result is available. + + > **Warning** + > Creating spans for every resolver execution can result in traces with + > hundreds or thousands of spans, severely impacting performance and + > trace readability. Instrumentations MUST NOT create resolver execution + > spans by default for all resolvers. + + Instrumentations SHOULD provide configuration options to control which + resolvers generate spans. Recommended strategies include: + + - **Manual selection**: Allow developers to explicitly mark specific + resolvers for tracing (e.g., via annotations, decorators, or configuration) + - **Asynchronous resolvers only**: Only trace resolvers that return + promises or other asynchronous constructs + - **Depth-based filtering**: Only trace resolvers at the top N levels + of the query (e.g., top 2 levels) + - **Performance-based filtering**: Only trace resolvers that exceed + a certain execution time threshold + + The selection criteria SHOULD be documented clearly for users to + understand which resolvers will generate spans. attributes: - ref: graphql.selection.name requirement_level: recommended From 4d9fa6c3887b1ab5a47c80bba061ceea6a4a9a6f Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 7 Aug 2025 23:02:22 +0200 Subject: [PATCH 5/7] change the name of the resolver span --- spec/spans.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/spans.yml b/spec/spans.yml index 1ea4315..aa4d1b6 100644 --- a/spec/spans.yml +++ b/spec/spans.yml @@ -128,18 +128,18 @@ groups: - ref: graphql.operation.hash requirement_level: recommended - - id: span.graphql.server.resolver.execution + - id: span.graphql.server.field.execution type: span stability: development span_kind: internal brief: > - This span represents the execution of a GraphQL field resolver. + This span represents the execution of a GraphQL field. note: | **Span name** SHOULD be `{graphql.selection.field.coordinate}`. - This span covers the execution of an individual field resolver, - including both synchronous and asynchronous resolvers. - The span ends when the resolver result is available. + This span covers the execution of an individual field, including both + synchronous and asynchronous resolvers. The span ends when the resolver + result is available. > **Warning** > Creating spans for every resolver execution can result in traces with From 10bc3bbe2c2e2087c20788fb5b8bc048e848ca5f Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Thu, 28 Aug 2025 22:31:12 +0200 Subject: [PATCH 6/7] Align with what we have discussed in the wg --- spec/registry.yaml | 6 +++--- spec/spans.yml | 33 ++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/spec/registry.yaml b/spec/registry.yaml index c3a97cc..8619358 100644 --- a/spec/registry.yaml +++ b/spec/registry.yaml @@ -30,7 +30,7 @@ groups: brief: "GraphQL subscription operation" stability: development examples: ["query", "mutation", "subscription"] - - id: graphql.operation.hash + - id: graphql.document.hash brief: "The hash of the operation document." type: string stability: development @@ -41,7 +41,7 @@ groups: note: > The hash algorithm used SHOULD be specified as part of the value (e.g., "sha256:..."). This can be used for monitoring operation distribution and caching strategies. - - id: graphql.operation.id + - id: graphql.document.id brief: > The document identifier for persisted operations. type: string @@ -61,7 +61,7 @@ groups: brief: "The path of the selection that is being resolved." type: string stability: development - examples: ["/persons/0/address", "person[0].address"] + examples: ["person[0].address"] note: > The path represents the location of the field being resolved within the result structure. - id: graphql.selection.field.name diff --git a/spec/spans.yml b/spec/spans.yml index aa4d1b6..34abd63 100644 --- a/spec/spans.yml +++ b/spec/spans.yml @@ -1,10 +1,10 @@ groups: - - id: span.graphql.server.operation + - id: span.graphql.server.request type: span stability: development span_kind: server brief: > - This span represents an incoming GraphQL operation on a server implementation. + This span represents an incoming GraphQL request on a server implementation. note: | **Span name** SHOULD be of the format `{graphql.operation.type}` provided `graphql.operation.type` is available. If `graphql.operation.type` is not available, @@ -28,17 +28,16 @@ groups: > This prevents potential security issues and ensures span names remain meaningful. attributes: - ref: graphql.operation.type - requirement_level: required + requirement_level: + conditionally_required: If parsing succeeded - ref: graphql.operation.name requirement_level: conditionally_required: If available and not empty. - - ref: graphql.operation.hash + - ref: graphql.document.hash requirement_level: recommended - - ref: graphql.operation.id + - ref: graphql.document.id requirement_level: conditionally_required: If using persisted operations. - - ref: user_agent.original - requirement_level: recommended - id: span.graphql.server.document.parsing type: span @@ -53,13 +52,14 @@ groups: where the document string is parsed into an abstract syntax tree (AST). attributes: - ref: graphql.operation.type - requirement_level: required + requirement_level: + conditionally_required: If parsing succeeded - ref: graphql.operation.name requirement_level: conditionally_required: If available and not empty. - - ref: graphql.operation.hash + - ref: graphql.document.hash requirement_level: recommended - - ref: graphql.operation.id + - ref: graphql.document.id requirement_level: conditionally_required: If using persisted operations. @@ -80,9 +80,9 @@ groups: - ref: graphql.operation.name requirement_level: conditionally_required: If available and not empty. - - ref: graphql.operation.hash + - ref: graphql.document.hash requirement_level: recommended - - ref: graphql.operation.id + - ref: graphql.document.id requirement_level: conditionally_required: If using persisted operations. @@ -103,9 +103,9 @@ groups: - ref: graphql.operation.name requirement_level: conditionally_required: If available and not empty. - - ref: graphql.operation.hash + - ref: graphql.document.hash requirement_level: recommended - - ref: graphql.operation.id + - ref: graphql.document.id requirement_level: conditionally_required: If using persisted operations. @@ -125,8 +125,11 @@ groups: requirement_level: required - ref: graphql.operation.name requirement_level: recommended - - ref: graphql.operation.hash + - ref: graphql.document.hash requirement_level: recommended + - ref: graphql.document.id + requirement_level: + conditionally_required: If using persisted operations. - id: span.graphql.server.field.execution type: span From 59341b731bcb4a39472fc3eb6a606265797de3fa Mon Sep 17 00:00:00 2001 From: PascalSenn Date: Thu, 28 Aug 2025 22:32:08 +0200 Subject: [PATCH 7/7] change from @marcotc Co-authored-by: Marco Costa --- spec/registry.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/registry.yaml b/spec/registry.yaml index 8619358..755c4ae 100644 --- a/spec/registry.yaml +++ b/spec/registry.yaml @@ -10,7 +10,7 @@ groups: stability: development examples: ["FindBookById", "GetUserProfile"] note: > - This represents the operation name as specified in the GraphQL query document. + This represents the operation name as specified in the GraphQL operation document. When the operation name is not provided, this attribute SHOULD be omitted. - id: graphql.operation.type brief: "The type of the operation being executed."