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,353 @@
1
+ """Interceptors and utilities for dealing with WSGI-based apps/frameworks"""
2
+ import os
3
+ import sys
4
+ import pstats
5
+ from sys import settrace
6
+ from datetime import datetime, timedelta
7
+ import importlib.util as _importlib_util
8
+ import inspect
9
+ from functools import wraps
10
+ import threading
11
+ import pythonagent.agent
12
+ from ..base import EntryPointInterceptor
13
+ #from lib import LazyWsgiRequest
14
+ from pythonagent import config
15
+ ENTRY_WSGI =8
16
+ #from agent.internal.proxy import *
17
+ from pyclbr import readmodule
18
+ import os
19
+ import inspect
20
+ from pythonagent.utils import get_true_false
21
+ import cProfile
22
+ import pstats
23
+ from pythonagent.agent.probes.frameworks.test import dumpstacks
24
+ from pythonagent.agent.probes.havoc.havoc_manager import NDHavocException
25
+ #from pythonagent.agent.probes.frameworks.aisess import Aisession
26
+ #import AIsession
27
+ #import aisess
28
+ #aisess
29
+ #import importlib.util
30
+ #spec = importlib.util.spec_from_file_location("aisess", "/home/cavisson/my-shop/env/env/lib/python3.7/site-packages/pythonagent/agent/probes/frameworks/aisess.py")
31
+ #foo = importlib.util.module_from_spec(spec)
32
+ #spec.loader.exec_module(foo)
33
+ from pythonagent.utils import get_current_thread_time_in_us, get_current_timestamp_in_us, get_validated_duration
34
+ #os.environ['Aisession_flag'] = 'True'
35
+
36
+ #profiler = None
37
+ def aiflagset(s,st,d,sname):
38
+ os.environ['Aisession_flag'] = 'True'
39
+ global duration
40
+ duration = d
41
+ global starttime
42
+ starttime = st
43
+ global istart
44
+ istart = s
45
+ global sess
46
+ sess = sname
47
+
48
+ class WSGIInterceptor(EntryPointInterceptor):
49
+
50
+ def attach(self, application):
51
+ super(WSGIInterceptor, self).attach(application, patched_method_name='application_callable')
52
+
53
+ def application_callable(self, application, instance, environ, start_response):
54
+ path = environ.get('PATH_INFO', '/')
55
+ query = environ.get('QUERY_STRING', '')
56
+ method = environ.get('REQUEST_METHOD', 'GET')
57
+ url = path + ('?' + query if query else '')
58
+
59
+ raw_headers = [
60
+ (k[5:].lower().replace('_', '-'), v)
61
+ for k, v in environ.items() if k.startswith('HTTP_')
62
+ ]
63
+ req_headers = self.parse_headers(raw_headers, "$", "|")
64
+ bt_header_value = self.parse_headers(raw_headers, "=", "&")
65
+
66
+ fp_instance = environ.get('HTTP_CAVNDFPINSTANCE')
67
+
68
+ nd_cookie_key = self.agent.nd_cookie_key
69
+ nv_cookie_key = self.agent.nv_cookie_key
70
+
71
+ nd_cookie_value = None
72
+ nv_cookie_value = None
73
+
74
+ cookie_str = environ.get('HTTP_COOKIE', '')
75
+ for cookie in cookie_str.split('; '):
76
+ parts = cookie.split('=', 1)
77
+ if len(parts) == 2:
78
+ k, v = parts
79
+ k = k.strip()
80
+ if k == nd_cookie_key:
81
+ nd_cookie_value = v
82
+ if k == nv_cookie_key:
83
+ nv_cookie_value = v
84
+
85
+ bt_name = url + "|" + method
86
+ correlation_header = ""
87
+ cpu_time_before = get_current_thread_time_in_us()
88
+
89
+ bt = self.start_business_transaction(bt_name, correlation_header, nd_cookie_value, nv_cookie_value, bt_header_value, fp_instance)
90
+
91
+ # Build FQM using same approach as MethodInterceptor
92
+ fqmforentry = "default.default.default"
93
+ try:
94
+ app_cls = type(instance)
95
+ if hasattr(app_cls, "__module__"): # Class Level Method
96
+ fqmforentry = app_cls.__module__ + "." + app_cls.__name__ + "." + application.__name__
97
+ else: # Module Level Method
98
+ fqmforentry = app_cls.__name__ + "." + application.__name__
99
+ except Exception as e:
100
+ self.agent.logger.debug("Could not build entry_point_fqm: {}".format(e))
101
+
102
+ self.agent.http_req_resp_wrapper(bt, req_headers, "req", 200)
103
+
104
+ # Invoking inbound havoc failure at the entry of the instrumented method
105
+ # so that the transaction fails at the start and does not continue
106
+ # processing to capture callouts or other downstream operations.
107
+ try:
108
+ context = self.agent.get_transaction_context()
109
+ btname = context.btname if context else None
110
+ self.agent.havoc_monitor.apply_inbound_service_failure(url, btname)
111
+ except Exception as e:
112
+ if isinstance(e, NDHavocException):
113
+ self.agent.set_current_status_code(503)
114
+ cpu_time_after = get_current_thread_time_in_us()
115
+ cpu_time = cpu_time_after - cpu_time_before
116
+ rc = self.agent.end_business_transaction(bt, cpu_time)
117
+ raise e
118
+ else:
119
+ self.agent.logger.debug("Non-Havoc Exception {}".format(e))
120
+
121
+ self.agent.logger.debug("Modulename: WSGI interceptor class || bt value is :{0}".format(bt))
122
+
123
+ try:
124
+ self.agent.logger.info("Modulename: WSGI interceptor class || request is :{0} || request path is {1} || query_string: {2} || host: {3} ".format(request,(request.path,request.query_string,request.host)))
125
+ except:
126
+ pass
127
+
128
+ before_time = get_current_timestamp_in_us()
129
+ if bt:
130
+ self.agent.method_entry(bt, fqmforentry, before_time)
131
+
132
+ try:
133
+ response = application(instance, environ, self._make_start_response_wrapper(start_response))
134
+ self.agent.logger.info("Modulename: WSGI interceptor class || response is :{0}".format(response))
135
+
136
+ except:
137
+ with self.log_exceptions():
138
+ if bt:
139
+ bt.add_exception(*sys.exc_info())
140
+
141
+ raise
142
+
143
+ finally:
144
+ cpu_time_after = get_current_thread_time_in_us()
145
+ cpu_time = cpu_time_after - cpu_time_before
146
+ after_time = get_current_timestamp_in_us()
147
+ duration = get_validated_duration(before_time, after_time, fqmforentry)
148
+ if bt:
149
+ self.agent.method_exit(bt, fqmforentry, duration, cpu_time, before_time)
150
+ self.end_business_transaction(bt, cpu_time)
151
+
152
+ return response
153
+
154
+ def _make_start_response_wrapper(self, start_response):
155
+ @wraps(start_response)
156
+ def start_response_wrapper(status, headers, exc_info=None):
157
+ #Deal with HTTP status codes, errors and EUM correlation.
158
+ #See https://www.python.org/dev/peps/pep-0333/#the-start-response-callable for more information.
159
+ with self.log_exceptions():
160
+ bt = self.bt
161
+
162
+ nd_cookie_key = self.agent.nd_cookie_key
163
+ cookie_domain = self.agent.cookie_domain
164
+ x_cav_nv = None
165
+ x_nd_cookie_value = None
166
+ x_cav_nv_headers = None
167
+
168
+ if self.agent.cookie_method_position:
169
+ r_commit, m_entry, m_exit = self.decode_cookie_method_position(self.agent.cookie_method_position)
170
+
171
+ if self.agent.header_in_response:
172
+ x_cav_nv = get_true_false(self.agent.header_in_response)
173
+
174
+ if x_cav_nv:
175
+ nd_cookie_value = self.agent.sdk_getNDSessionCookie(bt)
176
+ x_nd_cookie_value = nd_cookie_value
177
+ if x_nd_cookie_value:
178
+ headers.append(('X-CavNV', x_nd_cookie_value.decode()))
179
+ x_cav_nv_headers = "|X-CavNV$" + x_nd_cookie_value.decode()
180
+
181
+ if nd_cookie_key: # X-CavNV Enabled and CavNV Enabled
182
+ if nd_cookie_value:
183
+ if cookie_domain:
184
+ nd_cookie_str = "{}={}; Path=/; Domain={}".format(nd_cookie_key, nd_cookie_value.decode(), cookie_domain)
185
+ else:
186
+ nd_cookie_str = "{}={}; Path=/".format(nd_cookie_key, nd_cookie_value.decode())
187
+
188
+ headers.append(('Set-Cookie', nd_cookie_str))
189
+ else: # X-CavNV Enabled and CavNV Disabled
190
+ if self.agent.disable_nd_nv:
191
+ if cookie_domain:
192
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/; Domain={}".format(cookie_domain)
193
+ else:
194
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/"
195
+ headers.append(('Set-Cookie', nd_cookie_str))
196
+ self.agent.disable_nd_nv = False
197
+
198
+ else:
199
+ if nd_cookie_key: # X-CavNV Disabled and CavNV Enabled
200
+ nd_cookie_value = self.agent.sdk_getNDSessionCookie(bt)
201
+ if nd_cookie_value:
202
+ if cookie_domain:
203
+ nd_cookie_str = "{}={}; Path=/; Domain={}".format(nd_cookie_key, nd_cookie_value.decode(), cookie_domain)
204
+ else:
205
+ nd_cookie_str = "{}={}; Path=/".format(nd_cookie_key, nd_cookie_value.decode())
206
+
207
+ headers.append(('Set-Cookie', nd_cookie_str))
208
+ else: # X-CavNV Disabled and CavNV Disabled
209
+ if self.agent.disable_nd_nv:
210
+ if cookie_domain:
211
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/; Domain={}".format(cookie_domain)
212
+ else:
213
+ nd_cookie_str = "CavNV=None; Max-Age=0; Path=/"
214
+
215
+ headers.append(('Set-Cookie', nd_cookie_str))
216
+ self.agent.disable_nd_nv = False
217
+
218
+ resp_headers = self.parse_headers(headers.copy(), "$", "|")
219
+
220
+ # if x_cav_nv_headers:
221
+ # resp_headers = resp_headers + x_cav_nv_headers
222
+
223
+ #traceparent_bytes, tracestate_bytes = self.agent.get_trace_headers(bt)
224
+ #telemetry_headers = "|Traceparent$" + traceparent_bytes.decode() + "|Tracestate$" + tracestate_bytes.decode()
225
+ #resp_headers = resp_headers + telemetry_headers
226
+
227
+ self.agent.http_req_resp_wrapper(bt, resp_headers, "resp", 200)
228
+ self.agent.logger.info("Modulename: WSGI interceptor class || bt value is :{0}".format(bt))
229
+
230
+ if bt:
231
+ # Store the HTTP status code and deal with errors.
232
+ status_code, msg = status.split(' ', 1)
233
+ self.handle_http_status_code(bt, int(status_code), msg)
234
+
235
+ # Inject EUM metadata into the response headers.
236
+ # inject_eum_metadata(self.agent.eum_config, bt, headers)
237
+
238
+ return start_response(status, headers, exc_info)
239
+
240
+ return start_response_wrapper
241
+
242
+ def parse_headers(self, input_list, key_val_sep, item_value_sep):
243
+ output_str = ""
244
+ for key, value in input_list:
245
+ value = value.replace("|", "%7C") # CavSF has | in value
246
+ value = value.replace(":", "%3A") # Host as : in value
247
+ value = value.replace("=", "%3D") # Accept, Accept-Language, and ND-NV cookies have = in value
248
+ new_str = key + key_val_sep + value + item_value_sep
249
+ output_str += new_str
250
+ output_str = output_str[:-1] # Remove last |
251
+ # output_str = output_str.replace("&", "|")
252
+
253
+ return output_str
254
+
255
+ def parse_bt_headers(self, input_list):
256
+ output_str = ""
257
+ for key, value in input_list:
258
+ new_str = key + "=" + value + "&"
259
+ output_str += new_str
260
+ output_str = output_str[:-1] # Remove last |
261
+ # output_str = output_str.replace("&", "|")
262
+
263
+ return output_str
264
+
265
+ def parse_cookie_method_position(self, mpos):
266
+ mpos_bin = format(mpos, '04b')
267
+ return (get_true_false(mpos_bin[0]), get_true_false(mpos_bin[1]),
268
+ get_true_false(mpos_bin[2]), get_true_false(mpos_bin[3]))
269
+
270
+ def decode_cookie_method_position(self, mpos):
271
+ mpos_bin = format(mpos, '03b')
272
+ return get_true_false(mpos_bin[0]), get_true_false(mpos_bin[1]), get_true_false(mpos_bin[2])
273
+
274
+
275
+ class WSGIMiddleware(object):
276
+ #self.agent.logger.info('Modulename: WSGIMiddleware class ')
277
+
278
+ def __init__(self, application=None):
279
+ self._application = application
280
+ self._configured = False
281
+ self._interceptor = WSGIInterceptor(pythonagent.agent.get_agent_instance(), None)
282
+ self._load_application_lock = threading.Lock()
283
+ #get_script()
284
+ #from pythonagent import config
285
+ #config.WSGI_SCRIPT_ALIAS = input("Enter the absolute path of WSGI application script. (command to get 'readlink -f <script_path.py>') : ")
286
+
287
+ def load_application(self):
288
+ wsgi_callable = config.WSGI_CALLABLE_OBJECT or 'application'
289
+
290
+ # Fix 2: honour CAV_ prefix first, fall back to legacy unprefixed name for compatibility
291
+ config.WSGI_SCRIPT_ALIAS = (
292
+ os.environ.get('CAV_WSGI_SCRIPT_ALIAS') or
293
+ os.environ.get('WSGI_SCRIPT_ALIAS') or
294
+ config.WSGI_SCRIPT_ALIAS
295
+ )
296
+
297
+ if not config.WSGI_SCRIPT_ALIAS and not config.WSGI_MODULE:
298
+ raise AttributeError(
299
+ 'Cannot get WSGI application: the agent cannot load your '
300
+ 'application. You must set CAV_WSGI_MODULE or '
301
+ 'CAV_WSGI_SCRIPT_ALIAS in order to load your application.')
302
+
303
+ if config.WSGI_MODULE:
304
+ module_name = config.WSGI_MODULE
305
+
306
+ if ':' in module_name:
307
+ module_name, wsgi_callable = module_name.split(':', 1)
308
+
309
+ __import__(module_name)
310
+ # __import__('a.b') returns the top-level package 'a', not 'a.b'.
311
+ # sys.modules lookup always gives the exact sub-module requested.
312
+ wsgi_module = sys.modules[module_name]
313
+ else:
314
+ # Fix 1: imp.load_source was removed in Python 3.12; use importlib.util instead.
315
+ spec = _importlib_util.spec_from_file_location('wsgi_module', config.WSGI_SCRIPT_ALIAS)
316
+ wsgi_module = _importlib_util.module_from_spec(spec)
317
+ sys.modules['wsgi_module'] = wsgi_module
318
+ spec.loader.exec_module(wsgi_module)
319
+
320
+ if wsgi_callable.endswith('()'): # factory callable e.g. "create_app()"
321
+ app = getattr(wsgi_module, wsgi_callable[:-2])
322
+ app = app()
323
+ else:
324
+ app = getattr(wsgi_module, wsgi_callable)
325
+
326
+ self._application = app
327
+
328
+ def wsgi_application(self, environ, start_response):
329
+ return self._application(environ, start_response)
330
+
331
+ def __call__(self, environ, start_response):
332
+ #config.WSGI_SCRIPT_ALIAS = input("Enter the absolute path of WSGI application script. (command to get 'readlink -f <script_path.py>') : ")
333
+ if not self._configured:
334
+ # Approach 7: translate UWSGI_* env vars to CAV_* before configure() reads them
335
+ config.map_uwsgi_environ()
336
+ pythonagent.agent.configure(environ)
337
+ self._configured = True
338
+
339
+ if not self._application:
340
+ # CORE-60212 - Double-checked locking: only the first burst of
341
+ # concurrent requests enters load_application(); all subsequent
342
+ # calls skip straight through once _application is set.
343
+ with self._load_application_lock:
344
+ if not self._application:
345
+ self.load_application()
346
+
347
+ # The interceptor expects an unbound function to call, hence why this function is called like this.
348
+ return self._interceptor.application_callable(WSGIMiddleware.wsgi_application, self, environ, start_response)
349
+
350
+ def get_script():
351
+ config.WSGI_SCRIPT_ALIAS = input("Enter the absolute path of WSGI application script. (command to get 'readlink -f <script_path.py>') : ")
352
+
353
+
@@ -0,0 +1,76 @@
1
+ """gRPC client + server instrumentation entry point.
2
+
3
+ grpc.insecure_channel/secure_channel/server are bare module-level factory
4
+ functions, not class methods, so BaseInterceptor.attach() (which requires
5
+ self.cls.__mro__) does not apply here - these are patched directly with
6
+ plain setattr-based wrapping instead, following the same intent as attach()
7
+ (preserve original callable, guard against double-patching) without reusing
8
+ machinery that only works against classes.
9
+ """
10
+
11
+ import inspect
12
+ from functools import wraps
13
+
14
+ __all__ = ['intercept_grpc']
15
+
16
+
17
+ def _bind_args(func, args, kwargs):
18
+ bound = inspect.signature(func).bind(*args, **kwargs)
19
+ bound.apply_defaults()
20
+ return bound
21
+
22
+
23
+ def _wrap_channel_factory(agent, mod, factory_name):
24
+ from .client_interceptor import NDGrpcClientInterceptor
25
+
26
+ real_factory = getattr(mod, factory_name)
27
+
28
+ @wraps(real_factory)
29
+ def wrapper(*args, **kwargs):
30
+ bound = _bind_args(real_factory, args, kwargs)
31
+ target = bound.arguments.get('target')
32
+ channel = real_factory(*args, **kwargs)
33
+ try:
34
+ interceptor = NDGrpcClientInterceptor(agent, target, "grpc." + factory_name)
35
+ channel = mod.intercept_channel(channel, interceptor)
36
+ except Exception:
37
+ agent.logger.exception("Error registering gRPC client interceptor for target %s", target)
38
+ return channel
39
+
40
+ wrapper._pythonagent_intercepted = True
41
+ setattr(mod, factory_name, wrapper)
42
+
43
+
44
+ def _wrap_server_factory(agent, mod):
45
+ from .server_interceptor import NDGrpcServerInterceptor
46
+
47
+ real_server = mod.server
48
+
49
+ @wraps(real_server)
50
+ def wrapper(*args, **kwargs):
51
+ bound = _bind_args(real_server, args, kwargs)
52
+ try:
53
+ interceptor = NDGrpcServerInterceptor(agent)
54
+ interceptors = bound.arguments.get('interceptors') or ()
55
+ bound.arguments['interceptors'] = tuple(interceptors) + (interceptor,)
56
+ except Exception:
57
+ agent.logger.exception("Error registering gRPC server interceptor")
58
+ return real_server(*args, **kwargs)
59
+ return real_server(*bound.args, **bound.kwargs)
60
+
61
+ wrapper._pythonagent_intercepted = True
62
+ setattr(mod, 'server', wrapper)
63
+
64
+
65
+ def intercept_grpc(agent, mod):
66
+ if getattr(mod, '_pythonagent_grpc_intercepted', False):
67
+ return
68
+
69
+ if hasattr(mod, 'insecure_channel') and not hasattr(mod.insecure_channel, '_pythonagent_intercepted'):
70
+ _wrap_channel_factory(agent, mod, 'insecure_channel')
71
+ if hasattr(mod, 'secure_channel') and not hasattr(mod.secure_channel, '_pythonagent_intercepted'):
72
+ _wrap_channel_factory(agent, mod, 'secure_channel')
73
+ if hasattr(mod, 'server') and not hasattr(mod.server, '_pythonagent_intercepted'):
74
+ _wrap_server_factory(agent, mod)
75
+
76
+ mod._pythonagent_grpc_intercepted = True
@@ -0,0 +1,132 @@
1
+ """Client-side gRPC interceptor.
2
+
3
+ Implements all four grpc client interceptor shapes (unary-unary, unary-stream,
4
+ stream-unary, stream-stream) in one class so every stub flavor (blocking,
5
+ future/callback-based, streaming) is covered uniformly through gRPC's own
6
+ public interceptor SPI - the same approach used by the Java agent's
7
+ ClientInterceptor-based design, adapted to grpc-python's shape-split ABCs.
8
+ """
9
+
10
+ from collections import namedtuple
11
+
12
+ import grpc
13
+
14
+ from pythonagent.agent.probes.base import ExitCallInterceptor
15
+ from pythonagent.utils import get_current_timestamp_in_us, get_validated_duration, generate_callout_id
16
+
17
+
18
+ class _ClientCallDetails(
19
+ namedtuple('_ClientCallDetails',
20
+ ('method', 'timeout', 'metadata', 'credentials', 'wait_for_ready', 'compression')),
21
+ grpc.ClientCallDetails):
22
+ """Immutable ClientCallDetails carrying an extra CavNDFPInstance metadata entry."""
23
+ pass
24
+
25
+
26
+ class NDGrpcClientInterceptor(ExitCallInterceptor,
27
+ grpc.UnaryUnaryClientInterceptor,
28
+ grpc.UnaryStreamClientInterceptor,
29
+ grpc.StreamUnaryClientInterceptor,
30
+ grpc.StreamStreamClientInterceptor):
31
+ """One interceptor instance per intercepted channel.
32
+
33
+ `target` is captured at channel-creation time (see agent/probes/grpc/__init__.py)
34
+ since grpc.ClientCallDetails carries no channel/host identity of its own.
35
+ """
36
+
37
+ def __init__(self, agent, target, req_method=None):
38
+ super(NDGrpcClientInterceptor, self).__init__(agent, None)
39
+ self.backend_name = "GRPC_" + str(target)
40
+ self.req_method = req_method
41
+
42
+ def _begin(self, client_call_details):
43
+ bt = self.bt
44
+ method = client_call_details.method or ""
45
+ callout_id = generate_callout_id()
46
+ start_time = get_current_timestamp_in_us()
47
+
48
+ exit_call = None
49
+ with self.log_exceptions():
50
+ exit_call = self.http_call_begin(
51
+ bt, self.backend_name, method, self.req_method, start_time, self.backend_name, callout_id)
52
+
53
+ metadata = list(client_call_details.metadata or [])
54
+ with self.log_exceptions():
55
+ headers_value = self.agent.get_nd_header(bt, self.backend_name, callout_id)
56
+ if headers_value:
57
+ headers_value = headers_value.decode() if isinstance(headers_value, bytes) else headers_value
58
+ metadata.append(('cavndfpinstance', headers_value))
59
+
60
+ new_details = _ClientCallDetails(
61
+ client_call_details.method,
62
+ client_call_details.timeout,
63
+ metadata,
64
+ client_call_details.credentials,
65
+ client_call_details.wait_for_ready,
66
+ client_call_details.compression)
67
+
68
+ return bt, new_details, exit_call, method, start_time
69
+
70
+ def _end(self, bt, exit_call, method, start_time, status_code):
71
+ with self.log_exceptions():
72
+ current_time = get_current_timestamp_in_us()
73
+ duration = get_validated_duration(start_time, current_time, "GRPC")
74
+ method = self.req_method
75
+ self.http_call_end(bt, exit_call, method, status_code, duration, start_time, self.backend_name)
76
+
77
+ @staticmethod
78
+ def _status_code_from_response(response):
79
+ try:
80
+ code = response.code()
81
+ except Exception:
82
+ return 200
83
+ return 200 if (code is None or code == grpc.StatusCode.OK) else 500
84
+
85
+ def intercept_unary_unary(self, continuation, client_call_details, request):
86
+ bt, new_details, exit_call, method, start_time = self._begin(client_call_details)
87
+ try:
88
+ response = continuation(new_details, request)
89
+ except Exception:
90
+ self._end(bt, exit_call, method, start_time, 500)
91
+ raise
92
+ self._end(bt, exit_call, method, start_time, self._status_code_from_response(response))
93
+ return response
94
+
95
+ def intercept_stream_unary(self, continuation, client_call_details, request_iterator):
96
+ bt, new_details, exit_call, method, start_time = self._begin(client_call_details)
97
+ try:
98
+ response = continuation(new_details, request_iterator)
99
+ except Exception:
100
+ self._end(bt, exit_call, method, start_time, 500)
101
+ raise
102
+ self._end(bt, exit_call, method, start_time, self._status_code_from_response(response))
103
+ return response
104
+
105
+ def intercept_unary_stream(self, continuation, client_call_details, request):
106
+ bt, new_details, exit_call, method, start_time = self._begin(client_call_details)
107
+ try:
108
+ response_iterator = continuation(new_details, request)
109
+ except Exception:
110
+ self._end(bt, exit_call, method, start_time, 500)
111
+ raise
112
+ return self._wrap_response_iterator(bt, exit_call, method, start_time, response_iterator)
113
+
114
+ def intercept_stream_stream(self, continuation, client_call_details, request_iterator):
115
+ bt, new_details, exit_call, method, start_time = self._begin(client_call_details)
116
+ try:
117
+ response_iterator = continuation(new_details, request_iterator)
118
+ except Exception:
119
+ self._end(bt, exit_call, method, start_time, 500)
120
+ raise
121
+ return self._wrap_response_iterator(bt, exit_call, method, start_time, response_iterator)
122
+
123
+ def _wrap_response_iterator(self, bt, exit_call, method, start_time, response_iterator):
124
+ status_code = 200
125
+ try:
126
+ for response in response_iterator:
127
+ yield response
128
+ except Exception:
129
+ status_code = 500
130
+ raise
131
+ finally:
132
+ self._end(bt, exit_call, method, start_time, status_code)