sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/_types.py +2 -2
- sentry_sdk/ai/monitoring.py +7 -6
- sentry_sdk/client.py +31 -6
- sentry_sdk/consts.py +83 -5
- sentry_sdk/integrations/_asgi_common.py +2 -3
- sentry_sdk/integrations/arq.py +2 -1
- sentry_sdk/integrations/celery/__init__.py +5 -5
- sentry_sdk/integrations/celery/beat.py +2 -2
- sentry_sdk/integrations/cohere.py +10 -10
- sentry_sdk/integrations/django/asgi.py +2 -2
- sentry_sdk/integrations/grpc/__init__.py +18 -1
- sentry_sdk/integrations/huggingface_hub.py +2 -2
- sentry_sdk/integrations/logging.py +26 -32
- sentry_sdk/integrations/loguru.py +124 -50
- sentry_sdk/integrations/openai.py +4 -4
- sentry_sdk/integrations/redis/_async_common.py +10 -3
- sentry_sdk/integrations/redis/_sync_common.py +6 -1
- sentry_sdk/integrations/redis/redis_cluster.py +11 -5
- sentry_sdk/integrations/redis/utils.py +3 -3
- sentry_sdk/integrations/tornado.py +1 -1
- sentry_sdk/logger.py +32 -5
- sentry_sdk/opentelemetry/contextvars_context.py +9 -1
- sentry_sdk/opentelemetry/span_processor.py +7 -1
- sentry_sdk/opentelemetry/tracing.py +27 -3
- sentry_sdk/opentelemetry/utils.py +8 -0
- sentry_sdk/scope.py +2 -11
- sentry_sdk/tracing.py +12 -12
- sentry_sdk/tracing_utils.py +4 -2
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/METADATA +3 -3
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/RECORD +34 -34
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/WHEEL +1 -1
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a2.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
|
|
3
|
+
import sentry_sdk
|
|
3
4
|
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
4
5
|
from sentry_sdk.integrations.logging import (
|
|
5
6
|
BreadcrumbHandler,
|
|
6
7
|
EventHandler,
|
|
7
8
|
_BaseHandler,
|
|
8
9
|
)
|
|
10
|
+
from sentry_sdk.logger import _log_level_to_otel
|
|
9
11
|
|
|
10
12
|
from typing import TYPE_CHECKING
|
|
11
13
|
|
|
12
14
|
if TYPE_CHECKING:
|
|
13
15
|
from logging import LogRecord
|
|
14
|
-
from typing import
|
|
16
|
+
from typing import Any, Optional
|
|
15
17
|
|
|
16
18
|
try:
|
|
17
19
|
import loguru
|
|
18
20
|
from loguru import logger
|
|
19
21
|
from loguru._defaults import LOGURU_FORMAT as DEFAULT_FORMAT
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from loguru import Message
|
|
20
25
|
except ImportError:
|
|
21
26
|
raise DidNotEnable("LOGURU is not installed")
|
|
22
27
|
|
|
@@ -31,6 +36,10 @@ class LoggingLevels(enum.IntEnum):
|
|
|
31
36
|
CRITICAL = 50
|
|
32
37
|
|
|
33
38
|
|
|
39
|
+
DEFAULT_LEVEL = LoggingLevels.INFO.value
|
|
40
|
+
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
|
|
41
|
+
|
|
42
|
+
|
|
34
43
|
SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
|
|
35
44
|
"TRACE": "DEBUG",
|
|
36
45
|
"DEBUG": "DEBUG",
|
|
@@ -41,59 +50,76 @@ SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
|
|
|
41
50
|
"CRITICAL": "CRITICAL",
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
53
|
+
# Map Loguru level numbers to corresponding OTel level numbers
|
|
54
|
+
SEVERITY_TO_OTEL_SEVERITY = {
|
|
55
|
+
LoggingLevels.CRITICAL: 21, # fatal
|
|
56
|
+
LoggingLevels.ERROR: 17, # error
|
|
57
|
+
LoggingLevels.WARNING: 13, # warn
|
|
58
|
+
LoggingLevels.SUCCESS: 11, # info
|
|
59
|
+
LoggingLevels.INFO: 9, # info
|
|
60
|
+
LoggingLevels.DEBUG: 5, # debug
|
|
61
|
+
LoggingLevels.TRACE: 1, # trace
|
|
62
|
+
}
|
|
51
63
|
|
|
52
64
|
|
|
53
65
|
class LoguruIntegration(Integration):
|
|
54
66
|
identifier = "loguru"
|
|
55
67
|
|
|
68
|
+
level = DEFAULT_LEVEL # type: Optional[int]
|
|
69
|
+
event_level = DEFAULT_EVENT_LEVEL # type: Optional[int]
|
|
70
|
+
breadcrumb_format = DEFAULT_FORMAT
|
|
71
|
+
event_format = DEFAULT_FORMAT
|
|
72
|
+
sentry_logs_level = DEFAULT_LEVEL # type: Optional[int]
|
|
73
|
+
|
|
56
74
|
def __init__(
|
|
57
75
|
self,
|
|
58
76
|
level=DEFAULT_LEVEL,
|
|
59
77
|
event_level=DEFAULT_EVENT_LEVEL,
|
|
60
78
|
breadcrumb_format=DEFAULT_FORMAT,
|
|
61
79
|
event_format=DEFAULT_FORMAT,
|
|
80
|
+
sentry_logs_level=DEFAULT_LEVEL,
|
|
62
81
|
):
|
|
63
|
-
# type: (Optional[int], Optional[int], str | loguru.FormatFunction, str | loguru.FormatFunction) -> None
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
breadcrumb_handler = None
|
|
70
|
-
if event_handler is not None:
|
|
71
|
-
logger.remove(event_handler)
|
|
72
|
-
event_handler = None
|
|
73
|
-
|
|
74
|
-
if level is not None:
|
|
75
|
-
breadcrumb_handler = logger.add(
|
|
76
|
-
LoguruBreadcrumbHandler(level=level),
|
|
77
|
-
level=level,
|
|
78
|
-
format=breadcrumb_format,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
if event_level is not None:
|
|
82
|
-
event_handler = logger.add(
|
|
83
|
-
LoguruEventHandler(level=event_level),
|
|
84
|
-
level=event_level,
|
|
85
|
-
format=event_format,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
_ADDED_HANDLERS = (breadcrumb_handler, event_handler)
|
|
82
|
+
# type: (Optional[int], Optional[int], str | loguru.FormatFunction, str | loguru.FormatFunction, Optional[int]) -> None
|
|
83
|
+
LoguruIntegration.level = level
|
|
84
|
+
LoguruIntegration.event_level = event_level
|
|
85
|
+
LoguruIntegration.breadcrumb_format = breadcrumb_format
|
|
86
|
+
LoguruIntegration.event_format = event_format
|
|
87
|
+
LoguruIntegration.sentry_logs_level = sentry_logs_level
|
|
89
88
|
|
|
90
89
|
@staticmethod
|
|
91
90
|
def setup_once():
|
|
92
91
|
# type: () -> None
|
|
93
|
-
|
|
92
|
+
if LoguruIntegration.level is not None:
|
|
93
|
+
logger.add(
|
|
94
|
+
LoguruBreadcrumbHandler(level=LoguruIntegration.level),
|
|
95
|
+
level=LoguruIntegration.level,
|
|
96
|
+
format=LoguruIntegration.breadcrumb_format,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if LoguruIntegration.event_level is not None:
|
|
100
|
+
logger.add(
|
|
101
|
+
LoguruEventHandler(level=LoguruIntegration.event_level),
|
|
102
|
+
level=LoguruIntegration.event_level,
|
|
103
|
+
format=LoguruIntegration.event_format,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if LoguruIntegration.sentry_logs_level is not None:
|
|
107
|
+
logger.add(
|
|
108
|
+
loguru_sentry_logs_handler,
|
|
109
|
+
level=LoguruIntegration.sentry_logs_level,
|
|
110
|
+
)
|
|
94
111
|
|
|
95
112
|
|
|
96
113
|
class _LoguruBaseHandler(_BaseHandler):
|
|
114
|
+
def __init__(self, *args, **kwargs):
|
|
115
|
+
# type: (*Any, **Any) -> None
|
|
116
|
+
if kwargs.get("level"):
|
|
117
|
+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
118
|
+
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
super().__init__(*args, **kwargs)
|
|
122
|
+
|
|
97
123
|
def _logging_to_event_level(self, record):
|
|
98
124
|
# type: (LogRecord) -> str
|
|
99
125
|
try:
|
|
@@ -107,24 +133,72 @@ class _LoguruBaseHandler(_BaseHandler):
|
|
|
107
133
|
class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
|
|
108
134
|
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
# type: (*Any, **Any) -> None
|
|
112
|
-
if kwargs.get("level"):
|
|
113
|
-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
114
|
-
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
super().__init__(*args, **kwargs)
|
|
136
|
+
pass
|
|
118
137
|
|
|
119
138
|
|
|
120
139
|
class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
|
|
121
140
|
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
# type: (*Any, **Any) -> None
|
|
125
|
-
if kwargs.get("level"):
|
|
126
|
-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
127
|
-
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
128
|
-
)
|
|
142
|
+
pass
|
|
129
143
|
|
|
130
|
-
|
|
144
|
+
|
|
145
|
+
def loguru_sentry_logs_handler(message):
|
|
146
|
+
# type: (Message) -> None
|
|
147
|
+
# This is intentionally a callable sink instead of a standard logging handler
|
|
148
|
+
# since otherwise we wouldn't get direct access to message.record
|
|
149
|
+
client = sentry_sdk.get_client()
|
|
150
|
+
|
|
151
|
+
if not client.is_active():
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if not client.options["_experiments"].get("enable_logs", False):
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
record = message.record
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
LoguruIntegration.sentry_logs_level is None
|
|
161
|
+
or record["level"].no < LoguruIntegration.sentry_logs_level
|
|
162
|
+
):
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
otel_severity_number, otel_severity_text = _log_level_to_otel(
|
|
166
|
+
record["level"].no, SEVERITY_TO_OTEL_SEVERITY
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
attrs = {"sentry.origin": "auto.logger.loguru"} # type: dict[str, Any]
|
|
170
|
+
|
|
171
|
+
project_root = client.options["project_root"]
|
|
172
|
+
if record.get("file"):
|
|
173
|
+
if project_root is not None and record["file"].path.startswith(project_root):
|
|
174
|
+
attrs["code.file.path"] = record["file"].path[len(project_root) + 1 :]
|
|
175
|
+
else:
|
|
176
|
+
attrs["code.file.path"] = record["file"].path
|
|
177
|
+
|
|
178
|
+
if record.get("line") is not None:
|
|
179
|
+
attrs["code.line.number"] = record["line"]
|
|
180
|
+
|
|
181
|
+
if record.get("function"):
|
|
182
|
+
attrs["code.function.name"] = record["function"]
|
|
183
|
+
|
|
184
|
+
if record.get("thread"):
|
|
185
|
+
attrs["thread.name"] = record["thread"].name
|
|
186
|
+
attrs["thread.id"] = record["thread"].id
|
|
187
|
+
|
|
188
|
+
if record.get("process"):
|
|
189
|
+
attrs["process.pid"] = record["process"].id
|
|
190
|
+
attrs["process.executable.name"] = record["process"].name
|
|
191
|
+
|
|
192
|
+
if record.get("name"):
|
|
193
|
+
attrs["logger.name"] = record["name"]
|
|
194
|
+
|
|
195
|
+
client._capture_experimental_log(
|
|
196
|
+
{
|
|
197
|
+
"severity_text": otel_severity_text,
|
|
198
|
+
"severity_number": otel_severity_number,
|
|
199
|
+
"body": record["message"],
|
|
200
|
+
"attributes": attrs,
|
|
201
|
+
"time_unix_nano": int(record["time"].timestamp() * 1e9),
|
|
202
|
+
"trace_id": None,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
@@ -156,7 +156,7 @@ def _new_chat_completion_common(f, *args, **kwargs):
|
|
|
156
156
|
if should_send_default_pii() and integration.include_prompts:
|
|
157
157
|
set_data_normalized(
|
|
158
158
|
span,
|
|
159
|
-
|
|
159
|
+
SPANDATA.AI_RESPONSES,
|
|
160
160
|
list(map(lambda x: x.message, res.choices)),
|
|
161
161
|
)
|
|
162
162
|
_calculate_chat_completion_usage(
|
|
@@ -331,15 +331,15 @@ def _new_embeddings_create_common(f, *args, **kwargs):
|
|
|
331
331
|
should_send_default_pii() and integration.include_prompts
|
|
332
332
|
):
|
|
333
333
|
if isinstance(kwargs["input"], str):
|
|
334
|
-
set_data_normalized(span,
|
|
334
|
+
set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, [kwargs["input"]])
|
|
335
335
|
elif (
|
|
336
336
|
isinstance(kwargs["input"], list)
|
|
337
337
|
and len(kwargs["input"]) > 0
|
|
338
338
|
and isinstance(kwargs["input"][0], str)
|
|
339
339
|
):
|
|
340
|
-
set_data_normalized(span,
|
|
340
|
+
set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, kwargs["input"])
|
|
341
341
|
if "model" in kwargs:
|
|
342
|
-
set_data_normalized(span,
|
|
342
|
+
set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"])
|
|
343
343
|
|
|
344
344
|
response = yield f, args, kwargs
|
|
345
345
|
|
|
@@ -44,13 +44,20 @@ def patch_redis_async_pipeline(
|
|
|
44
44
|
) as span:
|
|
45
45
|
with capture_internal_exceptions():
|
|
46
46
|
span_data = get_db_data_fn(self)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
command_seq = self._execution_strategy._command_queue
|
|
50
|
+
except AttributeError:
|
|
51
|
+
if is_cluster:
|
|
52
|
+
command_seq = self._command_stack
|
|
53
|
+
else:
|
|
54
|
+
command_seq = self.command_stack
|
|
55
|
+
|
|
47
56
|
pipeline_data = _get_pipeline_data(
|
|
48
57
|
is_cluster=is_cluster,
|
|
49
58
|
get_command_args_fn=get_command_args_fn,
|
|
50
59
|
is_transaction=False if is_cluster else self.is_transaction,
|
|
51
|
-
|
|
52
|
-
self._command_stack if is_cluster else self.command_stack
|
|
53
|
-
),
|
|
60
|
+
command_seq=command_seq,
|
|
54
61
|
)
|
|
55
62
|
_update_span(span, span_data, pipeline_data)
|
|
56
63
|
_create_breadcrumb("redis.pipeline.execute", span_data, pipeline_data)
|
|
@@ -44,12 +44,17 @@ def patch_redis_pipeline(
|
|
|
44
44
|
only_if_parent=True,
|
|
45
45
|
) as span:
|
|
46
46
|
with capture_internal_exceptions():
|
|
47
|
+
try:
|
|
48
|
+
command_seq = self._execution_strategy.command_queue
|
|
49
|
+
except AttributeError:
|
|
50
|
+
command_seq = self.command_stack
|
|
51
|
+
|
|
47
52
|
span_data = get_db_data_fn(self)
|
|
48
53
|
pipeline_data = _get_pipeline_data(
|
|
49
54
|
is_cluster=is_cluster,
|
|
50
55
|
get_command_args_fn=get_command_args_fn,
|
|
51
56
|
is_transaction=False if is_cluster else self.transaction,
|
|
52
|
-
|
|
57
|
+
command_seq=command_seq,
|
|
53
58
|
)
|
|
54
59
|
_update_span(span, span_data, pipeline_data)
|
|
55
60
|
_create_breadcrumb("redis.pipeline.execute", span_data, pipeline_data)
|
|
@@ -37,11 +37,17 @@ def _get_async_cluster_db_data(async_redis_cluster_instance):
|
|
|
37
37
|
def _get_async_cluster_pipeline_db_data(async_redis_cluster_pipeline_instance):
|
|
38
38
|
# type: (AsyncClusterPipeline[Any]) -> dict[str, Any]
|
|
39
39
|
with capture_internal_exceptions():
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
client = getattr(async_redis_cluster_pipeline_instance, "cluster_client", None)
|
|
41
|
+
if client is None:
|
|
42
|
+
# In older redis-py versions, the AsyncClusterPipeline had a `_client`
|
|
43
|
+
# attr but it is private so potentially problematic and mypy does not
|
|
44
|
+
# recognize it - see
|
|
45
|
+
# https://github.com/redis/redis-py/blame/v5.0.0/redis/asyncio/cluster.py#L1386
|
|
46
|
+
client = (
|
|
47
|
+
async_redis_cluster_pipeline_instance._client # type: ignore[attr-defined]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return _get_async_cluster_db_data(client)
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
def _get_cluster_db_data(redis_cluster_instance):
|
|
@@ -147,7 +147,7 @@ def _parse_rediscluster_command(command):
|
|
|
147
147
|
return command.args
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
def _get_pipeline_data(is_cluster, get_command_args_fn, is_transaction,
|
|
150
|
+
def _get_pipeline_data(is_cluster, get_command_args_fn, is_transaction, command_seq):
|
|
151
151
|
# type: (bool, Any, bool, Sequence[Any]) -> dict[str, Any]
|
|
152
152
|
data = {
|
|
153
153
|
"redis.is_cluster": is_cluster,
|
|
@@ -155,14 +155,14 @@ def _get_pipeline_data(is_cluster, get_command_args_fn, is_transaction, command_
|
|
|
155
155
|
} # type: dict[str, Any]
|
|
156
156
|
|
|
157
157
|
commands = []
|
|
158
|
-
for i, arg in enumerate(
|
|
158
|
+
for i, arg in enumerate(command_seq):
|
|
159
159
|
if i >= _MAX_NUM_COMMANDS:
|
|
160
160
|
break
|
|
161
161
|
|
|
162
162
|
command = get_command_args_fn(arg)
|
|
163
163
|
commands.append(_get_safe_command(command[0], command[1:]))
|
|
164
164
|
|
|
165
|
-
data["redis.commands.count"] = len(
|
|
165
|
+
data["redis.commands.count"] = len(command_seq)
|
|
166
166
|
data["redis.commands.first_ten"] = commands
|
|
167
167
|
|
|
168
168
|
return data
|
|
@@ -85,7 +85,7 @@ class TornadoIntegration(Integration):
|
|
|
85
85
|
else:
|
|
86
86
|
|
|
87
87
|
@coroutine # type: ignore
|
|
88
|
-
def sentry_execute_request_handler(self, *args, **kwargs):
|
|
88
|
+
def sentry_execute_request_handler(self, *args, **kwargs):
|
|
89
89
|
# type: (RequestHandler, *Any, **Any) -> Any
|
|
90
90
|
with _handle_request_impl(self):
|
|
91
91
|
result = yield from old_execute(self, *args, **kwargs)
|
sentry_sdk/logger.py
CHANGED
|
@@ -3,14 +3,24 @@ import functools
|
|
|
3
3
|
import time
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from sentry_sdk import get_client
|
|
6
|
+
from sentry_sdk import get_client
|
|
7
7
|
from sentry_sdk.utils import safe_repr
|
|
8
8
|
|
|
9
|
+
OTEL_RANGES = [
|
|
10
|
+
# ((severity level range), severity text)
|
|
11
|
+
# https://opentelemetry.io/docs/specs/otel/logs/data-model
|
|
12
|
+
((1, 4), "trace"),
|
|
13
|
+
((5, 8), "debug"),
|
|
14
|
+
((9, 12), "info"),
|
|
15
|
+
((13, 16), "warn"),
|
|
16
|
+
((17, 20), "error"),
|
|
17
|
+
((21, 24), "fatal"),
|
|
18
|
+
]
|
|
19
|
+
|
|
9
20
|
|
|
10
21
|
def _capture_log(severity_text, severity_number, template, **kwargs):
|
|
11
22
|
# type: (str, int, str, **Any) -> None
|
|
12
23
|
client = get_client()
|
|
13
|
-
scope = get_current_scope()
|
|
14
24
|
|
|
15
25
|
attrs = {
|
|
16
26
|
"sentry.message.template": template,
|
|
@@ -18,7 +28,7 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
|
|
|
18
28
|
if "attributes" in kwargs:
|
|
19
29
|
attrs.update(kwargs.pop("attributes"))
|
|
20
30
|
for k, v in kwargs.items():
|
|
21
|
-
attrs[f"sentry.message.
|
|
31
|
+
attrs[f"sentry.message.parameter.{k}"] = v
|
|
22
32
|
|
|
23
33
|
attrs = {
|
|
24
34
|
k: (
|
|
@@ -36,7 +46,6 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
|
|
|
36
46
|
|
|
37
47
|
# noinspection PyProtectedMember
|
|
38
48
|
client._capture_experimental_log(
|
|
39
|
-
scope,
|
|
40
49
|
{
|
|
41
50
|
"severity_text": severity_text,
|
|
42
51
|
"severity_number": severity_number,
|
|
@@ -51,6 +60,24 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
|
|
|
51
60
|
trace = functools.partial(_capture_log, "trace", 1)
|
|
52
61
|
debug = functools.partial(_capture_log, "debug", 5)
|
|
53
62
|
info = functools.partial(_capture_log, "info", 9)
|
|
54
|
-
warning = functools.partial(_capture_log, "
|
|
63
|
+
warning = functools.partial(_capture_log, "warn", 13)
|
|
55
64
|
error = functools.partial(_capture_log, "error", 17)
|
|
56
65
|
fatal = functools.partial(_capture_log, "fatal", 21)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _otel_severity_text(otel_severity_number):
|
|
69
|
+
# type: (int) -> str
|
|
70
|
+
for (lower, upper), severity in OTEL_RANGES:
|
|
71
|
+
if lower <= otel_severity_number <= upper:
|
|
72
|
+
return severity
|
|
73
|
+
|
|
74
|
+
return "default"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _log_level_to_otel(level, mapping):
|
|
78
|
+
# type: (int, dict[Any, int]) -> tuple[int, str]
|
|
79
|
+
for py_level, otel_severity_number in sorted(mapping.items(), reverse=True):
|
|
80
|
+
if level >= py_level:
|
|
81
|
+
return otel_severity_number, _otel_severity_text(otel_severity_number)
|
|
82
|
+
|
|
83
|
+
return 0, "default"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from typing import cast, TYPE_CHECKING
|
|
2
2
|
|
|
3
|
-
from opentelemetry.trace import set_span_in_context
|
|
3
|
+
from opentelemetry.trace import get_current_span, set_span_in_context
|
|
4
|
+
from opentelemetry.trace.span import INVALID_SPAN
|
|
4
5
|
from opentelemetry.context import Context, get_value, set_value
|
|
5
6
|
from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext
|
|
6
7
|
|
|
7
8
|
import sentry_sdk
|
|
9
|
+
from sentry_sdk.tracing import Span
|
|
8
10
|
from sentry_sdk.opentelemetry.consts import (
|
|
9
11
|
SENTRY_SCOPES_KEY,
|
|
10
12
|
SENTRY_FORK_ISOLATION_SCOPE_KEY,
|
|
@@ -60,6 +62,12 @@ class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
|
|
|
60
62
|
else:
|
|
61
63
|
new_scope = current_scope.fork()
|
|
62
64
|
|
|
65
|
+
# carry forward a wrapped span reference since the otel context is always the
|
|
66
|
+
# source of truth for the active span
|
|
67
|
+
current_span = get_current_span(context)
|
|
68
|
+
if current_span != INVALID_SPAN:
|
|
69
|
+
new_scope._span = Span(otel_span=get_current_span(context))
|
|
70
|
+
|
|
63
71
|
if should_use_isolation_scope:
|
|
64
72
|
new_isolation_scope = should_use_isolation_scope
|
|
65
73
|
elif should_fork_isolation_scope:
|
|
@@ -29,6 +29,7 @@ from sentry_sdk.opentelemetry.utils import (
|
|
|
29
29
|
get_profile_context,
|
|
30
30
|
get_sentry_meta,
|
|
31
31
|
set_sentry_meta,
|
|
32
|
+
delete_sentry_meta,
|
|
32
33
|
)
|
|
33
34
|
from sentry_sdk.profiler.continuous_profiler import (
|
|
34
35
|
try_autostart_continuous_profiler,
|
|
@@ -173,6 +174,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
173
174
|
# TODO-neel-potel sort and cutoff max spans
|
|
174
175
|
|
|
175
176
|
sentry_sdk.capture_event(transaction_event)
|
|
177
|
+
self._cleanup_references([span] + collected_spans)
|
|
176
178
|
|
|
177
179
|
def _append_child_span(self, span):
|
|
178
180
|
# type: (ReadableSpan) -> None
|
|
@@ -253,7 +255,6 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
253
255
|
profile.__exit__(None, None, None)
|
|
254
256
|
if profile.valid():
|
|
255
257
|
event["profile"] = profile
|
|
256
|
-
set_sentry_meta(span, "profile", None)
|
|
257
258
|
|
|
258
259
|
return event
|
|
259
260
|
|
|
@@ -314,6 +315,11 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
314
315
|
|
|
315
316
|
return common_json
|
|
316
317
|
|
|
318
|
+
def _cleanup_references(self, spans):
|
|
319
|
+
# type: (List[ReadableSpan]) -> None
|
|
320
|
+
for span in spans:
|
|
321
|
+
delete_sentry_meta(span)
|
|
322
|
+
|
|
317
323
|
def _log_debug_info(self):
|
|
318
324
|
# type: () -> None
|
|
319
325
|
import pprint
|
|
@@ -7,6 +7,7 @@ from sentry_sdk.opentelemetry import (
|
|
|
7
7
|
SentrySampler,
|
|
8
8
|
SentrySpanProcessor,
|
|
9
9
|
)
|
|
10
|
+
from sentry_sdk.utils import logger
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def patch_readable_span():
|
|
@@ -28,8 +29,31 @@ def patch_readable_span():
|
|
|
28
29
|
|
|
29
30
|
def setup_sentry_tracing():
|
|
30
31
|
# type: () -> None
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
trace
|
|
32
|
+
# TracerProvider can only be set once. If we're the first ones setting it,
|
|
33
|
+
# there's no issue. If it already exists, we need to patch it.
|
|
34
|
+
from opentelemetry.trace import _TRACER_PROVIDER
|
|
35
|
+
|
|
36
|
+
if _TRACER_PROVIDER is not None:
|
|
37
|
+
logger.debug("[Tracing] Detected an existing TracerProvider, patching")
|
|
38
|
+
tracer_provider = _TRACER_PROVIDER
|
|
39
|
+
tracer_provider.sampler = SentrySampler() # type: ignore[attr-defined]
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
logger.debug("[Tracing] No TracerProvider set, creating a new one")
|
|
43
|
+
tracer_provider = TracerProvider(sampler=SentrySampler())
|
|
44
|
+
trace.set_tracer_provider(tracer_provider)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
existing_span_processors = (
|
|
48
|
+
tracer_provider._active_span_processor._span_processors # type: ignore[attr-defined]
|
|
49
|
+
)
|
|
50
|
+
except Exception:
|
|
51
|
+
existing_span_processors = []
|
|
52
|
+
|
|
53
|
+
for span_processor in existing_span_processors:
|
|
54
|
+
if isinstance(span_processor, SentrySpanProcessor):
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
tracer_provider.add_span_processor(SentrySpanProcessor()) # type: ignore[attr-defined]
|
|
34
58
|
|
|
35
59
|
set_global_textmap(SentryPropagator())
|
|
@@ -464,6 +464,14 @@ def set_sentry_meta(span, key, value):
|
|
|
464
464
|
span._sentry_meta = sentry_meta # type: ignore[union-attr]
|
|
465
465
|
|
|
466
466
|
|
|
467
|
+
def delete_sentry_meta(span):
|
|
468
|
+
# type: (Union[AbstractSpan, ReadableSpan]) -> None
|
|
469
|
+
try:
|
|
470
|
+
del span._sentry_meta # type: ignore[union-attr]
|
|
471
|
+
except AttributeError:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
|
|
467
475
|
def get_profile_context(span):
|
|
468
476
|
# type: (ReadableSpan) -> Optional[dict[str, str]]
|
|
469
477
|
if not span.attributes:
|
sentry_sdk/scope.py
CHANGED
|
@@ -172,8 +172,8 @@ class Scope:
|
|
|
172
172
|
"_flags",
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
-
def __init__(self, ty=None
|
|
176
|
-
# type: (Optional[ScopeType]
|
|
175
|
+
def __init__(self, ty=None):
|
|
176
|
+
# type: (Optional[ScopeType]) -> None
|
|
177
177
|
self._type = ty
|
|
178
178
|
|
|
179
179
|
self._event_processors = [] # type: List[EventProcessor]
|
|
@@ -185,9 +185,6 @@ class Scope:
|
|
|
185
185
|
|
|
186
186
|
self.client = NonRecordingClient() # type: sentry_sdk.client.BaseClient
|
|
187
187
|
|
|
188
|
-
if client is not None:
|
|
189
|
-
self.set_client(client)
|
|
190
|
-
|
|
191
188
|
self.clear()
|
|
192
189
|
|
|
193
190
|
incoming_trace_information = self._load_trace_data_from_env()
|
|
@@ -732,12 +729,6 @@ class Scope:
|
|
|
732
729
|
"""Get current tracing span."""
|
|
733
730
|
return self._span
|
|
734
731
|
|
|
735
|
-
@span.setter
|
|
736
|
-
def span(self, span):
|
|
737
|
-
# type: (Optional[Span]) -> None
|
|
738
|
-
"""Set current tracing span."""
|
|
739
|
-
self._span = span
|
|
740
|
-
|
|
741
732
|
@property
|
|
742
733
|
def profile(self):
|
|
743
734
|
# type: () -> Optional[Profile]
|
sentry_sdk/tracing.py
CHANGED
|
@@ -15,7 +15,6 @@ from opentelemetry.trace.status import Status, StatusCode
|
|
|
15
15
|
from opentelemetry.sdk.trace import ReadableSpan
|
|
16
16
|
from opentelemetry.version import __version__ as otel_version
|
|
17
17
|
|
|
18
|
-
import sentry_sdk
|
|
19
18
|
from sentry_sdk.consts import (
|
|
20
19
|
DEFAULT_SPAN_NAME,
|
|
21
20
|
DEFAULT_SPAN_ORIGIN,
|
|
@@ -189,7 +188,7 @@ class Span:
|
|
|
189
188
|
If otel_span is passed explicitly, just acts as a proxy.
|
|
190
189
|
|
|
191
190
|
If span is passed explicitly, use it. The only purpose of this param
|
|
192
|
-
|
|
191
|
+
is backwards compatibility with start_transaction(transaction=...).
|
|
193
192
|
|
|
194
193
|
If only_if_parent is True, just return an INVALID_SPAN
|
|
195
194
|
and avoid instrumentation if there's no active parent span.
|
|
@@ -271,19 +270,21 @@ class Span:
|
|
|
271
270
|
)
|
|
272
271
|
)
|
|
273
272
|
|
|
274
|
-
def
|
|
275
|
-
# type: () ->
|
|
276
|
-
# XXX use_span? https://github.com/open-telemetry/opentelemetry-python/blob/3836da8543ce9751051e38a110c0468724042e62/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547
|
|
277
|
-
#
|
|
278
|
-
# create a Context object with parent set as current span
|
|
273
|
+
def activate(self):
|
|
274
|
+
# type: () -> None
|
|
279
275
|
ctx = otel_trace.set_span_in_context(self._otel_span)
|
|
280
276
|
# set as the implicit current context
|
|
281
277
|
self._ctx_token = context.attach(ctx)
|
|
282
278
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
self.
|
|
279
|
+
def deactivate(self):
|
|
280
|
+
# type: () -> None
|
|
281
|
+
if self._ctx_token:
|
|
282
|
+
context.detach(self._ctx_token)
|
|
283
|
+
del self._ctx_token
|
|
286
284
|
|
|
285
|
+
def __enter__(self):
|
|
286
|
+
# type: () -> Span
|
|
287
|
+
self.activate()
|
|
287
288
|
return self
|
|
288
289
|
|
|
289
290
|
def __exit__(self, ty, value, tb):
|
|
@@ -299,8 +300,7 @@ class Span:
|
|
|
299
300
|
self.set_status(SPANSTATUS.OK)
|
|
300
301
|
|
|
301
302
|
self.finish()
|
|
302
|
-
|
|
303
|
-
del self._ctx_token
|
|
303
|
+
self.deactivate()
|
|
304
304
|
|
|
305
305
|
@property
|
|
306
306
|
def description(self):
|
sentry_sdk/tracing_utils.py
CHANGED
|
@@ -729,9 +729,10 @@ def start_child_span_decorator(func):
|
|
|
729
729
|
)
|
|
730
730
|
return await func(*args, **kwargs)
|
|
731
731
|
|
|
732
|
-
with
|
|
732
|
+
with sentry_sdk.start_span(
|
|
733
733
|
op=OP.FUNCTION,
|
|
734
734
|
name=qualname_from_function(func),
|
|
735
|
+
only_if_parent=True,
|
|
735
736
|
):
|
|
736
737
|
return await func(*args, **kwargs)
|
|
737
738
|
|
|
@@ -757,9 +758,10 @@ def start_child_span_decorator(func):
|
|
|
757
758
|
)
|
|
758
759
|
return func(*args, **kwargs)
|
|
759
760
|
|
|
760
|
-
with
|
|
761
|
+
with sentry_sdk.start_span(
|
|
761
762
|
op=OP.FUNCTION,
|
|
762
763
|
name=qualname_from_function(func),
|
|
764
|
+
only_if_parent=True,
|
|
763
765
|
):
|
|
764
766
|
return func(*args, **kwargs)
|
|
765
767
|
|