Skip to content
Open
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
12 changes: 12 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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"
}
88 changes: 88 additions & 0 deletions spec/registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 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."
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.document.hash
brief: "The hash of the operation document."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a server was to generate a normalized hash/signature (example), do you imagine that would be a separate attribute? Is that worth mentioning? At first glance I took this to be a hash of the original raw document.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From [RFC] Add Appendix A: Persisted Documents, the sentiment so far is that the hash is created from the raw document, as even small modifications (e.g. removing whitespaces) can change error locations and confuse clients.

That being said, there's quite a bit being discussed in that RFC (e.g. what exactly the hash prefix, the part before :, means and its semantics) that it makes me think that we can either: follow that "Persisted Documents RFC"s guidance in its current state; delay the introduction of fields related to persisted queries until that RFC is published or at least closer to being published.

Regarding the Operation Signature, I think it is a useful data point but I'm not sure if we want to commit to adding this concept at this time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fair and is less of a concern to internal APIs. Using the original document makes sense.

For us, we have two hashes but that doesn't mean we need to have it in a spec.

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.document.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: ["person[0].address"]
note: >
The path represents the location of the field being resolved within the result structure.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also add the precision here about field aliasing?
It is implicit that the path should always use aliased field name if any, but should we make it explicit ?

Suggested change
The path represents the location of the field being resolved within the result structure.
The path represents the location of the field being resolved within the result structure. If a field in the path has an alias, it SHOULD be used, otherwise the field name is used.

- 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.
177 changes: 177 additions & 0 deletions spec/spans.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
groups:
- id: span.graphql.server.request
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description, we are talking about a Graphql Operation.
Should the id be span.graphql.server.operation instead of request ? It would also help to avoid the confusion between the transport (an HTTP request for example) and the actual operation.

type: span
stability: development
span_kind: server
brief: >
This span represents an incoming GraphQL request on a server implementation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here, if you agree with the previous comment.

Suggested change
This span represents an incoming GraphQL request on a server implementation.
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.
Comment on lines +13 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
For operation domains with bounded cardinality, instrumentations MAY provide
a configuration option to enable a more descriptive span name following the
`{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 or internal GraphQL APIs, the cardinality is bounded and using the operation
> name in the span name is more acceptable.

I think it's clearer to focus on the condition causing the risk (cardinality), and use "persisted operations" only only exemplify, instead of leading with "persisted operations", as one could interpret "persisted operations" as being the main deciding factor that applies here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exclude automated persisted queries

>
> 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:
conditionally_required: If parsing succeeded
- ref: graphql.operation.name
requirement_level:
conditionally_required: If available and not empty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means it is optional (but allowed) to include this attribute when the operation name is not available in the document?

- ref: graphql.document.hash
requirement_level: recommended
- ref: graphql.document.id
requirement_level:
conditionally_required: If using persisted operations.

- 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:
conditionally_required: If parsing succeeded
- ref: graphql.operation.name
requirement_level:
conditionally_required: If available and not empty.
- ref: graphql.document.hash
requirement_level: recommended
- ref: graphql.document.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.document.hash
requirement_level: recommended
- ref: graphql.document.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.document.hash
requirement_level: recommended
- ref: graphql.document.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.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
stability: development
span_kind: internal
brief: >
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, 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
- 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