Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dbm for all mysql and postgres integrations #8935

Merged
merged 58 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0231efd
add dbm sql propagator to pymysql
wconti27 Apr 4, 2024
61474e1
add tests
wconti27 Apr 4, 2024
957764d
Merge branch 'main' into conti/enable-dbm-for-pymysql
wconti27 Apr 10, 2024
8ec0b59
use ensure text
wconti27 Apr 10, 2024
621278d
add some service naming tests
wconti27 Apr 10, 2024
98b5984
fix code
wconti27 Apr 10, 2024
ab9d23d
more fixes
wconti27 Apr 10, 2024
f7d37cf
add release note
wconti27 Apr 10, 2024
e91ba94
always use strings for assertions
wconti27 Apr 10, 2024
f15331f
enable dbm for all mysql integrations
wconti27 Apr 10, 2024
a7462dd
add aiomysql dbm
wconti27 Apr 10, 2024
1c79968
try having pymysql run all mysql tests
wconti27 Apr 10, 2024
0c00ce9
fix error
wconti27 Apr 10, 2024
ec04778
comment out some tests
wconti27 Apr 10, 2024
c6dd196
fix test
wconti27 Apr 10, 2024
afd28ac
disable ITR
wconti27 Apr 10, 2024
e6579ee
fixservice naming and tests
wconti27 Apr 11, 2024
311006f
add aiopg changes
wconti27 Apr 11, 2024
a5fdd75
aiopg
wconti27 Apr 11, 2024
223736e
update suite names
wconti27 Apr 11, 2024
423d152
more changes
wconti27 Apr 11, 2024
3f78359
merge in service naming changes
wconti27 Apr 11, 2024
a3bb0ba
fix failure
wconti27 Apr 11, 2024
36f8d6b
Merge branch 'conti/fix-missing-service-naming-changes' into conti/en…
wconti27 Apr 11, 2024
29f4e00
add asyncpg and aiopg dbm
wconti27 Apr 11, 2024
6418f4d
more changes
wconti27 Apr 11, 2024
d2dd036
fix lint
wconti27 Apr 11, 2024
476338e
Merge branch 'main' into conti/fix-missing-service-naming-changes
wconti27 Apr 11, 2024
142f298
more fixes
wconti27 Apr 11, 2024
f16532a
fix asyncpg
wconti27 Apr 11, 2024
4df7280
enable dbm for asyncpg
wconti27 Apr 11, 2024
85f5419
fix remaining tests
wconti27 Apr 12, 2024
396e405
add release note
wconti27 Apr 12, 2024
7b33d43
Merge branch 'conti/fix-missing-service-naming-changes' into conti/en…
wconti27 Apr 12, 2024
fe62170
update release note
wconti27 Apr 12, 2024
ceb8457
merge with main
wconti27 Apr 12, 2024
60c4e13
use core dispatch for dbm
wconti27 Apr 15, 2024
21889aa
fix asyncpg
wconti27 Apr 15, 2024
c304d03
fix config
wconti27 Apr 15, 2024
a8df656
more changes
wconti27 Apr 15, 2024
0ad30df
remove breakpoint
wconti27 Apr 15, 2024
63d98b2
fix suitespec
wconti27 Apr 15, 2024
deee813
abstract tests
wconti27 Apr 15, 2024
27b6787
fix remaining tests
wconti27 Apr 15, 2024
906c450
fix dbapi async
wconti27 Apr 15, 2024
78164fe
aiopg try to get tests to run
wconti27 Apr 16, 2024
d783a6e
fix lint
wconti27 Apr 16, 2024
1cf5735
force aiopg to fail
wconti27 Apr 16, 2024
712090c
add aiopg to suite spec
wconti27 Apr 16, 2024
3fb23f0
remove aiopg changes
wconti27 Apr 16, 2024
df9d64f
Merge branch 'main' into conti/enable-dbm-for-all-mysql-integrations
wconti27 Apr 16, 2024
3478fbf
revert circleci changes
wconti27 Apr 16, 2024
389e9b9
use dispatch without results
wconti27 Apr 16, 2024
b464711
explicitly name dbm dispatch channel
wconti27 Apr 17, 2024
8937c7e
use dispatch with results
wconti27 Apr 18, 2024
db2bbe7
Merge branch 'main' into conti/enable-dbm-for-all-mysql-integrations
wconti27 Apr 18, 2024
593326f
fix remaining test failures
wconti27 Apr 18, 2024
64e1abe
fix asyncpg error
wconti27 Apr 18, 2024
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
14 changes: 12 additions & 2 deletions ddtrace/contrib/aiomysql/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ddtrace.constants import SPAN_KIND
from ddtrace.constants import SPAN_MEASURED_KEY
from ddtrace.contrib import dbapi
from ddtrace.internal import core
from ddtrace.internal.constants import COMPONENT
from ddtrace.internal.schema import schematize_database_operation
from ddtrace.internal.utils.wrappers import unwrap
Expand All @@ -16,12 +17,16 @@
from ...ext import db
from ...ext import net
from ...internal.schema import schematize_service_name
from ...propagation._database_monitoring import _DBM_Propagator
from .. import trace_utils


