cavisson-pythonagent 0.0.1__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.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. pythonagent/vendor/werkzeug/wsgi.py +1000 -0
@@ -0,0 +1,340 @@
1
+ import os
2
+ import sys
3
+ import threading
4
+ import pythonagent.agent
5
+ from pythonagent import config
6
+ from ..base import EntryPointInterceptor
7
+ from pythonagent.utils import get_current_thread_time_in_us, get_true_false, get_current_timestamp_in_us, get_validated_duration
8
+ from functools import wraps
9
+ from pythonagent.agent.probes.havoc.havoc_manager import NDHavocException
10
+
11
+
12
+ class ASGIInterceptor(EntryPointInterceptor):
13
+
14
+ def attach(self, application, wrapper_func=None, patched_method_name=None):
15
+ super(ASGIInterceptor, self).attach(application, patched_method_name='application_callable')
16
+
17
+ async def application_callable(self, application, instance, scope, receive, send):
18
+ # Pass lifespan and WebSocket scopes through without starting a BT.
19
+ # Lifespan has no path/method/headers; WebSocket BTs are not meaningful.
20
+ if scope.get("type") != "http":
21
+ return await application(instance, scope, receive, send)
22
+
23
+ bt = None
24
+ cpu_time_before = None
25
+ other_info = {}
26
+
27
+ try:
28
+ try:
29
+ bt_name = scope.get("path") + "|" + scope.get("method")
30
+ other_info["path"] = scope.get("path")
31
+ except Exception:
32
+ bt_name = "/default|GET"
33
+ other_info["path"] = "/default"
34
+
35
+ correlation_header = ""
36
+
37
+ request_headers_list = scope.get("headers")
38
+ req_headers = self.parse_headers(request_headers_list, "$", "|")
39
+ bt_header_value = self.parse_headers(request_headers_list, "=", "&")
40
+
41
+ fp_instance = None
42
+ nd_cookie_value = None
43
+ nv_cookie_value = None
44
+
45
+ nd_cookie_key = self.agent.nd_cookie_key
46
+ nv_cookie_key = self.agent.nv_cookie_key
47
+
48
+ if scope.get("headers"):
49
+ for header_key_val in scope.get("headers"):
50
+ header_key = header_key_val[0].decode()
51
+ header_val = header_key_val[1].decode()
52
+
53
+ if header_key.lower() == 'cavndfpinstance':
54
+ fp_instance = header_val
55
+
56
+ if header_key.lower() == 'cookie':
57
+ cookie_list = header_val.split("; ")
58
+ for cookie in cookie_list:
59
+ # maxsplit=1 preserves Base64 padding (=) in cookie values
60
+ cookie_key_val = cookie.split("=", 1)
61
+ if len(cookie_key_val) < 2:
62
+ continue
63
+ cookie_key = cookie_key_val[0]
64
+ cookie_val = cookie_key_val[1]
65
+ if cookie_key == nd_cookie_key:
66
+ nd_cookie_value = cookie_val
67
+ if cookie_key == nv_cookie_key:
68
+ nv_cookie_value = cookie_val
69
+
70
+ cpu_time_before = get_current_thread_time_in_us()
71
+ bt = self.start_business_transaction(bt_name, correlation_header, nd_cookie_value, nv_cookie_value, bt_header_value, fp_instance)
72
+
73
+ # Build FQM using same approach as MethodInterceptor
74
+ fqmforentry = "default.default.default"
75
+ try:
76
+ app_cls = type(instance)
77
+ if hasattr(app_cls, "__module__"): # Class Level Method
78
+ fqmforentry = app_cls.__module__ + "." + app_cls.__name__ + "." + application.__name__
79
+ else: # Module Level Method
80
+ fqmforentry = app_cls.__name__ + "." + application.__name__
81
+ except Exception as e:
82
+ self.agent.logger.debug("Could not build entry_point_fqm: {}".format(e))
83
+
84
+ self.agent.http_req_resp_wrapper(bt, req_headers, "req", 200)
85
+
86
+ except Exception as e:
87
+ self.agent.logger.exception("Exception in Start BT for ASGI {}".format(e))
88
+
89
+ # Invoking inbound havoc failure at the entry of the instrumented method
90
+ # so that the transaction fails at the start and does not continue
91
+ # processing to capture callouts or other downstream operations.
92
+ try:
93
+ context = self.agent.get_transaction_context()
94
+ btname = context.btname if context else None
95
+ self.agent.havoc_monitor.apply_inbound_service_failure(other_info.get("path"), btname)
96
+ except Exception as e:
97
+ if isinstance(e, NDHavocException):
98
+ cpu_time_after = get_current_thread_time_in_us()
99
+ cpu_time = cpu_time_after - (cpu_time_before or 0)
100
+ self.agent.set_current_status_code(503)
101
+ self.agent.end_business_transaction(bt, cpu_time)
102
+ self.agent.havoc_monitor.refresh_configs(self.agent)
103
+ raise e
104
+ else:
105
+ self.agent.logger.debug("Non-Havoc Exception {}".format(e))
106
+
107
+ other_info["cpu_time_before"] = cpu_time_before
108
+ other_info["fqmforentry"] = fqmforentry
109
+ # Store bt in other_info so send_wrapper uses the per-request bt instead of
110
+ # self.bt (which is thread-local and wrong under concurrent async requests).
111
+ other_info["bt"] = bt
112
+
113
+ before_time = get_current_timestamp_in_us()
114
+ other_info["before_time"] = before_time
115
+ if bt:
116
+ self.agent.method_entry(bt, fqmforentry, before_time)
117
+ try:
118
+ return await application(instance, scope, receive, self.wrapped_send(send, other_info))
119
+ except Exception as e:
120
+ if isinstance(e, NDHavocException):
121
+ try:
122
+ cpu_time_after = get_current_thread_time_in_us()
123
+ cpu_time = cpu_time_after - (cpu_time_before or 0)
124
+ self.agent.set_current_status_code(503)
125
+ fqmforentry = other_info.get("fqmforentry", "default.default.default")
126
+ before_time = other_info.get("before_time", 0)
127
+ after_time = get_current_timestamp_in_us()
128
+ duration = get_validated_duration(before_time, after_time, fqmforentry)
129
+ if bt:
130
+ self.agent.method_exit(bt, fqmforentry, duration, cpu_time, before_time)
131
+ self.agent.end_business_transaction(bt, cpu_time)
132
+ except Exception as cleanup_error:
133
+ self.agent.logger.exception(
134
+ "Failed to close ASGI transaction after Havoc failure: {}".format(cleanup_error)
135
+ )
136
+ raise
137
+
138
+ def wrapped_send(self, send, other_info):
139
+ @wraps(send)
140
+ async def send_wrapper(*args, **kwargs):
141
+ send_event_dict = args[0]
142
+ # Use the closed-over bt — not self.bt — to avoid thread-local races
143
+ # when multiple async requests share one OS thread.
144
+ bt = other_info.get("bt")
145
+
146
+ if send_event_dict.get('type') == 'http.response.start':
147
+ status = send_event_dict.get('status', 200)
148
+
149
+ # Inject session cookies before forwarding the response start event
150
+ try:
151
+ self._inject_cookies(bt, send_event_dict)
152
+ except Exception as e:
153
+ self.agent.logger.debug("Exception injecting ASGI cookies: {}".format(e))
154
+
155
+ resp_headers = self.parse_headers(send_event_dict.get('headers', []), "$", "|")
156
+ self.agent.http_req_resp_wrapper(bt, resp_headers, "resp", status)
157
+
158
+ try:
159
+ self.handle_http_status_code(bt, status, "")
160
+ except Exception as e:
161
+ self.agent.logger.debug("Exception in handle_http_status_code for ASGI: {}".format(e))
162
+
163
+ await send(*args, **kwargs)
164
+
165
+ if send_event_dict.get("type") == "http.response.body":
166
+ more_body = send_event_dict.get("more_body", False)
167
+ if not more_body:
168
+ try:
169
+ if bt:
170
+ cpu_time_after = get_current_thread_time_in_us()
171
+ cpu_time_before = other_info.get("cpu_time_before")
172
+
173
+ if cpu_time_before:
174
+ cpu_time = cpu_time_after - cpu_time_before
175
+ else:
176
+ cpu_time = cpu_time_after - 0
177
+
178
+ fqmforentry = other_info.get("fqmforentry", "default.default.default")
179
+ before_time = other_info.get("before_time", 0)
180
+ after_time = get_current_timestamp_in_us()
181
+ duration = get_validated_duration(before_time, after_time, fqmforentry)
182
+ self.agent.method_exit(bt, fqmforentry, duration, cpu_time, before_time)
183
+
184
+ self.agent.end_business_transaction(bt, cpu_time)
185
+ except Exception as e:
186
+ self.agent.logger.warning("Exception in End BT for ASGI {}".format(e))
187
+
188
+ return send_wrapper
189
+
190
+ def _inject_cookies(self, bt, send_event_dict):
191
+ """Mirror WSGI cookie injection: append CavNV / X-CavNV headers to
192
+ an http.response.start event dict before it is forwarded to the client."""
193
+ nd_cookie_key = self.agent.nd_cookie_key
194
+ cookie_domain = self.agent.cookie_domain
195
+ x_cav_nv = None
196
+
197
+ # Copy headers list so the original is not mutated unexpectedly
198
+ response_headers = list(send_event_dict.get('headers', []))
199
+
200
+ if self.agent.header_in_response:
201
+ x_cav_nv = get_true_false(self.agent.header_in_response)
202
+
203
+ if x_cav_nv:
204
+ nd_cookie_value = self.agent.sdk_getNDSessionCookie(bt)
205
+ if nd_cookie_value:
206
+ response_headers.append((b'x-cavnv', nd_cookie_value))
207
+
208
+ if nd_cookie_key:
209
+ if nd_cookie_value:
210
+ if cookie_domain:
211
+ nd_cookie_str = "{}={}; Path=/; Domain={}".format(
212
+ nd_cookie_key, nd_cookie_value.decode(), cookie_domain)
213
+ else:
214
+ nd_cookie_str = "{}={}; Path=/".format(
215
+ nd_cookie_key, nd_cookie_value.decode())
216
+ response_headers.append((b'set-cookie', nd_cookie_str.encode()))
217
+ else:
218
+ if self.agent.disable_nd_nv:
219
+ if cookie_domain:
220
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/; Domain={}".format(cookie_domain)
221
+ else:
222
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/"
223
+ response_headers.append((b'set-cookie', nd_cookie_str.encode()))
224
+ self.agent.disable_nd_nv = False
225
+ else:
226
+ if nd_cookie_key:
227
+ nd_cookie_value = self.agent.sdk_getNDSessionCookie(bt)
228
+ if nd_cookie_value:
229
+ if cookie_domain:
230
+ nd_cookie_str = "{}={}; Path=/; Domain={}".format(
231
+ nd_cookie_key, nd_cookie_value.decode(), cookie_domain)
232
+ else:
233
+ nd_cookie_str = "{}={}; Path=/".format(
234
+ nd_cookie_key, nd_cookie_value.decode())
235
+ response_headers.append((b'set-cookie', nd_cookie_str.encode()))
236
+ else:
237
+ if self.agent.disable_nd_nv:
238
+ if cookie_domain:
239
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/; Domain={}".format(cookie_domain)
240
+ else:
241
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/"
242
+ response_headers.append((b'set-cookie', nd_cookie_str.encode()))
243
+ self.agent.disable_nd_nv = False
244
+
245
+ send_event_dict['headers'] = response_headers
246
+
247
+ def parse_headers(self, input_list, key_val_sep, item_value_sep):
248
+ output_str = ""
249
+ if input_list:
250
+ for key, value in input_list:
251
+ key = key.decode()
252
+ value = value.decode()
253
+ value = value.replace("|", "%7C") # CavSF has | in value
254
+ value = value.replace(":", "%3A") # Host as : in value
255
+ value = value.replace("=", "%3D") # Accept, Accept-Language, and ND-NV cookies have = in value
256
+ new_str = key + key_val_sep + value + item_value_sep
257
+ output_str += new_str
258
+ output_str = output_str[:-1] # Remove last |
259
+ return output_str
260
+
261
+
262
+ class ASGIMiddleware:
263
+ """Generic ASGI middleware that instruments any ASGI callable for transaction capture.
264
+
265
+ Analogous to WSGIMiddleware in wsgi.py. Supports two usage modes:
266
+ 1. Wrap an existing app directly: ASGIMiddleware(app)
267
+ 2. Load app from config: set ASGI_MODULE env var, use module-level `application` singleton
268
+ """
269
+
270
+ def __init__(self, application=None):
271
+ self._application = application
272
+ self._interceptor = None # lazy — created on first request to avoid circular import
273
+ self._load_application_lock = threading.Lock()
274
+
275
+ def _load_application(self):
276
+ asgi_callable = config.ASGI_CALLABLE_OBJECT or 'application'
277
+ module_str = os.environ.get('ASGI_MODULE') or config.ASGI_MODULE
278
+
279
+ if not module_str:
280
+ raise AttributeError(
281
+ 'Cannot load ASGI application: set ASGI_MODULE '
282
+ '(e.g. myapp.asgi:application).')
283
+
284
+ if ':' in module_str:
285
+ module_str, asgi_callable = module_str.split(':', 1)
286
+
287
+ __import__(module_str)
288
+ asgi_module = sys.modules[module_str]
289
+
290
+ if asgi_callable.endswith('()'):
291
+ app = getattr(asgi_module, asgi_callable[:-2])()
292
+ else:
293
+ app = getattr(asgi_module, asgi_callable)
294
+
295
+ self._application = app
296
+
297
+ async def asgi_application(self, scope, receive, send):
298
+ return await self._application(scope, receive, send)
299
+
300
+ async def __call__(self, scope, receive, send):
301
+ if self._interceptor is None:
302
+ self._interceptor = ASGIInterceptor(pythonagent.agent.get_agent_instance(), None)
303
+ if not self._application:
304
+ with self._load_application_lock:
305
+ if not self._application:
306
+ self._load_application()
307
+
308
+ return await self._interceptor.application_callable(
309
+ ASGIMiddleware.asgi_application, self, scope, receive, send
310
+ )
311
+
312
+
313
+ # Module-level singleton — allows: uvicorn pythonagent.agent.probes.frameworks.asgi:application
314
+ application = ASGIMiddleware()
315
+
316
+
317
+ def intercept_uvicorn_config(agent, mod):
318
+ """Patch uvicorn Config.load to auto-wrap any ASGI app with our interceptor.
319
+
320
+ Fires when uvicorn.config is imported. After Config.load() runs, self.loaded_app
321
+ is the real ASGI callable. We wrap it with ASGIMiddleware so all HTTP requests
322
+ go through our instrumentation regardless of which ASGI framework is used.
323
+
324
+ Frameworks already instrumented at method level (Django ASGI, FastAPI/Starlette)
325
+ have _cav_asgi_patched set on their base class and are skipped to avoid double BTs.
326
+ """
327
+ original_load = mod.Config.load
328
+
329
+ def patched_load(self):
330
+ original_load(self)
331
+ loaded = getattr(self, 'loaded_app', None)
332
+ if loaded is None or isinstance(loaded, ASGIMiddleware):
333
+ return
334
+ # Skip if the app's class hierarchy has already been instrumented at method level.
335
+ for klass in type(loaded).__mro__:
336
+ if klass.__dict__.get('_cav_asgi_patched'):
337
+ return
338
+ self.loaded_app = ASGIMiddleware(loaded)
339
+
340
+ mod.Config.load = patched_load
@@ -0,0 +1,27 @@
1
+ """Interceptor for Bottle.
2
+
3
+ """
4
+
5
+ import sys
6
+
7
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
8
+ from pythonagent.agent.probes.base import BaseInterceptor
9
+
10
+
11
+ class BottleInterceptor(BaseInterceptor):
12
+ def add_exception(self, func, *args, **kwargs):
13
+ self.agent.logger.info('Modulename: BottleInterceptor class')
14
+ with self.log_exceptions():
15
+ bt = self.bt
16
+ self.agent.logger.info("Modulename: BottleInterceptor class inside add_exception function bt value is :{0}".format(bt))
17
+ if bt:
18
+ bt.add_exception(*sys.exc_info())
19
+ return func(*args, **kwargs)
20
+
21
+
22
+
23
+ def intercept_bottle(agent, mod):
24
+ agent.logger.warning("Instrument module: bottle{}".format(", mod: {}".format(mod) if mod else ""))
25
+ WSGIInterceptor(agent, mod.Bottle).attach('__call__')
26
+ #print("framework.bottle.intercept_bottle: intercepting bottle ......")
27
+ BottleInterceptor(agent, mod.HTTPError).attach('__init__', patched_method_name='add_exception')
@@ -0,0 +1,25 @@
1
+ """Interceptor for Cherry framework.
2
+
3
+ """
4
+ from __future__ import unicode_literals
5
+ import sys
6
+
7
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
8
+ from pythonagent.agent.probes.base import BaseInterceptor
9
+
10
+ class CherrypyExceptionAdder(BaseInterceptor):
11
+ def add_exception(self, func, *args, **kwargs):
12
+ with self.log_exceptions():
13
+ bt = self.bt
14
+ self.agent.logger.info("Modulename: cherry exception class || bt value is {0} :".format(bt))
15
+ if bt:
16
+ bt.add_exception(*sys.exc_info())
17
+ self.agent.logger.debug("Exception occured as !!! {0}".format(sys.exc_info()))
18
+ return func(*args, **kwargs)
19
+
20
+
21
+ def intercept_cherrypy(agent, mod):
22
+ agent.logger.warning("Instrument module: cherrypy{}".format(", mod: {}".format(mod) if mod else ""))
23
+ WSGIInterceptor(agent, mod.Application).attach('__call__')
24
+ CherrypyExceptionAdder(agent, mod._cprequest.Request).attach('handle_error', patched_method_name='add_exception')
25
+ CherrypyExceptionAdder(agent, mod.HTTPError).attach('set_response', patched_method_name='add_exception')
@@ -0,0 +1,128 @@
1
+ """Interceptor for Django.
2
+
3
+ """
4
+
5
+
6
+ from __future__ import unicode_literals
7
+ import sys
8
+
9
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
10
+ from pythonagent.agent.probes.frameworks.asgi import ASGIInterceptor
11
+ from pythonagent.agent.probes.base import BaseInterceptor
12
+ import logging
13
+ #from django.conf import settings
14
+
15
+
16
+
17
+ def add_exception(interceptor, exc_info):
18
+ with interceptor.log_exceptions():
19
+ bt = interceptor.bt
20
+ if bt:
21
+ bt.add_exception(*exc_info)
22
+
23
+ #from django.utils.deprecation import MiddlewareMixin
24
+
25
+
26
+
27
+
28
+ class DjangoBaseHandlerInterceptor(BaseInterceptor):
29
+ def _load_middleware(self, load_middleware, base_handler, **kwargs):
30
+ self.agent.logger.info("Loading middleware of django !")
31
+ self.agent.logger.info('Modulename: DjangoBaseHandlerInterceptor class inside _load_middleware function : loading middleware of django !')
32
+ #settings.MIDDLEWARE.append('agent.my_middleware.CustomMiddleware')
33
+ load_middleware(base_handler, **kwargs)
34
+
35
+ if hasattr(base_handler, '_exception_middleware'):
36
+ base_handler._exception_middleware.insert(0, pythonagentDjangoMiddleware(self).process_exception)
37
+ #base_handler._request_middleware.insert(0, pythonagentDjangoMiddleware(self).process_request)
38
+ #base_handler._response_middleware.insert(0, pythonagentDjangoMiddleware(self).process_response)
39
+ #base_handler._template_response_middleware.insert(0, pythonagentDjangoMiddleware(self).process_template_response)
40
+ #base_handler._view_middleware.insert(0, pythonagentDjangoMiddleware(self).process_view)
41
+
42
+
43
+
44
+ def _handle_uncaught_exception(self, handle_uncaught_exception, base_handler, request, resolver, exc_info):
45
+ add_exception(self, exc_info)
46
+ return handle_uncaught_exception(base_handler, request, resolver, exc_info)
47
+
48
+
49
+ class DjangoExceptionInterceptor(BaseInterceptor):
50
+ def _handle_uncaught_exception(self, handle_uncaught_exception, request, resolver, exc_info):
51
+ add_exception(self, exc_info)
52
+ return handle_uncaught_exception(request, resolver, exc_info)
53
+
54
+
55
+
56
+
57
+ class pythonagentDjangoMiddleware(object):
58
+ def __init__(self, interceptor):
59
+ self.interceptor = interceptor
60
+ self.logger = logging.getLogger('pythonagent.agent')
61
+
62
+ def process_exception(self, request, exception):
63
+ add_exception(self.interceptor, sys.exc_info())
64
+ #print("Django application process_request called and request as= ", request)
65
+
66
+
67
+ def intercept_django_wsgi_handler(agent, mod):
68
+ agent.logger.warning("Instrument module: django.core.handlers.wsgi{}".format(", mod: {}".format(mod) if mod else ""))
69
+ WSGIInterceptor(agent, mod.WSGIHandler).attach('__call__')
70
+
71
+
72
+ def intercept_django_asgi_handler(agent, mod):
73
+ agent.logger.warning("Instrument module: django.core.handlers.asgi{}".format(", mod: {}".format(mod) if mod else ""))
74
+ # Patch base class handle()
75
+ ASGIInterceptor(agent, mod.ASGIHandler).attach('handle')
76
+
77
+ # Patch a concrete subclass if it overrides handle or __call__ in its own __dict__.
78
+ # This is needed because Python MRO resolves the subclass method first, bypassing
79
+ # any patch we placed on the base class (e.g. Saleor's PatchedASGIHandler overrides
80
+ # handle() to fix a Django memory leak and its own handle shadows our base-class patch).
81
+ def _patch_subclass(cls):
82
+ if 'handle' in cls.__dict__:
83
+ ASGIInterceptor(agent, cls).attach('handle')
84
+ if '__call__' in cls.__dict__:
85
+ ASGIInterceptor(agent, cls).attach('__call__')
86
+
87
+ # Patch any ASGIHandler subclasses already defined when the hook fires
88
+ # (covers the late-bootstrap case where sys.modules already has the module).
89
+ queue = list(mod.ASGIHandler.__subclasses__())
90
+ while queue:
91
+ subclass = queue.pop(0)
92
+ _patch_subclass(subclass)
93
+ queue.extend(subclass.__subclasses__())
94
+
95
+ # Install __init_subclass__ hook so subclasses defined AFTER this hook fires
96
+ # (the common case: Saleor imports django.core.handlers.asgi first, then
97
+ # defines PatchedASGIHandler) are automatically patched at class-definition time.
98
+ _original_init_subclass = mod.ASGIHandler.__dict__.get('__init_subclass__', None)
99
+
100
+ @classmethod
101
+ def _auto_patch_subclass(cls, **kwargs):
102
+ if _original_init_subclass is not None:
103
+ _original_init_subclass.__func__(cls, **kwargs)
104
+ else:
105
+ super(mod.ASGIHandler, cls).__init_subclass__(**kwargs)
106
+ _patch_subclass(cls)
107
+
108
+ mod.ASGIHandler.__init_subclass__ = _auto_patch_subclass
109
+
110
+ # Prevent the generic uvicorn.config hook from double-wrapping Django ASGI apps.
111
+ # The hook checks _cav_asgi_patched on the class MRO before adding ASGIMiddleware.
112
+ mod.ASGIHandler._cav_asgi_patched = True
113
+
114
+
115
+ def intercept_django_base_handler(agent, mod):
116
+ agent.logger.warning("Instrument module: django.core.handlers.base{}".format(", mod: {}".format(mod) if mod else ""))
117
+ base_handler_methods = ['load_middleware']
118
+
119
+ try:
120
+ import django.core.handlers.exception
121
+ if hasattr(django.core.handlers.exception, 'handle_uncaught_exception'):
122
+ DjangoExceptionInterceptor(agent, django.core.handlers.exception).attach('handle_uncaught_exception')
123
+ else:
124
+ base_handler_methods.append('handle_uncaught_exception')
125
+ except ImportError:
126
+ base_handler_methods.append('handle_uncaught_exception')
127
+
128
+ DjangoBaseHandlerInterceptor(agent, mod.BaseHandler).attach(base_handler_methods)
@@ -0,0 +1,21 @@
1
+
2
+ from __future__ import unicode_literals
3
+ import sys
4
+
5
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
6
+ from pythonagent.agent.probes.base import BaseInterceptor
7
+
8
+ class FalconExceptionAdder(BaseInterceptor):
9
+ def add_exception(self, func, *args, **kwargs):
10
+ with self.log_exceptions():
11
+ bt = self.bt
12
+ if bt:
13
+ bt.add_exception(*sys.exc_info())
14
+ return func(*args, **kwargs)
15
+
16
+
17
+ def intercept_falcon(agent, mod):
18
+ agent.logger.warning("Instrument module: falcon{}".format(", mod: {}".format(mod) if mod else ""))
19
+ WSGIInterceptor(agent, mod.App).attach('__call__')
20
+ FalconExceptionAdder(agent, mod.App).attach('_handle_exception', patched_method_name='add_exception')
21
+ #FalconExceptionAdder(agent, mod.Request).attach('set_response', patched_method_name='add_exception')
@@ -0,0 +1,35 @@
1
+
2
+ """Interceptor for FastApi framework.
3
+
4
+ """
5
+
6
+ from __future__ import unicode_literals
7
+ import sys
8
+
9
+ from pythonagent.agent.probes.frameworks.asgi import ASGIInterceptor
10
+ from pythonagent.agent.probes.base import BaseInterceptor
11
+
12
+
13
+ class FastapiInterceptor(BaseInterceptor):
14
+ def _run_endpoint_function(self, run_endpoint_function):
15
+ with self.log_exceptions():
16
+ bt = self.bt
17
+ # self.agent.logger.info("Modulename: FlaskInterceptor class || bt value is {0} :".format(bt))
18
+ if bt:
19
+ pass
20
+ return run_endpoint_function
21
+
22
+ def intercept_fastapi_asgi_handler(agent, mod):
23
+ agent.logger.warning("Instrument module: starlette.routing{}".format(", mod: {}".format(mod) if mod else ""))
24
+ ASGIInterceptor(agent, mod.Router).attach('__call__')
25
+ #ASGIInterceptor(agent, mod.Router).attach('app')
26
+
27
+ # Prevent the generic uvicorn.config hook from double-wrapping FastAPI/Starlette apps.
28
+ mod.Router._cav_asgi_patched = True
29
+ try:
30
+ import starlette.applications
31
+ starlette.applications.Starlette._cav_asgi_patched = True
32
+ except Exception:
33
+ pass
34
+
35
+
@@ -0,0 +1,30 @@
1
+
2
+ """Interceptor for Flask framework.
3
+
4
+ """
5
+
6
+ from __future__ import unicode_literals
7
+ import sys
8
+
9
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
10
+ from pythonagent.agent.probes.base import BaseInterceptor
11
+
12
+
13
+ class FlaskInterceptor(BaseInterceptor):
14
+ def _handle_user_exception(self, handle_user_exception, flask, e):
15
+ self.agent.logger.info('Modulename: FlaskInterceptor class')
16
+ with self.log_exceptions():
17
+ bt = self.bt
18
+ self.agent.logger.info("Modulename: FlaskInterceptor class || bt value is {0} :".format(bt))
19
+ if bt:
20
+ #bt.add_exception(*sys.exc_info())
21
+ self.agent.logger.debug("Exception occured as !!! {0}".format(sys.exc_info()))
22
+ #print('returning from _handle method !!')
23
+ return handle_user_exception(flask, e)
24
+
25
+
26
+ def intercept_flask(agent, mod):
27
+ #print('=============inside flask instrumentation==========')
28
+ agent.logger.warning("Instrument module: flask{}".format(", mod: {}".format(mod) if mod else ""))
29
+ WSGIInterceptor(agent, mod.Flask).attach('wsgi_app')
30
+ FlaskInterceptor(agent, mod.Flask).attach('handle_user_exception')
@@ -0,0 +1,56 @@
1
+ """Interceptor for Pyramid.
2
+
3
+ """
4
+
5
+
6
+ from __future__ import unicode_literals
7
+ import sys
8
+
9
+ from pythonagent.agent.probes.frameworks.wsgi import WSGIInterceptor
10
+ from pythonagent.agent.probes.base import BaseInterceptor
11
+
12
+ class PyramidRouterInterceptor(BaseInterceptor):
13
+ """
14
+ Pyramid's interceptor class
15
+ """
16
+ def _handle_request(self, handle_request, router_instance, req):
17
+ """
18
+
19
+ Parameters
20
+ ----------
21
+ handle_request: original method pyramid.router.Router.handle_request
22
+ that is being overridden
23
+ router_instance: pyramid.router.Router instance
24
+ req: request object
25
+
26
+ Returns
27
+ -------
28
+ Whatever pyramid.router.Router.handle_request returns
29
+
30
+ """
31
+ try:
32
+ return handle_request(router_instance, req)
33
+ except Exception:
34
+ with self.log_exceptions():
35
+ bt = self.bt
36
+ if bt:
37
+ bt.add_exception(*sys.exc_info())
38
+ raise
39
+
40
+
41
+ def intercept_pyramid(agent, mod):
42
+ """
43
+
44
+ Parameters
45
+ ----------
46
+
47
+ mod: pyramid.router module
48
+
49
+ Returns
50
+ -------
51
+ None
52
+ """
53
+ #mod = '/home/cavisson/shop/my-shop/env/env/lib/python3.7/site-packages/pyramid/router.py'
54
+ agent.logger.warning("Instrument module: pyramid{}".format(", mod: {}".format(mod) if mod else ""))
55
+ WSGIInterceptor(agent, mod.Router).attach('__call__')
56
+ PyramidRouterInterceptor(agent, mod.Router).attach('handle_request')