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,115 @@
1
+ """FastAPI 계측 — ASGI scope/headers 파싱 + TraceContext 주입.
2
+
3
+ TX 패킷을 발사하지 않는다. scope 에서 값을 꺼내 ctx 필드에 옮기기만 한다.
4
+ """
5
+ import logging as logging_module
6
+ import sys
7
+
8
+ from whatap.conf.configure import Configure as conf
9
+ from whatap.util.hash_util import HashUtil
10
+ from whatap.util.hexa32 import Hexa32
11
+ from whatap.util.keygen import KeyGen
12
+ from whatap.util.userid_util import UseridUtil
13
+
14
+ logger = logging_module.getLogger(__name__)
15
+
16
+
17
+ def parse_headers(scope):
18
+ """ASGI raw headers(list of [bytes, bytes]) → lowercase str dict."""
19
+ headers = {}
20
+ for item in scope.get('headers') or ():
21
+ if not item or len(item) < 2:
22
+ continue
23
+ name, value = item[0], item[1]
24
+ try:
25
+ k = (name.decode('latin-1').lower()
26
+ if isinstance(name, (bytes, bytearray))
27
+ else str(name).lower())
28
+ v = (value.decode('latin-1')
29
+ if isinstance(value, (bytes, bytearray))
30
+ else str(value))
31
+ except Exception:
32
+ continue
33
+ headers[k] = v
34
+ return headers
35
+
36
+
37
+ def client_ip(scope, headers):
38
+ """trace_http_client_ip_header_key → x-forwarded-for → scope['client']."""
39
+ configured = conf.trace_http_client_ip_header_key
40
+ if configured:
41
+ v = headers.get(configured.lower())
42
+ if v:
43
+ return v.split(',')[0].strip()
44
+ xff = headers.get('x-forwarded-for')
45
+ if xff:
46
+ return xff.split(',')[0].strip()
47
+ client = scope.get('client')
48
+ if client:
49
+ return client[0]
50
+ return ''
51
+
52
+
53
+ def resolve_user_id(headers, fallback_ip):
54
+ """user_header_ticket → WhaTap 쿠키 → 신규발급. (userid, raw_value)."""
55
+ try:
56
+ if conf.user_header_ticket:
57
+ ticket = headers.get(conf.user_header_ticket)
58
+ if ticket:
59
+ return HashUtil.hashFromString(ticket), ticket
60
+ return 0, ''
61
+
62
+ cookie = headers.get('cookie')
63
+ if cookie:
64
+ if len(cookie) >= conf.trace_user_cookie_limit:
65
+ return _fallback_userid(fallback_ip)
66
+ idx = cookie.find(UseridUtil.WHATAP_R)
67
+ if idx >= 0:
68
+ end = cookie.find(';', idx)
69
+ start = idx + len(UseridUtil.WHATAP_R) + 1
70
+ value = cookie[start:end] if end > 0 else cookie[start:]
71
+ return Hexa32.toLong32(value), value
72
+
73
+ fresh = KeyGen.next()
74
+ return fresh, Hexa32.toString32(fresh)
75
+ except Exception:
76
+ _, exc, _ = sys.exc_info()
77
+ logger.debug('A057', 10, str(exc))
78
+ return _fallback_userid(fallback_ip)
79
+
80
+
81
+ def _fallback_userid(fallback_ip):
82
+ if fallback_ip:
83
+ return HashUtil.hashFromString(fallback_ip), fallback_ip
84
+ return 0, ''
85
+
86
+
87
+ def populate_ctx(ctx, scope, headers):
88
+ """ctx 의 요청 메타 / 유저 / mtrace 필드 일괄 주입."""
89
+ ctx.host = (headers.get('host') or '').split(':')[0]
90
+ ctx.service_name = scope.get('path') or ''
91
+ ctx.http_method = scope.get('method') or ''
92
+ ctx.remoteIp = client_ip(scope, headers)
93
+ ctx.userAgentString = headers.get('user-agent', '')
94
+ ctx.referer = headers.get('referer', '')
95
+
96
+ if conf.trace_user_enabled:
97
+ if conf.trace_user_using_ip:
98
+ ctx.userid = ctx.remoteIp
99
+ else:
100
+ ctx.userid, ctx._rawuserid = resolve_user_id(headers, ctx.remoteIp)
101
+
102
+ caller = headers.get(conf._trace_mtrace_caller_key.lower(), '')
103
+ if caller:
104
+ ctx.setTransfer(caller)
105
+ if conf.stat_mtrace_enabled:
106
+ info = headers.get(conf._trace_mtrace_info_key.lower(), '')
107
+ if info:
108
+ ctx.setTransferInfo(info)
109
+ callee = headers.get(conf._trace_mtrace_callee_key.lower(), '')
110
+ if callee:
111
+ ctx.setTxid(callee)
112
+
113
+ poid = headers.get(conf._trace_mtrace_caller_poid_key.lower(), '')
114
+ if poid:
115
+ ctx.mcaller_poid = poid
@@ -0,0 +1,67 @@
1
+ """FastAPI TX 라이프사이클 — TX_START / TX_END 발사 전담.
2
+
3
+ `start_interceptor` / `end_interceptor` 직접 호출은 이 모듈 두 함수
4
+ (`begin`, `finish`) 안에만 존재한다. 패키지 전체에서 이 두 함수 외로
5
+ 라이프사이클 패킷을 발사하지 않는다.
6
+ """
7
+ from whatap.conf.configure import Configure as conf
8
+ from whatap.net import async_sender
9
+ from whatap.net.packet_type_enum import PacketTypeEnum
10
+ from whatap.trace.mod.application.wsgi import (
11
+ end_interceptor,
12
+ isIgnore,
13
+ start_interceptor,
14
+ )
15
+ from whatap.trace.trace_error import interceptor_error
16
+ from whatap.util.date_util import DateUtil
17
+
18
+
19
+ def is_ignored(service_name):
20
+ try:
21
+ return isIgnore(service_name)
22
+ except Exception:
23
+ return False
24
+
25
+
26
+ def begin(ctx):
27
+ """TX_START 발사."""
28
+ start_interceptor(ctx)
29
+
30
+
31
+ def finish(ctx, scope, headers, status_code):
32
+ """query_string 반영 / 정적 마킹 / status 에러 / 헤더 프로파일 / TX_END."""
33
+ qs = scope.get('query_string') or b''
34
+ if qs:
35
+ try:
36
+ qs_str = (qs.decode('latin-1')
37
+ if isinstance(qs, (bytes, bytearray))
38
+ else str(qs))
39
+ except Exception:
40
+ qs_str = ''
41
+ if qs_str and '?' not in (ctx.service_name or ''):
42
+ ctx.service_name = (ctx.service_name or '') + '?' + qs_str
43
+
44
+ name = ctx.service_name or ''
45
+ if '.' in name:
46
+ ext = name.rsplit('.', 1)[-1].split('?')[0]
47
+ if ext in conf.web_static_content_extensions:
48
+ ctx.isStaticContents = 'true'
49
+
50
+ if status_code and status_code >= 400:
51
+ interceptor_error(
52
+ status_code,
53
+ ['HTTPResponse', 'Status {}'.format(status_code)],
54
+ ctx=ctx,
55
+ )
56
+
57
+ if conf.profile_http_header_enabled and headers:
58
+ text = ''.join(
59
+ '{}={}\n'.format(k, headers[k]) for k in sorted(headers.keys())
60
+ )
61
+ ctx.start_time = DateUtil.nowSystem()
62
+ async_sender.send_packet(
63
+ PacketTypeEnum.TX_MSG, ctx,
64
+ ['HTTP-HEADERS', 'HTTP-HEADERS', text],
65
+ )
66
+
67
+ end_interceptor(ctx=ctx)
@@ -0,0 +1,52 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.trace_error import interceptor_error
3
+ from whatap.trace.mod.application.wsgi import interceptor
4
+ from whatap.trace.trace_context_manager import TraceContextManager
5
+
6
+ def instrument(module):
7
+ def wrapper(fn):
8
+ @trace_handler(fn, True)
9
+ def trace(*args, **kwargs):
10
+ flask_instance = args[0]
11
+ environ = args[1]
12
+ original_start_response = args[2]
13
+
14
+ def custom_start_response(status, response_headers, exc_info=None):
15
+ ctx = TraceContextManager.getLocalContext()
16
+ ctx.status = status[:3]
17
+ return original_start_response(status, response_headers, exc_info)
18
+
19
+ new_args = (flask_instance, environ, custom_start_response)
20
+ callback = interceptor(fn, *new_args, **kwargs)
21
+ return callback
22
+
23
+ return trace
24
+
25
+ module.Flask.wsgi_app = wrapper(module.Flask.wsgi_app)
26
+
27
+ def wrapper(fn):
28
+ @trace_handler(fn)
29
+ def trace(*args, **kwargs):
30
+ from werkzeug.exceptions import HTTPException
31
+ callback = fn(*args, **kwargs)
32
+ if callback is None:
33
+ e = args[1]
34
+ errors = [e.__class__.__name__]
35
+ status_code = getattr(e, 'code', 500)
36
+ #Flask 레이어의 예외 처리
37
+ if isinstance(e, HTTPException):
38
+ errors.append(e.description)
39
+
40
+ #Flask 예외 객체가 아닌 경우의 예외처리
41
+ #에러 코드와 메세지가 함께 나타날 수 있음.
42
+ else:
43
+ status_code = 500
44
+ errors.append(str(e))
45
+ interceptor_error(status_code, errors)
46
+
47
+ return callback
48
+
49
+ return trace
50
+ if hasattr(module.Flask, '_find_error_handler'):
51
+ module.Flask._find_error_handler = wrapper(
52
+ module.Flask._find_error_handler)
@@ -0,0 +1,224 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.trace_error import interceptor_error
3
+ from whatap.trace.mod.application.wsgi import \
4
+ start_interceptor, isIgnore, \
5
+ end_interceptor
6
+ from whatap.trace.trace_context_manager import TraceContextManager
7
+ from whatap.conf.configure import Configure as conf
8
+ from whatap.util.keygen import KeyGen
9
+ import whatap.util.bit_util as bit_util
10
+ from whatap.util.hash_util import HashUtil as hash_util
11
+ from whatap.util.date_util import DateUtil
12
+ from whatap.net.packet_type_enum import PacketTypeEnum
13
+ from whatap.util.hexa32 import Hexa32 as hexa32
14
+ import whatap.net.async_sender as async_sender
15
+ import logging as logging_module
16
+ import sys
17
+ from whatap.trace.trace_context import TraceContext
18
+
19
+
20
+ logger = logging_module.getLogger(__name__)
21
+ def toDjangoHeaderName(src):
22
+
23
+ return 'HTTP_' + src.upper().replace('-','_')
24
+ class UseridUtil(object):
25
+ WHATAP_R = "WHATAP"
26
+
27
+ @staticmethod
28
+ def getUserId(args, defValue):
29
+ try:
30
+
31
+ if conf.user_header_ticket:
32
+ ticket = UseridUtil.getHeader(args, conf.user_header_ticket)
33
+ if ticket:
34
+ return hash_util.hashFromString(ticket), ticket
35
+ return 0,""
36
+ cookie = UseridUtil.getHeader(args, "HTTP_COOKIE")
37
+ if cookie:
38
+ if len(cookie) >= conf.trace_user_cookie_limit :
39
+ return defValue
40
+
41
+ x1 = cookie.find(UseridUtil.WHATAP_R)
42
+ if x1 >= 0:
43
+ x2 = cookie.find(';', x1)
44
+ if x2 > 0:
45
+ value = cookie[x1 + len(UseridUtil.WHATAP_R) + 1: x2]
46
+ else:
47
+ value = cookie[x1 + len(UseridUtil.WHATAP_R) + 1:]
48
+ return hexa32.toLong32(value), value
49
+ userid=KeyGen.next()
50
+ return userid, hexa32.toString32(userid)
51
+ except Exception:
52
+ exc_type, exc_value, exc_traceback = sys.exc_info()
53
+ logger.debug("A502",10, str(exc_value))
54
+
55
+ @staticmethod
56
+ def setUserId(request, response, cookieValue):
57
+ try:
58
+ if not conf.user_header_ticket:
59
+ if request.cookies:
60
+ cookie = request.cookies.get(UseridUtil.WHATAP_R)
61
+ else:
62
+ cookie = None
63
+ if not cookie:
64
+ cookieDomain=conf.trace_user_cookie_domain if conf.trace_user_cookie_domain else None
65
+ UseridUtil.setCookie( response, UseridUtil.WHATAP_R, cookieValue, bit_util.INT_MAX_VALUE, "/", cookieDomain )
66
+ except Exception:
67
+ exc_type, exc_value, exc_traceback = sys.exc_info()
68
+ logger.debug("A503", 10, str(exc_value))
69
+
70
+ @staticmethod
71
+ def setCookie(response, key, value, max_age, path, domain):
72
+ response.set_cookie(key, value, max_age=max_age, path=path, domain=domain)
73
+
74
+ @staticmethod
75
+ def getRemoteAddr(args):
76
+ if conf.trace_http_client_ip_header_key:
77
+ header_val = UseridUtil.getHeader(args, conf.trace_http_client_ip_header_key)
78
+ if header_val:
79
+ return header_val.split(',')[0].strip()
80
+ x_forwarded_for = UseridUtil.getHeader(args, "x-forwarded-for")
81
+ if x_forwarded_for:
82
+ return x_forwarded_for.split(',')[0].strip()
83
+ return UseridUtil.getHeader(args, "REMOTE_ADDR")
84
+ #return UseridUtil.getHeader(args, "REMOTE_ADDR")
85
+
86
+ @staticmethod
87
+ def getHeader(args, key):
88
+
89
+ if args:
90
+ environ = args[0]
91
+ wsgiHeaderKey = 'HTTP_'+key.upper().replace('-','_')
92
+ return environ.get(key, environ.get(wsgiHeaderKey))
93
+ else:
94
+ return None
95
+
96
+
97
+ def parseServiceName(environ):
98
+ return environ.get('PATH_INFO', '')
99
+
100
+
101
+ def interceptor(rn_environ, *args, **kwargs):
102
+ if not isinstance(rn_environ, tuple):
103
+ rn_environ = (rn_environ, args[1])
104
+ fn, environ = rn_environ
105
+
106
+ ctx = TraceContext()
107
+ ctx.host = environ.get('HTTP_HOST', '').split(':')[0]
108
+ ctx.service_name = parseServiceName(environ)
109
+ ctx.remoteIp = UseridUtil.getRemoteAddr(args)
110
+ ctx.userAgentString = environ.get('HTTP_USER_AGENT', '')
111
+ ctx.referer = environ.get('HTTP_REFERER', '')
112
+
113
+ if conf.trace_user_enabled:
114
+ if conf.trace_user_using_ip:
115
+ ctx.userid = UseridUtil.getRemoteAddr(args)
116
+ else:
117
+ ctx.userid, ctx._rawuserid = UseridUtil.getUserId(args, ctx.remoteIp)
118
+
119
+ mstt = environ.get('HTTP_{}'.format(
120
+ conf._trace_mtrace_caller_key.upper().replace('-', '_')), '')
121
+
122
+ if mstt:
123
+ ctx.setTransfer(mstt)
124
+ if conf.stat_mtrace_enabled:
125
+ val = environ.get('HTTP_{}'.format(
126
+ conf._trace_mtrace_info_key.upper().replace('-', '_')), '')
127
+ if val and len(val):
128
+ ctx.setTransferInfo(val)
129
+ pass
130
+
131
+ myid = environ.get('HTTP_{}'.format(
132
+ conf._trace_mtrace_callee_key.upper().replace('-', '_')), '')
133
+ if myid:
134
+ ctx.setTxid(myid)
135
+ caller_poid = environ.get('HTTP_{}'.format(
136
+ conf._trace_mtrace_caller_poid_key.upper().replace('-', '_')), '')
137
+
138
+ if caller_poid:
139
+ ctx.mcaller_poid = caller_poid
140
+
141
+ try:
142
+ if isIgnore(ctx.service_name):
143
+ ctx.is_ignored = True
144
+ TraceContextManager.end(ctx.id)
145
+ return fn(*args, **kwargs)
146
+ except Exception as e:
147
+ pass
148
+
149
+ start_interceptor(ctx)
150
+
151
+ try:
152
+
153
+ callback = fn(*args, **kwargs)
154
+ ctx = TraceContextManager.getLocalContext()
155
+ if ctx:
156
+ query_string = environ.get('QUERY_STRING', '')
157
+ if query_string:
158
+ ctx.service_name += '?{}'.format(query_string)
159
+
160
+ if ctx.service_name.find('.') > -1 and ctx.service_name.split('.')[
161
+ 1] in conf.web_static_content_extensions:
162
+ ctx.isStaticContents = 'true'
163
+
164
+ if getattr(callback, 'status_code', None):
165
+ status_code = callback.status_code
166
+ errors = [callback.reason_phrase, callback.__class__.__name__]
167
+ interceptor_error(status_code, errors)
168
+
169
+ if conf.profile_http_header_enabled:
170
+ keys = []
171
+ for key, value in environ.items():
172
+ if key.startswith('HTTP_'):
173
+ keys.append(key)
174
+ keys.sort()
175
+
176
+ text = ''
177
+ for key in keys:
178
+ text += '{}={}\n'.format(key.split('HTTP_')[1].lower(),
179
+ environ[key])
180
+
181
+ datas = ['HTTP-HEADERS', 'HTTP-HEADERS', text]
182
+ ctx.start_time = DateUtil.nowSystem()
183
+ async_sender.send_packet(PacketTypeEnum.TX_MSG, ctx, datas)
184
+ return callback
185
+ finally:
186
+ ctx = TraceContextManager.getLocalContext()
187
+ if ctx:
188
+ end_interceptor(ctx=ctx)
189
+
190
+ def instrument(module):
191
+ def wrapper(fn):
192
+ @trace_handler(fn, True)
193
+ def trace(*args, **kwargs):
194
+ environ = args[0]
195
+ callback = interceptor((fn, environ), *args, **kwargs)
196
+
197
+ return callback
198
+
199
+ return trace
200
+ if hasattr(module, "application"):
201
+
202
+ module.application = wrapper(module.application)
203
+
204
+
205
+ def run_after_request_hooks_wrapper(fn):
206
+ @trace_handler(fn, True)
207
+ def trace(*args, **kwargs):
208
+ request = args[0]
209
+ response = args[1]
210
+ ctx = TraceContextManager.getLocalContext()
211
+ if ctx and conf.trace_user_enabled:
212
+ if not conf.trace_user_using_ip:
213
+ UseridUtil.setUserId(request, response, ctx._rawuserid )
214
+ callback = fn(*args, **kwargs)
215
+
216
+ return callback
217
+
218
+ return trace
219
+
220
+
221
+ if hasattr(module, "run_after_request_hooks"):
222
+
223
+ module.run_after_request_hooks = run_after_request_hooks_wrapper(module.run_after_request_hooks)
224
+
@@ -0,0 +1,170 @@
1
+ from whatap.trace import get_dict
2
+ from whatap.trace.trace_handler import trace_handler
3
+ from whatap.trace.trace_error import interceptor_step_error
4
+ from whatap.trace.mod.application.wsgi import \
5
+ start_interceptor, end_interceptor
6
+ from whatap.trace.trace_context import TraceContext
7
+ from whatap.trace.trace_context_manager import TraceContextManager
8
+ import whatap.net.async_sender as async_sender
9
+ from whatap.net.packet_type_enum import PacketTypeEnum
10
+ from whatap.util.date_util import DateUtil
11
+ try:
12
+ from importlib.metadata import version as _get_version
13
+ except ImportError:
14
+ from pkg_resources import get_distribution
15
+ def _get_version(name):
16
+ return get_distribution(name).version
17
+ from graphql.language.ast import OperationDefinitionNode
18
+
19
+
20
+ def parseServiceName(graphql_doc):
21
+ # GraphQL-core 버전 확인
22
+ graphql_core_version = _get_version("graphql-core")
23
+
24
+ # GraphQL-core 3.x 이상 버전일 경우
25
+ if graphql_core_version.startswith("3") or graphql_core_version.startswith("4"):
26
+ try:
27
+ # OperationDefinitionNode 타입의 노드 필터링 (3.x 이상 버전 로직)
28
+ op_def = [
29
+ i for i in graphql_doc.definitions
30
+ if isinstance(i, OperationDefinitionNode)
31
+ ][0]
32
+ except (IndexError, KeyError):
33
+ return "GraphQL unknown operation"
34
+
35
+ # operation의 타입이 OperationType Enum인 경우
36
+ op = op_def.operation.value # Enum 값을 문자열로 변환
37
+ else:
38
+ try:
39
+ # 이전 버전 로직 (OperationDefinition 사용)
40
+ op_def = [
41
+ i for i in graphql_doc.definitions
42
+ if type(i).__name__ == "OperationDefinition"
43
+ ][0]
44
+ except (IndexError, KeyError):
45
+ return "GraphQL unknown operation"
46
+
47
+ # operation의 타입이 문자열인 경우
48
+ op = op_def.operation # 문자열 그대로 사용
49
+
50
+ name = op_def.name
51
+ fields = op_def.selection_set.selections if op_def.selection_set else []
52
+
53
+ return "/GraphQL %s %s" % (op.upper(), name.value if name else "+".join([f.name.value for f in fields]))
54
+
55
+
56
+ def intercept_execute(fn, *args, **kwargs):
57
+ ctx = TraceContextManager.getLocalContext()
58
+ if not ctx:
59
+ ctx = TraceContext()
60
+ is_transaction_started = False
61
+ else:
62
+ is_transaction_started = not ctx.is_ignored
63
+ if not is_transaction_started:
64
+ if len(args) > 1 and hasattr(args[1],"definitions"):
65
+ name = parseServiceName(args[1])
66
+ if name:
67
+ ctx.service_name = name
68
+ start_interceptor(ctx)
69
+ start_time = DateUtil.nowSystem()
70
+ try:
71
+ callback = fn(*args, **kwargs)
72
+ return callback
73
+ except Exception as e:
74
+ interceptor_step_error(e)
75
+ finally:
76
+ if not is_transaction_started:
77
+ end_interceptor()
78
+ else:
79
+ text = "graphql.execute"
80
+ payloads = [text, '']
81
+ ctx.elapsed = DateUtil.nowSystem() - start_time
82
+ async_sender.send_packet(PacketTypeEnum.TX_METHOD, ctx, payloads)
83
+
84
+ def parseDocumentName(op_def):
85
+ op = op_def.operation
86
+ name = op_def.name
87
+ fields = op_def.selection_set.selections
88
+ if not fields:
89
+ fields = []
90
+ return "GraphQL %s %s" % (op.upper(), name if name else "+".join([f.name.value for f in fields]))
91
+
92
+
93
+ def parseSelectionSet(gnode, tokens = [], indent=0):
94
+ nameFound = False
95
+
96
+ if hasattr(gnode, "selection_set"):
97
+ if hasattr(gnode, "name") and hasattr(gnode, "selection_set"):
98
+ if gnode.selection_set:
99
+ tokens.append(" "*indent+gnode.name.value+"{")
100
+ nameFound = True
101
+ else:
102
+ tokens.append(" "*indent+gnode.name.value)
103
+
104
+ if hasattr(gnode, "selection_set"):
105
+ if gnode.selection_set:
106
+ if gnode.selection_set.selections:
107
+ for sel in gnode.selection_set.selections:
108
+ parseSelectionSet(sel, tokens, indent = indent+1)
109
+ if nameFound:
110
+ tokens.append(" "*indent+"}")
111
+
112
+ def parseDocument(defi):
113
+ tokens = []
114
+ if hasattr(defi, "name"):
115
+ tokens.append(defi.name.value+"{")
116
+
117
+ if hasattr(defi, "selection_set"):
118
+ if defi.selection_set:
119
+ if defi.selection_set.selections:
120
+ for sel in defi.selection_set.selections:
121
+ if sel:
122
+ parseSelectionSet(sel, tokens, 1)
123
+
124
+ if hasattr(defi, "name"):
125
+ tokens.append("}")
126
+ tokens.reverse()
127
+ return "\n".join(tokens)
128
+
129
+ def intercept_execute_method( fn, *args, **kwargs):
130
+ ctx = TraceContextManager.getLocalContext()
131
+ start_time = DateUtil.nowSystem()
132
+ try:
133
+ callback = fn(*args, **kwargs)
134
+ return callback
135
+ except Exception as e:
136
+ if ctx:
137
+ interceptor_step_error(e)
138
+ finally:
139
+ if ctx and len(args) > 4 and not args[4] and args[3]:
140
+ operation_definition = args[3][0]
141
+ text = operation_definition.name.value
142
+ arg = parseDocument(operation_definition)
143
+
144
+ payloads = [text, arg]
145
+ ctx.elapsed = DateUtil.nowSystem() - start_time
146
+ async_sender.send_packet(PacketTypeEnum.TX_METHOD, ctx, payloads)
147
+
148
+ def instrument_graphql(module):
149
+ def wrapper(fn):
150
+ @trace_handler(fn, start=True)
151
+ def trace(*args, **kwargs):
152
+ callback = intercept_execute(fn, *args, **kwargs)
153
+ return callback
154
+
155
+ return trace
156
+ if hasattr(module, 'execute'):
157
+ module.execute = wrapper(module.execute)
158
+
159
+ def wrapper( fn):
160
+ @trace_handler(fn, start=True)
161
+ def trace(*args, **kwargs):
162
+ callback = intercept_execute_method( fn, *args, **kwargs)
163
+ return callback
164
+ return trace
165
+
166
+ # if hasattr(module, 'execute_operation'):
167
+ # module.execute_operation = wrapper(module.execute_operation)
168
+
169
+ if hasattr(module, 'resolve_field'):
170
+ module.resolve_field = wrapper(module.resolve_field)
@@ -0,0 +1,39 @@
1
+ from whatap.trace import get_dict
2
+ from whatap.trace.trace_handler import trace_handler
3
+ from whatap.trace.trace_error import interceptor_step_error
4
+ from whatap.trace.mod.application.wsgi import \
5
+ start_interceptor, end_interceptor
6
+ from whatap.trace.trace_context import TraceContext
7
+ from whatap.trace.trace_context_manager import TraceContextManager
8
+ import whatap.net.async_sender as async_sender
9
+ from whatap.net.packet_type_enum import PacketTypeEnum
10
+ from whatap.util.date_util import DateUtil
11
+
12
+ def intercept_worker(fn, *args, **kwargs):
13
+ ctx = TraceContext()
14
+ worker_ctx = args[1]
15
+ ctx.service_name = worker_ctx.call_id_stack
16
+ start_interceptor(ctx)
17
+
18
+ try:
19
+ callback = fn(*args, **kwargs)
20
+ return callback
21
+ except Exception as e:
22
+ interceptor_step_error(e)
23
+ finally:
24
+ end_interceptor()
25
+
26
+ def instrument_nameko_spawn_worker(module):
27
+ def wrapper(fn):
28
+ @trace_handler(fn, start=True)
29
+ def trace(*args, **kwargs):
30
+ callback = intercept_worker(fn, *args, **kwargs)
31
+ return callback
32
+
33
+ return trace
34
+
35
+ if hasattr(module, 'ServiceContainer') and hasattr(module.ServiceContainer, '_run_worker'):
36
+ from eventlet.corolocal import local
37
+ TraceContextManager.local = local()
38
+ module.ServiceContainer._run_worker = wrapper(module.ServiceContainer._run_worker)
39
+