graphene.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from contextlib import contextmanager
  2. import sentry_sdk
  3. from sentry_sdk.consts import OP
  4. from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
  5. from sentry_sdk.scope import should_send_default_pii
  6. from sentry_sdk.utils import (
  7. capture_internal_exceptions,
  8. ensure_integration_enabled,
  9. event_from_exception,
  10. package_version,
  11. )
  12. try:
  13. from graphene.types import schema as graphene_schema # type: ignore
  14. except ImportError:
  15. raise DidNotEnable("graphene is not installed")
  16. from typing import TYPE_CHECKING
  17. if TYPE_CHECKING:
  18. from collections.abc import Generator
  19. from typing import Any, Dict, Union
  20. from graphene.language.source import Source # type: ignore
  21. from graphql.execution import ExecutionResult
  22. from graphql.type import GraphQLSchema
  23. from sentry_sdk._types import Event
  24. class GrapheneIntegration(Integration):
  25. identifier = "graphene"
  26. @staticmethod
  27. def setup_once() -> None:
  28. version = package_version("graphene")
  29. _check_minimum_version(GrapheneIntegration, version)
  30. _patch_graphql()
  31. def _patch_graphql() -> None:
  32. old_graphql_sync = graphene_schema.graphql_sync
  33. old_graphql_async = graphene_schema.graphql
  34. @ensure_integration_enabled(GrapheneIntegration, old_graphql_sync)
  35. def _sentry_patched_graphql_sync(
  36. schema: "GraphQLSchema",
  37. source: "Union[str, Source]",
  38. *args: "Any",
  39. **kwargs: "Any",
  40. ) -> "ExecutionResult":
  41. scope = sentry_sdk.get_isolation_scope()
  42. scope.add_event_processor(_event_processor)
  43. with graphql_span(schema, source, kwargs):
  44. result = old_graphql_sync(schema, source, *args, **kwargs)
  45. with capture_internal_exceptions():
  46. client = sentry_sdk.get_client()
  47. for error in result.errors or []:
  48. event, hint = event_from_exception(
  49. error,
  50. client_options=client.options,
  51. mechanism={
  52. "type": GrapheneIntegration.identifier,
  53. "handled": False,
  54. },
  55. )
  56. sentry_sdk.capture_event(event, hint=hint)
  57. return result
  58. async def _sentry_patched_graphql_async(
  59. schema: "GraphQLSchema",
  60. source: "Union[str, Source]",
  61. *args: "Any",
  62. **kwargs: "Any",
  63. ) -> "ExecutionResult":
  64. integration = sentry_sdk.get_client().get_integration(GrapheneIntegration)
  65. if integration is None:
  66. return await old_graphql_async(schema, source, *args, **kwargs)
  67. scope = sentry_sdk.get_isolation_scope()
  68. scope.add_event_processor(_event_processor)
  69. with graphql_span(schema, source, kwargs):
  70. result = await old_graphql_async(schema, source, *args, **kwargs)
  71. with capture_internal_exceptions():
  72. client = sentry_sdk.get_client()
  73. for error in result.errors or []:
  74. event, hint = event_from_exception(
  75. error,
  76. client_options=client.options,
  77. mechanism={
  78. "type": GrapheneIntegration.identifier,
  79. "handled": False,
  80. },
  81. )
  82. sentry_sdk.capture_event(event, hint=hint)
  83. return result
  84. graphene_schema.graphql_sync = _sentry_patched_graphql_sync
  85. graphene_schema.graphql = _sentry_patched_graphql_async
  86. def _event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
  87. if should_send_default_pii():
  88. request_info = event.setdefault("request", {})
  89. request_info["api_target"] = "graphql"
  90. elif event.get("request", {}).get("data"):
  91. del event["request"]["data"]
  92. return event
  93. @contextmanager
  94. def graphql_span(
  95. schema: "GraphQLSchema", source: "Union[str, Source]", kwargs: "Dict[str, Any]"
  96. ) -> "Generator[None, None, None]":
  97. operation_name = kwargs.get("operation_name")
  98. operation_type = "query"
  99. op = OP.GRAPHQL_QUERY
  100. if source.strip().startswith("mutation"):
  101. operation_type = "mutation"
  102. op = OP.GRAPHQL_MUTATION
  103. elif source.strip().startswith("subscription"):
  104. operation_type = "subscription"
  105. op = OP.GRAPHQL_SUBSCRIPTION
  106. sentry_sdk.add_breadcrumb(
  107. crumb={
  108. "data": {
  109. "operation_name": operation_name,
  110. "operation_type": operation_type,
  111. },
  112. "category": "graphql.operation",
  113. },
  114. )
  115. _graphql_span = sentry_sdk.start_span(op=op, name=operation_name)
  116. _graphql_span.set_data("graphql.document", source)
  117. _graphql_span.set_data("graphql.operation.name", operation_name)
  118. _graphql_span.set_data("graphql.operation.type", operation_type)
  119. _graphql_span.__enter__()
  120. try:
  121. yield
  122. finally:
  123. _graphql_span.__exit__(None, None, None)