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.
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/PKG-INFO +1 -1
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/__init__.py +89 -8
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/linux/amd64/whatap_python +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/linux/arm64/whatap_python +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/windows/whatap_python.exe +0 -0
- whatap_python-2.0.3rc1/whatap/build.py +4 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/configuration.py +7 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/llm_log_sink_task.py +2 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/async_sender.py +19 -2
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/udp_session.py +31 -2
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/__init__.py +31 -0
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/endpoint.py +73 -0
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/exception_log.py +63 -0
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/scope.py +115 -0
- whatap_python-2.0.3rc1/whatap/trace/mod/application/fastapi/transaction.py +67 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/util.py +29 -3
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/util.py +19 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_handler.py +0 -5
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/PKG-INFO +1 -1
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/SOURCES.txt +6 -1
- whatap_python-2.0.2rc1/whatap/build.py +0 -4
- whatap_python-2.0.2rc1/whatap/trace/mod/application/fastapi.py +0 -476
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/README.md +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/pyproject.toml +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/setup.cfg +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/setup.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/LICENSE +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/README.rst +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/__main__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/darwin/amd64/whatap_python +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/agent/darwin/arm64/whatap_python +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/bootstrap/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/bootstrap/sitecustomize.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/configure.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/conf/license.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/control/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/counter_manager.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/base_task.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/llm_stat_task.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/counter/tasks/openfiledescriptor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/data_inputx.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/io/data_outputx.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/definitions.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_input_message.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_log_sink_pack.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_output_message.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_step_status.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_system_message.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tool_calls.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tool_results.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/log_sink_packs/llm_tx_status.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/pricing.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/anthropic/messages/messages_extractor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/interceptor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/chat/chat_extractor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/completions/completions_extractor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/content_parser.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/embeddings/embeddings_extractor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/openai/responses/responses_extractor.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/providers/stream_accumulator.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/active_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/api_status_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/base_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/error_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/feature_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/meter.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/perf_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/llm/stats/token_usage_stat.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/packet_enum.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/packet_type_enum.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/param_def.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/stackhelper.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/net/udp_thread.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/logSinkPack.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/pack.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/pack_enum.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/pack/tagCountPack.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/scripts/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/kombu.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/amqp/pika.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/bottle.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/celery.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/cherrypy.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django_asgi.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/django_py3.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/flask.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/frappe.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/graphql.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/nameko.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/odoo.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/starlette.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/tornado.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/application/wsgi.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/cxoracle.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/mongo.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/mysql.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/neo4j.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/psycopg2.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/psycopg3.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/redis.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/sqlalchemy.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/database/sqlite3.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/email/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/email/smtp.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/django.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/httplib.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/httpx.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/requests.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/httpc/urllib3.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/logging.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/plugin.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/multiple.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/mod/standalone/single.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/simple_trace_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_context.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_context_manager.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_error.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_import.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/trace/trace_module_definition.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/bit_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/hyperloglog.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/murmurhash.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/cardinality/registerset.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/compare_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/date_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/debug_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/escape_literal_sql.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/frame_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/hash_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/hexa32.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/int_set.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/ip_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/keygen.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/linked_list.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/linked_map.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/metering_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/request_double_queue.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/request_queue.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/string_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/throttle_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/util/userid_util.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/__init__.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/blob_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/boolean_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/decimal_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/double_summary.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/double_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/float_array.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/float_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/int_array.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/ip4_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/list_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/long_array.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/long_summary.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/map_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/null_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/number_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/summary_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_array.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_hash_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/text_value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/value.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/value/value_enum.py +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap/whatap.conf +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/dependency_links.txt +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/entry_points.txt +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/not-zip-safe +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/requires.txt +0 -0
- {whatap_python-2.0.2rc1 → whatap_python-2.0.3rc1}/whatap_python.egg-info/top_level.txt +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
553
|
-
|
|
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))
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|