config._add(
"aiomysql",
dict(_default_service=schematize_service_name("mysql")),
dict(
_default_service=schematize_service_name("mysql"),
_dbm_propagator=_DBM_Propagator(0, "query"),
),
)


Expand All @@ -43,7 +48,7 @@ async def patched_connect(connect_func, _, args, kwargs):
tags = {}
for tag, attr in CONN_ATTR_BY_TAG.items():
if hasattr(conn, attr):
tags[tag] = getattr(conn, attr)
tags[tag] = trace_utils._convert_to_string(getattr(conn, attr, None))
tags[db.SYSTEM] = "mysql"

c = AIOTracedConnection(conn)
Expand Down Expand Up @@ -83,6 +88,11 @@ async def _trace_method(self, method, resource, extra_tags, *args, **kwargs):
# set analytics sample rate
s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aiomysql.get_analytics_sample_rate())

# dispatch DBM
result = core.dispatch_with_results("aiomysql.execute", (config.aiomysql, s, args, kwargs)).result
if result:
s, args, kwargs = result.value

try:
result = await method(*args, **kwargs)
return result
Expand Down
12 changes: 10 additions & 2 deletions ddtrace/contrib/asyncpg/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from ddtrace import Pin
from ddtrace import config
from ddtrace.internal import core
from ddtrace.internal.constants import COMPONENT
from ddtrace.vendor import wrapt

Expand All @@ -17,6 +18,7 @@
from ...internal.schema import schematize_database_operation
from ...internal.schema import schematize_service_name
from ...internal.utils import get_argument_value
from ...propagation._database_monitoring import _DBM_Propagator
from ..trace_utils import ext_service
from ..trace_utils import unwrap
from ..trace_utils import wrap
Expand All @@ -37,6 +39,7 @@
"asyncpg",
dict(
_default_service=schematize_service_name("postgres"),
_dbm_propagator=_DBM_Propagator(0, "query"),
),
)

Expand Down Expand Up @@ -113,16 +116,21 @@ async def _traced_query(pin, method, query, args, kwargs):

# set span.kind to the type of request being performed
span.set_tag_str(SPAN_KIND, SpanKind.CLIENT)

span.set_tag(SPAN_MEASURED_KEY)
span.set_tags(pin.tags)

# dispatch DBM
result = core.dispatch_with_results("asyncpg.execute", (config.asyncpg, method, span, args, kwargs)).result
if result:
span, args, kwargs = result.value
wconti27 marked this conversation as resolved.
Show resolved Hide resolved

return await method(*args, **kwargs)


