Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ Managing Scope (advanced)
.. autofunction:: sentry_sdk.api.push_scope

.. autofunction:: sentry_sdk.api.new_scope
.. autofunction:: sentry_sdk.api.isolation_scope

.. autofunction:: sentry_sdk.api.get_current_scope
.. autofunction:: sentry_sdk.api.get_isolation_scope
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ visit the `GitHub repository <https://github.com/getsentry/sentry-python>`_.

.. toctree::
api
tracing
integrations
apidocs
66 changes: 66 additions & 0 deletions docs/tracing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
=======
Tracing
=======

With Performance Monitoring, Sentry tracks your software's performance, measuring variables such as throughput and latency.

Manual Instrumentation
=====================

You can manually start transactions and spans to trace custom operations in your application.

Transactions
------------
A transaction represents a single instance of a service being called. It forms the root of a trace tree.

.. code-block:: python

import sentry_sdk

# Start a transaction as a context manager
with sentry_sdk.start_transaction(name="process-order"):
# Your application logic here
pass

Spans
-----
Spans represent individual units of work within a transaction, such as a database query or an API call.

.. code-block:: python

import sentry_sdk

# Start a child span under the current transaction
with sentry_sdk.start_span(op="db.query", name="SELECT * FROM users"):
# Your operation here
pass


Managing Context with Scopes
============================

Sentry use **Scopes** to manage execution context and event enrichment. In SDK 2.x, top-level APIs replace the deprecated Hub model.

Isolation Scope
---------------
The `isolation_scope` should be used for isolating data that belongs to a single request or job lifecycle. It propagates data across child scopes.

.. code-block:: python

import sentry_sdk

with sentry_sdk.isolation_scope() as scope:
scope.set_tag("user_type", "admin")
# Operations triggered here will include the tag

New Scope
---------
The `new_scope` forks the current scope for local, short-lived modifications.

.. code-block:: python

import sentry_sdk

with sentry_sdk.new_scope() as scope:
scope.set_extra("temp_debug_data", 123)
# Changes are discarded when existing the block
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T":
origin=AsyncPGIntegration.origin,
) as span:
span.set_data(SPANDATA.DB_SYSTEM, "postgresql")
span.set_data("db.driver", "asyncpg")
addr = kwargs.get("addr")
if addr:
try:
Expand All @@ -192,6 +193,7 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T":

def _set_db_data(span: "Span", conn: "Any") -> None:
span.set_data(SPANDATA.DB_SYSTEM, "postgresql")
span.set_data("db.driver", "asyncpg")

addr = conn._addr
if addr:
Expand Down
3 changes: 3 additions & 0 deletions sentry_sdk/integrations/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ def _set_db_data(span: "Span", conn: "Any") -> None:
if db_system is not None:
span.set_data(SPANDATA.DB_SYSTEM, db_system)

if hasattr(conn.engine, "dialect") and hasattr(conn.engine.dialect, "driver"):
span.set_data("db.driver", conn.engine.dialect.driver)

if conn.engine.url is None:
return

