Skip to content

Commit d48d3eb

Browse files
Add DB connection attributes in spans (#2274)
--------- Co-authored-by: Ivana Kellyerova <[email protected]>
1 parent b719952 commit d48d3eb

File tree

8 files changed

+143
-27
lines changed

8 files changed

+143
-27
lines changed

sentry_sdk/consts.py

+31
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ class SPANDATA:
6868
See: https://develop.sentry.dev/sdk/performance/span-data-conventions/
6969
"""
7070

71+
DB_NAME = "db.name"
72+
"""
73+
The name of the database being accessed. For commands that switch the database, this should be set to the target database (even if the command fails).
74+
Example: myDatabase
75+
"""
76+
7177
DB_OPERATION = "db.operation"
7278
"""
7379
The name of the operation being executed, e.g. the MongoDB command name such as findAndModify, or the SQL keyword.
@@ -118,6 +124,31 @@ class SPANDATA:
118124
Example: 418
119125
"""
120126

127+
SERVER_ADDRESS = "server.address"
128+
"""
129+
Name of the database host.
130+
Example: example.com
131+
"""
132+
133+
SERVER_PORT = "server.port"
134+
"""
135+
Logical server port number
136+
Example: 80; 8080; 443
137+
"""
138+
139+
SERVER_SOCKET_ADDRESS = "server.socket.address"
140+
"""
141+
Physical server IP address or Unix socket address.
142+
Example: 10.5.3.2
143+
"""
144+
145+
SERVER_SOCKET_PORT = "server.socket.port"
146+
"""
147+
Physical server port.
148+
Recommended: If different than server.port.
149+
Example: 16456
150+
"""
151+
121152

122153
class OP:
123154
CACHE_GET_ITEM = "cache.get_item"

sentry_sdk/integrations/django/__init__.py

+21-7
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ def execute(self, sql, params=None):
612612
with record_sql_queries(
613613
hub, self.cursor, sql, params, paramstyle="format", executemany=False
614614
) as span:
615-
_set_db_system_on_span(span, self.db.vendor)
615+
_set_db_data(span, self.db.vendor, self.db.get_connection_params())
616616
return real_execute(self, sql, params)
617617

618618
def executemany(self, sql, param_list):
@@ -624,7 +624,7 @@ def executemany(self, sql, param_list):
624624
with record_sql_queries(
625625
hub, self.cursor, sql, param_list, paramstyle="format", executemany=True
626626
) as span:
627-
_set_db_system_on_span(span, self.db.vendor)
627+
_set_db_data(span, self.db.vendor, self.db.get_connection_params())
628628
return real_executemany(self, sql, param_list)
629629

630630
def connect(self):
@@ -637,7 +637,7 @@ def connect(self):
637637
hub.add_breadcrumb(message="connect", category="query")
638638

639639
with hub.start_span(op=OP.DB, description="connect") as span:
640-
_set_db_system_on_span(span, self.vendor)
640+
_set_db_data(span, self.vendor, self.get_connection_params())
641641
return real_connect(self)
642642

643643
CursorWrapper.execute = execute
@@ -646,8 +646,22 @@ def connect(self):
646646
ignore_logger("django.db.backends")
647647

648648

649-
# https://github.com/django/django/blob/6a0dc2176f4ebf907e124d433411e52bba39a28e/django/db/backends/base/base.py#L29
650-
# Avaliable in Django 1.8+
651-
def _set_db_system_on_span(span, vendor):
652-
# type: (Span, str) -> None
649+
def _set_db_data(span, vendor, connection_params):
650+
# type: (Span, str, Dict[str, str]) -> None
653651
span.set_data(SPANDATA.DB_SYSTEM, vendor)
652+
653+
db_name = connection_params.get("dbname") or connection_params.get("database")
654+
if db_name is not None:
655+
span.set_data(SPANDATA.DB_NAME, db_name)
656+
657+
server_address = connection_params.get("host")
658+
if server_address is not None:
659+
span.set_data(SPANDATA.SERVER_ADDRESS, server_address)
660+
661+
server_port = connection_params.get("port")
662+
if server_port is not None:
663+
span.set_data(SPANDATA.SERVER_PORT, server_port)
664+
665+
server_socket_address = connection_params.get("unix_socket")
666+
if server_socket_address is not None:
667+
span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address)

sentry_sdk/integrations/pymongo.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ def _strip_pii(command):
8585
return command
8686

8787

88+
def _get_db_data(event):
89+
# type: (Any) -> Dict[str, Any]
90+
data = {}
91+
92+
data[SPANDATA.DB_SYSTEM] = "mongodb"
93+
94+
db_name = event.database_name
95+
if db_name is not None:
96+
data[SPANDATA.DB_NAME] = db_name
97+
98+
server_address = event.connection_id[0]
99+
if server_address is not None:
100+
data[SPANDATA.SERVER_ADDRESS] = server_address
101+
102+
server_port = event.connection_id[1]
103+
if server_port is not None:
104+
data[SPANDATA.SERVER_PORT] = server_port
105+
106+
return data
107+
108+
88109
class CommandTracer(monitoring.CommandListener):
89110
def __init__(self):
90111
# type: () -> None
@@ -121,10 +142,10 @@ def started(self, event):
121142
pass
122143

123144
data = {"operation_ids": {}} # type: Dict[str, Any]
124-
125145
data["operation_ids"]["operation"] = event.operation_id
126146
data["operation_ids"]["request"] = event.request_id
127-
data[SPANDATA.DB_SYSTEM] = "mongodb"
147+
148+
data.update(_get_db_data(event))
128149

129150
try:
130151
lsid = command.pop("lsid")["id"]

sentry_sdk/integrations/sqlalchemy.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ def _before_cursor_execute(
6767
span = ctx_mgr.__enter__()
6868

6969
if span is not None:
70-
db_system = _get_db_system(conn.engine.name)
71-
if db_system is not None:
72-
span.set_data(SPANDATA.DB_SYSTEM, db_system)
70+
_set_db_data(span, conn)
7371
context._sentry_sql_span = span
7472

7573

@@ -128,3 +126,22 @@ def _get_db_system(name):
128126
return "oracle"
129127

130128
return None
129+
130+
131+
def _set_db_data(span, conn):
132+
# type: (Span, Any) -> None
133+
db_system = _get_db_system(conn.engine.name)
134+
if db_system is not None:
135+
span.set_data(SPANDATA.DB_SYSTEM, db_system)
136+
137+
db_name = conn.engine.url.database
138+
if db_name is not None:
139+
span.set_data(SPANDATA.DB_NAME, db_name)
140+
141+
server_address = conn.engine.url.host
142+
if server_address is not None:
143+
span.set_data(SPANDATA.SERVER_ADDRESS, server_address)
144+
145+
server_port = conn.engine.url.port
146+
if server_port is not None:
147+
span.set_data(SPANDATA.SERVER_PORT, server_port)

test-requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,3 @@ asttokens
1313
responses
1414
pysocks
1515
ipdb
16-
mockupdb

tests/integrations/django/test_basic.py

+42-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from __future__ import absolute_import
22

33
import json
4+
import os
5+
import random
46
import re
57
import pytest
6-
import random
78
from functools import partial
89

910
from werkzeug.test import Client
@@ -584,9 +585,7 @@ def test_django_connect_trace(sentry_init, client, capture_events, render_span_t
584585

585586
@pytest.mark.forked
586587
@pytest_mark_django_db_decorator(transaction=True)
587-
def test_django_connect_breadcrumbs(
588-
sentry_init, client, capture_events, render_span_tree
589-
):
588+
def test_django_connect_breadcrumbs(sentry_init, capture_events):
590589
"""
591590
Verify we record a breadcrumb when opening a new database.
592591
"""
@@ -620,6 +619,43 @@ def test_django_connect_breadcrumbs(
620619
]
621620

622621

622+
@pytest.mark.forked
623+
@pytest_mark_django_db_decorator(transaction=True)
624+
def test_db_connection_span_data(sentry_init, client, capture_events):
625+
sentry_init(
626+
integrations=[DjangoIntegration()],
627+
send_default_pii=True,
628+
traces_sample_rate=1.0,
629+
)
630+
from django.db import connections
631+
632+
if "postgres" not in connections:
633+
pytest.skip("postgres tests disabled")
634+
635+
# trigger Django to open a new connection by marking the existing one as None.
636+
connections["postgres"].connection = None
637+
638+
events = capture_events()
639+
640+
content, status, headers = client.get(reverse("postgres_select"))
641+
assert status == "200 OK"
642+
643+
(event,) = events
644+
645+
for span in event["spans"]:
646+
if span.get("op") == "db":
647+
data = span.get("data")
648+
assert data.get(SPANDATA.DB_SYSTEM) == "postgresql"
649+
assert (
650+
data.get(SPANDATA.DB_NAME)
651+
== connections["postgres"].get_connection_params()["database"]
652+
)
653+
assert data.get(SPANDATA.SERVER_ADDRESS) == os.environ.get(
654+
"SENTRY_PYTHON_TEST_POSTGRES_HOST", "localhost"
655+
)
656+
assert data.get(SPANDATA.SERVER_PORT) == 5432
657+
658+
623659
@pytest.mark.parametrize(
624660
"transaction_style,client_url,expected_transaction,expected_source,expected_response",
625661
[
@@ -1059,11 +1095,7 @@ def dummy(a, b):
10591095
@pytest_mark_django_db_decorator()
10601096
@pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9")
10611097
def test_cache_spans_disabled_middleware(
1062-
sentry_init,
1063-
client,
1064-
capture_events,
1065-
use_django_caching_with_middlewares,
1066-
settings,
1098+
sentry_init, client, capture_events, use_django_caching_with_middlewares
10671099
):
10681100
sentry_init(
10691101
integrations=[
@@ -1141,11 +1173,7 @@ def test_cache_spans_disabled_templatetag(
11411173
@pytest_mark_django_db_decorator()
11421174
@pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9")
11431175
def test_cache_spans_middleware(
1144-
sentry_init,
1145-
client,
1146-
capture_events,
1147-
use_django_caching_with_middlewares,
1148-
settings,
1176+
sentry_init, client, capture_events, use_django_caching_with_middlewares
11491177
):
11501178
sentry_init(
11511179
integrations=[

tests/integrations/pymongo/test_pymongo.py

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def test_transactions(sentry_init, capture_events, mongo_server, with_pii):
5757
}
5858
for span in find, insert_success, insert_fail:
5959
assert span["data"][SPANDATA.DB_SYSTEM] == "mongodb"
60+
assert span["data"][SPANDATA.DB_NAME] == "test_db"
61+
assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost"
62+
assert span["data"][SPANDATA.SERVER_PORT] == mongo_server.port
6063
for field, value in common_tags.items():
6164
assert span["tags"][field] == value
6265

tests/integrations/sqlalchemy/test_sqlalchemy.py

+3
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class Address(Base):
122122

123123
for span in event["spans"]:
124124
assert span["data"][SPANDATA.DB_SYSTEM] == "sqlite"
125+
assert span["data"][SPANDATA.DB_NAME] == ":memory:"
126+
assert SPANDATA.SERVER_ADDRESS not in span["data"]
127+
assert SPANDATA.SERVER_PORT not in span["data"]
125128

126129
assert (
127130
render_span_tree(event)

0 commit comments

Comments
 (0)