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.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- 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)
|