whatap-python 2.1.0__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 (227) hide show
  1. whatap/LICENSE +0 -0
  2. whatap/README.rst +49 -0
  3. whatap/__init__.py +923 -0
  4. whatap/__main__.py +4 -0
  5. whatap/agent/darwin/amd64/whatap_python +0 -0
  6. whatap/agent/darwin/arm64/whatap_python +0 -0
  7. whatap/agent/linux/amd64/whatap_python +0 -0
  8. whatap/agent/linux/arm64/whatap_python +0 -0
  9. whatap/agent/windows/whatap_python.exe +0 -0
  10. whatap/bootstrap/__init__.py +0 -0
  11. whatap/bootstrap/sitecustomize.py +19 -0
  12. whatap/build.py +4 -0
  13. whatap/conf/__init__.py +0 -0
  14. whatap/conf/configuration.py +280 -0
  15. whatap/conf/configure.py +105 -0
  16. whatap/conf/license.py +49 -0
  17. whatap/control/__init__.py +0 -0
  18. whatap/counter/__init__.py +14 -0
  19. whatap/counter/counter_manager.py +45 -0
  20. whatap/counter/tasks/__init__.py +3 -0
  21. whatap/counter/tasks/base_task.py +26 -0
  22. whatap/counter/tasks/llm_evaluator_task.py +501 -0
  23. whatap/counter/tasks/llm_log_sink_task.py +309 -0
  24. whatap/counter/tasks/llm_stat_task.py +78 -0
  25. whatap/counter/tasks/openfiledescriptor.py +67 -0
  26. whatap/io/__init__.py +1 -0
  27. whatap/io/data_inputx.py +161 -0
  28. whatap/io/data_outputx.py +262 -0
  29. whatap/llm/__init__.py +17 -0
  30. whatap/llm/definitions.py +43 -0
  31. whatap/llm/evaluators/__init__.py +136 -0
  32. whatap/llm/evaluators/base.py +114 -0
  33. whatap/llm/evaluators/builtins/__init__.py +91 -0
  34. whatap/llm/evaluators/builtins/answer_relevance.py +46 -0
  35. whatap/llm/evaluators/builtins/combined_judge.py +271 -0
  36. whatap/llm/evaluators/builtins/factuality.py +71 -0
  37. whatap/llm/evaluators/builtins/hallucination.py +97 -0
  38. whatap/llm/evaluators/builtins/llm_judge.py +516 -0
  39. whatap/llm/evaluators/builtins/pii_leak.py +214 -0
  40. whatap/llm/evaluators/builtins/prompt_injection.py +71 -0
  41. whatap/llm/evaluators/builtins/toxicity.py +53 -0
  42. whatap/llm/evaluators/builtins/url_scan.py +194 -0
  43. whatap/llm/evaluators/registry.py +192 -0
  44. whatap/llm/evaluators/sampler.py +83 -0
  45. whatap/llm/evaluators/scope.py +334 -0
  46. whatap/llm/features.py +66 -0
  47. whatap/llm/log_sink_packs/__init__.py +9 -0
  48. whatap/llm/log_sink_packs/llm_input_message.py +16 -0
  49. whatap/llm/log_sink_packs/llm_log_sink_pack.py +72 -0
  50. whatap/llm/log_sink_packs/llm_output_message.py +19 -0
  51. whatap/llm/log_sink_packs/llm_step_eval_status.py +94 -0
  52. whatap/llm/log_sink_packs/llm_step_status.py +118 -0
  53. whatap/llm/log_sink_packs/llm_system_message.py +16 -0
  54. whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
  55. whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
  56. whatap/llm/log_sink_packs/llm_tx_status.py +108 -0
  57. whatap/llm/pricing.py +236 -0
  58. whatap/llm/prompt_meta.py +288 -0
  59. whatap/llm/providers/__init__.py +0 -0
  60. whatap/llm/providers/anthropic/__init__.py +37 -0
  61. whatap/llm/providers/anthropic/messages/__init__.py +0 -0
  62. whatap/llm/providers/anthropic/messages/messages.py +70 -0
  63. whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
  64. whatap/llm/providers/anthropic/messages/messages_extractor.py +126 -0
  65. whatap/llm/providers/interceptor.py +182 -0
  66. whatap/llm/providers/openai/__init__.py +133 -0
  67. whatap/llm/providers/openai/chat/__init__.py +0 -0
  68. whatap/llm/providers/openai/chat/chat.py +82 -0
  69. whatap/llm/providers/openai/chat/chat_context.py +78 -0
  70. whatap/llm/providers/openai/chat/chat_extractor.py +127 -0
  71. whatap/llm/providers/openai/completions/__init__.py +0 -0
  72. whatap/llm/providers/openai/completions/completions.py +70 -0
  73. whatap/llm/providers/openai/completions/completions_context.py +31 -0
  74. whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
  75. whatap/llm/providers/openai/content_parser.py +41 -0
  76. whatap/llm/providers/openai/embeddings/__init__.py +0 -0
  77. whatap/llm/providers/openai/embeddings/embeddings.py +59 -0
  78. whatap/llm/providers/openai/embeddings/embeddings_context.py +25 -0
  79. whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
  80. whatap/llm/providers/openai/responses/__init__.py +0 -0
  81. whatap/llm/providers/openai/responses/responses.py +70 -0
  82. whatap/llm/providers/openai/responses/responses_context.py +88 -0
  83. whatap/llm/providers/openai/responses/responses_extractor.py +126 -0
  84. whatap/llm/providers/stream_accumulator.py +73 -0
  85. whatap/llm/stats/__init__.py +35 -0
  86. whatap/llm/stats/active_stat.py +86 -0
  87. whatap/llm/stats/answer_relevance_eval_stat.py +10 -0
  88. whatap/llm/stats/api_status_stat.py +35 -0
  89. whatap/llm/stats/base_stat.py +107 -0
  90. whatap/llm/stats/combined_judge_eval_stat.py +11 -0
  91. whatap/llm/stats/error_stat.py +59 -0
  92. whatap/llm/stats/eval_stat.py +225 -0
  93. whatap/llm/stats/factuality_eval_stat.py +10 -0
  94. whatap/llm/stats/feature_stat.py +104 -0
  95. whatap/llm/stats/finish_stat.py +105 -0
  96. whatap/llm/stats/hallucination_eval_stat.py +10 -0
  97. whatap/llm/stats/meter.py +18 -0
  98. whatap/llm/stats/perf_stat.py +117 -0
  99. whatap/llm/stats/pii_leak_eval_stat.py +12 -0
  100. whatap/llm/stats/prompt_injection_eval_stat.py +10 -0
  101. whatap/llm/stats/token_usage_stat.py +133 -0
  102. whatap/llm/stats/toxicity_eval_stat.py +10 -0
  103. whatap/llm/stats/url_scan_eval_stat.py +12 -0
  104. whatap/net/__init__.py +0 -0
  105. whatap/net/async_sender.py +107 -0
  106. whatap/net/packet_enum.py +44 -0
  107. whatap/net/packet_type_enum.py +31 -0
  108. whatap/net/param_def.py +69 -0
  109. whatap/net/stackhelper.py +87 -0
  110. whatap/net/udp_session.py +394 -0
  111. whatap/net/udp_thread.py +54 -0
  112. whatap/pack/__init__.py +0 -0
  113. whatap/pack/logSinkPack.py +77 -0
  114. whatap/pack/pack.py +34 -0
  115. whatap/pack/pack_enum.py +41 -0
  116. whatap/pack/tagCountPack.py +61 -0
  117. whatap/scripts/__init__.py +208 -0
  118. whatap/trace/__init__.py +12 -0
  119. whatap/trace/mod/__init__.py +0 -0
  120. whatap/trace/mod/amqp/__init__.py +0 -0
  121. whatap/trace/mod/amqp/kombu.py +122 -0
  122. whatap/trace/mod/amqp/pika.py +62 -0
  123. whatap/trace/mod/application/__init__.py +0 -0
  124. whatap/trace/mod/application/bottle.py +34 -0
  125. whatap/trace/mod/application/celery.py +81 -0
  126. whatap/trace/mod/application/cherrypy.py +30 -0
  127. whatap/trace/mod/application/django.py +287 -0
  128. whatap/trace/mod/application/django_asgi.py +266 -0
  129. whatap/trace/mod/application/django_py3.py +251 -0
  130. whatap/trace/mod/application/fastapi/__init__.py +31 -0
  131. whatap/trace/mod/application/fastapi/endpoint.py +73 -0
  132. whatap/trace/mod/application/fastapi/exception_log.py +63 -0
  133. whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
  134. whatap/trace/mod/application/fastapi/scope.py +115 -0
  135. whatap/trace/mod/application/fastapi/transaction.py +67 -0
  136. whatap/trace/mod/application/flask.py +52 -0
  137. whatap/trace/mod/application/frappe.py +224 -0
  138. whatap/trace/mod/application/graphql.py +170 -0
  139. whatap/trace/mod/application/nameko.py +39 -0
  140. whatap/trace/mod/application/odoo.py +63 -0
  141. whatap/trace/mod/application/starlette.py +126 -0
  142. whatap/trace/mod/application/tornado.py +163 -0
  143. whatap/trace/mod/application/wsgi.py +195 -0
  144. whatap/trace/mod/database/__init__.py +0 -0
  145. whatap/trace/mod/database/cxoracle.py +49 -0
  146. whatap/trace/mod/database/mongo.py +169 -0
  147. whatap/trace/mod/database/mysql.py +80 -0
  148. whatap/trace/mod/database/neo4j.py +90 -0
  149. whatap/trace/mod/database/psycopg2.py +45 -0
  150. whatap/trace/mod/database/psycopg3.py +359 -0
  151. whatap/trace/mod/database/redis.py +122 -0
  152. whatap/trace/mod/database/sqlalchemy.py +213 -0
  153. whatap/trace/mod/database/sqlite3.py +130 -0
  154. whatap/trace/mod/database/util.py +630 -0
  155. whatap/trace/mod/email/__init__.py +0 -0
  156. whatap/trace/mod/email/smtp.py +78 -0
  157. whatap/trace/mod/httpc/__init__.py +0 -0
  158. whatap/trace/mod/httpc/django.py +31 -0
  159. whatap/trace/mod/httpc/httplib.py +70 -0
  160. whatap/trace/mod/httpc/httpx.py +62 -0
  161. whatap/trace/mod/httpc/requests.py +20 -0
  162. whatap/trace/mod/httpc/urllib3.py +27 -0
  163. whatap/trace/mod/httpc/util.py +388 -0
  164. whatap/trace/mod/logging.py +161 -0
  165. whatap/trace/mod/plugin.py +84 -0
  166. whatap/trace/mod/standalone/__init__.py +0 -0
  167. whatap/trace/mod/standalone/multiple.py +293 -0
  168. whatap/trace/mod/standalone/single.py +135 -0
  169. whatap/trace/simple_trace_context.py +18 -0
  170. whatap/trace/trace_context.py +212 -0
  171. whatap/trace/trace_context_manager.py +244 -0
  172. whatap/trace/trace_error.py +84 -0
  173. whatap/trace/trace_handler.py +89 -0
  174. whatap/trace/trace_import.py +91 -0
  175. whatap/trace/trace_module_definition.py +156 -0
  176. whatap/util/__init__.py +0 -0
  177. whatap/util/bit_util.py +49 -0
  178. whatap/util/cardinality/__init__.py +0 -0
  179. whatap/util/cardinality/hyperloglog.py +84 -0
  180. whatap/util/cardinality/murmurhash.py +20 -0
  181. whatap/util/cardinality/registerset.py +60 -0
  182. whatap/util/compare_util.py +19 -0
  183. whatap/util/date_util.py +55 -0
  184. whatap/util/debug_util.py +73 -0
  185. whatap/util/escape_literal_sql.py +233 -0
  186. whatap/util/frame_util.py +20 -0
  187. whatap/util/hash_util.py +103 -0
  188. whatap/util/hexa32.py +66 -0
  189. whatap/util/int_set.py +199 -0
  190. whatap/util/ip_util.py +63 -0
  191. whatap/util/keygen.py +11 -0
  192. whatap/util/linked_list.py +113 -0
  193. whatap/util/linked_map.py +359 -0
  194. whatap/util/metering_util.py +103 -0
  195. whatap/util/request_double_queue.py +68 -0
  196. whatap/util/request_queue.py +60 -0
  197. whatap/util/string_util.py +20 -0
  198. whatap/util/throttle_util.py +99 -0
  199. whatap/util/userid_util.py +134 -0
  200. whatap/value/__init__.py +1 -0
  201. whatap/value/blob_value.py +38 -0
  202. whatap/value/boolean_value.py +33 -0
  203. whatap/value/decimal_value.py +36 -0
  204. whatap/value/double_summary.py +86 -0
  205. whatap/value/double_value.py +33 -0
  206. whatap/value/float_array.py +42 -0
  207. whatap/value/float_value.py +34 -0
  208. whatap/value/int_array.py +42 -0
  209. whatap/value/ip4_value.py +50 -0
  210. whatap/value/list_value.py +105 -0
  211. whatap/value/long_array.py +44 -0
  212. whatap/value/long_summary.py +83 -0
  213. whatap/value/map_value.py +154 -0
  214. whatap/value/null_value.py +21 -0
  215. whatap/value/number_value.py +33 -0
  216. whatap/value/summary_value.py +39 -0
  217. whatap/value/text_array.py +58 -0
  218. whatap/value/text_hash_value.py +37 -0
  219. whatap/value/text_value.py +43 -0
  220. whatap/value/value.py +26 -0
  221. whatap/value/value_enum.py +80 -0
  222. whatap/whatap.conf +14 -0
  223. whatap_python-2.1.0.dist-info/METADATA +87 -0
  224. whatap_python-2.1.0.dist-info/RECORD +227 -0
  225. whatap_python-2.1.0.dist-info/WHEEL +5 -0
  226. whatap_python-2.1.0.dist-info/entry_points.txt +6 -0
  227. whatap_python-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,630 @@
