|
1 | | -import re |
2 | 1 | import uuid |
3 | | -import contextlib |
4 | | -import math |
5 | 2 | import random |
6 | 3 | import time |
7 | 4 |
|
8 | 5 | from datetime import datetime, timedelta |
9 | | -from numbers import Real |
10 | 6 |
|
11 | 7 | import sentry_sdk |
12 | 8 |
|
13 | | -from sentry_sdk.utils import ( |
14 | | - capture_internal_exceptions, |
15 | | - logger, |
16 | | - to_string, |
| 9 | +from sentry_sdk.utils import logger |
| 10 | +from sentry_sdk.tracing_utils import ( |
| 11 | + SENTRY_TRACE_REGEX, |
| 12 | + EnvironHeaders, |
| 13 | + is_valid_sample_rate, |
| 14 | + maybe_create_breadcrumbs_from_span, |
17 | 15 | ) |
18 | | -from sentry_sdk._compat import PY2 |
19 | 16 | from sentry_sdk._types import MYPY |
20 | 17 |
|
21 | | -if PY2: |
22 | | - from collections import Mapping |
23 | | -else: |
24 | | - from collections.abc import Mapping |
25 | 18 |
|
26 | 19 | if MYPY: |
27 | 20 | import typing |
|
35 | 28 |
|
36 | 29 | from sentry_sdk._types import SamplingContext |
37 | 30 |
|
38 | | -_traceparent_header_format_re = re.compile( |
39 | | - "^[ \t]*" # whitespace |
40 | | - "([0-9a-f]{32})?" # trace_id |
41 | | - "-?([0-9a-f]{16})?" # span_id |
42 | | - "-?([01])?" # sampled |
43 | | - "[ \t]*$" # whitespace |
44 | | -) |
45 | | - |
46 | | - |
47 | | -class EnvironHeaders(Mapping): # type: ignore |
48 | | - def __init__( |
49 | | - self, |
50 | | - environ, # type: typing.Mapping[str, str] |
51 | | - prefix="HTTP_", # type: str |
52 | | - ): |
53 | | - # type: (...) -> None |
54 | | - self.environ = environ |
55 | | - self.prefix = prefix |
56 | | - |
57 | | - def __getitem__(self, key): |
58 | | - # type: (str) -> Optional[Any] |
59 | | - return self.environ[self.prefix + key.replace("-", "_").upper()] |
60 | | - |
61 | | - def __len__(self): |
62 | | - # type: () -> int |
63 | | - return sum(1 for _ in iter(self)) |
64 | | - |
65 | | - def __iter__(self): |
66 | | - # type: () -> Generator[str, None, None] |
67 | | - for k in self.environ: |
68 | | - if not isinstance(k, str): |
69 | | - continue |
70 | | - |
71 | | - k = k.replace("-", "_").upper() |
72 | | - if not k.startswith(self.prefix): |
73 | | - continue |
74 | | - |
75 | | - yield k[len(self.prefix) :] |
76 | | - |
77 | 31 |
|
78 | 32 | class _SpanRecorder(object): |
79 | 33 | """Limits the number of spans recorded in a transaction.""" |
@@ -325,7 +279,7 @@ def from_traceparent( |
325 | 279 | if traceparent.startswith("00-") and traceparent.endswith("-00"): |
326 | 280 | traceparent = traceparent[3:-3] |
327 | 281 |
|
328 | | - match = _traceparent_header_format_re.match(str(traceparent)) |
| 282 | + match = SENTRY_TRACE_REGEX.match(str(traceparent)) |
329 | 283 | if match is None: |
330 | 284 | return None |
331 | 285 |
|
@@ -422,7 +376,7 @@ def finish(self, hub=None): |
422 | 376 | except AttributeError: |
423 | 377 | self.timestamp = datetime.utcnow() |
424 | 378 |
|
425 | | - _maybe_create_breadcrumbs_from_span(hub, self) |
| 379 | + maybe_create_breadcrumbs_from_span(hub, self) |
426 | 380 | return None |
427 | 381 |
|
428 | 382 | def to_json(self): |
@@ -618,7 +572,7 @@ def _set_initial_sampling_decision(self, sampling_context): |
618 | 572 | # Since this is coming from the user (or from a function provided by the |
619 | 573 | # user), who knows what we might get. (The only valid values are |
620 | 574 | # booleans or numbers between 0 and 1.) |
621 | | - if not _is_valid_sample_rate(sample_rate): |
| 575 | + if not is_valid_sample_rate(sample_rate): |
622 | 576 | logger.warning( |
623 | 577 | "[Tracing] Discarding {transaction_description} because of invalid sample rate.".format( |
624 | 578 | transaction_description=transaction_description, |
@@ -661,114 +615,3 @@ def _set_initial_sampling_decision(self, sampling_context): |
661 | 615 | sample_rate=float(sample_rate), |
662 | 616 | ) |
663 | 617 | ) |
664 | | - |
665 | | - |
666 | | -def _is_valid_sample_rate(rate): |
667 | | - # type: (Any) -> bool |
668 | | - """ |
669 | | - Checks the given sample rate to make sure it is valid type and value (a |
670 | | - boolean or a number between 0 and 1, inclusive). |
671 | | - """ |
672 | | - |
673 | | - # both booleans and NaN are instances of Real, so a) checking for Real |
674 | | - # checks for the possibility of a boolean also, and b) we have to check |
675 | | - # separately for NaN |
676 | | - if not isinstance(rate, Real) or math.isnan(rate): |
677 | | - logger.warning( |
678 | | - "[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got {rate} of type {type}.".format( |
679 | | - rate=rate, type=type(rate) |
680 | | - ) |
681 | | - ) |
682 | | - return False |
683 | | - |
684 | | - # in case rate is a boolean, it will get cast to 1 if it's True and 0 if it's False |
685 | | - rate = float(rate) |
686 | | - if rate < 0 or rate > 1: |
687 | | - logger.warning( |
688 | | - "[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got {rate}.".format( |
689 | | - rate=rate |
690 | | - ) |
691 | | - ) |
692 | | - return False |
693 | | - |
694 | | - return True |
695 | | - |
696 | | - |
697 | | -def _format_sql(cursor, sql): |
698 | | - # type: (Any, str) -> Optional[str] |
699 | | - |
700 | | - real_sql = None |
701 | | - |
702 | | - # If we're using psycopg2, it could be that we're |
703 | | - # looking at a query that uses Composed objects. Use psycopg2's mogrify |
704 | | - # function to format the query. We lose per-parameter trimming but gain |
705 | | - # accuracy in formatting. |
706 | | - try: |
707 | | - if hasattr(cursor, "mogrify"): |
708 | | - real_sql = cursor.mogrify(sql) |
709 | | - if isinstance(real_sql, bytes): |
710 | | - real_sql = real_sql.decode(cursor.connection.encoding) |
711 | | - except Exception: |
712 | | - real_sql = None |
713 | | - |
714 | | - return real_sql or to_string(sql) |
715 | | - |
716 | | - |
717 | | -@contextlib.contextmanager |
718 | | -def record_sql_queries( |
719 | | - hub, # type: sentry_sdk.Hub |
720 | | - cursor, # type: Any |
721 | | - query, # type: Any |
722 | | - params_list, # type: Any |
723 | | - paramstyle, # type: Optional[str] |
724 | | - executemany, # type: bool |
725 | | -): |
726 | | - # type: (...) -> Generator[Span, None, None] |
727 | | - |
728 | | - # TODO: Bring back capturing of params by default |
729 | | - if hub.client and hub.client.options["_experiments"].get( |
730 | | - "record_sql_params", False |
731 | | - ): |
732 | | - if not params_list or params_list == [None]: |
733 | | - params_list = None |
734 | | - |
735 | | - if paramstyle == "pyformat": |
736 | | - paramstyle = "format" |
737 | | - else: |
738 | | - params_list = None |
739 | | - paramstyle = None |
740 | | - |
741 | | - query = _format_sql(cursor, query) |
742 | | - |
743 | | - data = {} |
744 | | - if params_list is not None: |
745 | | - data["db.params"] = params_list |
746 | | - if paramstyle is not None: |
747 | | - data["db.paramstyle"] = paramstyle |
748 | | - if executemany: |
749 | | - data["db.executemany"] = True |
750 | | - |
751 | | - with capture_internal_exceptions(): |
752 | | - hub.add_breadcrumb(message=query, category="query", data=data) |
753 | | - |
754 | | - with hub.start_span(op="db", description=query) as span: |
755 | | - for k, v in data.items(): |
756 | | - span.set_data(k, v) |
757 | | - yield span |
758 | | - |
759 | | - |
760 | | -def _maybe_create_breadcrumbs_from_span(hub, span): |
761 | | - # type: (sentry_sdk.Hub, Span) -> None |
762 | | - if span.op == "redis": |
763 | | - hub.add_breadcrumb( |
764 | | - message=span.description, type="redis", category="redis", data=span._tags |
765 | | - ) |
766 | | - elif span.op == "http": |
767 | | - hub.add_breadcrumb(type="http", category="httplib", data=span._data) |
768 | | - elif span.op == "subprocess": |
769 | | - hub.add_breadcrumb( |
770 | | - type="subprocess", |
771 | | - category="subprocess", |
772 | | - message=span.description, |
773 | | - data=span._data, |
774 | | - ) |
0 commit comments