whatap-python 2.0.2rc1__tar.gz → 2.0.3rc1__tar.gz

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 (207) hide show
  1. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/PKG-INFO +1 -1
  2. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/__init__.py +89 -8
  3. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/linux/amd64/whatap_python +0 -0
  4. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/linux/arm64/whatap_python +0 -0
  5. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/windows/whatap_python.exe +0 -0
  6. whatap_python-2.0.3rc1/whatap/build.py +4 -0
  7. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/configuration.py +7 -0
  8. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/llm_log_sink_task.py +2 -0
  9. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/async_sender.py +19 -2
  10. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/udp_session.py +31 -2
  11. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/__init__.py +31 -0
  12. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/endpoint.py +73 -0
  13. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/exception_log.py +63 -0
  14. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
  15. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/scope.py +115 -0
  16. whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/transaction.py +67 -0
  17. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/util.py +29 -3
  18. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/util.py +19 -0
  19. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_handler.py +0 -5
  20. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/PKG-INFO +1 -1
  21. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/SOURCES.txt +6 -1
  22. whatap_python-2.0.2rc1/whatap/build.py +0 -4
  23. whatap_python-2.0.2rc1/whatap/trace/mod/application/fastapi.py +0 -476
  24. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/README.md +0 -0
  25. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/pyproject.toml +0 -0
  26. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/setup.cfg +0 -0
  27. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/setup.py +0 -0
  28. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/LICENSE +0 -0
  29. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/README.rst +0 -0
  30. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/__main__.py +0 -0
  31. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/darwin/amd64/whatap_python +0 -0
  32. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/darwin/arm64/whatap_python +0 -0
  33. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/bootstrap/__init__.py +0 -0
  34. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/bootstrap/sitecustomize.py +0 -0
  35. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/__init__.py +0 -0
  36. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/configure.py +0 -0
  37. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/license.py +0 -0
  38. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/control/__init__.py +0 -0
  39. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/__init__.py +0 -0
  40. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/counter_manager.py +0 -0
  41. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/__init__.py +0 -0
  42. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/base_task.py +0 -0
  43. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/llm_stat_task.py +0 -0
  44. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/openfiledescriptor.py +0 -0
  45. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/__init__.py +0 -0
  46. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/data_inputx.py +0 -0
  47. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/data_outputx.py +0 -0
  48. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/__init__.py +0 -0
  49. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/definitions.py +0 -0
  50. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/__init__.py +0 -0
  51. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_input_message.py +0 -0
  52. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_log_sink_pack.py +0 -0
  53. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_output_message.py +0 -0
  54. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_step_status.py +0 -0
  55. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_system_message.py +0 -0
  56. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tool_calls.py +0 -0
  57. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tool_results.py +0 -0
  58. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tx_status.py +0 -0
  59. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/pricing.py +0 -0
  60. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/__init__.py +0 -0
  61. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/__init__.py +0 -0
  62. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/__init__.py +0 -0
  63. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages.py +0 -0
  64. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages_context.py +0 -0
  65. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages_extractor.py +0 -0
  66. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/interceptor.py +0 -0
  67. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/__init__.py +0 -0
  68. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/__init__.py +0 -0
  69. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat.py +0 -0
  70. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat_context.py +0 -0
  71. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat_extractor.py +0 -0
  72. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/__init__.py +0 -0
  73. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions.py +0 -0
  74. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions_context.py +0 -0
  75. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions_extractor.py +0 -0
  76. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/content_parser.py +0 -0
  77. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/__init__.py +0 -0
  78. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings.py +0 -0
  79. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings_context.py +0 -0
  80. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings_extractor.py +0 -0
  81. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/__init__.py +0 -0
  82. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses.py +0 -0
  83. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses_context.py +0 -0
  84. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses_extractor.py +0 -0
  85. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/stream_accumulator.py +0 -0
  86. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/__init__.py +0 -0
  87. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/active_stat.py +0 -0
  88. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/api_status_stat.py +0 -0
  89. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/base_stat.py +0 -0
  90. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/error_stat.py +0 -0
  91. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/feature_stat.py +0 -0
  92. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/meter.py +0 -0
  93. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/perf_stat.py +0 -0
  94. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/token_usage_stat.py +0 -0
  95. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/__init__.py +0 -0
  96. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/packet_enum.py +0 -0
  97. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/packet_type_enum.py +0 -0
  98. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/param_def.py +0 -0
  99. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/stackhelper.py +0 -0
  100. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/udp_thread.py +0 -0
  101. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/__init__.py +0 -0
  102. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/logSinkPack.py +0 -0
  103. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/pack.py +0 -0
  104. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/pack_enum.py +0 -0
  105. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/tagCountPack.py +0 -0
  106. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/scripts/__init__.py +0 -0
  107. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/__init__.py +0 -0
  108. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/__init__.py +0 -0
  109. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/__init__.py +0 -0
  110. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/kombu.py +0 -0
  111. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/pika.py +0 -0
  112. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/__init__.py +0 -0
  113. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/bottle.py +0 -0
  114. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/celery.py +0 -0
  115. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/cherrypy.py +0 -0
  116. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django.py +0 -0
  117. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django_asgi.py +0 -0
  118. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django_py3.py +0 -0
  119. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/flask.py +0 -0
  120. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/frappe.py +0 -0
  121. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/graphql.py +0 -0
  122. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/nameko.py +0 -0
  123. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/odoo.py +0 -0
  124. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/starlette.py +0 -0
  125. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/tornado.py +0 -0
  126. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/wsgi.py +0 -0
  127. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/__init__.py +0 -0
  128. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/cxoracle.py +0 -0
  129. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/mongo.py +0 -0
  130. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/mysql.py +0 -0
  131. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/neo4j.py +0 -0
  132. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/psycopg2.py +0 -0
  133. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/psycopg3.py +0 -0
  134. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/redis.py +0 -0
  135. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/sqlalchemy.py +0 -0
  136. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/sqlite3.py +0 -0
  137. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/email/__init__.py +0 -0
  138. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/email/smtp.py +0 -0
  139. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/__init__.py +0 -0
  140. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/django.py +0 -0
  141. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/httplib.py +0 -0
  142. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/httpx.py +0 -0
  143. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/requests.py +0 -0
  144. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/urllib3.py +0 -0
  145. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/logging.py +0 -0
  146. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/plugin.py +0 -0
  147. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/__init__.py +0 -0
  148. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/multiple.py +0 -0
  149. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/single.py +0 -0
  150. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/simple_trace_context.py +0 -0
  151. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_context.py +0 -0
  152. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_context_manager.py +0 -0
  153. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_error.py +0 -0
  154. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_import.py +0 -0
  155. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_module_definition.py +0 -0
  156. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/__init__.py +0 -0
  157. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/bit_util.py +0 -0
  158. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/__init__.py +0 -0
  159. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/hyperloglog.py +0 -0
  160. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/murmurhash.py +0 -0
  161. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/registerset.py +0 -0
  162. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/compare_util.py +0 -0
  163. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/date_util.py +0 -0
  164. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/debug_util.py +0 -0
  165. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/escape_literal_sql.py +0 -0
  166. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/frame_util.py +0 -0
  167. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/hash_util.py +0 -0
  168. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/hexa32.py +0 -0
  169. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/int_set.py +0 -0
  170. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/ip_util.py +0 -0
  171. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/keygen.py +0 -0
  172. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/linked_list.py +0 -0
  173. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/linked_map.py +0 -0
  174. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/metering_util.py +0 -0
  175. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/request_double_queue.py +0 -0
  176. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/request_queue.py +0 -0
  177. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/string_util.py +0 -0
  178. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/throttle_util.py +0 -0
  179. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/userid_util.py +0 -0
  180. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/__init__.py +0 -0
  181. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/blob_value.py +0 -0
  182. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/boolean_value.py +0 -0
  183. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/decimal_value.py +0 -0
  184. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/double_summary.py +0 -0
  185. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/double_value.py +0 -0
  186. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/float_array.py +0 -0
  187. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/float_value.py +0 -0
  188. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/int_array.py +0 -0
  189. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/ip4_value.py +0 -0
  190. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/list_value.py +0 -0
  191. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/long_array.py +0 -0
  192. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/long_summary.py +0 -0
  193. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/map_value.py +0 -0
  194. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/null_value.py +0 -0
  195. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/number_value.py +0 -0
  196. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/summary_value.py +0 -0
  197. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_array.py +0 -0
  198. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_hash_value.py +0 -0
  199. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_value.py +0 -0
  200. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/value.py +0 -0
  201. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/value_enum.py +0 -0
  202. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/whatap.conf +0 -0
  203. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/dependency_links.txt +0 -0
  204. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/entry_points.txt +0 -0
  205. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/not-zip-safe +0 -0
  206. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/requires.txt +0 -0
  207. {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: whatap-python
3
- Version: 2.0.2rc1
3
+ Version: 2.0.3rc1
4
4
  Summary: Monitoring and Profiling Service
5
5
  Home-page: https://www.whatap.io
6
6
  Author: whatap
@@ -301,9 +301,45 @@ from whatap.trace.trace_module_definition import DEFINITION, IMPORT_HOOKS, \
301
301
  PLUGIN
302
302
 
303
303
 
304
+ def _build_ignore_instrumentation_set():
305
+ """whatap.conf 의 ignore_instrumentation_set 값을 정규화하여 반환.
306
+ Configure.load() 가 _set 접미사 키를 콤마 split 해 list 로 만들지만,
307
+ 환경변수/직접 setProperty 등 다른 경로로 들어온 경우(str)도 함께 처리한다.
308
+ """
309
+ raw = getattr(conf, 'ignore_instrumentation_set', None) or []
310
+ if isinstance(raw, str):
311
+ raw = raw.split(',')
312
+ return {str(item).strip().lower() for item in raw if item and str(item).strip()}
313
+
314
+
315
+ def _is_instrumentation_ignored(definition_key, ignore_set):
316
+ """DEFINITION 키(예: 'database.redis', 'logging')가 무시 대상인지 판정.
317
+
318
+ 매칭 규칙(셋 중 하나라도 맞으면 True):
319
+ 1) 키 전체가 일치 예: 'database.redis' == 'database.redis'
320
+ 2) 카테고리(점 앞)와 일치 예: 'database' → 'database.*' 모두
321
+ 3) 라이브러리(점 뒤)와 일치 예: 'redis' → 'database.redis'
322
+ """
323
+ if not ignore_set:
324
+ return False
325
+ key_lower = definition_key.lower()
326
+ if key_lower in ignore_set:
327
+ return True
328
+ if '.' in key_lower:
329
+ category, lib = key_lower.split('.', 1)
330
+ if category in ignore_set or lib in ignore_set:
331
+ return True
332
+ return False
333
+
334
+
304
335
  def hooks(home):
336
+ ignore_set = _build_ignore_instrumentation_set()
337
+ skipped_keys = []
305
338
  try:
306
339
  for key, value_list in DEFINITION.items():
340
+ if _is_instrumentation_ignored(key, ignore_set):
341
+ skipped_keys.append(key)
342
+ continue
307
343
  for value in value_list:
308
344
  if len(value) >= 3 and isinstance(value[2], str):
309
345
  module_path = value[2]
@@ -318,7 +354,14 @@ def hooks(home):
318
354
  logging.debug(e, extra={'id': 'MODULE ERROR'})
319
355
  finally:
320
356
  try:
321
- if conf.trace_logging_enabled:
357
+ if skipped_keys:
358
+ logging.debug(
359
+ 'WHATAP: ignore_instrumentation_set skipped: {}'.format(
360
+ ','.join(skipped_keys)),
361
+ extra={'id': 'WA_IGN'})
362
+
363
+ if conf.trace_logging_enabled and not _is_instrumentation_ignored(
364
+ 'logging', ignore_set):
322
365
  logging_module = sys.modules.get("logging")
323
366
  from whatap.trace.mod.logging import instrument_logging
324
367
  instrument_logging(logging_module)
@@ -535,12 +578,47 @@ def go(batch=False, opts={}, llm=False):
535
578
  label = 'LLM golang module' if llm else 'golang module'
536
579
 
537
580
  if sys.platform == 'win32':
538
- DETACHED_PROCESS = 0x00000008
581
+ # SESSIONNAME 환경변수는 NSSM이 부모 세션에서 상속받아 부정확함
582
+ # ProcessIdToSessionId로 실제 프로세스 세션 ID 확인
583
+ try:
584
+ import ctypes
585
+ _sid = ctypes.c_ulong(0)
586
+ ctypes.windll.kernel32.ProcessIdToSessionId(
587
+ ctypes.windll.kernel32.GetCurrentProcessId(),
588
+ ctypes.byref(_sid)
589
+ )
590
+ is_session0 = (_sid.value == 0)
591
+ except Exception:
592
+ is_session0 = not os.environ.get('SESSIONNAME')
593
+
539
594
  cmd_args.append('foreground')
540
- process = subprocess.Popen(cmd_args,
541
- cwd=home_path, env=newenv,
542
- creationflags=DETACHED_PROCESS,
543
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
595
+
596
+ if is_session0:
597
+ # Session 0에서만 WHATAP_FOREGROUND=1 추가 주입.
598
+ # IsAnInteractiveSession()이 false로 떨어지는 경로에서
599
+ # foreground CLI 인자를 보강하는 안전망.
600
+ newenv['WHATAP_FOREGROUND'] = '1'
601
+
602
+ # Session 0에서는 PIPE를 읽지 않으면 버퍼 고갈로 Go 프로세스가 블로킹됨
603
+ # 로그 파일로 리디렉션
604
+ # CREATE_NO_WINDOW: Session 0 서비스 컨텍스트에 적합한 플래그
605
+ CREATE_NO_WINDOW = 0x08000000
606
+ log_dir = os.path.join(home_path, 'logs')
607
+ go_log_path = os.path.join(log_dir, AGENT_NAME + '.log')
608
+ try:
609
+ go_out = open(go_log_path, 'ab')
610
+ except Exception:
611
+ go_out = subprocess.DEVNULL
612
+ process = subprocess.Popen(cmd_args,
613
+ cwd=home_path, env=newenv,
614
+ creationflags=CREATE_NO_WINDOW,
615
+ stdout=go_out, stderr=go_out)
616
+ else:
617
+ DETACHED_PROCESS = 0x00000008
618
+ process = subprocess.Popen(cmd_args,
619
+ cwd=home_path, env=newenv,
620
+ creationflags=DETACHED_PROCESS,
621
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
544
622
  else:
545
623
  process = subprocess.Popen(cmd_args,
546
624
  cwd=home_path, env=newenv,
@@ -549,8 +627,11 @@ def go(batch=False, opts={}, llm=False):
549
627
 
550
628
  time.sleep(0.5)
551
629
  if process.poll() is not None:
552
- stdouts, errs = process.communicate()
553
- whatap_print("executed {} ".format(label), str(stdouts,"utf8"), str(errs, "utf8"))
630
+ if sys.platform == 'win32' and is_session0:
631
+ whatap_print("executed {} (exit code: {}, log: {})".format(label, process.returncode, go_log_path))
632
+ else:
633
+ stdouts, errs = process.communicate()
634
+ whatap_print("executed {} ".format(label), str(stdouts,"utf8"), str(errs, "utf8"))
554
635
  else:
555
636
  write_file(home, file_name, str(process.pid))
556
637
  whatap_print("executed {} in background (PID: {})".format(label, process.pid))
@@ -0,0 +1,4 @@
1
+ app = 'Python'
2
+ name = 'whatap-python'
3
+ version = '2.0.3rc1'
4
+ release_date = '20260506'
@@ -19,6 +19,13 @@ Configuration = {
19
19
  "trace_ignore_url_prefix": None,
20
20
  "trace_websocket_enabled": False,
21
21
 
22
+ # 자동 계측에서 제외할 라이브러리 목록(콤마 구분).
23
+ # - DEFINITION 키 전체: database.redis, llm.openai
24
+ # - 카테고리만: database (database.* 전부 스킵)
25
+ # - 라이브러리 이름만: redis, openai, django (해당 키만 스킵)
26
+ # 예: ignore_instrumentation_set=redis,openai,application.django
27
+ "ignore_instrumentation_set": [],
28
+
22
29
  "llm_api_hosts": "",
23
30
 
24
31
  "debug": False,
@@ -82,6 +82,8 @@ class LlmLogSinkTask(object):
82
82
 
83
83
  try:
84
84
  self._send_log_sink(tx)
85
+ # 트랜잭션 경계에서 LLM 버퍼에 남아있는 데이터 flush (큐를 거쳐 순서 보장)
86
+ async_sender.flush_llm_relaypack()
85
87
  except Exception as e:
86
88
  logging.warning('[LLM] send_llm_tx_status failed: %s' % e, extra={'id': 'LLM022'})
87
89
 
@@ -12,6 +12,9 @@ class SendType(Enum):
12
12
  DATAS = 1
13
13
  RELAY = 2
14
14
  LLM_RELAY = 3
15
+ LLM_FLUSH = 4
16
+
17
+ _LLM_IDLE_FLUSH_SEC = 0.1
15
18
 
16
19
  _STEP_TYPES = {
17
20
  PacketTypeEnum.TX_DB_CONN, PacketTypeEnum.TX_DB_FETCH,
@@ -48,13 +51,25 @@ def send_llm_relaypack( packbytes):
48
51
  return
49
52
  q.put((SendType.LLM_RELAY, packbytes))
50
53
 
54
+ def flush_llm_relaypack():
55
+ """LLM 버퍼 명시적 flush 요청. 큐를 거치므로 이전에 enqueue된 LLM_RELAY들이 모두 처리된 뒤 flush된다."""
56
+ _initThread()
57
+ global q
58
+ if q.full():
59
+ return
60
+ q.put((SendType.LLM_FLUSH, None))
61
+
51
62
  def startWhatapThread():
52
63
  def __sendPackets():
53
64
  global q
54
65
  while True:
55
- packet_env = q.get()
66
+ try:
67
+ packet_env = q.get(timeout=_LLM_IDLE_FLUSH_SEC)
68
+ except queue.Empty:
69
+ # idle 상태에서 LLM 버퍼에 남아있는 데이터 flush
70
+ udp_session.UdpSession.flush_llm_buffer()
71
+ continue
56
72
  if not packet_env:
57
- time.sleep(0.1)
58
73
  continue
59
74
  sendType,params = packet_env
60
75
  if sendType == SendType.DATAS:
@@ -66,6 +81,8 @@ def startWhatapThread():
66
81
  elif sendType == SendType.LLM_RELAY:
67
82
  packbytes = params
68
83
  udp_session.UdpSession.send_llm_relaypack(packbytes)
84
+ elif sendType == SendType.LLM_FLUSH:
85
+ udp_session.UdpSession.flush_llm_buffer()
69
86
  t = threading.Thread(target=__sendPackets)
70
87
  t.setDaemon(True)
71
88
  t.start()
@@ -28,7 +28,9 @@ class UdpSession(object):
28
28
  s = None
29
29
  llm_s = None
30
30
  buffer_arr = []
31
+ llm_buffer_arr = []
31
32
  thread_lock = threading.Lock()
33
+ llm_thread_lock = threading.Lock()
32
34
  socket_lock = threading.Lock()
33
35
  read_timeout = 5
34
36
 
@@ -344,7 +346,8 @@ class UdpSession(object):
344
346
 
345
347
  @classmethod
346
348
  def send_llm_relaypack(cls, packbytes):
347
- """LLM 데이터를 LLM Go Agent 소켓으로만 직접 전송. APM 소켓으로는 보내지 않음."""
349
+ """LLM 데이터를 LLM Go Agent 소켓으로 버퍼링 전송. 48KB 누적 자동 flush.
350
+ 명시적 flush는 flush_llm_buffer()를 사용한다."""
348
351
  if not packbytes:
349
352
  return
350
353
  if not cls.llm_s:
@@ -358,7 +361,33 @@ class UdpSession(object):
358
361
  dout.writeByte(PacketTypeEnum.RELAY_PACK)
359
362
  dout.writeInt(PacketEnum.PACKET_VERSION)
360
363
  dout.writeIntBytes(packbytes)
361
- cls.llm_s.send(dout.toByteArray())
364
+ entry = dout.toByteArray()
365
+
366
+ with cls.llm_thread_lock:
367
+ current_size = sum(len(x) for x in cls.llm_buffer_arr)
368
+ if cls.llm_buffer_arr and current_size + len(entry) > PacketEnum.PACKET_BUFFER_SIZE:
369
+ cls._flush_llm_buffer_locked()
370
+ cls.llm_buffer_arr.append(entry)
371
+ except Exception as e:
372
+ logging.debug(e, extra={'id': 'WA921'}, exc_info=True)
373
+
374
+ @classmethod
375
+ def flush_llm_buffer(cls):
376
+ """LLM 버퍼에 남아있는 데이터를 즉시 전송한다. idle/트랜잭션 경계에서 호출."""
377
+ if not cls.llm_s:
378
+ return
379
+ with cls.llm_thread_lock:
380
+ cls._flush_llm_buffer_locked()
381
+
382
+ @classmethod
383
+ def _flush_llm_buffer_locked(cls):
384
+ """llm_thread_lock을 이미 보유한 상태에서 호출되어야 한다."""
385
+ if not cls.llm_buffer_arr:
386
+ return
387
+ sendbuf = b''.join(cls.llm_buffer_arr)
388
+ cls.llm_buffer_arr = []
389
+ try:
390
+ cls.llm_s.send(sendbuf)
362
391
  except ConnectionRefusedError:
363
392
  cls.udp_llm()
364
393
  except Exception as e:
@@ -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