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,152 @@
1
+
2
+ """This module contains all interactions with raw Python frame objects.
3
+
4
+ This module's public functions should never ever return a raw frame object to
5
+ the caller. To explain all the weird try: finally: del frame stuff, see:
6
+ https://docs.python.org/3.4/library/inspect.html#the-interpreter-stack.
7
+
8
+ """
9
+
10
+ from __future__ import unicode_literals
11
+ import sys
12
+ import traceback
13
+
14
+
15
+ def is_internal_frame(filename):
16
+ """Return True if this filename belongs to an internal CavAgent module.
17
+
18
+ """
19
+ return 'site-packages/CavAgent' in filename
20
+
21
+
22
+ class FrameInfo(object):
23
+ """Copy of the important info from a Python frame.
24
+
25
+ """
26
+ __slots__ = ('class_name', 'name', 'filename', 'lineno', 'internal')
27
+
28
+ def __init__(self, frame):
29
+ try:
30
+ try:
31
+ self.class_name = frame.f_locals['self'].__class__.__name__
32
+ except:
33
+ self.class_name = None
34
+
35
+ code = frame.f_code
36
+ self.name = code.co_name
37
+ self.filename = code.co_filename
38
+ self.lineno = code.co_firstlineno
39
+ self.internal = is_internal_frame(self.filename)
40
+ finally:
41
+ del frame
42
+
43
+ def __eq__(self, rhs):
44
+ try:
45
+ return (
46
+ self.name == rhs.name and
47
+ self.filename == rhs.filename and
48
+ self.lineno == rhs.lineno and
49
+ self.class_name == rhs.class_name
50
+ )
51
+ except:
52
+ return False
53
+
54
+ def __ne__(self, rhs):
55
+ return not self == rhs
56
+
57
+ def __hash__(self):
58
+ return hash((self.name, self.filename, self.lineno))
59
+
60
+
61
+ def _frame_hash(frame):
62
+ try:
63
+ code = frame.f_code
64
+ key = (code.co_name, code.co_filename, code.co_firstlineno)
65
+ return hash(key)
66
+ finally:
67
+ del frame
68
+
69
+
70
+ def _get_root_frame(thread_id):
71
+ # Internal use only - get the root frame for the specified thread.
72
+ frames = sys._current_frames()
73
+ try:
74
+ return frames.get(thread_id, None)
75
+ finally:
76
+ del frames
77
+
78
+
79
+ def get_formatted_stack(thread_id):
80
+ """Return a list of formatted strings representing the current stack.
81
+
82
+ """
83
+ root = _get_root_frame(thread_id)
84
+ try:
85
+ return traceback.format_stack(root)
86
+ finally:
87
+ del root
88
+
89
+
90
+ def get_frame_info(level):
91
+ """Drop in replacement for `sys._getframe` which returns a FrameInfo object.
92
+
93
+ """
94
+ frame = sys._getframe(level + 1)
95
+ try:
96
+ return FrameInfo(frame)
97
+ finally:
98
+ del frame
99
+
100
+
101
+ def get_stack(frame_cache, from_root=True, thread_id=None, greenlet_ref=None):
102
+ """Generate a list of FrameInfo objects representing the python call stack.
103
+
104
+ By default this function tries to generate a full call stack from the root
105
+ frame on the specified thread or greenlet id. When calling this function
106
+ for this purpose a thread_id must *always* be specified.
107
+
108
+ Passing `from_root=False` will generate a stack from the current frame
109
+ outwards. When calling in this way a thread or greenlet id is not required.
110
+
111
+ `frame_cache` is a dict which is used to cache FrameInfo objects in order
112
+ to reduce the memory footprint of generating stacks with many duplicate
113
+ frames. The management of this cache is left up to the caller.
114
+
115
+ """
116
+
117
+ frame = None
118
+ try:
119
+ if from_root:
120
+ if greenlet_ref:
121
+ ctx = greenlet_ref()
122
+ if ctx:
123
+ # note that ctx.gr_frame may be none indicating that the current BT is not running
124
+ # under the context of greenlet but thread
125
+ frame = ctx.gr_frame or _get_root_frame(thread_id)
126
+ else:
127
+ return None
128
+ else:
129
+ frame = _get_root_frame(thread_id)
130
+
131
+ if frame is None:
132
+ return None
133
+ else:
134
+ frame = sys._getframe()
135
+
136
+ stack = []
137
+ while frame:
138
+ f_hash = _frame_hash(frame)
139
+ try:
140
+ stack.append(frame_cache[f_hash])
141
+ except KeyError:
142
+ f_info = FrameInfo(frame)
143
+ frame_cache[f_hash] = f_info
144
+ stack.append(f_info)
145
+
146
+ next_frame = frame.f_back
147
+ del frame
148
+ frame = next_frame
149
+ del next_frame
150
+ return stack
151
+ finally:
152
+ del frame
@@ -0,0 +1,39 @@
1
+
2
+ def create_heap_dump_muppy(self):
3
+ try:
4
+ import os
5
+ import sys
6
+
7
+ # Use bundled Pympler library from agent vendor package to avoid external dependency installation.
8
+
9
+ from pythonagent.vendor.pympler import muppy, summary
10
+ all_objects = muppy.get_objects()
11
+ sum1 = summary.summarize(all_objects)
12
+ sum1_sorted = sorted(sum1, key=lambda x: x[2], reverse=True)
13
+
14
+ all_list = []
15
+ for item in sum1_sorted: # Iterate over the sorted data
16
+ obj_type = item[0] # Object type
17
+ obj_count = item[1] # Object count
18
+ obj_size = item[2] # Size in bytes
19
+ formatted_line = f"{obj_type: <40}\t{obj_count: <12}\t{obj_size: <15}"
20
+
21
+ all_list.append(formatted_line)
22
+
23
+ # Add header with fixed width
24
+ header = f"{'types': <40}\t{'# objects': <12}\t{'total size(bytes)': <15}"
25
+
26
+ all_list.insert(0, header)
27
+ return "\n".join(all_list)
28
+
29
+ except ImportError as e:
30
+ self.logger.error(f"Pympler library is not found{e}")
31
+ return "\nUnable to capture heap dump \n\n\n"
32
+
33
+
34
+
35
+ except Exception as e:
36
+ self.logger.error(f"Exception in heap dump{e}")
37
+ return "\nUnable to capture heap dump\n\n\n"
38
+
39
+
@@ -0,0 +1,70 @@
1
+
2
+
3
+ from pythonagent.agent.probes import frameworks, http, sql, logging, mongodb, cache, message_brokers, coroutines, openai, langchain, elasticdb, Instrumentation
4
+
5
+ BT = (
6
+ # # Entry points
7
+ ('flask', frameworks.intercept_flask),
8
+ ('django.core.handlers.wsgi', frameworks.intercept_django_wsgi_handler),
9
+ ('django.core.handlers.base', frameworks.intercept_django_base_handler),
10
+ ('django.core.handlers.asgi', frameworks.intercept_django_asgi_handler),
11
+ ('bottle', frameworks.intercept_bottle),
12
+ ('aiohttp.web_app', frameworks.intercept_aiohttp_web),
13
+ ('tornado.web', frameworks.intercept_tornado_async_web),
14
+ ('cherrypy', frameworks.intercept_cherrypy),
15
+ ('pyramid', frameworks.intercept_pyramid),
16
+ ('falcon', frameworks.intercept_falcon),
17
+ ('starlette.routing', frameworks.intercept_fastapi_asgi_handler),
18
+ ('uvicorn.config', frameworks.intercept_uvicorn_config),
19
+ # # HTTP exit calls
20
+ ('httplib', http.intercept_httplib),
21
+ ('http.client', http.intercept_httplib),
22
+ ('urllib3', http.intercept_urllib3),
23
+ ('requests', http.intercept_requests),
24
+ ('tornado.httpclient', http.intercept_tornado_httpclient),
25
+ ('aiohttp.client', http.intercept_aiohttp_client),
26
+ ('httpx', http.intercept_httpx_client),
27
+ #('boto.https_connection', http.intercept_boto),
28
+
29
+ # # Logging
30
+ ('logging', logging.intercept_logging),
31
+
32
+ # # SQL exit calls
33
+ ('psycopg2', sql.psycopg2.intercept_psycopg2_connection),
34
+ ('pymysql.connections', sql.pymysql.intercept_pymysql_connections),
35
+ ('mysql.connector.connection', sql.mysql_connector.intercept_mysql_connector_connection),
36
+ ('mysql.connector.connection_cext', sql.mysql_connector_cext.intercept_mysql_connector_cext_connection),
37
+ ('MySQLdb.connections', sql.mysqldb.intercept_MySQLdb_connection),
38
+ # # MongoDB
39
+ ('pymongo', mongodb.intercept_pymongo),
40
+ #Dynamo
41
+ ('botocore.client', sql.dynamodb.intercept_dynamodb),
42
+ ('botocore.endpoint', sql.botocores3.intercept_s3),
43
+
44
+ # #REDIS
45
+ ('redis.connection', cache.intercept_redis),
46
+ ('redis.asyncio.connection', cache.intercept_redis_asyncio),
47
+ ('elasticsearch',elasticdb.intercept_elastic),
48
+
49
+ # RABBIT MQ
50
+ ('pika', message_brokers.intercept_pika),
51
+
52
+ # Asyncio
53
+ ('asyncio', coroutines.intercept_asyncio),
54
+
55
+ # OpenAI
56
+ ('openai', openai.intercept_openai),
57
+
58
+ # LangChain
59
+ #('langchain_core.language_models', langchain.intercept_langchain_core_language_models),
60
+ #('langchain_core.runnables', langchain.intercept_langchain_core_runnables),
61
+ ('langchain_core.tools', langchain.intercept_langchain_core_tools),
62
+ ('langchain_community.vectorstores', langchain.intercept_langchain_community_vectorstores),
63
+ ('langchain_community.embeddings', langchain.intercept_langchain_community_embeddings),
64
+ ('langchain_openai', langchain.intercept_langchain_openai),
65
+
66
+ # LangGraph
67
+ #('langgraph.pregel', langchain.intercept_langgraph_pregel),
68
+ #('langgraph.utils.runnable', langchain.intercept_langgraph_utils_runnable),
69
+
70
+ )
@@ -0,0 +1,122 @@
1
+ from __future__ import unicode_literals
2
+
3
+ import copy
4
+ import logging
5
+ import logging.handlers
6
+ import os.path
7
+ import os
8
+ import re
9
+ from datetime import datetime
10
+ import sys
11
+ from pythonagent import config
12
+ from pythonagent.lib import default_log_formatter, mkdir
13
+
14
+ LOGGING_MAX_BYTES = 10 * 1024 * 1024
15
+ LOGGING_MAX_NUM_FILES = 5
16
+
17
+
18
+ def get_log_level():
19
+ default_logging_level = logging.WARNING
20
+ allowed_logging_levels = {'DEBUG': logging.DEBUG,
21
+ 'INFO': logging.INFO,
22
+ 'WARNING': logging.WARNING,
23
+ 'ERROR': logging.ERROR, # Added for testing
24
+ 'CRITICAL': logging.CRITICAL # Added for testing
25
+ }
26
+
27
+ level = config.LOGGING_LEVEL.upper()
28
+ ret_val = allowed_logging_levels.get(level, default_logging_level)
29
+ # NOTSET=0
30
+ # DEBUG=10
31
+ # INFO=20
32
+ # WARN=30
33
+ # ERROR=40
34
+ # CRITICAL=50
35
+ return allowed_logging_levels.get(level, default_logging_level)
36
+
37
+
38
+ def get_log_filename():
39
+ non_alphanumeric = re.compile(r'\W+')
40
+ sanitize = lambda x: non_alphanumeric.sub('_', x)
41
+ filename = 'nd_trace_'+'_'.join(
42
+ map(sanitize, [config.APP_NAME.lower(), config.TIER.lower(), datetime.now().strftime("%Y%m%d%H%M%S"), str(os.getpid())])) + '.log_0'
43
+ return os.path.join(config.LOGS_DIR, filename)
44
+
45
+
46
+ def debug_enabled(logger):
47
+ for handler in logger.handlers:
48
+ if handler.__class__ is logging.StreamHandler:
49
+ return True
50
+ return False
51
+
52
+
53
+ def enable_debug(logger):
54
+ logger.setLevel(logging.DEBUG)
55
+
56
+ for handler in logger.handlers:
57
+ handler.setLevel(logging.DEBUG)
58
+
59
+ stream_handler = logging.StreamHandler()
60
+ stream_handler.setLevel(logging.DEBUG)
61
+ stream_handler.setFormatter(default_log_formatter)
62
+ logger.addHandler(stream_handler)
63
+
64
+
65
+ def disable_debug(logger):
66
+ level = get_log_level()
67
+ logger.setLevel(level)
68
+
69
+ handlers = copy.copy(logger.handlers)
70
+ logger.handlers = []
71
+ for handler in handlers:
72
+ if handler.__class__ is not logging.StreamHandler:
73
+ handler.setLevel(level)
74
+ logger.addHandler(handler)
75
+
76
+
77
+ def configure_logging():
78
+ try:
79
+ logger = logging.getLogger('pythonagent.agent')
80
+ level = get_log_level()
81
+ print("Logging level:", level)
82
+ logger.setLevel(level)
83
+ path = get_log_filename()
84
+
85
+ print("Logging file path name", path)
86
+
87
+ agent_env = None
88
+
89
+ if "CAV_APP_AGENT_ENV" in os.environ:
90
+ if os.environ['CAV_APP_AGENT_ENV'] == "AWS_LAMBDA":
91
+ agent_env = "AWS_LAMBDA"
92
+ else:
93
+ agent_env = "NATIVE"
94
+ else:
95
+ agent_env = "NATIVE"
96
+
97
+ if agent_env == "AWS_LAMBDA":
98
+ consolHandler = logging.StreamHandler(sys.stdout)
99
+ consolHandler.setLevel(level)
100
+ consolHandler.setFormatter(default_log_formatter)
101
+ logger.addHandler(consolHandler)
102
+
103
+ else:
104
+ mkdir(os.path.dirname(path))
105
+ rotating_file_handler = logging.handlers.RotatingFileHandler(path, maxBytes=LOGGING_MAX_BYTES,
106
+ backupCount=LOGGING_MAX_NUM_FILES - 1)
107
+ # logging.getLogger('pythonagent.agent').addHandler()
108
+ rotating_file_handler.setLevel(level)
109
+ rotating_file_handler.setFormatter(default_log_formatter)
110
+ logger.addHandler(rotating_file_handler)
111
+
112
+ if config.DEBUG_LOG:
113
+ enable_debug(logger)
114
+
115
+ has_handlers = logger.hasHandlers()
116
+ logger.warning("has handlers {}".format(str(has_handlers)))
117
+ logger.propagate = False
118
+ logger.warning("has handlers {}".format(str(has_handlers)))
119
+
120
+
121
+ except:
122
+ logging.getLogger('pythonagent.agent').exception('Logging configuration failed.')
File without changes
@@ -0,0 +1,276 @@
1
+ # Purpose: collect a rich, point-in-time snapshot of the monitored Python application/process/
2
+ # deployment environment, for the auto-discovery response's "****Start Metadata****" block
3
+ # (sent from Agent.autodiscovery_py() in agent/internal/agent/agent.py). Mirrors the sibling
4
+ # NodeJS agent's lib/metaData/agentMetaData.js + lib/autoDiscovery.js buildMetadataBlock(), adapted
5
+ # to Python's runtime (no package.json/npm, no cluster module - platform/os/proc_compat stand in).
6
+ #
7
+ # Deliberately stdlib + /proc only (no psutil or other external dependency) - same constraint as
8
+ # the sibling proc_compat.py this module builds on, and it keeps a metadata-collection failure
9
+ # (e.g. a missing optional dependency) from ever being able to break auto-discovery.
10
+ #
11
+ # Every value collected here is synchronous and local to this process - no shelling out, no
12
+ # network calls - so calling collect_metadata()/build_metadata_block() cannot add meaningful
13
+ # latency to a discovery response. collect_metadata() returns a plain dict; callers are
14
+ # responsible for escaping (escape_metadata_value) and formatting into "key=value;" lines -
15
+ # build_metadata_block() does that end-to-end for the auto-discovery caller.
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ import platform
21
+ import logging
22
+
23
+ from pythonagent.agent.internal.proc_compat import get_process_name, get_virtual_memory, get_rss_bytes
24
+
25
+ logger = logging.getLogger('pythonagent.agent')
26
+
27
+
28
+ _VERSION_LINE_PATTERN = re.compile(r'^\s*VERSION\s+(\S+)', re.IGNORECASE | re.MULTILINE)
29
+ _BUILD_LINE_PATTERN = re.compile(r'^\s*BUILD\s+(\S+)', re.IGNORECASE | re.MULTILINE)
30
+
31
+
32
+ def _resolve_agent_version():
33
+ """Resolve this agent's actual shipped version/build from the "VERSION x.y.z / BUILD n" file
34
+ that install.sh lays down at $NDHOME/python/version - the same convention the sibling
35
+ .NET/native agents use for their own top-level 'version' file (see e.g.
36
+ cavisson_python_agent/version, dotnetagent/agent/version). This is the authoritative,
37
+ release-tracked version; the 'pythonagent' pip distribution's own version (setup.py) is just a
38
+ placeholder ("0.0.1", "A small example package") and not meaningful here.
39
+
40
+ Returns (version, build) - either may be None (field omitted) if NDHOME isn't set, the file is
41
+ missing, or a line can't be parsed - never raises, since this must not be able to block agent
42
+ metadata collection.
43
+ """
44
+ ndhome = os.environ.get('NDHOME')
45
+ if not ndhome:
46
+ return None, None
47
+ try:
48
+ with open(os.path.join(ndhome, 'python', 'version'), 'r') as f:
49
+ contents = f.read()
50
+ except (IOError, OSError):
51
+ return None, None
52
+
53
+ version_match = _VERSION_LINE_PATTERN.search(contents)
54
+ build_match = _BUILD_LINE_PATTERN.search(contents)
55
+ return (
56
+ version_match.group(1) if version_match else None,
57
+ build_match.group(1) if build_match else None,
58
+ )
59
+
60
+
61
+ # Resolved once at import time - the installed version file can't change within a process lifetime.
62
+ _AGENT_VERSION, _AGENT_BUILD = _resolve_agent_version()
63
+
64
+ # Env var keys whose VALUE gets redacted in the metadata block - matched case-insensitively
65
+ # against the key name, not the value, since we can't know a value is secret just by looking at
66
+ # it. This list intentionally errs toward over-redacting common secret-naming conventions.
67
+ _SECRET_KEY_PATTERN = re.compile(r'key|secret|token|password|pwd|credential|auth|apikey|connectionstring|private', re.IGNORECASE)
68
+ _REDACTED_VALUE = '***REDACTED***'
69
+
70
+ # Env var keys dropped entirely (not just redacted) - not secrets, just noisy shell/terminal
71
+ # config that adds no diagnostic value and is heavy on ";"/"=".
72
+ _EXCLUDED_ENV_KEYS = re.compile(r'^(LS_COLORS|LESSOPEN|LESSCLOSE|XMODIFIERS|DBUS_SESSION_BUS_ADDRESS)$', re.IGNORECASE)
73
+
74
+
75
+ def _add_if_defined(target, key, value):
76
+ if value is None or value == '':
77
+ return
78
+ target[key] = value
79
+
80
+
81
+ def _get_process_uptime_sec():
82
+ """Return seconds since this process started, via /proc only (Linux). Best-effort - returns
83
+ None on any platform/parse failure rather than raising, since it's cosmetic metadata."""
84
+ try:
85
+ clk_tck = os.sysconf('SC_CLK_TCK')
86
+ with open('/proc/uptime', 'r') as f:
87
+ system_uptime = float(f.read().split()[0])
88
+ with open('/proc/self/stat', 'r') as f:
89
+ # Field 22 (starttime, in clock ticks since boot) - skip past the "(comm)" field
90
+ # since it may itself contain spaces/parens.
91
+ stat = f.read()
92
+ fields = stat[stat.rfind(')') + 2:].split()
93
+ starttime_ticks = float(fields[19])
94
+ process_start_uptime = starttime_ticks / clk_tck
95
+ return int(system_uptime - process_start_uptime)
96
+ except (IOError, OSError, ValueError, IndexError, AttributeError):
97
+ return None
98
+
99
+
100
+ def _collect_agent_identity(meta, agent):
101
+ _add_if_defined(meta, 'agentType', 'Python')
102
+ _add_if_defined(meta, 'agentVersion', _AGENT_VERSION)
103
+ _add_if_defined(meta, 'agentBuild', _AGENT_BUILD)
104
+ _add_if_defined(meta, 'agentTier', getattr(agent, 'tier_name', None))
105
+ _add_if_defined(meta, 'agentServer', getattr(agent, 'server_name', None))
106
+ _add_if_defined(meta, 'agentInstance', getattr(agent, 'instance_name', None))
107
+ _add_if_defined(meta, 'agentEnv', getattr(agent, 'cav_env', None))
108
+ _add_if_defined(meta, 'ndHome', os.environ.get('NDHOME'))
109
+ _add_if_defined(meta, 'testRunning', getattr(agent, 'test_running', None))
110
+ _add_if_defined(meta, 'nfEnabled', getattr(agent, 'nf_enabled', None))
111
+
112
+
113
+ def _collect_app_identity(meta):
114
+ try:
115
+ from pythonagent import config
116
+ _add_if_defined(meta, 'appName', getattr(config, 'APP_NAME', None))
117
+ _add_if_defined(meta, 'wsgiModule', getattr(config, 'WSGI_MODULE', None))
118
+ _add_if_defined(meta, 'wsgiCallable', getattr(config, 'WSGI_CALLABLE_OBJECT', None))
119
+ _add_if_defined(meta, 'asgiModule', getattr(config, 'ASGI_MODULE', None))
120
+ _add_if_defined(meta, 'asgiCallable', getattr(config, 'ASGI_CALLABLE_OBJECT', None))
121
+ except Exception as e:
122
+ logger.warning('agent_meta_data | Error collecting app identity from config : %s', e)
123
+
124
+ try:
125
+ _add_if_defined(meta, 'appEntryPoint', get_process_name())
126
+ _add_if_defined(meta, 'appCwd', os.getcwd())
127
+ except Exception as e:
128
+ logger.warning('agent_meta_data | Error collecting app identity : %s', e)
129
+
130
+
131
+ def _collect_runtime_versions(meta):
132
+ try:
133
+ _add_if_defined(meta, 'pythonVersion', platform.python_version())
134
+ _add_if_defined(meta, 'pythonImplementation', platform.python_implementation())
135
+ _add_if_defined(meta, 'pythonCompiler', platform.python_compiler())
136
+ _add_if_defined(meta, 'pythonBuild', ' '.join(platform.python_build()))
137
+ _add_if_defined(meta, 'isVirtualenv', hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))
138
+ except Exception as e:
139
+ logger.warning('agent_meta_data | Error collecting runtime versions : %s', e)
140
+
141
+
142
+ def _collect_os_info(meta):
143
+ try:
144
+ _add_if_defined(meta, 'osType', platform.system())
145
+ _add_if_defined(meta, 'osPlatform', sys.platform)
146
+ _add_if_defined(meta, 'osRelease', platform.release())
147
+ _add_if_defined(meta, 'osArch', platform.machine())
148
+ _add_if_defined(meta, 'hostname', platform.node())
149
+ _add_if_defined(meta, 'cpuCount', os.cpu_count() if hasattr(os, 'cpu_count') else None)
150
+
151
+ total, available, _percent, _used, _free = get_virtual_memory()
152
+ _add_if_defined(meta, 'totalMemBytes', total or None)
153
+ _add_if_defined(meta, 'freeMemBytesAtDiscovery', available or None)
154
+
155
+ try:
156
+ with open('/proc/uptime', 'r') as f:
157
+ _add_if_defined(meta, 'osUptimeSec', int(float(f.read().split()[0])))
158
+ except (IOError, OSError, ValueError, IndexError):
159
+ pass
160
+ except Exception as e:
161
+ logger.warning('agent_meta_data | Error collecting OS info : %s', e)
162
+
163
+
164
+ def _collect_process_snapshot(meta):
165
+ try:
166
+ _add_if_defined(meta, 'pid', os.getpid())
167
+ _add_if_defined(meta, 'ppid', os.getppid() if hasattr(os, 'getppid') else None)
168
+ _add_if_defined(meta, 'execPath', sys.executable)
169
+ # Deliberately interpreter flags/warnoptions (sys.flags/sys.warnoptions), NOT sys.argv[1:] -
170
+ # the application's own command-line arguments may include secrets, mirroring the NodeJS
171
+ # agent's choice of process.execArgv over process.argv.
172
+ _add_if_defined(meta, 'pythonWarnOptions', ' '.join(sys.warnoptions) if sys.warnoptions else None)
173
+ _add_if_defined(meta, 'processUptimeSec', _get_process_uptime_sec())
174
+ _add_if_defined(meta, 'memRssBytes', get_rss_bytes() or None)
175
+ except Exception as e:
176
+ logger.warning('agent_meta_data | Error collecting process snapshot : %s', e)
177
+
178
+
179
+ def _collect_deployment_hints(meta):
180
+ try:
181
+ env = os.environ
182
+ is_docker = False
183
+ try:
184
+ is_docker = os.path.exists('/.dockerenv')
185
+ except OSError:
186
+ pass
187
+
188
+ _add_if_defined(meta, 'isDocker', is_docker)
189
+ _add_if_defined(meta, 'isKubernetes', bool(env.get('KUBERNETES_SERVICE_HOST')))
190
+ _add_if_defined(meta, 'isAwsLambda', bool(env.get('AWS_LAMBDA_FUNCTION_NAME') or env.get('AWS_EXECUTION_ENV')))
191
+ _add_if_defined(meta, 'isEcs', bool(env.get('ECS_CONTAINER_METADATA_URI')))
192
+ _add_if_defined(meta, 'isAzureAppService', bool(env.get('WEBSITE_INSTANCE_ID')))
193
+ _add_if_defined(meta, 'isCloudRun', bool(env.get('K_SERVICE')))
194
+ _add_if_defined(meta, 'isHeroku', bool(env.get('DYNO')))
195
+ _add_if_defined(meta, 'isGunicorn', bool(env.get('SERVER_SOFTWARE', '').lower().startswith('gunicorn')))
196
+ _add_if_defined(meta, 'isUwsgi', bool(env.get('UWSGI_ORIGINAL_PROC_NAME')) or 'uwsgi' in sys.modules)
197
+
198
+ # Specific identifying values (operationally useful, not secret-shaped) - only meaningful
199
+ # when the corresponding hint above is true.
200
+ _add_if_defined(meta, 'k8sServiceHost', env.get('KUBERNETES_SERVICE_HOST'))
201
+ _add_if_defined(meta, 'lambdaFunctionName', env.get('AWS_LAMBDA_FUNCTION_NAME'))
202
+ _add_if_defined(meta, 'dyno', env.get('DYNO'))
203
+ except Exception as e:
204
+ logger.warning('agent_meta_data | Error collecting deployment hints : %s', e)
205
+
206
+
207
+ def _collect_launch_context(meta):
208
+ try:
209
+ env = os.environ
210
+ _add_if_defined(meta, 'virtualEnv', env.get('VIRTUAL_ENV'))
211
+ _add_if_defined(meta, 'condaEnv', env.get('CONDA_DEFAULT_ENV'))
212
+ _add_if_defined(meta, 'pythonPath', env.get('PYTHONPATH'))
213
+
214
+ # Multi-worker context (gunicorn/uwsgi pre-fork workers have no direct equivalent of
215
+ # NodeJS's cluster.worker.id, so surface whatever the WSGI server exposes via env).
216
+ _add_if_defined(meta, 'workerId', env.get('APP_WORKER_ID') or env.get('UWSGI_WORKER_ID'))
217
+ except Exception as e:
218
+ logger.warning('agent_meta_data | Error collecting launch context : %s', e)
219
+
220
+
221
+ def _collect_environment_variables(meta):
222
+ try:
223
+ for key, value in os.environ.items():
224
+ if _EXCLUDED_ENV_KEYS.match(key):
225
+ continue
226
+ meta['env.' + key] = _REDACTED_VALUE if _SECRET_KEY_PATTERN.search(key) else value
227
+ except Exception as e:
228
+ logger.warning('agent_meta_data | Error collecting environment variables : %s', e)
229
+
230
+
231
+ def collect_metadata(agent):
232
+ """Return a plain dict of metadata key -> raw (unescaped) value for the given Agent instance.
233
+
234
+ None/''-valued entries are never added, so callers can iterate the dict directly without
235
+ extra filtering.
236
+ """
237
+ meta = {}
238
+ _collect_agent_identity(meta, agent)
239
+ _collect_app_identity(meta)
240
+ _collect_runtime_versions(meta)
241
+ _collect_os_info(meta)
242
+ _collect_process_snapshot(meta)
243
+ _collect_deployment_hints(meta)
244
+ _collect_launch_context(meta)
245
+ _collect_environment_variables(meta)
246
+ return meta
247
+
248
+
249
+ def escape_metadata_value(value):
250
+ """Escape a value for embedding in a "key=value;" metadata line.
251
+
252
+ Mirrors the NodeJS agent's AgentSetting.escapeMetadataValue - a value containing ';', '=' or
253
+ a newline (env vars are the likely source) must not be able to corrupt the surrounding
254
+ "key=value;"-per-line, single-block message format.
255
+ """
256
+ text = value if isinstance(value, str) else str(value)
257
+ return text.replace('\\', '\\\\').replace(';', '\\;').replace('=', '\\=').replace('\n', '\\n').replace('\r', '\\r')
258
+
259
+
260
+ def build_metadata_block(agent):
261
+ """Build the "****Start Metadata****...****End Metadata****" block for the auto-discovery
262
+ response, mirroring the NodeJS agent's autoDiscovery.js buildMetadataBlock().
263
+
264
+ Always returns a well-formed (possibly empty) block rather than raising, so a metadata
265
+ collection failure never breaks the rest of the auto-discovery response.
266
+ """
267
+ lines = []
268
+ try:
269
+ meta = collect_metadata(agent)
270
+ for key, value in meta.items():
271
+ if value is None:
272
+ continue
273
+ lines.append(key + '=' + escape_metadata_value(value) + ';')
274
+ except Exception as e:
275
+ logger.error('agent_meta_data | Error collecting auto-discovery metadata : %s', e)
276
+ return '****Start Metadata****\n' + ''.join(lines) + '\n****End Metadata****\n'