diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 5ea7c00478..81fb8c385c 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -1,7 +1,9 @@ import json import os +import platform import random import socket +import sys import uuid import warnings from collections.abc import Iterable, Mapping @@ -241,6 +243,11 @@ def _serialized_v1_span_to_serialized_v2_span( if "version" in sdk_info: attributes["sentry.sdk.version"] = sdk_info["version"] + attributes["process.runtime.name"] = platform.python_implementation() + attributes["process.runtime.version"] = ( + f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + ) + if not attributes: return res diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index ad01672d41..4e813a8345 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -52,7 +52,8 @@ def setup_once() -> None: def add_python_runtime_context( event: "Event", hint: "Hint" ) -> "Optional[Event]": - if sentry_sdk.get_client().get_integration(StdlibIntegration) is not None: + client = sentry_sdk.get_client() + if client.get_integration(StdlibIntegration) is not None: contexts = event.setdefault("contexts", {}) if isinstance(contexts, dict) and "runtime" not in contexts: contexts["runtime"] = _RUNTIME_CONTEXT diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 5cf48ffc48..cb6723922b 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -1,4 +1,5 @@ import os +import platform import sys import warnings from collections import deque @@ -379,6 +380,15 @@ def set_global_attributes(self) -> None: self.set_attribute(SPANDATA.SENTRY_SDK_NAME, SDK_INFO["name"]) self.set_attribute(SPANDATA.SENTRY_SDK_VERSION, SDK_INFO["version"]) + self.set_attribute( + "process.runtime.name", + platform.python_implementation(), + ) + self.set_attribute( + "process.runtime.version", + f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}", + ) + options = sentry_sdk.get_client().options server_name = options.get("server_name") diff --git a/tests/integrations/huggingface_hub/test_huggingface_hub.py b/tests/integrations/huggingface_hub/test_huggingface_hub.py index 028e710ad2..91e0909731 100644 --- a/tests/integrations/huggingface_hub/test_huggingface_hub.py +++ b/tests/integrations/huggingface_hub/test_huggingface_hub.py @@ -521,6 +521,8 @@ def test_text_generation( "gen_ai.response.finish_reasons": "length", "gen_ai.response.streaming": False, "gen_ai.usage.total_tokens": 10, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.text_completion", "sentry.origin": "auto.ai.huggingface_hub", @@ -658,6 +660,8 @@ def test_text_generation_streaming( "gen_ai.response.finish_reasons": "length", "gen_ai.response.streaming": True, "gen_ai.usage.total_tokens": 10, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.text_completion", "sentry.origin": "auto.ai.huggingface_hub", @@ -799,6 +803,8 @@ def test_chat_completion( "gen_ai.usage.input_tokens": 10, "gen_ai.usage.output_tokens": 8, "gen_ai.usage.total_tokens": 18, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "auto.ai.huggingface_hub", @@ -948,6 +954,8 @@ def test_chat_completion_streaming( "gen_ai.response.finish_reasons": "stop", "gen_ai.response.model": "test-model-123", "gen_ai.response.streaming": True, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "auto.ai.huggingface_hub", @@ -1095,6 +1103,8 @@ def test_chat_completion_api_error( expected_data = { "gen_ai.operation.name": "chat", "gen_ai.request.model": "test-model", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "auto.ai.huggingface_hub", @@ -1294,6 +1304,8 @@ def test_chat_completion_with_tools( "gen_ai.usage.input_tokens": 10, "gen_ai.usage.output_tokens": 8, "gen_ai.usage.total_tokens": 18, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "auto.ai.huggingface_hub", @@ -1454,6 +1466,8 @@ def test_chat_completion_streaming_with_tools( "gen_ai.response.finish_reasons": "tool_calls", "gen_ai.response.model": "test-model-123", "gen_ai.response.streaming": True, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "auto.ai.huggingface_hub", diff --git a/tests/integrations/logging/test_logging.py b/tests/integrations/logging/test_logging.py index 6b54baa0c5..16275eeb3d 100644 --- a/tests/integrations/logging/test_logging.py +++ b/tests/integrations/logging/test_logging.py @@ -1,5 +1,6 @@ import logging import warnings +from unittest import mock import pytest @@ -537,6 +538,8 @@ def test_logger_with_all_attributes(sentry_init, capture_items): "logger.name": "test-logger", "sentry.origin": "auto.log.stdlib", "sentry.message.template": "log #%d", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.message.parameter.0": 1, "sentry.environment": "production", "sentry.sdk.version": VERSION, diff --git a/tests/integrations/loguru/test_loguru.py b/tests/integrations/loguru/test_loguru.py index 57b67d74b9..52ff4635bd 100644 --- a/tests/integrations/loguru/test_loguru.py +++ b/tests/integrations/loguru/test_loguru.py @@ -1,5 +1,5 @@ import re -from unittest.mock import MagicMock, patch +from unittest.mock import ANY, MagicMock, patch import pytest from loguru import logger @@ -466,6 +466,8 @@ def test_logger_with_all_attributes( "logger.name": "tests.integrations.loguru.test_loguru", "sentry.origin": "auto.log.loguru", "sentry.environment": "production", + "process.runtime.name": ANY, + "process.runtime.version": ANY, "sentry.sdk.version": VERSION, "sentry.severity_number": 13, "sentry.severity_text": "warn", diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 5ca4cb7d5a..df9e7079de 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -3832,6 +3832,8 @@ def test_ai_client_span_responses_api_no_pii( "gen_ai.usage.output_tokens": 10, "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -3878,6 +3880,8 @@ def test_ai_client_span_responses_api_no_pii( "gen_ai.usage.output_tokens": 10, "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -4139,6 +4143,8 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -4191,6 +4197,8 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -4626,6 +4634,8 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -4678,6 +4688,8 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", @@ -4975,6 +4987,8 @@ async def test_ai_client_span_streaming_responses_async_api( "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.release": mock.ANY, "sentry.sdk.name": "sentry.python", "sentry.sdk.version": mock.ANY, diff --git a/tests/test_logs.py b/tests/test_logs.py index 384e2c9464..0cf33cd6c5 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -495,6 +495,14 @@ def test_transport_format(sentry_init, capture_envelopes): "type": "integer", "value": 13, }, + "process.runtime.name": { + "type": "string", + "value": mock.ANY, + }, + "process.runtime.version": { + "type": "string", + "value": mock.ANY, + }, "sentry.severity_text": { "type": "string", "value": "warn", @@ -574,6 +582,14 @@ def record_lost_event(reason, data_category=None, item=None, *, quantity=1): "type": "integer", "value": 9, }, + "process.runtime.name": { + "type": "string", + "value": mock.ANY, + }, + "process.runtime.version": { + "type": "string", + "value": mock.ANY, + }, "sentry.severity_text": { "type": "string", "value": "info", diff --git a/tests/test_metrics.py b/tests/test_metrics.py index c05a0fad7e..e62a868dbe 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -284,6 +284,14 @@ def test_transport_format(sentry_init, capture_envelopes): "type": "string", "value": "1.0.0", }, + "process.runtime.name": { + "type": "string", + "value": mock.ANY, + }, + "process.runtime.version": { + "type": "string", + "value": mock.ANY, + }, "sentry.sdk.name": { "type": "string", "value": mock.ANY, diff --git a/tests/tracing/test_decorator.py b/tests/tracing/test_decorator.py index bfb652b529..a1a824553f 100644 --- a/tests/tracing/test_decorator.py +++ b/tests/tracing/test_decorator.py @@ -187,6 +187,8 @@ def my_agent(): assert agent_span["attributes"] == { "gen_ai.agent.name": "test_decorator.test_span_templates_ai_dicts..my_agent", "gen_ai.operation.name": "invoke_agent", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.invoke_agent", "sentry.origin": "manual", @@ -210,6 +212,8 @@ def my_agent(): "gen_ai.usage.input_tokens": 10, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.total_tokens": 30, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.execute_tool", "sentry.origin": "manual", @@ -239,6 +243,8 @@ def my_agent(): "gen_ai.usage.input_tokens": 11, "gen_ai.usage.output_tokens": 22, "gen_ai.usage.total_tokens": 33, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "manual", @@ -384,6 +390,8 @@ def my_agent(): assert agent_span["attributes"] == { "gen_ai.agent.name": "test_decorator.test_span_templates_ai_objects..my_agent", "gen_ai.operation.name": "invoke_agent", + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.invoke_agent", "sentry.origin": "manual", @@ -408,6 +416,8 @@ def my_agent(): "gen_ai.usage.input_tokens": 10, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.total_tokens": 30, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.execute_tool", "sentry.origin": "manual", @@ -436,6 +446,8 @@ def my_agent(): "gen_ai.usage.input_tokens": 11, "gen_ai.usage.output_tokens": 22, "gen_ai.usage.total_tokens": 33, + "process.runtime.name": mock.ANY, + "process.runtime.version": mock.ANY, "sentry.environment": "production", "sentry.op": "gen_ai.chat", "sentry.origin": "manual", diff --git a/tests/tracing/test_span_streaming.py b/tests/tracing/test_span_streaming.py index d8537ea779..d379bbc9d7 100644 --- a/tests/tracing/test_span_streaming.py +++ b/tests/tracing/test_span_streaming.py @@ -1722,4 +1722,12 @@ def test_default_attributes(sentry_init, capture_envelopes): "sentry.dist": {"value": "1.0", "type": "string"}, "sentry.origin": {"value": "manual", "type": "string"}, "sentry.sdk.integrations": {"value": mock.ANY, "type": "array"}, + "process.runtime.name": { + "type": "string", + "value": mock.ANY, + }, + "process.runtime.version": { + "type": "string", + "value": mock.ANY, + }, }