Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/integrations/stdlib.py: 76%
133 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import os
2import subprocess
3import sys
4import platform
6from sentry_sdk.hub import Hub
7from sentry_sdk.integrations import Integration
8from sentry_sdk.scope import add_global_event_processor
9from sentry_sdk.tracing_utils import EnvironHeaders
10from sentry_sdk.utils import capture_internal_exceptions, logger, safe_repr
12from sentry_sdk._types import MYPY
14if MYPY: 14 ↛ 15line 14 didn't jump to line 15, because the condition on line 14 was never true
15 from typing import Any
16 from typing import Callable
17 from typing import Dict
18 from typing import Optional
19 from typing import List
21 from sentry_sdk._types import Event, Hint
24try:
25 from httplib import HTTPConnection # type: ignore
26except ImportError:
27 from http.client import HTTPConnection
30_RUNTIME_CONTEXT = {
31 "name": platform.python_implementation(),
32 "version": "%s.%s.%s" % (sys.version_info[:3]),
33 "build": sys.version,
34}
37class StdlibIntegration(Integration):
38 identifier = "stdlib"
40 @staticmethod
41 def setup_once():
42 # type: () -> None
43 _install_httplib()
44 _install_subprocess()
46 @add_global_event_processor
47 def add_python_runtime_context(event, hint):
48 # type: (Event, Hint) -> Optional[Event]
49 if Hub.current.get_integration(StdlibIntegration) is not None:
50 contexts = event.setdefault("contexts", {})
51 if isinstance(contexts, dict) and "runtime" not in contexts:
52 contexts["runtime"] = _RUNTIME_CONTEXT
54 return event
57def _install_httplib():
58 # type: () -> None
59 real_putrequest = HTTPConnection.putrequest
60 real_getresponse = HTTPConnection.getresponse
62 def putrequest(self, method, url, *args, **kwargs):
63 # type: (HTTPConnection, str, str, *Any, **Any) -> Any
64 hub = Hub.current
65 if hub.get_integration(StdlibIntegration) is None: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true
66 return real_putrequest(self, method, url, *args, **kwargs)
68 host = self.host
69 port = self.port
70 default_port = self.default_port
72 real_url = url
73 if not real_url.startswith(("http://", "https://")): 73 ↛ 81line 73 didn't jump to line 81, because the condition on line 73 was never false
74 real_url = "%s://%s%s%s" % (
75 default_port == 443 and "https" or "http",
76 host,
77 port != default_port and ":%s" % port or "",
78 url,
79 )
81 span = hub.start_span(op="http", description="%s %s" % (method, real_url))
83 span.set_data("method", method)
84 span.set_data("url", real_url)
86 rv = real_putrequest(self, method, url, *args, **kwargs)
88 for key, value in hub.iter_trace_propagation_headers(span):
89 logger.debug(
90 "[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format(
91 key=key, value=value, real_url=real_url
92 )
93 )
94 self.putheader(key, value)
96 self._sentrysdk_span = span
98 return rv
100 def getresponse(self, *args, **kwargs):
101 # type: (HTTPConnection, *Any, **Any) -> Any
102 span = getattr(self, "_sentrysdk_span", None)
104 if span is None: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true
105 return real_getresponse(self, *args, **kwargs)
107 rv = real_getresponse(self, *args, **kwargs)
109 span.set_data("status_code", rv.status)
110 span.set_http_status(int(rv.status))
111 span.set_data("reason", rv.reason)
112 span.finish()
114 return rv
116 HTTPConnection.putrequest = putrequest
117 HTTPConnection.getresponse = getresponse
120def _init_argument(args, kwargs, name, position, setdefault_callback=None):
121 # type: (List[Any], Dict[Any, Any], str, int, Optional[Callable[[Any], Any]]) -> Any
122 """
123 given (*args, **kwargs) of a function call, retrieve (and optionally set a
124 default for) an argument by either name or position.
126 This is useful for wrapping functions with complex type signatures and
127 extracting a few arguments without needing to redefine that function's
128 entire type signature.
129 """
131 if name in kwargs:
132 rv = kwargs[name]
133 if setdefault_callback is not None:
134 rv = setdefault_callback(rv)
135 if rv is not None: 135 ↛ 148line 135 didn't jump to line 148, because the condition on line 135 was never false
136 kwargs[name] = rv
137 elif position < len(args): 137 ↛ 144line 137 didn't jump to line 144, because the condition on line 137 was never false
138 rv = args[position]
139 if setdefault_callback is not None: 139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never true
140 rv = setdefault_callback(rv)
141 if rv is not None: 141 ↛ 148line 141 didn't jump to line 148, because the condition on line 141 was never false
142 args[position] = rv
143 else:
144 rv = setdefault_callback and setdefault_callback(None)
145 if rv is not None:
146 kwargs[name] = rv
148 return rv
151def _install_subprocess():
152 # type: () -> None
153 old_popen_init = subprocess.Popen.__init__
155 def sentry_patched_popen_init(self, *a, **kw):
156 # type: (subprocess.Popen[Any], *Any, **Any) -> None
158 hub = Hub.current
159 if hub.get_integration(StdlibIntegration) is None: 159 ↛ 160line 159 didn't jump to line 160, because the condition on line 159 was never true
160 return old_popen_init(self, *a, **kw)
162 # Convert from tuple to list to be able to set values.
163 a = list(a)
165 args = _init_argument(a, kw, "args", 0) or []
166 cwd = _init_argument(a, kw, "cwd", 9)
168 # if args is not a list or tuple (and e.g. some iterator instead),
169 # let's not use it at all. There are too many things that can go wrong
170 # when trying to collect an iterator into a list and setting that list
171 # into `a` again.
172 #
173 # Also invocations where `args` is not a sequence are not actually
174 # legal. They just happen to work under CPython.
175 description = None
177 if isinstance(args, (list, tuple)) and len(args) < 100: 177 ↛ 181line 177 didn't jump to line 181, because the condition on line 177 was never false
178 with capture_internal_exceptions():
179 description = " ".join(map(str, args))
181 if description is None: 181 ↛ 182line 181 didn't jump to line 182, because the condition on line 181 was never true
182 description = safe_repr(args)
184 env = None
186 with hub.start_span(op="subprocess", description=description) as span:
188 for k, v in hub.iter_trace_propagation_headers(span):
189 if env is None: 189 ↛ 193line 189 didn't jump to line 193, because the condition on line 189 was never false
190 env = _init_argument(
191 a, kw, "env", 10, lambda x: dict(x or os.environ)
192 )
193 env["SUBPROCESS_" + k.upper().replace("-", "_")] = v
195 if cwd: 195 ↛ 198line 195 didn't jump to line 198, because the condition on line 195 was never false
196 span.set_data("subprocess.cwd", cwd)
198 rv = old_popen_init(self, *a, **kw)
200 span.set_tag("subprocess.pid", self.pid)
201 return rv
203 subprocess.Popen.__init__ = sentry_patched_popen_init # type: ignore
205 old_popen_wait = subprocess.Popen.wait
207 def sentry_patched_popen_wait(self, *a, **kw):
208 # type: (subprocess.Popen[Any], *Any, **Any) -> Any
209 hub = Hub.current
211 if hub.get_integration(StdlibIntegration) is None: 211 ↛ 212line 211 didn't jump to line 212, because the condition on line 211 was never true
212 return old_popen_wait(self, *a, **kw)
214 with hub.start_span(op="subprocess.wait") as span:
215 span.set_tag("subprocess.pid", self.pid)
216 return old_popen_wait(self, *a, **kw)
218 subprocess.Popen.wait = sentry_patched_popen_wait # type: ignore
220 old_popen_communicate = subprocess.Popen.communicate
222 def sentry_patched_popen_communicate(self, *a, **kw):
223 # type: (subprocess.Popen[Any], *Any, **Any) -> Any
224 hub = Hub.current
226 if hub.get_integration(StdlibIntegration) is None: 226 ↛ 227line 226 didn't jump to line 227, because the condition on line 226 was never true
227 return old_popen_communicate(self, *a, **kw)
229 with hub.start_span(op="subprocess.communicate") as span:
230 span.set_tag("subprocess.pid", self.pid)
231 return old_popen_communicate(self, *a, **kw)
233 subprocess.Popen.communicate = sentry_patched_popen_communicate # type: ignore
236def get_subprocess_traceparent_headers():
237 # type: () -> EnvironHeaders
238 return EnvironHeaders(os.environ, prefix="SUBPROCESS_")