diff --git a/sentry_sdk/integrations/sqlalchemy.py b/sentry_sdk/integrations/sqlalchemy.py index 8878ea4dac..e48f2356d0 100644 --- a/sentry_sdk/integrations/sqlalchemy.py +++ b/sentry_sdk/integrations/sqlalchemy.py @@ -1,7 +1,6 @@ -from sentry_sdk.consts import SPANDATA, SPANSTATUS +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version from sentry_sdk.traces import SpanStatus, StreamedSpan -from sentry_sdk.tracing import Span from sentry_sdk.tracing_utils import ( add_query_source, record_sql_queries, @@ -22,7 +21,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, ContextManager, Optional, Union + from typing import Any, ContextManager, Optional class SqlalchemyIntegration(Integration): @@ -79,34 +78,26 @@ def _after_cursor_execute( ) # Record query source immediately before span is finished: accurate end timestamp and before the span is flushed. - span: "Optional[Union[Span, StreamedSpan]]" = getattr( - context, "_sentry_sql_span", None - ) - if isinstance(span, StreamedSpan): - with capture_internal_exceptions(): - add_query_source(span) + span: "Optional[StreamedSpan]" = getattr(context, "_sentry_sql_span", None) + with capture_internal_exceptions(): + add_query_source(span) if ctx_mgr is not None: context._sentry_sql_span_manager = None ctx_mgr.__exit__(None, None, None) - if isinstance(span, Span): - with capture_internal_exceptions(): - add_query_source(span) - def _handle_error(context: "Any", *args: "Any") -> None: execution_context = context.execution_context if execution_context is None: return - span: "Optional[Span]" = getattr(execution_context, "_sentry_sql_span", None) + span: "Optional[StreamedSpan]" = getattr( + execution_context, "_sentry_sql_span", None + ) if span is not None: - if isinstance(span, StreamedSpan): - span.status = SpanStatus.ERROR - else: - span.set_status(SPANSTATUS.INTERNAL_ERROR) + span.status = SpanStatus.ERROR # _after_cursor_execute does not get called for crashing SQL stmts. Judging # from SQLAlchemy codebase it does seem like any error coming into this @@ -142,25 +133,16 @@ def _get_db_system(name: str) -> "Optional[str]": return None -def _set_db_data(span: "Union[Span, StreamedSpan]", conn: "Any") -> None: +def _set_db_data(span: "StreamedSpan", conn: "Any") -> None: db_system = _get_db_system(conn.engine.name) - if isinstance(span, StreamedSpan): - if db_system is not None: - span.set_attribute(SPANDATA.DB_SYSTEM_NAME, db_system) - else: - if db_system is not None: - span.set_data(SPANDATA.DB_SYSTEM, db_system) - - if isinstance(span, StreamedSpan): - set_on_span = span.set_attribute - else: - set_on_span = span.set_data + if db_system is not None: + span.set_attribute(SPANDATA.DB_SYSTEM_NAME, db_system) try: driver = conn.dialect.driver if driver: - set_on_span(SPANDATA.DB_DRIVER_NAME, driver) + span.set_attribute(SPANDATA.DB_DRIVER_NAME, driver) except Exception: pass @@ -168,17 +150,13 @@ def _set_db_data(span: "Union[Span, StreamedSpan]", conn: "Any") -> None: return db_name = conn.engine.url.database - if isinstance(span, StreamedSpan): - if db_name is not None: - span.set_attribute(SPANDATA.DB_NAMESPACE, db_name) - else: - if db_name is not None: - span.set_data(SPANDATA.DB_NAME, db_name) + if db_name is not None: + span.set_attribute(SPANDATA.DB_NAMESPACE, db_name) server_address = conn.engine.url.host if server_address is not None: - set_on_span(SPANDATA.SERVER_ADDRESS, server_address) + span.set_attribute(SPANDATA.SERVER_ADDRESS, server_address) server_port = conn.engine.url.port if server_port is not None: - set_on_span(SPANDATA.SERVER_PORT, server_port) + span.set_attribute(SPANDATA.SERVER_PORT, server_port) diff --git a/tests/integrations/sqlalchemy/test_sqlalchemy.py b/tests/integrations/sqlalchemy/test_sqlalchemy.py index e08bb0a423..a8e809c71f 100644 --- a/tests/integrations/sqlalchemy/test_sqlalchemy.py +++ b/tests/integrations/sqlalchemy/test_sqlalchemy.py @@ -9,24 +9,19 @@ from sqlalchemy.orm import relationship, sessionmaker import sentry_sdk -from sentry_sdk import capture_message, start_transaction +from sentry_sdk import capture_message from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration -from sentry_sdk.serializer import MAX_EVENT_BYTES from sentry_sdk.tracing_utils import record_sql_queries -from sentry_sdk.utils import json_dumps -@pytest.mark.parametrize("span_streaming", [True, False]) def test_orm_queries( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments={ "record_sql_params": True, }, @@ -61,14 +56,9 @@ class Address(Base): assert session.query(Person).first() == bob - if span_streaming: - items = capture_items("event") - capture_message("hi") - (event,) = (item.payload for item in items) - else: - events = capture_events() - capture_message("hi") - (event,) = events + items = capture_items("event") + capture_message("hi") + (event,) = (item.payload for item in items) for crumb in event["breadcrumbs"]["values"]: del crumb["timestamp"] @@ -91,17 +81,14 @@ class Address(Base): ] -@pytest.mark.parametrize("span_streaming", [True, False]) def test_transactions( sentry_init, - capture_events, capture_items, render_span_tree, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments={ "record_sql_params": True, }, @@ -132,37 +119,36 @@ class Address(Base): Session = sessionmaker(bind=engine) # noqa: N806 session = Session() - if span_streaming: - items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - with session.begin_nested(): - session.query(Person).first() - - for _ in range(2): - with pytest.raises(IntegrityError): - with session.begin_nested(): - session.add(Person(id=1, name="bob")) - session.add(Person(id=1, name="bob")) - - with session.begin_nested(): - session.query(Person).first() - - sentry_sdk.flush() - spans = [item.payload for item in items] - sqlalchemy_spans = [ - span - for span in spans - if span["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" - ] - for span in sqlalchemy_spans: - assert span["attributes"][SPANDATA.DB_SYSTEM_NAME] == "sqlite" - assert span["attributes"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" - assert span["attributes"][SPANDATA.DB_NAMESPACE] == ":memory:" - assert SPANDATA.SERVER_PORT not in span["attributes"] - - assert ( - render_span_tree(spans) - == """\ + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + with session.begin_nested(): + session.query(Person).first() + + for _ in range(2): + with pytest.raises(IntegrityError): + with session.begin_nested(): + session.add(Person(id=1, name="bob")) + session.add(Person(id=1, name="bob")) + + with session.begin_nested(): + session.query(Person).first() + + sentry_sdk.flush() + spans = [item.payload for item in items] + sqlalchemy_spans = [ + span + for span in spans + if span["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" + ] + for span in sqlalchemy_spans: + assert span["attributes"][SPANDATA.DB_SYSTEM_NAME] == "sqlite" + assert span["attributes"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" + assert span["attributes"][SPANDATA.DB_NAMESPACE] == ":memory:" + assert SPANDATA.SERVER_PORT not in span["attributes"] + + assert ( + render_span_tree(spans) + == """\ - sentry.op=null: name="custom parent" - sentry.op="db": name="SAVEPOINT sa_savepoint_1" - sentry.op="db": name="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?" @@ -177,61 +163,16 @@ class Address(Base): - sentry.op="db": name="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?" - sentry.op="db": name="RELEASE SAVEPOINT sa_savepoint_4"\ """ - ) - else: - events = capture_events() - with start_transaction(name="test_transaction", sampled=True): - with session.begin_nested(): - session.query(Person).first() - - for _ in range(2): - with pytest.raises(IntegrityError): - with session.begin_nested(): - session.add(Person(id=1, name="bob")) - session.add(Person(id=1, name="bob")) - - with session.begin_nested(): - session.query(Person).first() - - (event,) = events - - for span in event["spans"]: - assert span["data"][SPANDATA.DB_SYSTEM] == "sqlite" - assert span["data"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" - assert span["data"][SPANDATA.DB_NAME] == ":memory:" - assert SPANDATA.SERVER_ADDRESS not in span["data"] - assert SPANDATA.SERVER_PORT not in span["data"] - - assert ( - render_span_tree(event["spans"], event["contexts"]["trace"]) - == """\ -- op=null: description=null - - op="db": description="SAVEPOINT sa_savepoint_1" - - op="db": description="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?" - - op="db": description="RELEASE SAVEPOINT sa_savepoint_1" - - op="db": description="SAVEPOINT sa_savepoint_2" - - op="db": description="INSERT INTO person (id, name) VALUES (?, ?)" - - op="db": description="ROLLBACK TO SAVEPOINT sa_savepoint_2" - - op="db": description="SAVEPOINT sa_savepoint_3" - - op="db": description="INSERT INTO person (id, name) VALUES (?, ?)" - - op="db": description="ROLLBACK TO SAVEPOINT sa_savepoint_3" - - op="db": description="SAVEPOINT sa_savepoint_4" - - op="db": description="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?" - - op="db": description="RELEASE SAVEPOINT sa_savepoint_4"\ -""" - ) + ) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_transactions_no_engine_url( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments={ "record_sql_params": True, }, @@ -263,169 +204,65 @@ class Address(Base): Session = sessionmaker(bind=engine) # noqa: N806 session = Session() - if span_streaming: - items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - with session.begin_nested(): - session.query(Person).first() - - for _ in range(2): - with pytest.raises(IntegrityError): - with session.begin_nested(): - session.add(Person(id=1, name="bob")) - session.add(Person(id=1, name="bob")) - - with session.begin_nested(): - session.query(Person).first() - - sentry_sdk.flush() - spans = [item.payload for item in items] - sqlalchemy_spans = [ - span - for span in spans - if span["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" - ] - for span in sqlalchemy_spans: - assert span["attributes"][SPANDATA.DB_SYSTEM_NAME] == "sqlite" - assert span["attributes"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" - assert SPANDATA.DB_NAME not in span["attributes"] - assert SPANDATA.SERVER_PORT not in span["attributes"] - else: - events = capture_events() - with start_transaction(name="test_transaction", sampled=True): - with session.begin_nested(): - session.query(Person).first() - - for _ in range(2): - with pytest.raises(IntegrityError): - with session.begin_nested(): - session.add(Person(id=1, name="bob")) - session.add(Person(id=1, name="bob")) - - with session.begin_nested(): - session.query(Person).first() - - (event,) = events - for span in event["spans"]: - assert span["data"][SPANDATA.DB_SYSTEM] == "sqlite" - assert span["data"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" - assert SPANDATA.DB_NAME not in span["data"] - assert SPANDATA.SERVER_ADDRESS not in span["data"] - assert SPANDATA.SERVER_PORT not in span["data"] - - -@pytest.mark.parametrize("span_streaming", [True, False]) + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + with session.begin_nested(): + session.query(Person).first() + + for _ in range(2): + with pytest.raises(IntegrityError): + with session.begin_nested(): + session.add(Person(id=1, name="bob")) + session.add(Person(id=1, name="bob")) + + with session.begin_nested(): + session.query(Person).first() + + sentry_sdk.flush() + spans = [item.payload for item in items] + sqlalchemy_spans = [ + span + for span in spans + if span["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" + ] + for span in sqlalchemy_spans: + assert span["attributes"][SPANDATA.DB_SYSTEM_NAME] == "sqlite" + assert span["attributes"][SPANDATA.DB_DRIVER_NAME] == "pysqlite" + assert SPANDATA.DB_NAME not in span["attributes"] + assert SPANDATA.SERVER_PORT not in span["attributes"] + + def test_long_sql_query_preserved( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( traces_sample_rate=1, integrations=[SqlalchemyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", - ) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - if span_streaming: - items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - with engine.connect() as con: - con.execute( - text(" UNION ".join("SELECT {}".format(i) for i in range(100))) - ) - - sentry_sdk.flush() - spans = [item.payload for item in items] - name = spans[0]["name"] - assert name.startswith("SELECT 0 UNION SELECT 1") - assert name.endswith("SELECT 98 UNION SELECT 99") - else: - events = capture_events() - with start_transaction(name="test"): - with engine.connect() as con: - con.execute( - text(" UNION ".join("SELECT {}".format(i) for i in range(100))) - ) - - (event,) = events - description = event["spans"][0]["description"] - assert description.startswith("SELECT 0 UNION SELECT 1") - assert description.endswith("SELECT 98 UNION SELECT 99") - - -@pytest.mark.parametrize("max_value_length", [1024, None]) -def test_large_event_not_truncated(sentry_init, capture_events, max_value_length): - sentry_init( - traces_sample_rate=1, - integrations=[SqlalchemyIntegration()], - max_value_length=max_value_length, + trace_lifecycle="stream", ) - events = capture_events() - - long_str = "x" * (1034) - - scope = sentry_sdk.get_isolation_scope() - - @scope.add_event_processor - def processor(event, hint): - event["message"] = long_str - return event engine = create_engine( "sqlite:///:memory:", connect_args={"check_same_thread": False} ) - with start_transaction(name="test"): + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): with engine.connect() as con: - for _ in range(1500): - con.execute( - text(" UNION ".join("SELECT {}".format(i) for i in range(100))) - ) - - (event,) = events - - assert len(json_dumps(event)) > MAX_EVENT_BYTES - - # Some spans are discarded. - assert len(event["spans"]) == 1000 - - # Span descriptions are not truncated. - description = event["spans"][0]["description"] - assert len(description) == 1583 - assert description.startswith("SELECT 0") - assert description.endswith("SELECT 98 UNION SELECT 99") - - description = event["spans"][999]["description"] - assert len(description) == 1583 - assert description.startswith("SELECT 0") - assert description.endswith("SELECT 98 UNION SELECT 99") - - if max_value_length: - # Smoke check that truncation of other fields has not changed. - assert len(event["message"]) == 1024 - - # The _meta for other truncated fields should be there as well. - assert event["_meta"]["message"] == { - "": { - "len": 1034, - "rem": [["!limit", "x", 1021, 1024]], - } - } - else: - assert len(event["message"]) == 1034 + con.execute(text(" UNION ".join("SELECT {}".format(i) for i in range(100)))) + + sentry_sdk.flush() + spans = [item.payload for item in items] + name = spans[0]["name"] + assert name.startswith("SELECT 0 UNION SELECT 1") + assert name.endswith("SELECT 98 UNION SELECT 99") -@pytest.mark.parametrize("span_streaming", [True, False]) def test_engine_name_not_string( sentry_init, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) engine = create_engine( @@ -437,118 +274,71 @@ def test_engine_name_not_string( con.execute(text("SELECT 0")) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_query_source_disabled( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_options = { "integrations": [SqlalchemyIntegration()], "traces_sample_rate": 1.0, "enable_db_query_source": False, "db_query_source_threshold_ms": 0, - "trace_lifecycle": "stream" if span_streaming else "static", + "trace_lifecycle": "stream", } sentry_init(**sentry_options) - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() - bob = Person(name="Bob") - session.add(bob) + bob = Person(name="Bob") + session.add(bob) - assert session.query(Person).first() == bob + assert session.query(Person).first() == bob - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER not in attributes - assert SPANDATA.CODE_NAMESPACE not in attributes - assert SPANDATA.CODE_FILE_PATH not in attributes - assert SPANDATA.CODE_FUNCTION not in attributes - break - else: - raise AssertionError("No db span found") + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] + assert SPANDATA.CODE_LINE_NUMBER not in attributes + assert SPANDATA.CODE_NAMESPACE not in attributes + assert SPANDATA.CODE_FILE_PATH not in attributes + assert SPANDATA.CODE_FUNCTION not in attributes + break else: - events = capture_events() - - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - assert session.query(Person).first() == bob - - (event,) = events - - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) - - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data - assert SPANDATA.CODE_FUNCTION not in data - break - else: - raise AssertionError("No db span found") + raise AssertionError("No db span found") @pytest.mark.parametrize("enable_db_query_source", [None, True]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_query_source_enabled( sentry_init, - capture_events, capture_items, enable_db_query_source, - span_streaming, ): sentry_options = { "integrations": [SqlalchemyIntegration()], "traces_sample_rate": 1.0, "db_query_source_threshold_ms": 0, - "trace_lifecycle": "stream" if span_streaming else "static", + "trace_lifecycle": "stream", } if enable_db_query_source is not None: @@ -556,216 +346,115 @@ def test_query_source_enabled( sentry_init(**sentry_options) - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - bob = Person(name="Bob") - session.add(bob) - - assert session.query(Person).first() == bob - - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER in attributes - assert SPANDATA.CODE_NAMESPACE in attributes - assert SPANDATA.CODE_FILE_PATH in attributes - assert SPANDATA.CODE_FUNCTION in attributes - break - else: - raise AssertionError("No db span found") - else: - events = capture_events() - - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) - bob = Person(name="Bob") - session.add(bob) + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() - assert session.query(Person).first() == bob + bob = Person(name="Bob") + session.add(bob) - (event,) = events + assert session.query(Person).first() == bob - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data - break - else: - raise AssertionError("No db span found") + assert SPANDATA.CODE_LINE_NUMBER in attributes + assert SPANDATA.CODE_NAMESPACE in attributes + assert SPANDATA.CODE_FILE_PATH in attributes + assert SPANDATA.CODE_FUNCTION in attributes + break + else: + raise AssertionError("No db span found") -@pytest.mark.parametrize("span_streaming", [True, False]) def test_query_source( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], traces_sample_rate=1.0, enable_db_query_source=True, db_query_source_threshold_ms=0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) + + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() + + bob = Person(name="Bob") + session.add(bob) + + assert session.query(Person).first() == bob + + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] + + assert SPANDATA.CODE_LINE_NUMBER in attributes + assert SPANDATA.CODE_NAMESPACE in attributes + assert SPANDATA.CODE_FILE_PATH in attributes + assert SPANDATA.CODE_FUNCTION in attributes + + assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int + assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 + assert ( + attributes.get(SPANDATA.CODE_NAMESPACE) + == "tests.integrations.sqlalchemy.test_sqlalchemy" ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - assert session.query(Person).first() == bob - - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER in attributes - assert SPANDATA.CODE_NAMESPACE in attributes - assert SPANDATA.CODE_FILE_PATH in attributes - assert SPANDATA.CODE_FUNCTION in attributes - - assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int - assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 - assert ( - attributes.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.sqlalchemy.test_sqlalchemy" - ) - assert attributes.get(SPANDATA.CODE_FILE_PATH).endswith( - "tests/integrations/sqlalchemy/test_sqlalchemy.py" - ) - - is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep - assert is_relative_path - - assert attributes.get(SPANDATA.CODE_FUNCTION) == "test_query_source" - break - else: - raise AssertionError("No db span found") - else: - events = capture_events() - - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} + assert attributes.get(SPANDATA.CODE_FILE_PATH).endswith( + "tests/integrations/sqlalchemy/test_sqlalchemy.py" ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - assert session.query(Person).first() == bob - - (event,) = events - - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) - - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert ( - data.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.sqlalchemy.test_sqlalchemy" - ) - assert data.get(SPANDATA.CODE_FILEPATH).endswith( - "tests/integrations/sqlalchemy/test_sqlalchemy.py" - ) + is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep + assert is_relative_path - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep - assert is_relative_path - - assert data.get(SPANDATA.CODE_FUNCTION) == "test_query_source" - break - else: - raise AssertionError("No db span found") + assert attributes.get(SPANDATA.CODE_FUNCTION) == "test_query_source" + break + else: + raise AssertionError("No db span found") -@pytest.mark.parametrize("span_streaming", [True, False]) def test_query_source_with_module_in_search_path( sentry_init, - capture_events, capture_items, - span_streaming, ): """ Test that query source is relative to the path of the module it ran in @@ -775,7 +464,7 @@ def test_query_source_with_module_in_search_path( traces_sample_rate=1.0, enable_db_query_source=True, db_query_source_threshold_ms=0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) from sqlalchemy_helpers.helpers import ( @@ -783,449 +472,241 @@ def test_query_source_with_module_in_search_path( query_first_model_from_session, ) - if span_streaming: - items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - - add_model_to_session(bob, session) - - assert query_first_model_from_session(Person, session) == bob - - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER in attributes - assert SPANDATA.CODE_NAMESPACE in attributes - assert SPANDATA.CODE_FILE_PATH in attributes - assert SPANDATA.CODE_FUNCTION in attributes - - assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int - assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 - assert ( - attributes.get(SPANDATA.CODE_NAMESPACE) - == "sqlalchemy_helpers.helpers" - ) - assert ( - attributes.get(SPANDATA.CODE_FILE_PATH) - == "sqlalchemy_helpers/helpers.py" - ) - - is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep - assert is_relative_path - - assert ( - attributes.get(SPANDATA.CODE_FUNCTION) - == "query_first_model_from_session" - ) - break - else: - raise AssertionError("No db span found") - else: - events = capture_events() - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) - bob = Person(name="Bob") + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() - add_model_to_session(bob, session) + bob = Person(name="Bob") - assert query_first_model_from_session(Person, session) == bob + add_model_to_session(bob, session) - (event,) = events + assert query_first_model_from_session(Person, session) == bob - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data + assert SPANDATA.CODE_LINE_NUMBER in attributes + assert SPANDATA.CODE_NAMESPACE in attributes + assert SPANDATA.CODE_FILE_PATH in attributes + assert SPANDATA.CODE_FUNCTION in attributes - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert data.get(SPANDATA.CODE_NAMESPACE) == "sqlalchemy_helpers.helpers" - assert ( - data.get(SPANDATA.CODE_FILEPATH) == "sqlalchemy_helpers/helpers.py" - ) + assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int + assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 + assert ( + attributes.get(SPANDATA.CODE_NAMESPACE) == "sqlalchemy_helpers.helpers" + ) + assert ( + attributes.get(SPANDATA.CODE_FILE_PATH) + == "sqlalchemy_helpers/helpers.py" + ) - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep - assert is_relative_path + is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep + assert is_relative_path - assert ( - data.get(SPANDATA.CODE_FUNCTION) == "query_first_model_from_session" - ) - break - else: - raise AssertionError("No db span found") + assert ( + attributes.get(SPANDATA.CODE_FUNCTION) + == "query_first_model_from_session" + ) + break + else: + raise AssertionError("No db span found") -@pytest.mark.parametrize("span_streaming", [True, False]) def test_no_query_source_if_duration_too_short( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], traces_sample_rate=1.0, enable_db_query_source=True, db_query_source_threshold_ms=100, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 + items = capture_items("span") - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - self._ctx_mgr = record_sql_queries(*args, **kwargs) - - def __enter__(self): - self.span = self._ctx_mgr.__enter__() - self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0) - self.span._end_timestamp = datetime(2024, 1, 1, microsecond=99999) - return self.span - - def __exit__(self, type, value, traceback): - self.span._end_timestamp = None - self._ctx_mgr.__exit__(type, value, traceback) - - with mock.patch( - "sentry_sdk.integrations.sqlalchemy.record_sql_queries", - fake_record_sql_queries, - ): - assert session.query(Person).first() == bob - - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER not in attributes - assert SPANDATA.CODE_NAMESPACE not in attributes - assert SPANDATA.CODE_FILE_PATH not in attributes - assert SPANDATA.CODE_FUNCTION not in attributes - break - else: - raise AssertionError("No db span found") + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - else: - events = capture_events() - - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 - - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - self._ctx_mgr = record_sql_queries(*args, **kwargs) + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() - def __enter__(self): - self.span = self._ctx_mgr.__enter__() - self.span.start_timestamp = datetime(2024, 1, 1, microsecond=0) - return self.span + bob = Person(name="Bob") + session.add(bob) - def __exit__(self, type, value, traceback): - self._ctx_mgr.__exit__(type, value, traceback) - self.span.timestamp = datetime(2024, 1, 1, microsecond=99999) + class fake_record_sql_queries: # noqa: N801 + def __init__(self, *args, **kwargs): + self._ctx_mgr = record_sql_queries(*args, **kwargs) - with mock.patch( - "sentry_sdk.integrations.sqlalchemy.record_sql_queries", - fake_record_sql_queries, - ): - assert session.query(Person).first() == bob + def __enter__(self): + self.span = self._ctx_mgr.__enter__() + self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0) + self.span._end_timestamp = datetime(2024, 1, 1, microsecond=99999) + return self.span - (event,) = events + def __exit__(self, type, value, traceback): + self.span._end_timestamp = None + self._ctx_mgr.__exit__(type, value, traceback) - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) + with mock.patch( + "sentry_sdk.integrations.sqlalchemy.record_sql_queries", + fake_record_sql_queries, + ): + assert session.query(Person).first() == bob - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data - assert SPANDATA.CODE_FUNCTION not in data - break - else: - raise AssertionError("No db span found") + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] + + assert SPANDATA.CODE_LINE_NUMBER not in attributes + assert SPANDATA.CODE_NAMESPACE not in attributes + assert SPANDATA.CODE_FILE_PATH not in attributes + assert SPANDATA.CODE_FUNCTION not in attributes + break + else: + raise AssertionError("No db span found") -@pytest.mark.parametrize("span_streaming", [True, False]) def test_query_source_if_duration_over_threshold( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], traces_sample_rate=1.0, enable_db_query_source=True, db_query_source_threshold_ms=100, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - Base = declarative_base() # noqa: N806 + with sentry_sdk.traces.start_span(name="custom parent"): + Base = declarative_base() # noqa: N806 - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) + class Person(Base): + __tablename__ = "person" + id = Column(Integer, primary_key=True) + name = Column(String(250), nullable=False) - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} - ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - self._ctx_mgr = record_sql_queries(*args, **kwargs) - - def __enter__(self): - self.span = self._ctx_mgr.__enter__() - self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0) - self.span._end_timestamp = datetime(2024, 1, 1, microsecond=101000) - return self.span - - def __exit__(self, type, value, traceback): - self.span._end_timestamp = None - self._ctx_mgr.__exit__(type, value, traceback) - - with mock.patch( - "sentry_sdk.integrations.sqlalchemy.record_sql_queries", - fake_record_sql_queries, - ): - assert session.query(Person).first() == bob - - sentry_sdk.flush() - spans = [item.payload for item in items] - for span in spans: - if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( - "SELECT person" - ): - attributes = span["attributes"] - - assert SPANDATA.CODE_LINE_NUMBER in attributes - assert SPANDATA.CODE_NAMESPACE in attributes - assert SPANDATA.CODE_FILE_PATH in attributes - assert SPANDATA.CODE_FUNCTION in attributes - - assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int - assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 - assert ( - attributes.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.sqlalchemy.test_sqlalchemy" - ) - assert attributes.get(SPANDATA.CODE_FILE_PATH).endswith( - "tests/integrations/sqlalchemy/test_sqlalchemy.py" - ) - - is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep - assert is_relative_path - - assert ( - attributes.get(SPANDATA.CODE_FUNCTION) - == "test_query_source_if_duration_over_threshold" - ) - break - else: - raise AssertionError("No db span found") - else: - events = capture_events() + engine = create_engine( + "sqlite:///:memory:", connect_args={"check_same_thread": False} + ) + Base.metadata.create_all(engine) + + Session = sessionmaker(bind=engine) # noqa: N806 + session = Session() + + bob = Person(name="Bob") + session.add(bob) - with start_transaction(name="test_transaction", sampled=True): - Base = declarative_base() # noqa: N806 + class fake_record_sql_queries: # noqa: N801 + def __init__(self, *args, **kwargs): + self._ctx_mgr = record_sql_queries(*args, **kwargs) - class Person(Base): - __tablename__ = "person" - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) + def __enter__(self): + self.span = self._ctx_mgr.__enter__() + self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0) + self.span._end_timestamp = datetime(2024, 1, 1, microsecond=101000) + return self.span - engine = create_engine( - "sqlite:///:memory:", connect_args={"check_same_thread": False} + def __exit__(self, type, value, traceback): + self.span._end_timestamp = None + self._ctx_mgr.__exit__(type, value, traceback) + + with mock.patch( + "sentry_sdk.integrations.sqlalchemy.record_sql_queries", + fake_record_sql_queries, + ): + assert session.query(Person).first() == bob + + sentry_sdk.flush() + spans = [item.payload for item in items] + for span in spans: + if span["attributes"].get("sentry.op") == "db" and span["name"].startswith( + "SELECT person" + ): + attributes = span["attributes"] + + assert SPANDATA.CODE_LINE_NUMBER in attributes + assert SPANDATA.CODE_NAMESPACE in attributes + assert SPANDATA.CODE_FILE_PATH in attributes + assert SPANDATA.CODE_FUNCTION in attributes + + assert type(attributes.get(SPANDATA.CODE_LINE_NUMBER)) == int + assert attributes.get(SPANDATA.CODE_LINE_NUMBER) > 0 + assert ( + attributes.get(SPANDATA.CODE_NAMESPACE) + == "tests.integrations.sqlalchemy.test_sqlalchemy" + ) + assert attributes.get(SPANDATA.CODE_FILE_PATH).endswith( + "tests/integrations/sqlalchemy/test_sqlalchemy.py" + ) + + is_relative_path = attributes.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep + assert is_relative_path + + assert ( + attributes.get(SPANDATA.CODE_FUNCTION) + == "test_query_source_if_duration_over_threshold" ) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) # noqa: N806 - session = Session() - - bob = Person(name="Bob") - session.add(bob) - - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - self._ctx_mgr = record_sql_queries(*args, **kwargs) - - def __enter__(self): - self.span = self._ctx_mgr.__enter__() - self.span.start_timestamp = datetime(2024, 1, 1, microsecond=0) - return self.span - - def __exit__(self, type, value, traceback): - self._ctx_mgr.__exit__(type, value, traceback) - self.span.timestamp = datetime(2024, 1, 1, microsecond=101000) - - with mock.patch( - "sentry_sdk.integrations.sqlalchemy.record_sql_queries", - fake_record_sql_queries, - ): - assert session.query(Person).first() == bob - - (event,) = events - - for span in event["spans"]: - if span.get("op") == "db" and span.get("description").startswith( - "SELECT person" - ): - data = span.get("data", {}) - - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data - - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert ( - data.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.sqlalchemy.test_sqlalchemy" - ) - assert data.get(SPANDATA.CODE_FILEPATH).endswith( - "tests/integrations/sqlalchemy/test_sqlalchemy.py" - ) - - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep - assert is_relative_path - - assert ( - data.get(SPANDATA.CODE_FUNCTION) - == "test_query_source_if_duration_over_threshold" - ) - break - else: - raise AssertionError("No db span found") - - -@pytest.mark.parametrize("span_streaming", [True, False]) + break + else: + raise AssertionError("No db span found") + + def test_span_origin( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( integrations=[SqlalchemyIntegration()], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) engine = create_engine( "sqlite:///:memory:", connect_args={"check_same_thread": False} ) - if span_streaming: - items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - with engine.connect() as con: - con.execute(text("SELECT 0")) - - sentry_sdk.flush() - spans = [item.payload for item in items] - - assert spans[0]["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" - assert spans[1]["attributes"]["sentry.origin"] == "manual" - else: - events = capture_events() - with start_transaction(name="foo"): - with engine.connect() as con: - con.execute(text("SELECT 0")) + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + with engine.connect() as con: + con.execute(text("SELECT 0")) - (event,) = events + sentry_sdk.flush() + spans = [item.payload for item in items] - assert event["contexts"]["trace"]["origin"] == "manual" - assert event["spans"][0]["origin"] == "auto.db.sqlalchemy" + assert spans[0]["attributes"]["sentry.origin"] == "auto.db.sqlalchemy" + assert spans[1]["attributes"]["sentry.origin"] == "manual"