1
+ from whatap.trace.trace_context import TraceContext
2
+ from whatap.trace.trace_context_manager import TraceContextManager
3
+ from whatap.util.date_util import DateUtil
4
+ from whatap.net.packet_type_enum import PacketTypeEnum
5
+ from whatap.conf.configure import Configure as conf
6
+ from whatap.trace.trace_error import interceptor_step_error
7
+ from whatap import logging
8
+ import whatap.net.async_sender as async_sender
9
+ import os
10
+ import re
11
+
12
+
13
+ def _stringify(value):
14
+ if value is None:
15
+ return ''
16
+ if isinstance(value, str):
17
+ return value
18
+ if isinstance(value, (bytes, bytearray)):
19
+ try:
20
+ return bytes(value).decode('utf-8', errors='replace')
21
+ except Exception:
22
+ return str(value)
23
+ if hasattr(value, '__fspath__'):
24
+ try:
25
+ p = os.fspath(value)
26
+ if isinstance(p, (bytes, bytearray)):
27
+ return bytes(p).decode('utf-8', errors='replace')
28
+ return p
29
+ except Exception:
30
+ return str(value)
31
+ return str(value)
32
+
33
+
34
+ def extract_db_error_message(e):
35
+ try:
36
+ # PostgreSQL (psycopg2)
37
+ if hasattr(e, 'pgcode') and hasattr(e, 'pgerror'):
38
+ return str(e.pgerror)
39
+
40
+ # MySQL (PyMySQL, mysql-connector-python)
41
+ if hasattr(e, 'args') and len(e.args) > 1 and isinstance(e.args[1], str):
42
+ return e.args[1]
43
+
44
+ # 기본 에러 메시지
45
+ if hasattr(e, 'args') and len(e.args) > 0:
46
+ if isinstance(e.args[0], str):
47
+ return e.args[0]
48
+ return str(e.args[0])
49
+
50
+ return str(e)
51
+ except Exception as e:
52
+ logging.warning('[DB] extract_db_error_message failed: %s' % e, extra={'id': 'DB001'})
53
+ return "Unknown database error"
54
+
55
+
56
+
57
+ def addQuoteDict(arg_dict):
58
+ quoted_dict = dict()
59
+
60
+ for k, v in arg_dict.items():
61
+ if isinstance(v, str):
62
+ quoted_dict[k] = "'" + v.replace("'", "\\'") + "'"
63
+ else:
64
+ quoted_dict[k] = v
65
+
66
+ return quoted_dict
67
+
68
+ def addQuoteList(arg_list):
69
+ quoted_list = list()
70
+
71
+ for v in arg_list:
72
+ if isinstance(v, str):
73
+ quoted_list.append("'" + v.replace("'", "\\'") + "'")
74
+ else:
75
+ quoted_list.append("'" + str(v) + "'")
76
+
77
+ return tuple(quoted_list)
78
+
79
+ def addQuoteMany(arg_list, query_template=None):
80
+ """
81
+ executemany용 파라미터 리스트를 쿼리 문자열로 변환
82
+
83
+ Args:
84
+ arg_list: [(val1, val2, val3), (val4, val5, val6), ...] 형태의 리스트
85
+ query_template: 쿼리 템플릿 (제공되면 개별 쿼리들 생성)
86
+
87
+ Returns:
88
+ str: 포매팅된 문자열
89
+ """
90
+ if not arg_list:
91
+ return ""
92
+
93
+ try:
94
+ if query_template is None:
95
+ # 기존 방식: VALUES 절용 포맷
96
+ formatted_rows = []
97
+ for row in arg_list:
98
+ if isinstance(row, (list, tuple)):
99
+ quoted_values = []
100
+ for value in row:
101
+ if value is None:
102
+ quoted_values.append("NULL")
103
+ elif isinstance(value, str):
104
+ escaped = value.replace("'", "''")
105
+ quoted_values.append(f"'{escaped}'")
106
+ else:
107
+ quoted_values.append("'" + str(value) + "'")
108
+
109
+ formatted_row = "({})".format(", ".join(quoted_values))
110
+ formatted_rows.append(formatted_row)
111
+
112
+ result = ", ".join(formatted_rows)
113
+ else:
114
+ # 개별 쿼리 생성 방식
115
+ queries = []
116
+ for row in arg_list:
117
+ if isinstance(row, (list, tuple)):
118
+ try:
119
+ quoted_values = []
120
+ for value in row:
121
+ if value is None:
122
+ quoted_values.append("NULL")
123
+ elif isinstance(value, str):
124
+ escaped = value.replace("'", "''")
125
+ quoted_values.append(f"'{escaped}'")
126
+ else:
127
+ quoted_values.append(str(value))
128
+
129
+ individual_query = query_template % tuple(quoted_values)
130
+ queries.append(individual_query)
131
+ except Exception as e:
132
+ logging.warning('[DB] addQuoteMany row format failed: %s' % e, extra={'id': 'DB002'})
133
+ queries.append(f"{query_template} [failed to format row: {row}]")
134
+
135
+ result = ";\n".join(queries)
136
+
137
+ return result
138
+
139
+ except Exception as e:
140
+ logging.warning('[DB] addQuoteMany failed: %s' % e, extra={'id': 'DB003'})
141
+ return f"[{len(arg_list)} rows]"
142
+
143
+
144
+
145
+ def neo4jQuery(query,paremeter):
146
+ neo4j_query = query
147
+
148
+ for key, value in paremeter.items():
149
+ placeholder = f"${key}"
150
+ replacement = f"'{str(value)}'"
151
+ neo4j_query = neo4j_query.replace(placeholder, replacement)
152
+
153
+ return neo4j_query
154
+
155
+ def sqliteQuery(query, paremeter):
156
+ sqlite_query = query
157
+
158
+ def quote(v):
159
+ if v is None: return "NULL"
160
+ if isinstance(v, (bytes, bytearray)): return "X'" + v.hex() + "'"
161
+ return "'" + str(v).replace("'", "''") + "'"
162
+
163
+ # 이름 기반: :name / @name / $name (단일 세트)
164
+ if isinstance(paremeter, dict):
165
+ for pfx in (':', '@', '$'):
166
+ for k, v in paremeter.items():
167
+ sqlite_query = sqlite_query.replace(f"{pfx}{k}", quote(v))
168
+ return sqlite_query
169
+
170
+ # 포지셔널 + executemany 분기
171
+ if isinstance(paremeter, (list, tuple)):
172
+ # ── executemany: [tuple|dict, ...] ──
173
+ if isinstance(paremeter, list) and paremeter and isinstance(paremeter[0], (tuple, dict)):
174
+ stmts = []
175
+ for params in paremeter:
176
+ sqlite_query = query
177
+ if isinstance(params, dict):
178
+ for pfx in (':', '@', '$'):
179
+ for k, v in params.items():
180
+ sqlite_query = sqlite_query.replace(f"{pfx}{k}", quote(v))
181
+ else: # tuple
182
+ for i, v in enumerate(params, 1):
183
+ sqlite_query = sqlite_query.replace(f"?{i}", quote(v))
184
+ for v in params:
185
+ sqlite_query = sqlite_query.replace('?', quote(v), 1)
186
+ stmts.append(sqlite_query)
187
+ return ";\n".join(stmts)
188
+
189
+ # ── 단일 포지셔널 세트 (tuple 또는 스칼라 list) ──
190
+ seq = paremeter if isinstance(paremeter, tuple) else tuple(paremeter)
191
+ for i, v in enumerate(seq, 1):
192
+ sqlite_query = sqlite_query.replace(f"?{i}", quote(v))
193
+ for v in seq:
194
+ sqlite_query = sqlite_query.replace('?', quote(v), 1)
195
+ return sqlite_query
196
+
197
+ return sqlite_query
198
+
199
+ def interceptor_db_con(fn, db_info, *args, **kwargs):
200
+ ctx = TraceContextManager.getLocalContext()
201
+ if not ctx:
202
+ return fn(*args, **kwargs)
203
+
204
+ start_time = DateUtil.nowSystem()
205
+ ctx.start_time = start_time
206
+
207
+ ctx.db_opening = True
208
+ try:
209
+ callback = fn(*args, **kwargs)
210
+ finally:
211
+ ctx.db_opening = False
212
+
213
+ if not kwargs:
214
+ kwargs = dict(
215
+ x.split('=') for x in re.sub(r'\s*=\s*', '=', args[0]).split())
216
+
217
+ db_type = db_info.get('type')
218
+
219
+ if db_type == "sqlite":
220
+ text = "sqlite:"
221
+
222
+ elif db_type == "neo4j":
223
+ text = "neo4j"
224
+ text += "@"
225
+ text += db_info.get('uri','')
226
+ text += "/"
227
+ text += db_info.get('user', '')
228
+
229
+ #psycopg3 에서 db_str을 통해 하나의 문자열로 전부 데이터가 오는 경우
230
+ elif db_info.get('db_con_stc', '') == 'completed':
231
+ text = db_info.get('db_str', '')
232
+
233
+ else:
234
+ text = '{}://'.format(db_type)
235
+ text += kwargs.get('user', '')
236
+ text += "@"
237
+ text += kwargs.get('host', kwargs.get('dsn', ''))
238
+ text += '/'
239
+
240
+ text += _stringify(kwargs.get('database', kwargs.get('db', kwargs.get('dbname', ''))))
241
+ ctx.active_dbc = text
242
+ ctx.lctx['dbc'] = text
243
+
244
+ ctx.active_dbc = 0
245
+
246
+ datas = [text]
247
+ ctx.elapsed = DateUtil.nowSystem() - start_time
248
+ async_sender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, datas)
249
+
250
+ return callback
251
+
252
+
253
+ def _build_dbc_from_db_info(db_info):
254
+ db_type = db_info.get('type', 'db')
255
+ if db_type == "sqlite":
256
+ return "sqlite:"
257
+ elif db_type == "neo4j":
258
+ return "neo4j@{}/{}".format(db_info.get('uri', ''), db_info.get('user', ''))
259
+ elif db_info.get('db_con_stc', '') == 'completed':
260
+ return db_info.get('db_str', '')
261
+ else:
262
+ return "{}://{}@{}/{}".format(
263
+ db_type,
264
+ db_info.get('user', ''),
265
+ db_info.get('host', db_info.get('dsn', '')),
266
+ db_info.get('database', db_info.get('db', db_info.get('dbname', '')))
267
+ )
268
+
269
+
270
+ def interceptor_db_execute(fn, db_info, *args, **kwargs):
271
+ ctx = TraceContextManager.getLocalContext()
272
+ if not ctx:
273
+ return fn(*args, **kwargs)
274
+ # sendDebugProfile(ctx, 'interceptor_db_execute step -1')
275
+ if not ctx.lctx.get('dbc') and db_info:
276
+ ctx.lctx['dbc'] = _build_dbc_from_db_info(db_info)
277
+ self = args[0]
278
+ db_type = db_info.get('type')
279
+ query = None
280
+ if db_type == "neo4j":
281
+ try:
282
+ query = neo4jQuery(args[1], kwargs)
283
+ except Exception as e:
284
+ logging.warning('[DB] neo4jQuery failed: %s' % e, extra={'id': 'DB004'})
285
+
286
+ elif db_type == "sqlite" and len(args) > 2:
287
+ try:
288
+ query = sqliteQuery(args[1],args[2])
289
+ except Exception as e:
290
+ logging.warning('[DB] sqliteQuery failed: %s' % e, extra={'id': 'DB005'})
291
+
292
+ else:
293
+ if (len(args) > 2 and
294
+ isinstance(args[2], (list, tuple)) and
295
+ len(args[2]) > 0 and
296
+ isinstance(args[2][0], (list, tuple))):
297
+ try:
298
+ query_str = args[1]
299
+ if isinstance(query_str, bytes):
300
+ query_str = query_str.decode()
301
+
302
+ query_upper = query_str.upper()
303
+
304
+ if 'VALUES' in query_upper:
305
+ # INSERT 처리
306
+ values_pos = query_upper.find('VALUES')
307
+ before_values = query_str[:values_pos + 6]
308
+ query = f"{before_values} {addQuoteMany(args[2])}"
309
+ else:
310
+ # UPDATE, DELETE 등 처리 - 개별 쿼리로 표시
311
+ query = addQuoteMany(args[2], query_str)
312
+ except Exception as e:
313
+ logging.warning('[DB] executemany query format failed: %s' % e, extra={'id': 'DB006'})
314
+
315
+ elif len(args) > 2 and type(args[2]) == dict and args[2]:
316
+ try:
317
+ query = args[1] % addQuoteDict(args[2])
318
+ except Exception as e:
319
+ logging.warning('[DB] addQuoteDict failed: %s' % e, extra={'id': 'DB007'})
320
+ elif len(args) > 2 and type(args[2]) in (list, tuple) and args[2]:
321
+ try:
322
+ query = args[1] % addQuoteList(args[2])
323
+ except Exception as e:
324
+ logging.warning('[DB] addQuoteList failed: %s' % e, extra={'id': 'DB008'})
325
+ try:
326
+ if not query:
327
+ raw_query = args[1]
328
+ if isinstance(raw_query, (bytes, bytearray)):
329
+ query = bytes(raw_query).decode('utf-8', errors='replace')
330
+ else:
331
+ query = raw_query
332
+ except Exception as e:
333
+ logging.warning('[DB] query decode failed: %s' % e, extra={'id': 'DB009'})
334
+ query = args[1]
335
+
336
+ if not query:
337
+ return fn(*args, **kwargs)
338
+
339
+ start_time = DateUtil.nowSystem()
340
+ ctx.start_time = start_time
341
+ ctx.active_sqlhash = query
342
+ try:
343
+ callback = fn(*args, **kwargs)
344
+ return callback
345
+ except Exception as e:
346
+ interceptor_step_error(e)
347
+ finally:
348
+ try:
349
+ if hasattr(args[0], 'rowcount'):
350
+ count = args[0].rowcount
351
+ else:
352
+ count = -1
353
+ except AttributeError:
354
+ count = -1
355
+
356
+ datas = [ctx.lctx.get('dbc', ''), query, str(count)]
357
+ ctx.elapsed = DateUtil.nowSystem() - start_time
358
+ async_sender.send_packet(PacketTypeEnum.TX_SQL, ctx, datas)
359
+
360
+ if (count is not None) and (count > -1):
361
+ desc = '{0}: {1}'.format('Fetch count', count)
362
+ datas = [' ', ' ', desc]
363
+ ctx.elapsed = 0
364
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
365
+
366
+ ctx.active_sqlhash = 0
367
+
368
+
369
+ def interceptor_db_close(fn, *args, **kwargs):
370
+ ctx = TraceContextManager.getLocalContext()
371
+ ctx.db_opening = False
372
+
373
+ if not conf.profile_dbc_close:
374
+ try:
375
+ return fn(*args, **kwargs)
376
+ except Exception as e:
377
+ interceptor_step_error(e)
378
+ finally:
379
+ return
380
+ start_time = DateUtil.nowSystem()
381
+ ctx.start_time = start_time
382
+
383
+ try:
384
+ callback = fn(*args, **kwargs)
385
+ return callback
386
+ except Exception as e:
387
+ interceptor_step_error(e)
388
+ finally:
389
+ text = 'DB: Close Connection.'
390
+ datas = [' ', ' ', text]
391
+ ctx.elapsed = DateUtil.nowSystem() - start_time
392
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
393
+
394
+
395
+ async def async_interceptor_db_con(fn, db_info, *args, **kwargs):
396
+ ctx = TraceContextManager.getLocalContext()
397
+ if not ctx:
398
+ return await fn(*args, **kwargs)
399
+
400
+ start_time = DateUtil.nowSystem()
401
+ ctx.start_time = start_time
402
+
403
+ ctx.db_opening = True
404
+ try:
405
+ callback = await fn(*args, **kwargs)
406
+ finally:
407
+ ctx.db_opening = False
408
+
409
+ if not kwargs:
410
+ kwargs = dict(
411
+ x.split('=') for x in re.sub(r'\s*=\s*', '=', args[0]).split())
412
+
413
+ db_type = db_info.get('type')
414
+
415
+ if db_type == "sqlite":
416
+ text = "sqlite:"
417
+
418
+ elif db_type == "postgresql":
419
+ if db_info.get('db_con_stc', '') == 'completed':
420
+ text = db_info.get('db_str', '')
421
+ else:
422
+ text = '{}://'.format(db_type)
423
+ text += kwargs.get('user', '')
424
+ text += "@"
425
+ text += kwargs.get('host', kwargs.get('dsn', ''))
426
+ text += '/'
427
+
428
+ text += _stringify(kwargs.get('database', kwargs.get('db', kwargs.get('dbname', ''))))
429
+ ctx.active_dbc = text
430
+ ctx.lctx['dbc'] = text
431
+
432
+ ctx.active_dbc = 0
433
+
434
+ datas = [text]
435
+ ctx.elapsed = DateUtil.nowSystem() - start_time
436
+ async_sender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, datas)
437
+
438
+ return callback
439
+
440
+
441
+ async def async_interceptor_db_execute(fn, db_info, *args, **kwargs):
442
+ ctx = TraceContextManager.getLocalContext()
443
+ if not ctx:
444
+ return await fn(*args, **kwargs)
445
+ if not ctx.lctx.get('dbc') and db_info:
446
+ ctx.lctx['dbc'] = _build_dbc_from_db_info(db_info)
447
+ self = args[0]
448
+ db_type = db_info.get('type')
449
+ query = None
450
+ if db_type == "neo4j":
451
+ try:
452
+ query = neo4jQuery(args[1], kwargs)
453
+ except Exception as e:
454
+ logging.warning('[DB] neo4jQuery async failed: %s' % e, extra={'id': 'DB004'})
455
+
456
+ elif db_type == "sqlite" and len(args) > 2:
457
+ try:
458
+ query = sqliteQuery(args[1], args[2])
459
+ except Exception as e:
460
+ logging.warning('[DB] sqliteQuery async failed: %s' % e, extra={'id': 'DB005'})
461
+
462
+ else:
463
+ if (len(args) > 2 and
464
+ isinstance(args[2], (list, tuple)) and
465
+ len(args[2]) > 0 and
466
+ isinstance(args[2][0], (list, tuple))):
467
+ try:
468
+ # VALUES 절을 찾아서 전체를 교체
469
+ query_str = args[1]
470
+ if isinstance(query_str, bytes):
471
+ query_str = query_str.decode()
472
+
473
+ # VALUES (...) 부분을 찾아서 교체
474
+ query_upper = query_str.upper()
475
+ if 'VALUES' in query_upper:
476
+ values_pos = query_upper.find('VALUES')
477
+ before_values = query_str[:values_pos + 6] # "VALUES" 까지
478
+ query = f"{before_values} {addQuoteMany(args[2])}"
479
+ else:
480
+ # VALUES가 없으면 단순 치환 시도
481
+ query = query_str.replace('%s', addQuoteMany(args[2]), 1)
482
+ except Exception as e:
483
+ logging.warning('[DB] executemany query format async failed: %s' % e, extra={'id': 'DB006'})
484
+
485
+ elif len(args) > 2 and type(args[2]) == dict and args[2]:
486
+ try:
487
+ query = args[1] % addQuoteDict(args[2])
488
+ except Exception as e:
489
+ logging.warning('[DB] addQuoteDict async failed: %s' % e, extra={'id': 'DB007'})
490
+ elif len(args) > 2 and type(args[2]) in (list, tuple) and args[2]:
491
+ try:
492
+ query = args[1] % addQuoteList(args[2])
493
+ except Exception as e:
494
+ logging.warning('[DB] addQuoteList async failed: %s' % e, extra={'id': 'DB008'})
495
+
496
+ try:
497
+ if not query:
498
+ if hasattr(args[1], 'decode'):
499
+ query = args[1].decode()
500
+ else:
501
+ query = args[1]
502
+ except Exception as e:
503
+ logging.warning('[DB] query decode async failed: %s' % e, extra={'id': 'DB009'})
504
+ query = args[1]
505
+
506
+ if not query:
507
+ return await fn(*args, **kwargs)
508
+
509
+ start_time = DateUtil.nowSystem()
510
+ ctx.start_time = start_time
511
+ ctx.active_sqlhash = query
512
+
513
+ try:
514
+ callback = await fn(*args, **kwargs)
515
+ return callback
516
+ except Exception as e:
517
+ interceptor_step_error(e)
518
+ raise
519
+ finally:
520
+ try:
521
+ if hasattr(args[0], 'rowcount'):
522
+ count = args[0].rowcount
523
+ else:
524
+ count = -1
525
+ except AttributeError:
526
+ count = -1
527
+
528
+ datas = [ctx.lctx.get('dbc', ''), query, str(count)]
529
+ ctx.elapsed = DateUtil.nowSystem() - start_time
530
+ async_sender.send_packet(PacketTypeEnum.TX_SQL, ctx, datas)
531
+
532
+ if (count is not None) and (count > -1):
533
+ desc = '{0}: {1}'.format('Fetch count', count)
534
+ datas = [' ', ' ', desc]
535
+ ctx.elapsed = 0
536
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
537
+
538
+ ctx.active_sqlhash = 0
539
+
540
+
541
+ async def async_interceptor_db_close(fn, *args, **kwargs):
542
+ ctx = TraceContextManager.getLocalContext()
543
+ ctx.db_opening = False
544
+
545
+ if not conf.profile_dbc_close:
546
+ try:
547
+ return await fn(*args, **kwargs)
548
+ except Exception as e:
549
+ interceptor_step_error(e)
550
+ raise
551
+ finally:
552
+ return
553
+
554
+ start_time = DateUtil.nowSystem()
555
+ ctx.start_time = start_time
556
+
557
+ try:
558
+ callback = await fn(*args, **kwargs)
559
+ return callback
560
+ except Exception as e:
561
+ interceptor_step_error(e)
562
+ raise
563
+ finally:
564
+ text = 'DB: Close Connection.'
565
+ datas = [' ', ' ', text]
566
+ ctx.elapsed = DateUtil.nowSystem() - start_time
567
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
568
+
569
+
570
+ def interceptor_pool_get(db_info):
571
+ ctx = TraceContextManager.getLocalContext()
572
+ if not ctx:
573
+ return
574
+
575
+ start_time = DateUtil.nowSystem()
576
+
577
+ db_type = db_info.get('type', 'db')
578
+ text = f"{db_type}://{db_info.get('user', '')}@{db_info.get('host', '')}/{db_info.get('dbname', '')}"
579
+
580
+ ctx.active_dbc = text
581
+ ctx.lctx['dbc'] = text
582
+ ctx.active_dbc = 0
583
+
584
+ datas = [text]
585
+ ctx.elapsed = DateUtil.nowSystem() - start_time
586
+
587
+ async_sender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, datas)
588
+
589
+
590
+ def interceptor_pool_release():
591
+ ctx = TraceContextManager.getLocalContext()
592
+ if not ctx or not conf.profile_dbc_close:
593
+ return
594
+
595
+ start_time = DateUtil.nowSystem()
596
+ text = 'DB: Close Connection.'
597
+ datas = [' ', ' ', text]
598
+ ctx.elapsed = DateUtil.nowSystem() - start_time
599
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
600
+
601
+
602
+ async def async_interceptor_pool_get(db_info):
603
+ ctx = TraceContextManager.getLocalContext()
604
+ if not ctx:
605
+ return
606
+
607
+ start_time = DateUtil.nowSystem()
608
+
609
+ db_type = db_info.get('type', 'db')
610
+ text = f"{db_type}://{db_info.get('user', '')}@{db_info.get('host', '')}/{db_info.get('dbname', '')}"
611
+
612
+ ctx.active_dbc = text
613
+ ctx.lctx['dbc'] = text
614
+ ctx.active_dbc = 0
615
+
616
+ datas = [text]
617
+ ctx.elapsed = DateUtil.nowSystem() - start_time
618
+
619
+ async_sender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, datas)
620
+
621
+ async def async_interceptor_pool_release():
622
+ ctx = TraceContextManager.getLocalContext()
623
+ if not ctx or not conf.profile_dbc_close:
624
+ return
625
+
626
+ start_time = DateUtil.nowSystem()
627
+ text = 'DB: Close Connection.'
628
+ datas = [' ', ' ', text]
629
+ ctx.elapsed = DateUtil.nowSystem() - start_time
630
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
File without changes
@@ -0,0 +1,78 @@
1
+ from whatap.trace import get_dict
2
+ from whatap.trace.trace_handler import trace_handler
3
+ from whatap.trace.trace_error import \
4
+ interceptor_step_error
5
+ from whatap.trace.trace_context_manager import TraceContextManager
6
+ import whatap.net.async_sender as async_sender
7
+ from whatap.net.packet_type_enum import PacketTypeEnum
8
+ from whatap.util.date_util import DateUtil
9
+ import traceback
10
+
11
+ def intercept_connect(fn, *args, **kwargs):
12
+ ctx = TraceContextManager.getLocalContext()
13
+ start_time = DateUtil.nowSystem()
14
+ ctx.start_time = start_time
15
+
16
+ try:
17
+ callback = fn(*args, **kwargs)
18
+ return callback
19
+ except Exception as e:
20
+ interceptor_step_error(e)
21
+ finally:
22
+ text = None
23
+
24
+ if kwargs:
25
+ text = 'smtp://'
26
+ text += kwargs.get('host')
27
+ text += ':'
28
+ text += str(kwargs.get('port', 0))
29
+ elif args and len(args) > 2:
30
+ text = 'smtplilb.SMTP.connect('
31
+ text += args[1]
32
+ text += ','
33
+ text += str(args[2])
34
+ text += ')'
35
+ if text:
36
+ payloads = [text, '']
37
+ ctx.elapsed = DateUtil.nowSystem() - start_time
38
+ async_sender.send_packet(PacketTypeEnum.TX_METHOD, ctx, payloads)
39
+
40
+ def intercept_method(method, fn, *args, **kwargs):
41
+ ctx = TraceContextManager.getLocalContext()
42
+
43
+ start_time = DateUtil.nowSystem()
44
+ ctx.start_time = start_time
45
+
46
+ try:
47
+ callback = fn(*args, **kwargs)
48
+ return callback
49
+ except Exception as e:
50
+ interceptor_step_error(e)
51
+ finally:
52
+ payloads = [method, '']
53
+ ctx.elapsed = DateUtil.nowSystem() - start_time
54
+ async_sender.send_packet(PacketTypeEnum.TX_METHOD, ctx, payloads)
55
+
56
+ def instrument_smtp(module):
57
+ def wrapper(fn):
58
+ @trace_handler(fn)
59
+ def trace(*args, **kwargs):
60
+ callback = intercept_connect(fn, *args, **kwargs)
61
+ return callback
62
+
63
+ return trace
64
+
65
+ if hasattr(module, 'SMTP') and hasattr(module.SMTP, 'connect'):
66
+ module.SMTP.connect= wrapper(module.SMTP.connect)
67
+
68
+ def wrapper(fn, method):
69
+ @trace_handler(fn)
70
+ def trace(*args, **kwargs):
71
+ callback = intercept_method(method, fn, *args, **kwargs)
72
+ return callback
73
+
74
+ return trace
75
+
76
+ for method in ['sendmail',]:
77
+ if hasattr(module, 'SMTP') and hasattr(module.SMTP, method):
78
+ setattr(module.SMTP, method, wrapper(getattr(module.SMTP, method), 'smtplib.SMTP.{}'.format(method)))