Skip to content

Commit d6ea4c5

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 d6ea4c5

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

RELEASE.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Release type: patch
2+
3+
Use OTEL's semconv attribute registry for span attributes. Stops setting `component`, which has been deprecated.

strawberry/extensions/tracing/opentelemetry.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212

1313
from opentelemetry import trace
14+
from opentelemetry.semconv._incubating.attributes import graphql_attributes
1415
from opentelemetry.trace import SpanKind
1516

1617
from strawberry.extensions import LifecycleStep, SchemaExtension
@@ -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

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
from unittest.mock import MagicMock
33

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

@@ -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,16 @@ 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+
[mocker.call(GRAPHQL_OPERATION_NAME, "Example")]
174+
)
163175

164176

165177
@pytest.mark.asyncio

0 commit comments

Comments
 (0)