@with_traced_module
async def _traced_protocol_execute(asyncpg, pin, func, instance, args, kwargs):
state = get_argument_value(args, kwargs, 0, "state") # type: Union[str, PreparedStatement]
query = state if isinstance(state, str) else state.query
query = state if isinstance(state, str) or isinstance(state, bytes) else state.query
return await _traced_query(pin, func, query, args, kwargs)


Expand Down
9 changes: 8 additions & 1 deletion ddtrace/contrib/dbapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from ddtrace import config
from ddtrace.appsec._iast._utils import _is_iast_enabled
from ddtrace.internal import core
from ddtrace.internal.constants import COMPONENT

from ...appsec._constants import IAST_SPAN_TAGS
Expand Down Expand Up @@ -118,8 +119,14 @@ def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *arg
if not isinstance(self, FetchTracedCursor):
s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, self._self_config.get_analytics_sample_rate())

# dispatch DBM
if dbm_propagator:
args, kwargs = dbm_propagator.inject(s, args, kwargs)
# this check is necessary to prevent fetch methods from trying to add dbm propagation
result = core.dispatch_with_results(
f"{self._self_config.integration_name}.execute", (self._self_config, s, args, kwargs)
).result
if result:
s, args, kwargs = result.value

try:
return method(*args, **kwargs)
Expand Down
9 changes: 8 additions & 1 deletion ddtrace/contrib/dbapi_async/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ddtrace import config
from ddtrace.appsec._iast._utils import _is_iast_enabled
from ddtrace.internal import core
from ddtrace.internal.constants import COMPONENT

from ...appsec._constants import IAST_SPAN_TAGS
Expand Down Expand Up @@ -88,8 +89,14 @@ async def _trace_method(self, method, name, resource, extra_tags, dbm_propagator
if not isinstance(self, FetchTracedAsyncCursor):
s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, self._self_config.get_analytics_sample_rate())

# dispatch DBM
if dbm_propagator:
args, kwargs = dbm_propagator.inject(s, args, kwargs)
# this check is necessary to prevent fetch methods from trying to add dbm propagation
result = core.dispatch_with_results(
f"{self._self_config.integration_name}.execute", [self._self_config, s, args, kwargs]
).result
if result:
s, args, kwargs = result.value

try:
return await method(*args, **kwargs)
Expand Down
7 changes: 6 additions & 1 deletion ddtrace/contrib/mysql/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from ...internal.schema import schematize_database_operation
from ...internal.schema import schematize_service_name
from ...internal.utils.formats import asbool
from ...propagation._database_monitoring import _DBM_Propagator
from ..trace_utils import _convert_to_string


config._add(
Expand All @@ -21,6 +23,7 @@
_dbapi_span_name_prefix="mysql",
_dbapi_span_operation_name=schematize_database_operation("mysql.query", database_provider="mysql"),
trace_fetch_methods=asbool(os.getenv("DD_MYSQL_TRACE_FETCH_METHODS", default=False)),
_dbm_propagator=_DBM_Propagator(0, "query"),
),
)

Expand Down Expand Up @@ -58,7 +61,9 @@ def _connect(func, instance, args, kwargs):


def patch_conn(conn):
tags = {t: getattr(conn, a) for t, a in CONN_ATTR_BY_TAG.items() if getattr(conn, a, "") != ""}
tags = {
t: _convert_to_string(getattr(conn, a, None)) for t, a in CONN_ATTR_BY_TAG.items() if getattr(conn, a, "") != ""
}
tags[db.SYSTEM] = "mysql"
pin = Pin(tags=tags)

Expand Down
7 changes: 6 additions & 1 deletion ddtrace/contrib/mysqldb/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from ...internal.schema import schematize_service_name
from ...internal.utils.formats import asbool
from ...internal.utils.wrappers import unwrap as _u
from ...propagation._database_monitoring import _DBM_Propagator
from ..trace_utils import _convert_to_string


