Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
### Breaking Changes

### Bugs Fixed
- Update to the new stable OpenTelemetry database semantic conventions
(`db.system.name`, `db.query.text`, `db.operation.name`, `db.namespace`) when
mapping `CLIENT` spans to `RemoteDependencyData`
([#48979](https://github.com/Azure/azure-sdk-for-python/pull/48979))

### Other Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from opentelemetry._logs import LogRecord
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
from opentelemetry.semconv.attributes.db_attributes import (
DB_OPERATION_NAME,
DB_QUERY_TEXT,
DB_SYSTEM_NAME,
)
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_REQUEST_METHOD,
HTTP_RESPONSE_STATUS_CODE,
Expand Down Expand Up @@ -114,16 +119,22 @@ def _from_span(span: ReadableSpan):
url,
)
data = url
elif SpanAttributes.DB_SYSTEM in attributes:
db_system = attributes[SpanAttributes.DB_SYSTEM]
elif DB_SYSTEM_NAME in attributes or SpanAttributes.DB_SYSTEM in attributes:
db_system = attributes.get(DB_SYSTEM_NAME) or attributes.get(SpanAttributes.DB_SYSTEM)
dependency_type = db_system
target = trace_utils._get_target_for_db_dependency(
target,
db_system,
attributes,
)
if SpanAttributes.DB_STATEMENT in attributes:
# Use query text when available, otherwise fall back to the operation name.
# Support both stable and deprecated semantic conventions.
if DB_QUERY_TEXT in attributes:
data = attributes[DB_QUERY_TEXT]
elif SpanAttributes.DB_STATEMENT in attributes:
data = attributes[SpanAttributes.DB_STATEMENT]
elif DB_OPERATION_NAME in attributes:
data = attributes[DB_OPERATION_NAME]
elif SpanAttributes.DB_OPERATION in attributes:
data = attributes[SpanAttributes.DB_OPERATION]
elif SpanAttributes.MESSAGING_SYSTEM in attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
from urllib.parse import urlparse

from opentelemetry.semconv.attributes.client_attributes import CLIENT_ADDRESS
from opentelemetry.semconv.attributes.db_attributes import (
DB_OPERATION_NAME,
DB_QUERY_TEXT,
DB_SYSTEM_NAME,
)
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_REQUEST_METHOD,
HTTP_RESPONSE_STATUS_CODE,
Expand Down Expand Up @@ -420,8 +425,9 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
else:
status_code = 0
data.result_code = str(status_code)
elif SpanAttributes.DB_SYSTEM in span.attributes: # Database
db_system = span.attributes[SpanAttributes.DB_SYSTEM]
elif DB_SYSTEM_NAME in span.attributes or SpanAttributes.DB_SYSTEM in span.attributes: # Database
Comment thread
JacksonWeber marked this conversation as resolved.
# Prefer the new stable `db.system.name`, fall back to the deprecated `db.system`.
db_system = span.attributes.get(DB_SYSTEM_NAME) or span.attributes.get(SpanAttributes.DB_SYSTEM)
if db_system == DbSystemValues.MYSQL.value:
data.type = "mysql"
elif db_system == DbSystemValues.POSTGRESQL.value:
Expand All @@ -434,9 +440,14 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
data.type = "SQL"
else:
data.type = db_system
# data is the full statement or operation
if SpanAttributes.DB_STATEMENT in span.attributes:
# Use query text when available, otherwise fall back to the operation name.
# Support both stable and deprecated semantic conventions.
if DB_QUERY_TEXT in span.attributes:
data.data = span.attributes[DB_QUERY_TEXT]
elif SpanAttributes.DB_STATEMENT in span.attributes:
data.data = span.attributes[SpanAttributes.DB_STATEMENT]
elif DB_OPERATION_NAME in span.attributes:
data.data = span.attributes[DB_OPERATION_NAME]
elif SpanAttributes.DB_OPERATION in span.attributes:
data.data = span.attributes[SpanAttributes.DB_OPERATION]
# db specific logic for target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

from opentelemetry.semconv.attributes import (
client_attributes,
db_attributes,
server_attributes,
url_attributes,
user_agent_attributes,
)

from opentelemetry.semconv._incubating.attributes import db_attributes as db_attributes_incubating
from opentelemetry.context import Context
from opentelemetry.trace import get_current_span
from opentelemetry.sdk.trace.sampling import (
Expand Down Expand Up @@ -38,16 +41,16 @@ def _get_default_port_db(db_system: str) -> int:
return 9042
if db_system in (DbSystemValues.MARIADB.value, DbSystemValues.MYSQL.value):
return 3306
if db_system == DbSystemValues.MSSQL.value:
if db_system == DbSystemValues.MSSQL.value or db_system == db_attributes.DbSystemNameValues.MICROSOFT_SQL_SERVER.value:
return 1433
# TODO: Add in memcached
if db_system == "memcached":
return 11211
if db_system == DbSystemValues.DB2.value:
if db_system == DbSystemValues.DB2.value or db_system == db_attributes_incubating.DbSystemNameValues.IBM_DB2.value:
return 50000
if db_system == DbSystemValues.ORACLE.value:
if db_system == DbSystemValues.ORACLE.value or db_system == db_attributes_incubating.DbSystemNameValues.ORACLE_DB.value:
return 1521
if db_system == DbSystemValues.H2.value:
if db_system == DbSystemValues.H2.value or db_system == db_attributes_incubating.DbSystemNameValues.H2DATABASE.value:
return 8082
if db_system == DbSystemValues.DERBY.value:
return 1527
Expand All @@ -68,15 +71,19 @@ def _get_default_port_http(attributes: Attributes) -> int:
def _is_sql_db(db_system: str) -> bool:
return db_system in (
DbSystemValues.DB2.value,
db_attributes_incubating.DbSystemNameValues.IBM_DB2.value,
DbSystemValues.DERBY.value,
DbSystemValues.MARIADB.value,
DbSystemValues.MSSQL.value,
db_attributes.DbSystemNameValues.MICROSOFT_SQL_SERVER.value,
DbSystemValues.ORACLE.value,
db_attributes_incubating.DbSystemNameValues.ORACLE_DB.value,
DbSystemValues.SQLITE.value,
DbSystemValues.OTHER_SQL.value,
# spell-checker:ignore HSQLDB
DbSystemValues.HSQLDB.value,
DbSystemValues.H2.value,
db_attributes_incubating.DbSystemNameValues.H2DATABASE.value,
)


Expand Down Expand Up @@ -159,9 +166,11 @@ def _get_target_for_dependency_from_peer(attributes: Attributes) -> Optional[str
port = attributes[SpanAttributes.NET_PEER_PORT]
# TODO: check default port for rpc
# This logic assumes default ports never conflict across dependency types
if port != _get_default_port_http(attributes) and port != _get_default_port_db(
str(attributes.get(SpanAttributes.DB_SYSTEM))
):
# Honor both the new stable `db.system.name` and the deprecated `db.system`.
db_system_for_port = attributes.get(db_attributes.DB_SYSTEM_NAME) or attributes.get(
SpanAttributes.DB_SYSTEM
)
if port != _get_default_port_http(attributes) and port != _get_default_port_db(str(db_system_for_port)):
target = "{}:{}".format(target, port)
return target

Expand Down Expand Up @@ -231,7 +240,15 @@ def _get_target_for_db_dependency(
attributes: Attributes,
) -> Optional[str]:
if attributes:
db_name = attributes.get(SpanAttributes.DB_NAME)
if not target:
server_address = attributes.get(server_attributes.SERVER_ADDRESS)
if server_address:
target = str(server_address)
server_port = attributes.get(server_attributes.SERVER_PORT)
if server_port and server_port != _get_default_port_db(str(db_system)):
target = "{}:{}".format(target, server_port)
# Prefer new stable `db.namespace`, fall back to deprecated `db.name`.
db_name = attributes.get(db_attributes.DB_NAMESPACE) or attributes.get(SpanAttributes.DB_NAME)
if db_name:
if not target:
target = str(db_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
from opentelemetry._logs import LogRecord
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
from opentelemetry.semconv.attributes.db_attributes import (
DB_NAMESPACE,
DB_OPERATION_NAME,
DB_QUERY_TEXT,
DB_SYSTEM_NAME,
)
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_REQUEST_METHOD,
HTTP_RESPONSE_STATUS_CODE,
Expand Down Expand Up @@ -184,6 +190,27 @@ def test_db_dependency(self):
self.assertEqual(result.data, "SELECT * FROM table")
self.assertEqual(result.target, "mysql")

def test_db_dependency_stable_semconv(self):
self.span.kind = SpanKind.CLIENT
self.span.attributes = {
DB_SYSTEM_NAME: "postgresql",
DB_QUERY_TEXT: "SELECT * FROM table",
DB_NAMESPACE: "database",
"server.address": "stable-server",
}
result = _DependencyData._from_span(self.span)
self.assertEqual(result.type, "postgresql")
self.assertEqual(result.data, "SELECT * FROM table")
self.assertEqual(result.target, "stable-server|database")

self.span.attributes = {
DB_SYSTEM_NAME: "postgresql",
DB_OPERATION_NAME: "SELECT",
}
result = _DependencyData._from_span(self.span)
self.assertEqual(result.data, "SELECT")
self.assertEqual(result.target, "postgresql")

def test_messaging_dependency(self):
self.span.kind = SpanKind.CLIENT
self.span.attributes = {SpanAttributes.MESSAGING_SYSTEM: "kafka"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ def test_span_to_envelope_client_db(self):
start_time = 1575494316027613500
end_time = start_time + 1001000000

# SpanKind.CLIENT Db
# SpanKind.CLIENT Db, deprecated semconv
span = trace._Span(
name="test",
context=SpanContext(
Expand Down Expand Up @@ -661,6 +661,26 @@ def test_span_to_envelope_client_db(self):
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.target, "postgresql")

# Stable semconv
span._attributes = {
"db.system.name": "postgresql",
"db.query.text": "SELECT * from stable_test",
"db.namespace": "stableDb",
"server.address": "stable-server",
"server.port": 15432,
}
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.type, "postgresql")
self.assertEqual(envelope.data.base_data.data, "SELECT * from stable_test")
self.assertEqual(envelope.data.base_data.target, "stable-server:15432|stableDb")

span._attributes = {
"db.system.name": "postgresql",
"db.operation.name": "SELECT",
}
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.data, "SELECT")

# Type
span._attributes = {
"db.system": "mssql",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from azure.monitor.opentelemetry.exporter.export.trace._utils import (
_get_DJB2_sample_score,
_get_default_port_db,
_is_sql_db,
)

from azure.monitor.opentelemetry.exporter._constants import (
Expand All @@ -17,6 +19,22 @@
)


class TestDatabaseSystemNames(unittest.TestCase):
def test_legacy_and_stable_names(self):
test_cases = (
("h2", "h2database", 8082),
("db2", "ibm.db2", 50000),
("mssql", "microsoft.sql_server", 1433),
("oracle", "oracle.db", 1521),
)

for legacy_name, stable_name, default_port in test_cases:
for db_system in (legacy_name, stable_name):
with self.subTest(db_system=db_system):
self.assertTrue(_is_sql_db(db_system))
self.assertEqual(_get_default_port_db(db_system), default_port)


class TestGetDJB2SampleScore(unittest.TestCase):
"""Test cases for _get_DJB2_sample_score function."""

Expand Down
Loading