Skip to content

Commit 748ab2d

Browse files
committed
opentelemetry: use semconv attributes
* use attributes defined in opentelemetry's semconv * stop setting span attribute `component`, as that's been deprecated * fixes #3769
1 parent 951e56c commit 748ab2d

File tree

2 files changed

+42
-7
lines changed

2 files changed

+42
-7
lines changed

strawberry/extensions/tracing/opentelemetry.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from opentelemetry import trace
1414
from opentelemetry.trace import SpanKind
15+
from opentelemetry.semconv._incubating.attributes import graphql_attributes
1516

1617
from strawberry.extensions import LifecycleStep, SchemaExtension
1718
from strawberry.extensions.utils import get_path_from_info
@@ -60,11 +61,16 @@ def on_operation(self) -> Generator[None, None, None]:
6061
self._span_holder[LifecycleStep.OPERATION] = self._tracer.start_span(
6162
span_name, kind=SpanKind.SERVER
6263
)
63-
self._span_holder[LifecycleStep.OPERATION].set_attribute("component", "graphql")
64+
65+
# set the name if we have it. if we don't, we might populate it after parsing.
66+
if self._operation_name:
67+
self._span_holder[LifecycleStep.OPERATION].set_attribute(
68+
graphql_attributes.GRAPHQL_OPERATION_NAME, self._operation_name
69+
)
6470

6571
if self.execution_context.query:
6672
self._span_holder[LifecycleStep.OPERATION].set_attribute(
67-
"query", self.execution_context.query
73+
graphql_attributes.GRAPHQL_DOCUMENT, self.execution_context.query
6874
)
6975

7076
yield
@@ -76,6 +82,22 @@ def on_operation(self) -> Generator[None, None, None]:
7682
if not self._operation_name and self.execution_context.operation_name:
7783
span_name = f"GraphQL Query: {self.execution_context.operation_name}"
7884
self._span_holder[LifecycleStep.OPERATION].update_name(span_name)
85+
self._span_holder[LifecycleStep.OPERATION].set_attribute(
86+
graphql_attributes.GRAPHQL_OPERATION_NAME,
87+
self.execution_context.operation_name,
88+
)
89+
90+
# likewise for the operation type; we'll know it for sure after parsing.
91+
# note that this means ``self.execution_context.operation_type`` must
92+
# be kept in sync with ``graphql_attributes.GraphqlOperationTypeValues``.
93+
if self.execution_context.operation_type:
94+
self._span_holder[LifecycleStep.OPERATION].set_attribute(
95+
graphql_attributes.GRAPHQL_OPERATION_TYPE,
96+
graphql_attributes.GraphqlOperationTypeValues(
97+
self.execution_context.operation_type.value.lower()
98+
).value
99+
)
100+
79101
self._span_holder[LifecycleStep.OPERATION].end()
80102

81103
def on_validate(self) -> Generator[None, None, None]:
@@ -139,7 +161,6 @@ def convert_list_or_tuple_to_allowed_types(self, value: Iterable) -> str:
139161
def add_tags(self, span: Span, info: GraphQLResolveInfo, kwargs: Any) -> None:
140162
graphql_path = ".".join(map(str, get_path_from_info(info)))
141163

142-
span.set_attribute("component", "graphql")
143164
span.set_attribute("graphql.parentType", info.parent_type.name)
144165
span.set_attribute("graphql.path", graphql_path)
145166

tests/schema/extensions/test_opentelemetry.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
import pytest
55
from opentelemetry.trace import SpanKind
6+
from opentelemetry.semconv._incubating.attributes.graphql_attributes import (
7+
GRAPHQL_DOCUMENT,
8+
GRAPHQL_OPERATION_NAME,
9+
GRAPHQL_OPERATION_TYPE,
10+
GraphqlOperationTypeValues,
11+
)
612
from pytest_mock import MockerFixture
713

814
import strawberry
@@ -66,12 +72,14 @@ async def test_opentelemetry_sync_uses_global_tracer(global_tracer_mock):
6672
def _instrumentation_stages(mocker, query):
6773
return [
6874
mocker.call("GraphQL Query", kind=SpanKind.SERVER),
69-
mocker.call().set_attribute("component", "graphql"),
70-
mocker.call().set_attribute("query", query),
75+
mocker.call().set_attribute(GRAPHQL_DOCUMENT, query),
7176
mocker.call("GraphQL Parsing", context=mocker.ANY),
7277
mocker.call().end(),
7378
mocker.call("GraphQL Validation", context=mocker.ANY),
7479
mocker.call().end(),
80+
mocker.call().set_attribute(
81+
GRAPHQL_OPERATION_TYPE, GraphqlOperationTypeValues.QUERY.value
82+
),
7583
mocker.call().end(),
7684
]
7785

@@ -101,7 +109,6 @@ async def test_open_tracing(global_tracer_mock, mocker):
101109
[
102110
mocker.call("GraphQL Resolving: person", context=mocker.ANY),
103111
mocker.call().__enter__(),
104-
mocker.call().__enter__().set_attribute("component", "graphql"),
105112
mocker.call().__enter__().set_attribute("graphql.parentType", "Query"),
106113
mocker.call().__enter__().set_attribute("graphql.path", "person"),
107114
mocker.call().__exit__(None, None, None),
@@ -126,6 +133,7 @@ async def test_open_tracing_uses_operation_name(global_tracer_mock, mocker):
126133
[
127134
# if operation_name is supplied it is added to this span's tag
128135
mocker.call("GraphQL Query: Example", kind=SpanKind.SERVER),
136+
mocker.call().set_attribute(GRAPHQL_OPERATION_NAME, "Example"),
129137
*_instrumentation_stages(mocker, query)[1:],
130138
]
131139
)
@@ -154,12 +162,18 @@ def generate_trace(*args: str, **kwargs: Any):
154162

155163
await schema.execute(query)
156164

165+
# if operation_name is in the query, it is added to this span's name
157166
tracers[0].update_name.assert_has_calls(
158167
[
159-
# if operation_name is supplied it is added to this span's tag
160168
mocker.call("GraphQL Query: Example"),
161169
]
162170
)
171+
# and the span's attributes
172+
tracers[0].set_attribute.assert_has_calls(
173+
[
174+
mocker.call(GRAPHQL_OPERATION_NAME, "Example")
175+
]
176+
)
163177

164178

165179
@pytest.mark.asyncio

0 commit comments

Comments
 (0)