diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index cda3dc97c7..fc225e60be 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -51,6 +51,15 @@ class INSTRUMENTER: OTEL = "otel" +# See: https://develop.sentry.dev/sdk/performance/span-data-conventions/ +class SPANDATA: + DB_SYSTEM = "db.system" + """ + An identifier for the database management system (DBMS) product being used. + See: https://github.com/open-telemetry/opentelemetry-python/blob/e00306206ea25cf8549eca289e39e0b6ba2fa560/opentelemetry-semantic-conventions/src/opentelemetry/semconv/trace/__init__.py#L58 + """ + + class OP: DB = "db" DB_REDIS = "db.redis" diff --git a/sentry_sdk/integrations/redis.py b/sentry_sdk/integrations/redis.py index 3deae7483b..8d196d00b2 100644 --- a/sentry_sdk/integrations/redis.py +++ b/sentry_sdk/integrations/redis.py @@ -1,7 +1,7 @@ from __future__ import absolute_import from sentry_sdk import Hub -from sentry_sdk.consts import OP +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.hub import _should_send_default_pii from sentry_sdk.utils import ( SENSITIVE_DATA_SUBSTITUTE, @@ -63,6 +63,7 @@ def sentry_patched_execute(self, *args, **kwargs): "redis.commands", {"count": len(self.command_stack), "first_ten": commands}, ) + span.set_data(SPANDATA.DB_SYSTEM, "redis") return old_execute(self, *args, **kwargs) diff --git a/sentry_sdk/integrations/sqlalchemy.py b/sentry_sdk/integrations/sqlalchemy.py index 64e90aa187..2d6018d732 100644 --- a/sentry_sdk/integrations/sqlalchemy.py +++ b/sentry_sdk/integrations/sqlalchemy.py @@ -3,6 +3,7 @@ import re from sentry_sdk._types import TYPE_CHECKING +from sentry_sdk.consts import SPANDATA from sentry_sdk.hub import Hub from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk.tracing_utils import record_sql_queries @@ -67,6 +68,9 @@ def _before_cursor_execute( span = ctx_mgr.__enter__() if span is not None: + db_system = _get_db_system(conn.engine.name) + if db_system is not None: + span.set_data(SPANDATA.DB_SYSTEM, db_system) context._sentry_sql_span = span @@ -102,3 +106,24 @@ def _handle_error(context, *args): if ctx_mgr is not None: execution_context._sentry_sql_span_manager = None ctx_mgr.__exit__(None, None, None) + + +# See: https://docs.sqlalchemy.org/en/20/dialects/index.html +def _get_db_system(name): + # type: (str) -> Optional[str] + if "sqlite" in name: + return "sqlite" + + if "postgres" in name: + return "postgresql" + + if "mariadb" in name: + return "mariadb" + + if "mysql" in name: + return "mysql" + + if "oracle" in name: + return "oracle" + + return None diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index 657ba1527f..beb7901122 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -1,6 +1,7 @@ import mock from sentry_sdk import capture_message, start_transaction +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration from fakeredis import FakeStrictRedis @@ -53,7 +54,8 @@ def test_redis_pipeline(sentry_init, capture_events, is_transaction): "redis.commands": { "count": 3, "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], - } + }, + SPANDATA.DB_SYSTEM: "redis", } assert span["tags"] == { "redis.transaction": is_transaction, diff --git a/tests/integrations/rediscluster/test_rediscluster.py b/tests/integrations/rediscluster/test_rediscluster.py index 6c7e5f90a4..6425ca15e6 100644 --- a/tests/integrations/rediscluster/test_rediscluster.py +++ b/tests/integrations/rediscluster/test_rediscluster.py @@ -1,5 +1,6 @@ import pytest from sentry_sdk import capture_message +from sentry_sdk.consts import SPANDATA from sentry_sdk.api import start_transaction from sentry_sdk.integrations.redis import RedisIntegration @@ -71,7 +72,8 @@ def test_rediscluster_pipeline(sentry_init, capture_events): "redis.commands": { "count": 3, "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], - } + }, + SPANDATA.DB_SYSTEM: "redis", } assert span["tags"] == { "redis.transaction": False, # For Cluster, this is always False diff --git a/tests/integrations/sqlalchemy/test_sqlalchemy.py b/tests/integrations/sqlalchemy/test_sqlalchemy.py index d45ea36a19..ebd83f42fb 100644 --- a/tests/integrations/sqlalchemy/test_sqlalchemy.py +++ b/tests/integrations/sqlalchemy/test_sqlalchemy.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import relationship, sessionmaker from sentry_sdk import capture_message, start_transaction, configure_scope +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration from sentry_sdk.serializer import MAX_EVENT_BYTES from sentry_sdk.utils import json_dumps, MAX_STRING_LENGTH @@ -119,6 +120,9 @@ class Address(Base): (event,) = events + for span in event["spans"]: + assert span["data"][SPANDATA.DB_SYSTEM] == "sqlite" + assert ( render_span_tree(event) == """\