config._add(
Expand All @@ -29,6 +31,7 @@
_dbapi_span_operation_name=schematize_database_operation("mysql.query", database_provider="mysql"),
trace_fetch_methods=asbool(os.getenv("DD_MYSQLDB_TRACE_FETCH_METHODS", default=False)),
trace_connect=asbool(os.getenv("DD_MYSQLDB_TRACE_CONNECT", default=False)),
_dbm_propagator=_DBM_Propagator(0, "query"),
),
)

Expand Down Expand Up @@ -99,7 +102,9 @@ def _connect(func, instance, args, kwargs):

def patch_conn(conn, *args, **kwargs):
tags = {
t: kwargs[k] if k in kwargs else args[p] for t, (k, p) in KWPOS_BY_TAG.items() if k in kwargs or len(args) > p
t: _convert_to_string(kwargs[k]) if k in kwargs else _convert_to_string(args[p])
for t, (k, p) in KWPOS_BY_TAG.items()
if k in kwargs or len(args) > p
}
tags[db.SYSTEM] = "mysql"
tags[net.TARGET_PORT] = conn.port
Expand Down
5 changes: 4 additions & 1 deletion ddtrace/contrib/pymysql/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from ...internal.schema import schematize_database_operation
from ...internal.schema import schematize_service_name
from ...internal.utils.formats import asbool
from ...propagation._database_monitoring import _DBM_Propagator
from ..trace_utils import _convert_to_string


config._add(
Expand All @@ -21,6 +23,7 @@
_dbapi_span_name_prefix="pymysql",
_dbapi_span_operation_name=schematize_database_operation("pymysql.query", database_provider="mysql"),
trace_fetch_methods=asbool(os.getenv("DD_PYMYSQL_TRACE_FETCH_METHODS", default=False)),
_dbm_propagator=_DBM_Propagator(0, "query"),
),
)

Expand Down Expand Up @@ -53,7 +56,7 @@ def _connect(func, instance, args, kwargs):


def patch_conn(conn):
tags = {t: getattr(conn, a, "") for t, a in CONN_ATTR_BY_TAG.items()}
tags = {t: _convert_to_string(getattr(conn, a)) for t, a in CONN_ATTR_BY_TAG.items() if getattr(conn, a, "") != ""}
tags[db.SYSTEM] = "mysql"
pin = Pin(tags=tags)

Expand Down
11 changes: 11 additions & 0 deletions ddtrace/contrib/trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ddtrace.ext import net
from ddtrace.ext import user
from ddtrace.internal import core
from ddtrace.internal.compat import ensure_text
from ddtrace.internal.compat import ip_is_global
from ddtrace.internal.compat import parse
from ddtrace.internal.logger import get_logger
Expand Down Expand Up @@ -671,3 +672,13 @@ def extract_netloc_and_query_info_from_url(url):

class InterruptException(Exception):
pass


def _convert_to_string(attr):
# ensures attribute is converted to a string
if attr:
if isinstance(attr, int) or isinstance(attr, float):
return str(attr)
else:
return ensure_text(attr)
return attr
26 changes: 26 additions & 0 deletions ddtrace/propagation/_database_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Union # noqa:F401

import ddtrace
from ddtrace.internal import core
from ddtrace.internal.logger import get_logger
from ddtrace.settings.peer_service import PeerServiceConfig
from ddtrace.vendor.sqlcommenter import generate_sql_comment as _generate_sql_comment
Expand Down Expand Up @@ -122,3 +123,28 @@ def _get_dbm_comment(self, db_span):
# replace leading whitespace with trailing whitespace
return sql_comment.strip() + " "
return ""


def handle_dbm_injection(int_config, span, args, kwargs):
dbm_propagator = getattr(int_config, "_dbm_propagator", None)
if dbm_propagator:
args, kwargs = dbm_propagator.inject(span, args, kwargs)

return span, args, kwargs


def handle_dbm_injection_asyncpg(int_config, method, span, args, kwargs):
# bind_execute_many uses prepared statements which we want to avoid injection for
if method.__name__ != "bind_execute_many":
return handle_dbm_injection(int_config, span, args, kwargs)
return span, args, kwargs


