Skip to content

Commit

Permalink
opentelemetry: use semconv attributes
Browse files Browse the repository at this point in the history
* use attributes defined in opentelemetry's semconv
* stop setting span attribute `component`, as that's been deprecated
* fixes #3769
  • Loading branch information
guppy0130 committed Feb 17, 2025
1 parent 951e56c commit d6ea4c5
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 7 deletions.
3 changes: 3 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Release type: patch

Use OTEL's semconv attribute registry for span attributes. Stops setting `component`, which has been deprecated.
27 changes: 24 additions & 3 deletions strawberry/extensions/tracing/opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)

from opentelemetry import trace
from opentelemetry.semconv._incubating.attributes import graphql_attributes
from opentelemetry.trace import SpanKind

from strawberry.extensions import LifecycleStep, SchemaExtension
Expand Down Expand Up @@ -60,11 +61,16 @@ def on_operation(self) -> Generator[None, None, None]:
self._span_holder[LifecycleStep.OPERATION] = self._tracer.start_span(
span_name, kind=SpanKind.SERVER
)
self._span_holder[LifecycleStep.OPERATION].set_attribute("component", "graphql")

# set the name if we have it. if we don't, we might populate it after parsing.
if self._operation_name:
self._span_holder[LifecycleStep.OPERATION].set_attribute(
graphql_attributes.GRAPHQL_OPERATION_NAME, self._operation_name
)

if self.execution_context.query:
self._span_holder[LifecycleStep.OPERATION].set_attribute(
"query", self.execution_context.query
graphql_attributes.GRAPHQL_DOCUMENT, self.execution_context.query
)

yield
Expand All @@ -76,6 +82,22 @@ def on_operation(self) -> Generator[None, None, None]:
if not self._operation_name and self.execution_context.operation_name:
span_name = f"GraphQL Query: {self.execution_context.operation_name}"
self._span_holder[LifecycleStep.OPERATION].update_name(span_name)
self._span_holder[LifecycleStep.OPERATION].set_attribute(
graphql_attributes.GRAPHQL_OPERATION_NAME,
self.execution_context.operation_name,
)

# likewise for the operation type; we'll know it for sure after parsing.
# note that this means ``self.execution_context.operation_type`` must
# be kept in sync with ``graphql_attributes.GraphqlOperationTypeValues``.
if self.execution_context.operation_type:
self._span_holder[LifecycleStep.OPERATION].set_attribute(
graphql_attributes.GRAPHQL_OPERATION_TYPE,
graphql_attributes.GraphqlOperationTypeValues(
self.execution_context.operation_type.value.lower()
).value,
)

self._span_holder[LifecycleStep.OPERATION].end()

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

span.set_attribute("component", "graphql")
span.set_attribute("graphql.parentType", info.parent_type.name)
span.set_attribute("graphql.path", graphql_path)

Expand Down
20 changes: 16 additions & 4 deletions tests/schema/extensions/test_opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
from unittest.mock import MagicMock

import pytest
from opentelemetry.semconv._incubating.attributes.graphql_attributes import (
GRAPHQL_DOCUMENT,
GRAPHQL_OPERATION_NAME,
GRAPHQL_OPERATION_TYPE,
GraphqlOperationTypeValues,
)
from opentelemetry.trace import SpanKind
from pytest_mock import MockerFixture

Expand Down Expand Up @@ -66,12 +72,14 @@ async def test_opentelemetry_sync_uses_global_tracer(global_tracer_mock):
def _instrumentation_stages(mocker, query):
return [
mocker.call("GraphQL Query", kind=SpanKind.SERVER),
mocker.call().set_attribute("component", "graphql"),
mocker.call().set_attribute("query", query),
mocker.call().set_attribute(GRAPHQL_DOCUMENT, query),
mocker.call("GraphQL Parsing", context=mocker.ANY),
mocker.call().end(),
mocker.call("GraphQL Validation", context=mocker.ANY),
mocker.call().end(),
mocker.call().set_attribute(
GRAPHQL_OPERATION_TYPE, GraphqlOperationTypeValues.QUERY.value
),
mocker.call().end(),
]

Expand Down Expand Up @@ -101,7 +109,6 @@ async def test_open_tracing(global_tracer_mock, mocker):
[
mocker.call("GraphQL Resolving: person", context=mocker.ANY),
mocker.call().__enter__(),
mocker.call().__enter__().set_attribute("component", "graphql"),
mocker.call().__enter__().set_attribute("graphql.parentType", "Query"),
mocker.call().__enter__().set_attribute("graphql.path", "person"),
mocker.call().__exit__(None, None, None),
Expand All @@ -126,6 +133,7 @@ async def test_open_tracing_uses_operation_name(global_tracer_mock, mocker):
[
# if operation_name is supplied it is added to this span's tag
mocker.call("GraphQL Query: Example", kind=SpanKind.SERVER),
mocker.call().set_attribute(GRAPHQL_OPERATION_NAME, "Example"),
*_instrumentation_stages(mocker, query)[1:],
]
)
Expand Down Expand Up @@ -154,12 +162,16 @@ def generate_trace(*args: str, **kwargs: Any):

await schema.execute(query)

# if operation_name is in the query, it is added to this span's name
tracers[0].update_name.assert_has_calls(
[
# if operation_name is supplied it is added to this span's tag
mocker.call("GraphQL Query: Example"),
]
)
# and the span's attributes
tracers[0].set_attribute.assert_has_calls(
[mocker.call(GRAPHQL_OPERATION_NAME, "Example")]
)


@pytest.mark.asyncio
Expand Down

0 comments on commit d6ea4c5

Please sign in to comment.