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.
- whatap/LICENSE +0 -0
- whatap/README.rst +49 -0
- whatap/__init__.py +923 -0
- whatap/__main__.py +4 -0
- whatap/agent/darwin/amd64/whatap_python +0 -0
- whatap/agent/darwin/arm64/whatap_python +0 -0
- whatap/agent/linux/amd64/whatap_python +0 -0
- whatap/agent/linux/arm64/whatap_python +0 -0
- whatap/agent/windows/whatap_python.exe +0 -0
- whatap/bootstrap/__init__.py +0 -0
- whatap/bootstrap/sitecustomize.py +19 -0
- whatap/build.py +4 -0
- whatap/conf/__init__.py +0 -0
- whatap/conf/configuration.py +280 -0
- whatap/conf/configure.py +105 -0
- whatap/conf/license.py +49 -0
- whatap/control/__init__.py +0 -0
- whatap/counter/__init__.py +14 -0
- whatap/counter/counter_manager.py +45 -0
- whatap/counter/tasks/__init__.py +3 -0
- whatap/counter/tasks/base_task.py +26 -0
- whatap/counter/tasks/llm_evaluator_task.py +501 -0
- whatap/counter/tasks/llm_log_sink_task.py +309 -0
- whatap/counter/tasks/llm_stat_task.py +78 -0
- whatap/counter/tasks/openfiledescriptor.py +67 -0
- whatap/io/__init__.py +1 -0
- whatap/io/data_inputx.py +161 -0
- whatap/io/data_outputx.py +262 -0
- whatap/llm/__init__.py +17 -0
- whatap/llm/definitions.py +43 -0
- whatap/llm/evaluators/__init__.py +136 -0
- whatap/llm/evaluators/base.py +114 -0
- whatap/llm/evaluators/builtins/__init__.py +91 -0
- whatap/llm/evaluators/builtins/answer_relevance.py +46 -0
- whatap/llm/evaluators/builtins/combined_judge.py +271 -0
- whatap/llm/evaluators/builtins/factuality.py +71 -0
- whatap/llm/evaluators/builtins/hallucination.py +97 -0
- whatap/llm/evaluators/builtins/llm_judge.py +516 -0
- whatap/llm/evaluators/builtins/pii_leak.py +214 -0
- whatap/llm/evaluators/builtins/prompt_injection.py +71 -0
- whatap/llm/evaluators/builtins/toxicity.py +53 -0
- whatap/llm/evaluators/builtins/url_scan.py +194 -0
- whatap/llm/evaluators/registry.py +192 -0
- whatap/llm/evaluators/sampler.py +83 -0
- whatap/llm/evaluators/scope.py +334 -0
- whatap/llm/features.py +66 -0
- whatap/llm/log_sink_packs/__init__.py +9 -0
- whatap/llm/log_sink_packs/llm_input_message.py +16 -0
- whatap/llm/log_sink_packs/llm_log_sink_pack.py +72 -0
- whatap/llm/log_sink_packs/llm_output_message.py +19 -0
- whatap/llm/log_sink_packs/llm_step_eval_status.py +94 -0
- whatap/llm/log_sink_packs/llm_step_status.py +118 -0
- whatap/llm/log_sink_packs/llm_system_message.py +16 -0
- whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
- whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
- whatap/llm/log_sink_packs/llm_tx_status.py +108 -0
- whatap/llm/pricing.py +236 -0
- whatap/llm/prompt_meta.py +288 -0
- whatap/llm/providers/__init__.py +0 -0
- whatap/llm/providers/anthropic/__init__.py +37 -0
- whatap/llm/providers/anthropic/messages/__init__.py +0 -0
- whatap/llm/providers/anthropic/messages/messages.py +70 -0
- whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
- whatap/llm/providers/anthropic/messages/messages_extractor.py +126 -0
- whatap/llm/providers/interceptor.py +182 -0
- whatap/llm/providers/openai/__init__.py +133 -0
- whatap/llm/providers/openai/chat/__init__.py +0 -0
- whatap/llm/providers/openai/chat/chat.py +82 -0
- whatap/llm/providers/openai/chat/chat_context.py +78 -0
- whatap/llm/providers/openai/chat/chat_extractor.py +127 -0
- whatap/llm/providers/openai/completions/__init__.py +0 -0
- whatap/llm/providers/openai/completions/completions.py +70 -0
- whatap/llm/providers/openai/completions/completions_context.py +31 -0
- whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
- whatap/llm/providers/openai/content_parser.py +41 -0
- whatap/llm/providers/openai/embeddings/__init__.py +0 -0
- whatap/llm/providers/openai/embeddings/embeddings.py +59 -0
- whatap/llm/providers/openai/embeddings/embeddings_context.py +25 -0
- whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
- whatap/llm/providers/openai/responses/__init__.py +0 -0
- whatap/llm/providers/openai/responses/responses.py +70 -0
- whatap/llm/providers/openai/responses/responses_context.py +88 -0
- whatap/llm/providers/openai/responses/responses_extractor.py +126 -0
- whatap/llm/providers/stream_accumulator.py +73 -0
- whatap/llm/stats/__init__.py +35 -0
- whatap/llm/stats/active_stat.py +86 -0
- whatap/llm/stats/answer_relevance_eval_stat.py +10 -0
- whatap/llm/stats/api_status_stat.py +35 -0
- whatap/llm/stats/base_stat.py +107 -0
- whatap/llm/stats/combined_judge_eval_stat.py +11 -0
- whatap/llm/stats/error_stat.py +59 -0
- whatap/llm/stats/eval_stat.py +225 -0
- whatap/llm/stats/factuality_eval_stat.py +10 -0
- whatap/llm/stats/feature_stat.py +104 -0
- whatap/llm/stats/finish_stat.py +105 -0
- whatap/llm/stats/hallucination_eval_stat.py +10 -0
- whatap/llm/stats/meter.py +18 -0
- whatap/llm/stats/perf_stat.py +117 -0
- whatap/llm/stats/pii_leak_eval_stat.py +12 -0
- whatap/llm/stats/prompt_injection_eval_stat.py +10 -0
- whatap/llm/stats/token_usage_stat.py +133 -0
- whatap/llm/stats/toxicity_eval_stat.py +10 -0
- whatap/llm/stats/url_scan_eval_stat.py +12 -0
- whatap/net/__init__.py +0 -0
- whatap/net/async_sender.py +107 -0
- whatap/net/packet_enum.py +44 -0
- whatap/net/packet_type_enum.py +31 -0
- whatap/net/param_def.py +69 -0
- whatap/net/stackhelper.py +87 -0
- whatap/net/udp_session.py +394 -0
- whatap/net/udp_thread.py +54 -0
- whatap/pack/__init__.py +0 -0
- whatap/pack/logSinkPack.py +77 -0
- whatap/pack/pack.py +34 -0
- whatap/pack/pack_enum.py +41 -0
- whatap/pack/tagCountPack.py +61 -0
- whatap/scripts/__init__.py +208 -0
- whatap/trace/__init__.py +12 -0
- whatap/trace/mod/__init__.py +0 -0
- whatap/trace/mod/amqp/__init__.py +0 -0
- whatap/trace/mod/amqp/kombu.py +122 -0
- whatap/trace/mod/amqp/pika.py +62 -0
- whatap/trace/mod/application/__init__.py +0 -0
- whatap/trace/mod/application/bottle.py +34 -0
- whatap/trace/mod/application/celery.py +81 -0
- whatap/trace/mod/application/cherrypy.py +30 -0
- whatap/trace/mod/application/django.py +287 -0
- whatap/trace/mod/application/django_asgi.py +266 -0
- whatap/trace/mod/application/django_py3.py +251 -0
- whatap/trace/mod/application/fastapi/__init__.py +31 -0
- whatap/trace/mod/application/fastapi/endpoint.py +73 -0
- whatap/trace/mod/application/fastapi/exception_log.py +63 -0
- whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
- whatap/trace/mod/application/fastapi/scope.py +115 -0
- whatap/trace/mod/application/fastapi/transaction.py +67 -0
- whatap/trace/mod/application/flask.py +52 -0
- whatap/trace/mod/application/frappe.py +224 -0
- whatap/trace/mod/application/graphql.py +170 -0
- whatap/trace/mod/application/nameko.py +39 -0
- whatap/trace/mod/application/odoo.py +63 -0
- whatap/trace/mod/application/starlette.py +126 -0
- whatap/trace/mod/application/tornado.py +163 -0
- whatap/trace/mod/application/wsgi.py +195 -0
- whatap/trace/mod/database/__init__.py +0 -0
- whatap/trace/mod/database/cxoracle.py +49 -0
- whatap/trace/mod/database/mongo.py +169 -0
- whatap/trace/mod/database/mysql.py +80 -0
- whatap/trace/mod/database/neo4j.py +90 -0
- whatap/trace/mod/database/psycopg2.py +45 -0
- whatap/trace/mod/database/psycopg3.py +359 -0
- whatap/trace/mod/database/redis.py +122 -0
- whatap/trace/mod/database/sqlalchemy.py +213 -0
- whatap/trace/mod/database/sqlite3.py +130 -0
- whatap/trace/mod/database/util.py +630 -0
- whatap/trace/mod/email/__init__.py +0 -0
- whatap/trace/mod/email/smtp.py +78 -0
- whatap/trace/mod/httpc/__init__.py +0 -0
- whatap/trace/mod/httpc/django.py +31 -0
- whatap/trace/mod/httpc/httplib.py +70 -0
- whatap/trace/mod/httpc/httpx.py +62 -0
- whatap/trace/mod/httpc/requests.py +20 -0
- whatap/trace/mod/httpc/urllib3.py +27 -0
- whatap/trace/mod/httpc/util.py +388 -0
- whatap/trace/mod/logging.py +161 -0
- whatap/trace/mod/plugin.py +84 -0
- whatap/trace/mod/standalone/__init__.py +0 -0
- whatap/trace/mod/standalone/multiple.py +293 -0
- whatap/trace/mod/standalone/single.py +135 -0
- whatap/trace/simple_trace_context.py +18 -0
- whatap/trace/trace_context.py +212 -0
- whatap/trace/trace_context_manager.py +244 -0
- whatap/trace/trace_error.py +84 -0
- whatap/trace/trace_handler.py +89 -0
- whatap/trace/trace_import.py +91 -0
- whatap/trace/trace_module_definition.py +156 -0
- whatap/util/__init__.py +0 -0
- whatap/util/bit_util.py +49 -0
- whatap/util/cardinality/__init__.py +0 -0
- whatap/util/cardinality/hyperloglog.py +84 -0
- whatap/util/cardinality/murmurhash.py +20 -0
- whatap/util/cardinality/registerset.py +60 -0
- whatap/util/compare_util.py +19 -0
- whatap/util/date_util.py +55 -0
- whatap/util/debug_util.py +73 -0
- whatap/util/escape_literal_sql.py +233 -0
- whatap/util/frame_util.py +20 -0
- whatap/util/hash_util.py +103 -0
- whatap/util/hexa32.py +66 -0
- whatap/util/int_set.py +199 -0
- whatap/util/ip_util.py +63 -0
- whatap/util/keygen.py +11 -0
- whatap/util/linked_list.py +113 -0
- whatap/util/linked_map.py +359 -0
- whatap/util/metering_util.py +103 -0
- whatap/util/request_double_queue.py +68 -0
- whatap/util/request_queue.py +60 -0
- whatap/util/string_util.py +20 -0
- whatap/util/throttle_util.py +99 -0
- whatap/util/userid_util.py +134 -0
- whatap/value/__init__.py +1 -0
- whatap/value/blob_value.py +38 -0
- whatap/value/boolean_value.py +33 -0
- whatap/value/decimal_value.py +36 -0
- whatap/value/double_summary.py +86 -0
- whatap/value/double_value.py +33 -0
- whatap/value/float_array.py +42 -0
- whatap/value/float_value.py +34 -0
- whatap/value/int_array.py +42 -0
- whatap/value/ip4_value.py +50 -0
- whatap/value/list_value.py +105 -0
- whatap/value/long_array.py +44 -0
- whatap/value/long_summary.py +83 -0
- whatap/value/map_value.py +154 -0
- whatap/value/null_value.py +21 -0
- whatap/value/number_value.py +33 -0
- whatap/value/summary_value.py +39 -0
- whatap/value/text_array.py +58 -0
- whatap/value/text_hash_value.py +37 -0
- whatap/value/text_value.py +43 -0
- whatap/value/value.py +26 -0
- whatap/value/value_enum.py +80 -0
- whatap/whatap.conf +14 -0
- whatap_python-2.1.0.dist-info/METADATA +87 -0
- whatap_python-2.1.0.dist-info/RECORD +227 -0
- whatap_python-2.1.0.dist-info/WHEEL +5 -0
- whatap_python-2.1.0.dist-info/entry_points.txt +6 -0
- whatap_python-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
|
|
2
|
+
from whatap.conf.configure import Configure as conf
|
|
3
|
+
from whatap.trace.trace_context_manager import TraceContextManager
|
|
4
|
+
from whatap.trace.trace_context import TraceContext
|
|
5
|
+
from whatap.trace.trace_error import interceptor_error
|
|
6
|
+
from whatap.trace.mod.application.wsgi import \
|
|
7
|
+
isIgnore, start_interceptor, end_interceptor
|
|
8
|
+
from whatap.util.hash_util import HashUtil as hash_util
|
|
9
|
+
from whatap.util.userid_util import UseridUtil
|
|
10
|
+
from whatap.util.date_util import DateUtil
|
|
11
|
+
from whatap.util.hexa32 import Hexa32 as hexa32
|
|
12
|
+
from whatap.util.keygen import KeyGen
|
|
13
|
+
import whatap.util.throttle_util as throttle_util
|
|
14
|
+
import whatap.net.async_sender as async_sender
|
|
15
|
+
from whatap.net.packet_type_enum import PacketTypeEnum
|
|
16
|
+
from whatap import logging
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from functools import wraps
|
|
20
|
+
import logging as logging_module
|
|
21
|
+
import datetime, sys
|
|
22
|
+
|
|
23
|
+
SCOPE_ARGS_LENGTH = 2
|
|
24
|
+
HEADER = 'headers'
|
|
25
|
+
PATH = 'path'
|
|
26
|
+
USER_AGENT = 'user-agent'
|
|
27
|
+
REFERER = 'referer'
|
|
28
|
+
CLIENT = 'client'
|
|
29
|
+
COOKIE = 'cookie'
|
|
30
|
+
HOST = 'host'
|
|
31
|
+
QUERY_STRING = 'query_string'
|
|
32
|
+
WHATAP_CTX = '__whatap__ctx'
|
|
33
|
+
|
|
34
|
+
logger = logging_module.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
def blocking_handler_async():
|
|
37
|
+
def handler(func):
|
|
38
|
+
from django.http import HttpResponse
|
|
39
|
+
async def wrapper(*args, **kwargs):
|
|
40
|
+
scope, headers = parseHeaders(args)
|
|
41
|
+
|
|
42
|
+
if conf.throttle_enabled and scope and headers:
|
|
43
|
+
remote_ip = parseRemoteAddr(scope, headers)
|
|
44
|
+
path = scope.get(PATH)
|
|
45
|
+
if throttle_util.isblocking(remote_ip, path):
|
|
46
|
+
if conf.reject_event_enabled:
|
|
47
|
+
ctx = TraceContextManager.getLocalContext()
|
|
48
|
+
if not ctx:
|
|
49
|
+
ctx = TraceContext()
|
|
50
|
+
|
|
51
|
+
throttle_util.sendrejectevent(ctx, path, remote_ip)
|
|
52
|
+
if conf.throttle_blocked_forward:
|
|
53
|
+
response = HttpResponse(status=302)
|
|
54
|
+
response['Location'] = conf.throttle_blocked_forward
|
|
55
|
+
return response
|
|
56
|
+
status = '403 Forbidden'
|
|
57
|
+
start_response= args[2]
|
|
58
|
+
start_response(status, [])
|
|
59
|
+
response = HttpResponse(content=conf.throttle_blocked_message, status=403)
|
|
60
|
+
return response
|
|
61
|
+
return await func(*args, **kwargs)
|
|
62
|
+
|
|
63
|
+
return wrapper
|
|
64
|
+
|
|
65
|
+
return handler
|
|
66
|
+
|
|
67
|
+
def trace_handler_async(fn, start=False):
|
|
68
|
+
def handler(func):
|
|
69
|
+
@wraps(func)
|
|
70
|
+
async def wrapper(*args, **kwargs):
|
|
71
|
+
ctx = TraceContextManager.getLocalContext()
|
|
72
|
+
if not start and not ctx:
|
|
73
|
+
return fn(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
callback = await func(*args, **kwargs)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
ctx = TraceContextManager.getLocalContext()
|
|
79
|
+
if ctx and ctx.error_step ==e:
|
|
80
|
+
ctx.error_step= None
|
|
81
|
+
raise e
|
|
82
|
+
logging.debug(e, extra={'id': 'WA917'}, exc_info=True)
|
|
83
|
+
print(e, dict(extra={'id': 'WA917'}, exc_info=True))
|
|
84
|
+
import traceback
|
|
85
|
+
traceback.print_exc()
|
|
86
|
+
|
|
87
|
+
return await fn(*args, **kwargs)
|
|
88
|
+
else:
|
|
89
|
+
if ctx and ctx.error_step:
|
|
90
|
+
e = ctx.error_step
|
|
91
|
+
ctx.error_step = None
|
|
92
|
+
raise e
|
|
93
|
+
return callback
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
return handler
|
|
98
|
+
|
|
99
|
+
def instrument_handlers_async(module):
|
|
100
|
+
def get_response_wrapper(fn):
|
|
101
|
+
@trace_handler_async(fn, start=True)
|
|
102
|
+
async def trace(*args, **kwargs):
|
|
103
|
+
|
|
104
|
+
print('starting wrapped:', args, kwargs)
|
|
105
|
+
# request = get_request_fromargs(args)
|
|
106
|
+
startedAt = datetime.datetime.Now()
|
|
107
|
+
callback = await fn(*args, **kwargs)
|
|
108
|
+
|
|
109
|
+
print('elapsed:', datetime.datetime.now() - startedAt )
|
|
110
|
+
return callback
|
|
111
|
+
print('wrapper called')
|
|
112
|
+
return trace
|
|
113
|
+
|
|
114
|
+
if hasattr(module, 'BaseHandler') and hasattr(module.BaseHandler, 'get_response_async'):
|
|
115
|
+
module.BaseHandler.get_response_async = get_response_wrapper(module.BaseHandler.get_response_async)
|
|
116
|
+
|
|
117
|
+
def parseHeaders(args):
|
|
118
|
+
headers = {}
|
|
119
|
+
if len(args) > SCOPE_ARGS_LENGTH:
|
|
120
|
+
scope = args[1]
|
|
121
|
+
if HEADER in scope:
|
|
122
|
+
for arg in scope[HEADER]:
|
|
123
|
+
if arg and len(arg) == 2 and arg[0] and arg[1]:
|
|
124
|
+
headers[arg[0].decode('utf8').lower()] = arg[1].decode('utf8')
|
|
125
|
+
|
|
126
|
+
return scope, headers
|
|
127
|
+
return None, None
|
|
128
|
+
|
|
129
|
+
def parseRemoteAddr(scope, headers):
|
|
130
|
+
remoteIp = ''
|
|
131
|
+
if CLIENT in scope:
|
|
132
|
+
remoteIp = scope.get(CLIENT)[0]
|
|
133
|
+
if conf.trace_http_client_ip_header_key:
|
|
134
|
+
header_val = headers.get(conf.trace_http_client_ip_header_key, '')
|
|
135
|
+
remoteIp = header_val.split(',')[0].strip()
|
|
136
|
+
|
|
137
|
+
return remoteIp
|
|
138
|
+
|
|
139
|
+
def getUserId(scope, headers, defValue):
|
|
140
|
+
try:
|
|
141
|
+
if conf.user_header_ticket:
|
|
142
|
+
ticket = headers.get(conf.user_header_ticket, "")
|
|
143
|
+
if ticket:
|
|
144
|
+
return hash_util.hashFromString(ticket), ticket
|
|
145
|
+
return 0, ""
|
|
146
|
+
cookie = headers.get(COOKIE, "")
|
|
147
|
+
if cookie:
|
|
148
|
+
if len(cookie) >= conf.trace_user_cookie_limit :
|
|
149
|
+
return hash_util.hashFromString(defValue) if defValue else 0, defValue or ""
|
|
150
|
+
|
|
151
|
+
x1 = cookie.find(UseridUtil.WHATAP_R)
|
|
152
|
+
if x1 >= 0:
|
|
153
|
+
x2 = cookie.find(';', x1)
|
|
154
|
+
if x2 > 0:
|
|
155
|
+
value = cookie[x1 + len(UseridUtil.WHATAP_R) + 1: x2]
|
|
156
|
+
else:
|
|
157
|
+
value = cookie[x1 + len(UseridUtil.WHATAP_R) + 1:]
|
|
158
|
+
return hexa32.toLong32(value), value
|
|
159
|
+
userid = KeyGen.next()
|
|
160
|
+
return userid, hexa32.toString32(userid)
|
|
161
|
+
except Exception:
|
|
162
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
163
|
+
logger.debug("A502", 10, str(exc_value))
|
|
164
|
+
return hash_util.hashFromString(defValue) if defValue else 0, defValue or ""
|
|
165
|
+
|
|
166
|
+
async def interceptor_async(fn, *args, **kwargs):
|
|
167
|
+
|
|
168
|
+
scope, headers = parseHeaders(args)
|
|
169
|
+
if scope == None and headers == None:
|
|
170
|
+
return await fn(*args, **kwargs)
|
|
171
|
+
|
|
172
|
+
ctx = TraceContext()
|
|
173
|
+
ctx.host = headers.get(HOST, '').split(':')[0]
|
|
174
|
+
ctx.service_name = scope.get(PATH)
|
|
175
|
+
|
|
176
|
+
ctx.remoteIp = parseRemoteAddr(scope, headers)
|
|
177
|
+
|
|
178
|
+
ctx.userAgentString = headers.get(USER_AGENT, '')
|
|
179
|
+
ctx.referer = headers.get(REFERER, '')
|
|
180
|
+
|
|
181
|
+
if conf.trace_user_enabled:
|
|
182
|
+
if conf.trace_user_using_ip:
|
|
183
|
+
ctx.userid = parseRemoteAddr(scope, headers)
|
|
184
|
+
else:
|
|
185
|
+
ctx.userid, ctx._rawuserid = getUserId(scope, headers, ctx.remoteIp)
|
|
186
|
+
|
|
187
|
+
mstt = headers.get(
|
|
188
|
+
conf._trace_mtrace_caller_key.lower().replace('-', '_'), '')
|
|
189
|
+
|
|
190
|
+
if mstt:
|
|
191
|
+
ctx.setTransfer(mstt)
|
|
192
|
+
if conf.stat_mtrace_enabled:
|
|
193
|
+
val = headers.get(
|
|
194
|
+
conf._trace_mtrace_info_key.lower().replace('-', '_'), '')
|
|
195
|
+
if val and len(val):
|
|
196
|
+
ctx.setTransferInfo(val)
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
myid = headers.get(
|
|
200
|
+
conf._trace_mtrace_callee_key.lower().replace('-', '_'), '')
|
|
201
|
+
if myid:
|
|
202
|
+
ctx.setTxid(myid)
|
|
203
|
+
caller_poid = headers.get(
|
|
204
|
+
conf._trace_mtrace_caller_poid_key.upper().replace('-', '_'), '')
|
|
205
|
+
|
|
206
|
+
if caller_poid:
|
|
207
|
+
ctx.mcaller_poid = caller_poid
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
if isIgnore(ctx.service_name):
|
|
211
|
+
ctx.is_ignored = True
|
|
212
|
+
return fn(*args, **kwargs)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
start_interceptor(ctx)
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
scope[WHATAP_CTX]= ctx
|
|
220
|
+
callback = await fn(*args, **kwargs)
|
|
221
|
+
query_string = str(scope.get(QUERY_STRING, ''))
|
|
222
|
+
if query_string:
|
|
223
|
+
ctx.service_name += '?{}'.format(query_string)
|
|
224
|
+
|
|
225
|
+
if ctx.service_name.find('.') > -1 and ctx.service_name.split('.')[
|
|
226
|
+
1] in conf.web_static_content_extensions:
|
|
227
|
+
ctx.isStaticContents = 'true'
|
|
228
|
+
|
|
229
|
+
if getattr(callback, 'status_code', None):
|
|
230
|
+
status_code = callback.status_code
|
|
231
|
+
errors = [callback.reason_phrase, callback.__class__.__name__]
|
|
232
|
+
interceptor_error(status_code, errors)
|
|
233
|
+
|
|
234
|
+
if conf.profile_http_header_enabled:
|
|
235
|
+
keys = []
|
|
236
|
+
for key, value in headers.items():
|
|
237
|
+
keys.append(key)
|
|
238
|
+
keys.sort()
|
|
239
|
+
|
|
240
|
+
text = ''
|
|
241
|
+
for key in keys:
|
|
242
|
+
text += '{}={}\n'.format(key.lower(),
|
|
243
|
+
headers[key])
|
|
244
|
+
|
|
245
|
+
datas = ['HTTP-HEADERS', 'HTTP-HEADERS', text]
|
|
246
|
+
ctx.start_time = DateUtil.nowSystem()
|
|
247
|
+
async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
|
|
248
|
+
finally:
|
|
249
|
+
if ctx:
|
|
250
|
+
end_interceptor(ctx=ctx)
|
|
251
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""FastAPI / Starlette ASGI 계측 패키지.
|
|
2
|
+
|
|
3
|
+
외부 공개 심볼 — 변경 금지:
|
|
4
|
+
- instrument_applications (trace_module_definition.py)
|
|
5
|
+
- instrument (trace_module_definition.py)
|
|
6
|
+
- instrument_util (trace_module_definition.py)
|
|
7
|
+
- interceptor_error_log (whatap/__init__.py)
|
|
8
|
+
|
|
9
|
+
모듈 구성:
|
|
10
|
+
scope scope/headers 파싱 + ctx 주입 (parse_headers, client_ip,
|
|
11
|
+
resolve_user_id, populate_ctx)
|
|
12
|
+
transaction TX 라이프사이클 (is_ignored, begin, finish)
|
|
13
|
+
endpoint route template · 쿠키 · SSE 래핑 (upgrade_service_name,
|
|
14
|
+
write_whatap_cookie, wrap_streaming_response)
|
|
15
|
+
exception_log interceptor_error_log
|
|
16
|
+
instrumentation 훅 엔트리 3 개 (instrument_applications / instrument /
|
|
17
|
+
instrument_util)
|
|
18
|
+
"""
|
|
19
|
+
from whatap.trace.mod.application.fastapi.exception_log import interceptor_error_log
|
|
20
|
+
from whatap.trace.mod.application.fastapi.instrumentation import (
|
|
21
|
+
instrument,
|
|
22
|
+
instrument_applications,
|
|
23
|
+
instrument_util,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'instrument_applications',
|
|
28
|
+
'instrument',
|
|
29
|
+
'instrument_util',
|
|
30
|
+
'interceptor_error_log',
|
|
31
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""엔드포인트 도달 시에만 유효한 메타 / 응답 보강.
|
|
2
|
+
|
|
3
|
+
TX 라이프사이클에는 관여하지 않는다 (transaction.py 전담).
|
|
4
|
+
"""
|
|
5
|
+
import logging as logging_module
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from whatap.conf.configure import Configure as conf
|
|
9
|
+
from whatap.trace.trace_error import interceptor_step_error
|
|
10
|
+
from whatap.util import bit_util
|
|
11
|
+
from whatap.util.userid_util import UseridUtil
|
|
12
|
+
|
|
13
|
+
logger = logging_module.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def upgrade_service_name(ctx, dependant):
|
|
17
|
+
"""raw path(/items/42) → route template(/items/{item_id})."""
|
|
18
|
+
if dependant is None:
|
|
19
|
+
return
|
|
20
|
+
path = getattr(dependant, 'path', None)
|
|
21
|
+
if path:
|
|
22
|
+
ctx.service_name = path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def write_whatap_cookie(environ, response, raw_user_id):
|
|
26
|
+
"""응답 객체에 WhaTap userid 쿠키 기록. 조건 미충족 시 no-op."""
|
|
27
|
+
if response is None or not raw_user_id:
|
|
28
|
+
return
|
|
29
|
+
if not conf.trace_user_enabled or conf.trace_user_using_ip:
|
|
30
|
+
return
|
|
31
|
+
if conf.user_header_ticket:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
cookie = environ.get('cookie') if environ else None
|
|
35
|
+
if (cookie
|
|
36
|
+
and cookie.find(UseridUtil.WHATAP_R) >= 0
|
|
37
|
+
and cookie.find(raw_user_id) >= 0):
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response.set_cookie(
|
|
42
|
+
key=UseridUtil.WHATAP_R,
|
|
43
|
+
value=raw_user_id,
|
|
44
|
+
max_age=bit_util.INT_MAX_VALUE,
|
|
45
|
+
path='/',
|
|
46
|
+
domain=conf.trace_user_cookie_domain,
|
|
47
|
+
)
|
|
48
|
+
except Exception:
|
|
49
|
+
_, exc, _ = sys.exc_info()
|
|
50
|
+
logger.debug('A503', 10, str(exc))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def wrap_streaming_response(callback):
|
|
54
|
+
"""StreamingResponse body_iterator 에 step-error 포착 래퍼 장착."""
|
|
55
|
+
try:
|
|
56
|
+
from fastapi.responses import StreamingResponse
|
|
57
|
+
except ImportError:
|
|
58
|
+
return callback
|
|
59
|
+
if not isinstance(callback, StreamingResponse):
|
|
60
|
+
return callback
|
|
61
|
+
|
|
62
|
+
original = callback.body_iterator
|
|
63
|
+
|
|
64
|
+
async def _traced():
|
|
65
|
+
try:
|
|
66
|
+
async for chunk in original:
|
|
67
|
+
yield chunk
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
interceptor_step_error(exc)
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
callback.body_iterator = _traced()
|
|
73
|
+
return callback
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""엔드포인트 실행 중 발생한 unhandled exception 을 LogSink 로 전송.
|
|
2
|
+
|
|
3
|
+
외부(whatap/__init__.py)가 `interceptor_error_log` 이름으로 직접 import 하므로
|
|
4
|
+
함수명 · 시그니처 변경 금지.
|
|
5
|
+
"""
|
|
6
|
+
import inspect
|
|
7
|
+
import linecache
|
|
8
|
+
import sys
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
from whatap.conf.configure import Configure as conf
|
|
12
|
+
import whatap.io as whatapio
|
|
13
|
+
import whatap.net.async_sender as async_sender
|
|
14
|
+
import whatap.pack.logSinkPack as logSinkPack
|
|
15
|
+
from whatap.util.date_util import DateUtil
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def interceptor_error_log(trxid, e, fn, args, kwargs):
|
|
19
|
+
if conf.log_unhandled_exception == 'false':
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
error_class = ''
|
|
23
|
+
if e.args:
|
|
24
|
+
error_class = str(e.args[0])
|
|
25
|
+
elif hasattr(e, 'detail'):
|
|
26
|
+
error_class = getattr(e, 'detail')
|
|
27
|
+
|
|
28
|
+
_, _, tb = sys.exc_info()
|
|
29
|
+
last_filename = last_lineno = last_globals = None
|
|
30
|
+
for frame, lineno in traceback.walk_tb(tb):
|
|
31
|
+
last_filename = frame.f_code.co_filename
|
|
32
|
+
last_lineno = lineno
|
|
33
|
+
last_globals = frame.f_globals
|
|
34
|
+
|
|
35
|
+
fields = {
|
|
36
|
+
'errorClass': error_class,
|
|
37
|
+
'args': str(inspect.getcallargs(fn, *args, **kwargs)),
|
|
38
|
+
}
|
|
39
|
+
content = ''
|
|
40
|
+
if last_filename and last_lineno:
|
|
41
|
+
fields['filename'] = last_filename
|
|
42
|
+
fields['lineno'] = last_lineno
|
|
43
|
+
linecache.checkcache(last_filename)
|
|
44
|
+
for i, line in enumerate(linecache.getlines(last_filename, last_globals)):
|
|
45
|
+
if i == last_lineno - 1 and len(line.lstrip()) > 2:
|
|
46
|
+
indent = len(line) - len(line.lstrip())
|
|
47
|
+
content += ''.join(['-' * (indent - 1) + '>', line.lstrip()])
|
|
48
|
+
else:
|
|
49
|
+
content += line
|
|
50
|
+
|
|
51
|
+
pack = logSinkPack.getLogSinkPack(
|
|
52
|
+
t=DateUtil.now(),
|
|
53
|
+
category='UnhandledException',
|
|
54
|
+
tags={'@txid': trxid},
|
|
55
|
+
fields=fields,
|
|
56
|
+
line=DateUtil.now(),
|
|
57
|
+
content=content,
|
|
58
|
+
)
|
|
59
|
+
pack.pcode = conf.PCODE
|
|
60
|
+
|
|
61
|
+
buf = whatapio.DataOutputX()
|
|
62
|
+
buf.writePack(pack, None)
|
|
63
|
+
async_sender.send_relaypack(buf.toByteArray())
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""FastAPI 훅 엔트리포인트 — 3 개 모듈 함수.
|
|
2
|
+
|
|
3
|
+
- instrument_applications : fastapi.applications.FastAPI.__call__
|
|
4
|
+
ASGI 진입점. scope 파싱 → ctx 주입 → begin → wrapped_send → finish.
|
|
5
|
+
미들웨어 early-return / 404 / 405 / CORS preflight 등 엔드포인트
|
|
6
|
+
미도달 응답도 이 지점에서 캡처된다.
|
|
7
|
+
- instrument : fastapi.routing.run_endpoint_function
|
|
8
|
+
엔드포인트 도달 시 route template / setUserId 쿠키 / SSE body wrap /
|
|
9
|
+
unhandled exception 로깅. TX 라이프사이클에는 관여하지 않는다.
|
|
10
|
+
- instrument_util : fastapi.dependencies.utils.solve_dependencies
|
|
11
|
+
엔드포인트에서 쓰기 위한 request / response 를 dependant 에
|
|
12
|
+
`__whatap__` dict 로 부착.
|
|
13
|
+
"""
|
|
14
|
+
from copy import copy
|
|
15
|
+
|
|
16
|
+
from whatap.trace.mod.application.fastapi import endpoint, scope, transaction
|
|
17
|
+
from whatap.trace.mod.application.fastapi.exception_log import interceptor_error_log
|
|
18
|
+
from whatap.trace.trace_context import TraceContext, TraceContextManager
|
|
19
|
+
from whatap.trace.trace_error import interceptor_step_error
|
|
20
|
+
from whatap.trace.trace_handler import async_trace_handler, trace_handler
|
|
21
|
+
|
|
22
|
+
_WHATAP_DICT = '__whatap__'
|
|
23
|
+
_DEPENDANT = 'dependant'
|
|
24
|
+
_REQUEST = 'request'
|
|
25
|
+
_RESPONSE = 'response'
|
|
26
|
+
_REMOTE_ADDR = 'remoteAddr'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── FastAPI.__call__ : TX 생명주기 전담 ────────────────────────
|
|
30
|
+
|
|
31
|
+
def instrument_applications(module):
|
|
32
|
+
def wrapper(fn):
|
|
33
|
+
async def trace(instance, asgi_scope, receive, send):
|
|
34
|
+
if asgi_scope.get('type') != 'http':
|
|
35
|
+
await fn(instance, asgi_scope, receive, send)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
headers = scope.parse_headers(asgi_scope)
|
|
39
|
+
ctx = TraceContext()
|
|
40
|
+
scope.populate_ctx(ctx, asgi_scope, headers)
|
|
41
|
+
|
|
42
|
+
if transaction.is_ignored(ctx.service_name):
|
|
43
|
+
ctx.is_ignored = True
|
|
44
|
+
try:
|
|
45
|
+
await fn(instance, asgi_scope, receive, send)
|
|
46
|
+
finally:
|
|
47
|
+
TraceContextManager.end(ctx.id)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
transaction.begin(ctx)
|
|
51
|
+
|
|
52
|
+
status_holder = [0]
|
|
53
|
+
original_send = send
|
|
54
|
+
|
|
55
|
+
async def wrapped_send(message):
|
|
56
|
+
if message and message.get('type') == 'http.response.start':
|
|
57
|
+
try:
|
|
58
|
+
status_holder[0] = int(message.get('status') or 0)
|
|
59
|
+
except (TypeError, ValueError):
|
|
60
|
+
pass
|
|
61
|
+
await original_send(message)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
await fn(instance, asgi_scope, receive, wrapped_send)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
# 하위(endpoint wrapper)에서 이미 step_error 를 발사했으면
|
|
67
|
+
# ctx.error == 1. TX_ERROR 중복 방지를 위해 여기서는 스킵.
|
|
68
|
+
if not ctx.error:
|
|
69
|
+
interceptor_step_error(exc)
|
|
70
|
+
raise
|
|
71
|
+
finally:
|
|
72
|
+
try:
|
|
73
|
+
transaction.finish(ctx, asgi_scope, headers, status_holder[0])
|
|
74
|
+
finally:
|
|
75
|
+
TraceContextManager.end(ctx.id)
|
|
76
|
+
|
|
77
|
+
return trace
|
|
78
|
+
|
|
79
|
+
module.FastAPI.__call__ = wrapper(module.FastAPI.__call__)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── run_endpoint_function : endpoint 메타 보강만 ──────────────
|
|
83
|
+
|
|
84
|
+
def instrument(module):
|
|
85
|
+
def wrapper(fn):
|
|
86
|
+
@trace_handler(fn, True)
|
|
87
|
+
def trace(*args, **kwargs):
|
|
88
|
+
dependant = kwargs[_DEPENDANT] = copy(kwargs[_DEPENDANT])
|
|
89
|
+
if kwargs.get('is_coroutine'):
|
|
90
|
+
dependant.call = _wrap_async_endpoint(dependant.call, dependant)
|
|
91
|
+
else:
|
|
92
|
+
dependant.call = _wrap_sync_endpoint(dependant.call, dependant)
|
|
93
|
+
return fn(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
return trace
|
|
96
|
+
|
|
97
|
+
if hasattr(module, 'run_endpoint_function'):
|
|
98
|
+
module.run_endpoint_function = wrapper(module.run_endpoint_function)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _wrap_sync_endpoint(fn, dependant):
|
|
102
|
+
@trace_handler(fn, True)
|
|
103
|
+
def trace(*args, **kwargs):
|
|
104
|
+
if not hasattr(dependant, _WHATAP_DICT):
|
|
105
|
+
return fn(*args, **kwargs)
|
|
106
|
+
whatap_dict = _pop_whatap_dict(dependant)
|
|
107
|
+
ctx = TraceContextManager.getLocalContext()
|
|
108
|
+
try:
|
|
109
|
+
_pre_endpoint(ctx, dependant, whatap_dict)
|
|
110
|
+
callback = fn(*args, **kwargs)
|
|
111
|
+
return _post_endpoint(ctx, whatap_dict, callback)
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
interceptor_step_error(exc)
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
return trace
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _wrap_async_endpoint(fn, dependant):
|
|
120
|
+
@async_trace_handler(fn, True)
|
|
121
|
+
async def trace(*args, **kwargs):
|
|
122
|
+
if not hasattr(dependant, _WHATAP_DICT):
|
|
123
|
+
return await fn(*args, **kwargs)
|
|
124
|
+
whatap_dict = _pop_whatap_dict(dependant)
|
|
125
|
+
ctx = TraceContextManager.getLocalContext()
|
|
126
|
+
try:
|
|
127
|
+
_pre_endpoint(ctx, dependant, whatap_dict)
|
|
128
|
+
callback = await fn(*args, **kwargs)
|
|
129
|
+
return _post_endpoint(ctx, whatap_dict, callback)
|
|
130
|
+
except Exception as exc:
|
|
131
|
+
interceptor_step_error(exc)
|
|
132
|
+
if ctx is not None:
|
|
133
|
+
interceptor_error_log(ctx.id, exc, fn, args, kwargs)
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
return trace
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _pop_whatap_dict(dependant):
|
|
140
|
+
whatap_dict = getattr(dependant, _WHATAP_DICT)
|
|
141
|
+
try:
|
|
142
|
+
delattr(dependant, _WHATAP_DICT)
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
return whatap_dict
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _pre_endpoint(ctx, dependant, whatap_dict):
|
|
149
|
+
if ctx is None:
|
|
150
|
+
return
|
|
151
|
+
endpoint.upgrade_service_name(ctx, dependant)
|
|
152
|
+
if not ctx.remoteIp:
|
|
153
|
+
fb = whatap_dict.get(_REMOTE_ADDR)
|
|
154
|
+
if fb:
|
|
155
|
+
ctx.remoteIp = fb
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _post_endpoint(ctx, whatap_dict, callback):
|
|
159
|
+
if ctx is None:
|
|
160
|
+
return callback
|
|
161
|
+
environ = whatap_dict.get('headers')
|
|
162
|
+
response = whatap_dict.get(_RESPONSE)
|
|
163
|
+
endpoint.write_whatap_cookie(
|
|
164
|
+
environ, response, getattr(ctx, '_rawuserid', ''),
|
|
165
|
+
)
|
|
166
|
+
return endpoint.wrap_streaming_response(callback)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ── solve_dependencies : whatap_dict 세팅 ─────────────────────
|
|
170
|
+
|
|
171
|
+
def instrument_util(module):
|
|
172
|
+
def wrapper(fn):
|
|
173
|
+
async def trace(*args, **kwargs):
|
|
174
|
+
whatap_dict = None
|
|
175
|
+
if _DEPENDANT in kwargs:
|
|
176
|
+
whatap_dict = _attach_whatap_dict(kwargs)
|
|
177
|
+
ret = await fn(*args, **kwargs)
|
|
178
|
+
if whatap_dict is not None and hasattr(ret, '__len__') and len(ret) >= 4:
|
|
179
|
+
whatap_dict[_RESPONSE] = ret[3]
|
|
180
|
+
return ret
|
|
181
|
+
|
|
182
|
+
return trace
|
|
183
|
+
|
|
184
|
+
if hasattr(module, 'solve_dependencies'):
|
|
185
|
+
module.solve_dependencies = wrapper(module.solve_dependencies)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _attach_whatap_dict(kwargs):
|
|
189
|
+
dependant = kwargs[_DEPENDANT]
|
|
190
|
+
whatap_dict = {}
|
|
191
|
+
setattr(dependant, _WHATAP_DICT, whatap_dict)
|
|
192
|
+
request = kwargs.get(_REQUEST)
|
|
193
|
+
if request is not None:
|
|
194
|
+
if request.client is not None:
|
|
195
|
+
remote_addr = request.client.host
|
|
196
|
+
else:
|
|
197
|
+
remote_addr = request.headers.get('x-forwarded-for')
|
|
198
|
+
whatap_dict.update({
|
|
199
|
+
_REMOTE_ADDR: remote_addr,
|
|
200
|
+
'headers': copy(request.headers),
|
|
201
|
+
'cookies': copy(request.cookies),
|
|
202
|
+
'query_params': copy(request.query_params),
|
|
203
|
+
})
|
|
204
|
+
return whatap_dict
|