diff --git a/sentry_sdk/_log_batcher.py b/sentry_sdk/_log_batcher.py index b0a5f75d46..aee9b1db6f 100644 --- a/sentry_sdk/_log_batcher.py +++ b/sentry_sdk/_log_batcher.py @@ -134,6 +134,7 @@ def format_attribute(val: "int | float | str | bool") -> "Any": res = { "timestamp": int(log["time_unix_nano"]) / 1.0e9, "trace_id": log.get("trace_id", "00000000-0000-0000-0000-000000000000"), + "span_id": log.get("span_id"), "level": str(log["severity_text"]), "body": str(log["body"]), "attributes": { diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py index c5bc1366ff..5497a27a3d 100644 --- a/sentry_sdk/_types.py +++ b/sentry_sdk/_types.py @@ -224,6 +224,7 @@ class SDKInfo(TypedDict): "attributes": dict[str, str | bool | float | int], "time_unix_nano": int, "trace_id": Optional[str], + "span_id": Optional[str], }, ) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index aecbd68ccd..c51848e9af 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -927,11 +927,8 @@ def _capture_log(self, log: "Optional[Log]") -> None: if trace_id is not None and log.get("trace_id") is None: log["trace_id"] = trace_id - if ( - span_id is not None - and "sentry.trace.parent_span_id" not in log["attributes"] - ): - log["attributes"]["sentry.trace.parent_span_id"] = span_id + if span_id is not None and log.get("span_id") is None: + log["span_id"] = span_id # The user, if present, is always set on the isolation scope. if isolation_scope._user is not None: diff --git a/sentry_sdk/integrations/logging.py b/sentry_sdk/integrations/logging.py index 6492526c21..97fc99de0a 100644 --- a/sentry_sdk/integrations/logging.py +++ b/sentry_sdk/integrations/logging.py @@ -404,5 +404,6 @@ def _capture_log_from_record( "attributes": attrs, "time_unix_nano": int(record.created * 1e9), "trace_id": None, + "span_id": None, }, ) diff --git a/sentry_sdk/integrations/loguru.py b/sentry_sdk/integrations/loguru.py index 6c4da26c48..87e154d283 100644 --- a/sentry_sdk/integrations/loguru.py +++ b/sentry_sdk/integrations/loguru.py @@ -204,5 +204,6 @@ def loguru_sentry_logs_handler(message: "Message") -> None: "attributes": attrs, "time_unix_nano": int(record["time"].timestamp() * 1e9), "trace_id": None, + "span_id": None, } ) diff --git a/sentry_sdk/logger.py b/sentry_sdk/logger.py index 0c8c658881..afdad436ef 100644 --- a/sentry_sdk/logger.py +++ b/sentry_sdk/logger.py @@ -66,6 +66,7 @@ def _capture_log( "body": body, "time_unix_nano": time.time_ns(), "trace_id": None, + "span_id": None, }, ) diff --git a/tests/integrations/logging/test_logging.py b/tests/integrations/logging/test_logging.py index e7849253d6..7b144f4b55 100644 --- a/tests/integrations/logging/test_logging.py +++ b/tests/integrations/logging/test_logging.py @@ -438,6 +438,9 @@ def test_logger_with_all_attributes(sentry_init, capture_envelopes): logs = envelopes_to_logs(envelopes) + assert "span_id" in logs[0] + assert isinstance(logs[0]["span_id"], str) + attributes = logs[0]["attributes"] assert "process.pid" in attributes @@ -478,10 +481,6 @@ def test_logger_with_all_attributes(sentry_init, capture_envelopes): assert attributes.pop("sentry.sdk.name").startswith("sentry.python") - assert "sentry.trace.parent_span_id" in attributes - assert isinstance(attributes["sentry.trace.parent_span_id"], str) - del attributes["sentry.trace.parent_span_id"] - # Assert on the remaining non-dynamic attributes. assert attributes == { "foo": "bar", diff --git a/tests/integrations/loguru/test_loguru.py b/tests/integrations/loguru/test_loguru.py index ed7650700f..66cc336de5 100644 --- a/tests/integrations/loguru/test_loguru.py +++ b/tests/integrations/loguru/test_loguru.py @@ -418,6 +418,9 @@ def test_logger_with_all_attributes( logs = envelopes_to_logs(envelopes) + assert "span_id" in logs[0] + assert isinstance(logs[0]["span_id"], str) + attributes = logs[0]["attributes"] assert "process.pid" in attributes @@ -458,10 +461,6 @@ def test_logger_with_all_attributes( assert attributes.pop("sentry.sdk.name").startswith("sentry.python") - assert "sentry.trace.parent_span_id" in attributes - assert isinstance(attributes["sentry.trace.parent_span_id"], str) - del attributes["sentry.trace.parent_span_id"] - # Assert on the remaining non-dynamic attributes. assert attributes == { "logger.name": "tests.integrations.loguru.test_loguru", diff --git a/tests/test_logs.py b/tests/test_logs.py index 15baa9328b..d82c92886e 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -55,7 +55,9 @@ def envelopes_to_logs(envelopes: List[Envelope]) -> List[Log]: "attributes": otel_attributes_to_dict(log_json["attributes"]), "time_unix_nano": int(float(log_json["timestamp"]) * 1e9), "trace_id": log_json["trace_id"], + "span_id": log_json["span_id"], } # type: Log + res.append(log) return res @@ -142,6 +144,7 @@ def _before_log(record, hint): "attributes", "time_unix_nano", "trace_id", + "span_id", } if record["severity_text"] in ["fatal", "error"]: @@ -319,7 +322,9 @@ def test_logs_tied_to_transactions(sentry_init, capture_envelopes): get_client().flush() logs = envelopes_to_logs(envelopes) - assert logs[0]["attributes"]["sentry.trace.parent_span_id"] == trx.span_id + + assert "span_id" in logs[0] + assert logs[0]["span_id"] == trx.span_id @minimum_python_37 @@ -336,7 +341,7 @@ def test_logs_tied_to_spans(sentry_init, capture_envelopes): get_client().flush() logs = envelopes_to_logs(envelopes) - assert logs[0]["attributes"]["sentry.trace.parent_span_id"] == span.span_id + assert logs[0]["span_id"] == span.span_id def test_auto_flush_logs_after_100(sentry_init, capture_envelopes): @@ -491,6 +496,7 @@ def record_lost_event(reason, data_category=None, item=None, *, quantity=1): "level": "info", "timestamp": mock.ANY, "trace_id": mock.ANY, + "span_id": mock.ANY, "attributes": { "sentry.environment": { "type": "string", @@ -516,10 +522,6 @@ def record_lost_event(reason, data_category=None, item=None, *, quantity=1): "type": "string", "value": "info", }, - "sentry.trace.parent_span_id": { - "type": "string", - "value": mock.ANY, - }, "server.address": { "type": "string", "value": "test-server",