Expand Down
110 changes: 37 additions & 73 deletions tests/integrations/pydantic_ai/test_pydantic_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -1637,7 +1637,7 @@ async def test_input_messages_error_handling(sentry_init, capture_events):
Test that _set_input_messages handles errors gracefully.
"""
import sentry_sdk
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import ai_client_span

sentry_init(
integrations=[PydanticAIIntegration()],
Expand All @@ -1646,14 +1646,11 @@ async def test_input_messages_error_handling(sentry_init, capture_events):
)

with sentry_sdk.start_transaction(op="test", name="test") as transaction:
span = sentry_sdk.start_span(op="test_span")

# Pass invalid messages that would cause an error
invalid_messages = [object()] # Plain object without expected attributes

# Should not raise, error is caught internally
_set_input_messages(span, invalid_messages)

# Should not raise, error is caught internally via ai_client_span
span = ai_client_span(invalid_messages, None, None, None)
span.finish()

# Should not crash
Expand Down Expand Up @@ -1789,34 +1786,21 @@ async def test_message_parts_with_list_content(sentry_init, capture_events):
"""
Test that message parts with list content are handled correctly.
"""
import sentry_sdk
from unittest.mock import MagicMock
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
from pydantic_ai import Agent

sentry_init(
integrations=[PydanticAIIntegration()],
traces_sample_rate=1.0,
send_default_pii=True,
)

with sentry_sdk.start_transaction(op="test", name="test") as transaction:
span = sentry_sdk.start_span(op="test_span")

# Create message with list content
mock_msg = MagicMock()
mock_part = MagicMock()
mock_part.content = ["item1", "item2", {"complex": "item"}]
mock_msg.parts = [mock_part]
mock_msg.instructions = None

messages = [mock_msg]

# Should handle list content
_set_input_messages(span, messages)
events = capture_events()
agent = Agent("test")

span.finish()
# Run with list content
await agent.run(["item1", "item2"])

# Should not crash
(transaction,) = events
assert transaction is not None


Expand Down Expand Up @@ -1896,35 +1880,27 @@ async def test_message_with_system_prompt_part(sentry_init, capture_events):
"""
Test that SystemPromptPart is handled with correct role.
"""
import sentry_sdk
from unittest.mock import MagicMock
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
from pydantic_ai import messages
from pydantic_ai import Agent, messages

sentry_init(
integrations=[PydanticAIIntegration()],
traces_sample_rate=1.0,
send_default_pii=True,
)

with sentry_sdk.start_transaction(op="test", name="test") as transaction:
span = sentry_sdk.start_span(op="test_span")

# Create message with SystemPromptPart
system_part = messages.SystemPromptPart(content="You are a helpful assistant")

mock_msg = MagicMock()
mock_msg.parts = [system_part]
mock_msg.instructions = None

msgs = [mock_msg]
events = capture_events()
agent = Agent("test")

# Should handle system prompt
_set_input_messages(span, msgs)
# Create message with SystemPromptPart
system_part = messages.SystemPromptPart(content="You are a helpful assistant")
history = [
messages.ModelRequest(parts=[system_part])
]

span.finish()
# Run with history
await agent.run("What did I say?", message_history=history)

# Should not crash
(transaction,) = events
assert transaction is not None


Expand All @@ -1935,7 +1911,7 @@ async def test_message_with_instructions(sentry_init, capture_events):
"""
import sentry_sdk
from unittest.mock import MagicMock
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import ai_client_span

sentry_init(
integrations=[PydanticAIIntegration()],
Expand All @@ -1944,8 +1920,6 @@ async def test_message_with_instructions(sentry_init, capture_events):
)

with sentry_sdk.start_transaction(op="test", name="test") as transaction:
span = sentry_sdk.start_span(op="test_span")

# Create message with instructions
mock_msg = MagicMock()
mock_msg.instructions = "System instructions here"
Expand All @@ -1955,9 +1929,8 @@ async def test_message_with_instructions(sentry_init, capture_events):

msgs = [mock_msg]

# Should extract system prompt from instructions
_set_input_messages(span, msgs)

# Should handle system prompt via ai_client_span wrappers
span = ai_client_span(msgs, None, None, None)
span.finish()

# Should not crash
Expand All @@ -1969,25 +1942,21 @@ async def test_set_input_messages_without_prompts(sentry_init, capture_events):
"""
Test that _set_input_messages respects _should_send_prompts().
"""
import sentry_sdk
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
from pydantic_ai import Agent

sentry_init(
integrations=[PydanticAIIntegration(include_prompts=False)],
traces_sample_rate=1.0,
send_default_pii=True,
)

with sentry_sdk.start_transaction(op="test", name="test") as transaction:
span = sentry_sdk.start_span(op="test_span")
events = capture_events()
agent = Agent("test")

# Even with messages, should not set them
messages = ["test"]
_set_input_messages(span, messages)
# Run with prompts disabled
await agent.run("test")

span.finish()

# Should not crash and should not set messages
(transaction,) = events
assert transaction is not None


Expand Down Expand Up @@ -2705,28 +2674,23 @@ async def test_binary_content_encoding_image(sentry_init, capture_events):
@pytest.mark.asyncio
async def test_binary_content_encoding_mixed_content(sentry_init, capture_events):
"""Test that BinaryContent mixed with text content is properly handled."""
from pydantic_ai import Agent

sentry_init(
integrations=[PydanticAIIntegration()],
traces_sample_rate=1.0,
send_default_pii=True,
)

events = capture_events()
agent = Agent("test")

with sentry_sdk.start_transaction(op="test", name="test"):
span = sentry_sdk.start_span(op="test_span")
binary_content = BinaryContent(
data=b"fake_image_bytes", media_type="image/jpeg"
)
user_part = UserPromptPart(
content=["Here is an image:", binary_content, "What do you see?"]
)
mock_msg = MagicMock()
mock_msg.parts = [user_part]
mock_msg.instructions = None
binary_content = BinaryContent(
data=b"fake_image_bytes", media_type="image/jpeg"
)

_set_input_messages(span, [mock_msg])
span.finish()
# Run with mixed content
await agent.run(["Here is an image:", binary_content, "What do you see?"])

(event,) = events
span_data = event["spans"][0]["data"]
Expand Down
Loading