if dbm_config.propagation_mode in ["full", "service"]:
core.on("aiomysql.execute", handle_dbm_injection, "result")
core.on("asyncpg.execute", handle_dbm_injection_asyncpg, "result")
core.on("dbapi.execute", handle_dbm_injection, "result")
core.on("mysql.execute", handle_dbm_injection, "result")
core.on("mysqldb.execute", handle_dbm_injection, "result")
core.on("psycopg.execute", handle_dbm_injection, "result")
core.on("pymysql.execute", handle_dbm_injection, "result")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
aiomysql, asyncpg, mysql, mysqldb, pymysql: Add Database Monitoring (DBM) for remaining
mysql and postgres integrations lacking support.
31 changes: 22 additions & 9 deletions tests/.suitespec.json
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@
"django": [
"ddtrace/contrib/django/*"
],
"aiopg": [
"ddtrace/contrib/aiopg/*"
],
"pytest": [
"ddtrace/contrib/pytest/*",
"ddtrace/contrib/pytest_bdd/*",
Expand Down Expand Up @@ -731,14 +734,16 @@
"@contrib",
"@tracing",
"@dbapi",
"@appsec"
"@appsec",
"tests/contrib/shared_tests.py"
],
"dbapi_async": [
"@bootstrap",
"@core",
"@contrib",
"@tracing",
"@dbapi"
"@dbapi",
"tests/contrib/shared_tests.py"
],
"asyncpg": [
"@bootstrap",
Expand All @@ -747,7 +752,8 @@
"@tracing",
"@pg",
"tests/contrib/asyncpg/*",
"tests/snapshots/tests.contrib.{suite}.*"
"tests/snapshots/tests.contrib.{suite}.*",
"tests/contrib/shared_tests.py"
],
"aiohttp": [
"@bootstrap",
Expand Down Expand Up @@ -1000,7 +1006,8 @@
"@dbapi",
"@mysql",
"tests/contrib/mysqldb/*",
"tests/contrib/mysql/*"
"tests/contrib/mysql/*",
"tests/contrib/shared_tests.py"
],
"pymysql": [
"@bootstrap",
Expand All @@ -1010,7 +1017,8 @@
"@dbapi",
"@mysql",
"tests/contrib/pymysql/*",
"tests/contrib/mysql/*"
"tests/contrib/mysql/*",
"tests/contrib/shared_tests.py"
],
"pylibmc": [
"@bootstrap",
Expand Down Expand Up @@ -1170,7 +1178,8 @@
"@dbapi",
"@pg",
"tests/contrib/psycopg/*",
"tests/snapshots/tests.contrib.psycopg.*"
"tests/snapshots/tests.contrib.psycopg.*",
"tests/contrib/shared_tests.py"
],
"psycopg2": [
"@bootstrap",
Expand All @@ -1180,7 +1189,8 @@
"@tracing",
"@pg",
"tests/contrib/psycopg2/*",
"tests/snapshots/tests.contrib.psycopg2.*"
"tests/snapshots/tests.contrib.psycopg2.*",
"tests/contrib/shared_tests.py"
],
"aiobotocore": [
"@bootstrap",
Expand All @@ -1198,7 +1208,8 @@
"@dbapi",
"@mysql",
"tests/contrib/aiomysql/*",
"tests/snapshots/tests.contrib.{suite}.*"
"tests/snapshots/tests.contrib.{suite}.*",
"tests/contrib/shared_tests.py"
],
"aiopg": [
"@bootstrap",
Expand All @@ -1207,7 +1218,9 @@
"@tracing",
"@dbapi",
"@pg",
"tests/contrib/aiopg/*"
"@aiopg",
"tests/contrib/aiopg/*",
"tests/contrib/shared_tests.py"
],
"aredis": [
"@bootstrap",
Expand Down
Loading
Loading