graphene.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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():
  28. # type: () -> None
  29. version = package_version("graphene")
  30. _check_minimum_version(GrapheneIntegration, version)
  31. _patch_graphql()
  32. def _patch_graphql():
  33. # type: () -> None
  34. old_graphql_sync = graphene_schema.graphql_sync
  35. old_graphql_async = graphene_schema.graphql
  36. @ensure_integration_enabled(GrapheneIntegration, old_graphql_sync)
  37. def _sentry_patched_graphql_sync(schema, source, *args, **kwargs):
  38. # type: (GraphQLSchema, Union[str, Source], Any, Any) -> ExecutionResult
  39. scope = sentry_sdk.get_isolation_scope()
  40. scope.add_event_processor(_event_processor)
  41. with graphql_span(schema, source, kwargs):
  42. result = old_graphql_sync(schema, source, *args, **kwargs)
  43. with capture_internal_exceptions():
  44. client = sentry_sdk.get_client()
  45. for error in result.errors or []:
  46. event, hint = event_from_exception(
  47. error,
  48. client_options=client.options,
  49. mechanism={
  50. "type": GrapheneIntegration.identifier,
  51. "handled": False,
  52. },
  53. )
  54. sentry_sdk.capture_event(event, hint=hint)
  55. return result
  56. async def _sentry_patched_graphql_async(schema, source, *args, **kwargs):
  57. # type: (GraphQLSchema, Union[str, Source], Any, Any) -> ExecutionResult
  58. integration = sentry_sdk.get_client().get_integration(GrapheneIntegration)
  59. if integration is None:
  60. return await old_graphql_async(schema, source, *args, **kwargs)
  61. scope = sentry_sdk.get_isolation_scope()
  62. scope.add_event_processor(_event_processor)
  63. with graphql_span(schema, source, kwargs):
  64. result = await old_graphql_async(schema, source, *args, **kwargs)
  65. with capture_internal_exceptions():
  66. client = sentry_sdk.get_client()
  67. for error in result.errors or []:
  68. event, hint = event_from_exception(
  69. error,
  70. client_options=client.options,
  71. mechanism={
  72. "type": GrapheneIntegration.identifier,
  73. "handled": False,
  74. },
  75. )
  76. sentry_sdk.capture_event(event, hint=hint)
  77. return result
  78. graphene_schema.graphql_sync = _sentry_patched_graphql_sync
  79. graphene_schema.graphql = _sentry_patched_graphql_async
  80. def _event_processor(event, hint):
  81. # type: (Event, Dict[str, Any]) -> Event
  82. if should_send_default_pii():
  83. request_info = event.setdefault("request", {})
  84. request_info["api_target"] = "graphql"
  85. elif event.get("request", {}).get("data"):
  86. del event["request"]["data"]
  87. return event
  88. @contextmanager
  89. def graphql_span(schema, source, kwargs):
  90. # type: (GraphQLSchema, Union[str, Source], Dict[str, Any]) -> Generator[None, None, None]
  91. operation_name = kwargs.get("operation_name")
  92. operation_type = "query"
  93. op = OP.GRAPHQL_QUERY
  94. if source.strip().startswith("mutation"):
  95. operation_type = "mutation"
  96. op = OP.GRAPHQL_MUTATION
  97. elif source.strip().startswith("subscription"):
  98. operation_type = "subscription"
  99. op = OP.GRAPHQL_SUBSCRIPTION
  100. sentry_sdk.add_breadcrumb(
  101. crumb={
  102. "data": {
  103. "operation_name": operation_name,
  104. "operation_type": operation_type,
  105. },
  106. "category": "graphql.operation",
  107. },
  108. )
  109. scope = sentry_sdk.get_current_scope()
  110. if scope.span:
  111. _graphql_span = scope.span.start_child(op=op, name=operation_name)
  112. else:
  113. _graphql_span = sentry_sdk.start_span(op=op, name=operation_name)
  114. _graphql_span.set_data("graphql.document", source)
  115. _graphql_span.set_data("graphql.operation.name", operation_name)
  116. _graphql_span.set_data("graphql.operation.type", operation_type)
  117. try:
  118. yield
  119. finally:
  120. _graphql_span.finish()