datadog 2.17.0 → 2.37.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +686 -1
- data/README.md +0 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +344 -126
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -4
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
- data/ext/datadog_profiling_native_extension/collectors_stack.c +314 -91
- data/ext/datadog_profiling_native_extension/collectors_stack.h +26 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +680 -352
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +18 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +65 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +40 -1
- data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +48 -34
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +455 -430
- data/ext/datadog_profiling_native_extension/heap_recorder.h +16 -7
- data/ext/datadog_profiling_native_extension/http_transport.c +66 -68
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +73 -69
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +10 -4
- data/ext/datadog_profiling_native_extension/profiling.c +65 -19
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +64 -59
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +20 -16
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +32 -10
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +253 -131
- data/ext/datadog_profiling_native_extension/stack_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +9 -4
- data/ext/libdatadog_api/crashtracker.c +16 -15
- data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
- data/ext/libdatadog_api/datadog_ruby_common.c +65 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +40 -1
- data/ext/libdatadog_api/ddsketch.c +102 -0
- data/ext/libdatadog_api/di.c +203 -0
- data/ext/libdatadog_api/extconf.rb +21 -25
- data/ext/libdatadog_api/feature_flags.c +554 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +13 -1
- data/ext/libdatadog_api/library_config.c +88 -37
- data/ext/libdatadog_api/library_config.h +6 -0
- data/ext/libdatadog_api/process_discovery.c +25 -24
- data/ext/libdatadog_extconf_helpers.rb +107 -14
- data/lib/datadog/ai_guard/api_client.rb +84 -0
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +44 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
- data/lib/datadog/ai_guard/configuration.rb +115 -0
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +80 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +86 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +45 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
- data/lib/datadog/ai_guard/evaluation.rb +108 -0
- data/lib/datadog/ai_guard/ext.rb +29 -0
- data/lib/datadog/ai_guard.rb +181 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
- data/lib/datadog/appsec/api_security/sampler.rb +62 -0
- data/lib/datadog/appsec/api_security.rb +14 -0
- data/lib/datadog/appsec/assets/blocked.html +10 -1
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +601 -74
- data/lib/datadog/appsec/assets/waf_rules/strict.json +48 -75
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/autoload.rb +2 -2
- data/lib/datadog/appsec/component.rb +37 -64
- data/lib/datadog/appsec/compressed_json.rb +3 -3
- data/lib/datadog/appsec/configuration.rb +427 -1
- data/lib/datadog/appsec/context.rb +77 -16
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
- data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -1
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +144 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +4 -2
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +2 -1
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +3 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +152 -13
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +138 -13
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +74 -24
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -2
- data/lib/datadog/appsec/contrib/graphql/integration.rb +2 -1
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +47 -13
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +40 -19
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +80 -62
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +47 -5
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patcher.rb +52 -26
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +3 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +167 -12
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +63 -10
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -21
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +14 -47
- data/lib/datadog/appsec/ext.rb +3 -0
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +17 -1
- data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
- data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
- data/lib/datadog/appsec/metrics/collector.rb +47 -5
- data/lib/datadog/appsec/metrics/exporter.rb +16 -3
- data/lib/datadog/appsec/metrics/telemetry.rb +21 -4
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
- data/lib/datadog/appsec/metrics.rb +5 -4
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -6
- data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
- data/lib/datadog/appsec/remote.rb +51 -76
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/security_engine/engine.rb +197 -0
- data/lib/datadog/appsec/security_engine/result.rb +45 -9
- data/lib/datadog/appsec/security_engine/runner.rb +44 -21
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
- data/lib/datadog/appsec/trace_keeper.rb +36 -0
- data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
- data/lib/datadog/appsec/utils/http/body.rb +38 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +32 -26
- data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
- data/lib/datadog/appsec.rb +10 -17
- data/lib/datadog/auto_instrument_base.rb +2 -1
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +5 -47
- data/lib/datadog/core/configuration/base.rb +17 -5
- data/lib/datadog/core/configuration/components.rb +192 -20
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/config_helper.rb +109 -0
- data/lib/datadog/core/configuration/deprecations.rb +36 -0
- data/lib/datadog/core/configuration/ext.rb +0 -1
- data/lib/datadog/core/configuration/option.rb +95 -65
- data/lib/datadog/core/configuration/option_definition.rb +42 -23
- data/lib/datadog/core/configuration/options.rb +57 -18
- data/lib/datadog/core/configuration/settings.rb +160 -25
- data/lib/datadog/core/configuration/stable_config.rb +11 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +413 -0
- data/lib/datadog/core/configuration.rb +4 -4
- data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
- data/lib/datadog/core/contrib/rails/utils.rb +7 -3
- data/lib/datadog/core/crashtracking/component.rb +73 -21
- data/lib/datadog/core/crashtracking/tag_builder.rb +9 -21
- data/lib/datadog/core/ddsketch.rb +19 -0
- data/lib/datadog/core/deprecations.rb +2 -2
- data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/core/environment/agent_info.rb +65 -1
- data/lib/datadog/core/environment/cgroup.rb +52 -25
- data/lib/datadog/core/environment/container.rb +140 -46
- data/lib/datadog/core/environment/ext.rb +13 -2
- data/lib/datadog/core/environment/git.rb +2 -2
- data/lib/datadog/core/environment/identity.rb +40 -4
- data/lib/datadog/core/environment/process.rb +122 -0
- data/lib/datadog/core/environment/socket.rb +13 -0
- data/lib/datadog/core/environment/variable_helpers.rb +3 -3
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/error.rb +6 -6
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/knuth_sampler.rb +57 -0
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +7 -7
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/pin.rb +8 -8
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
- data/lib/datadog/core/process_discovery.rb +51 -21
- data/lib/datadog/core/rate_limiter.rb +11 -1
- data/lib/datadog/core/remote/client/capabilities.rb +49 -6
- data/lib/datadog/core/remote/client.rb +15 -7
- data/lib/datadog/core/remote/component.rb +64 -36
- data/lib/datadog/core/remote/configuration/content.rb +15 -2
- data/lib/datadog/core/remote/configuration/digest.rb +14 -7
- data/lib/datadog/core/remote/configuration/repository.rb +13 -1
- data/lib/datadog/core/remote/configuration/target.rb +13 -6
- data/lib/datadog/core/remote/transport/config.rb +9 -36
- data/lib/datadog/core/remote/transport/http/config.rb +15 -55
- data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
- data/lib/datadog/core/remote/transport/http.rb +15 -24
- data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
- data/lib/datadog/core/remote/worker.rb +25 -37
- data/lib/datadog/core/runtime/ext.rb +0 -1
- data/lib/datadog/core/runtime/metrics.rb +11 -2
- data/lib/datadog/core/semaphore.rb +1 -4
- data/lib/datadog/core/tag_builder.rb +52 -0
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +65 -17
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -3
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +203 -89
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/event.rb +2 -7
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/logger.rb +5 -2
- data/lib/datadog/core/telemetry/logging.rb +22 -4
- data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
- data/lib/datadog/core/telemetry/request.rb +17 -5
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +8 -34
- data/lib/datadog/core/telemetry/transport/http.rb +21 -16
- data/lib/datadog/core/telemetry/transport/telemetry.rb +8 -17
- data/lib/datadog/core/telemetry/worker.rb +108 -32
- data/lib/datadog/core/transport/ext.rb +3 -0
- data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
- data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
- data/lib/datadog/core/transport/http/api/instance.rb +4 -21
- data/lib/datadog/core/transport/http/builder.rb +11 -7
- data/lib/datadog/core/transport/http/client.rb +80 -0
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/transport/http/response.rb +4 -0
- data/lib/datadog/core/transport/http.rb +24 -19
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/transport/response.rb +16 -2
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +4 -3
- data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -14
- data/lib/datadog/core/utils/network.rb +22 -1
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/only_once_successful.rb +8 -2
- data/lib/datadog/core/utils/safe_dup.rb +2 -2
- data/lib/datadog/core/utils/sequence.rb +2 -0
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +1 -1
- data/lib/datadog/core/utils.rb +10 -1
- data/lib/datadog/core/workers/async.rb +11 -2
- data/lib/datadog/core/workers/interval_loop.rb +51 -3
- data/lib/datadog/core/workers/polling.rb +2 -0
- data/lib/datadog/core/workers/queue.rb +96 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
- data/lib/datadog/core.rb +11 -2
- data/lib/datadog/data_streams/configuration.rb +50 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +511 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
- data/lib/datadog/data_streams/transport/http.rb +36 -0
- data/lib/datadog/data_streams/transport/stats.rb +39 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/base.rb +15 -7
- data/lib/datadog/di/boot.rb +11 -1
- data/lib/datadog/di/code_tracker.rb +217 -15
- data/lib/datadog/di/component.rb +143 -48
- data/lib/datadog/di/configuration.rb +235 -2
- data/lib/datadog/di/context.rb +76 -0
- data/lib/datadog/di/contrib/active_record.rb +34 -5
- data/lib/datadog/di/el/compiler.rb +168 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +44 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +620 -132
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +78 -15
- data/lib/datadog/di/probe_builder.rb +42 -3
- data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
- data/lib/datadog/di/probe_file_loader.rb +87 -0
- data/lib/datadog/di/probe_manager.rb +195 -100
- data/lib/datadog/di/probe_notification_builder.rb +285 -100
- data/lib/datadog/di/probe_notifier_worker.rb +120 -36
- data/lib/datadog/di/probe_repository.rb +198 -0
- data/lib/datadog/di/proc_responder.rb +36 -0
- data/lib/datadog/di/redactor.rb +24 -2
- data/lib/datadog/di/remote.rb +258 -95
- data/lib/datadog/di/serializer.rb +297 -20
- data/lib/datadog/di/transport/diagnostics.rb +12 -42
- data/lib/datadog/di/transport/http/diagnostics.rb +4 -34
- data/lib/datadog/di/transport/http/input.rb +12 -34
- data/lib/datadog/di/transport/http.rb +43 -19
- data/lib/datadog/di/transport/input.rb +121 -39
- data/lib/datadog/di/utils.rb +42 -14
- data/lib/datadog/di.rb +203 -10
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/error_tracking/configuration.rb +55 -2
- data/lib/datadog/error_tracking/filters.rb +2 -2
- data/lib/datadog/kit/appsec/events/v2.rb +253 -0
- data/lib/datadog/kit/appsec/events.rb +11 -10
- data/lib/datadog/kit/enable_core_dumps.rb +1 -1
- data/lib/datadog/kit/identity.rb +17 -11
- data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
- data/lib/datadog/open_feature/component.rb +100 -0
- data/lib/datadog/open_feature/configuration.rb +37 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +60 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +16 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +49 -0
- data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
- data/lib/datadog/open_feature/native_evaluator.rb +60 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +191 -0
- data/lib/datadog/open_feature/remote.rb +67 -0
- data/lib/datadog/open_feature/resolution_details.rb +35 -0
- data/lib/datadog/open_feature/transport.rb +121 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
- data/lib/datadog/opentelemetry/api/context.rb +10 -9
- data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +90 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +66 -2
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +13 -7
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +12 -9
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +14 -10
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
- data/lib/datadog/opentelemetry/trace.rb +4 -4
- data/lib/datadog/opentelemetry.rb +4 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +83 -15
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +52 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -4
- data/lib/datadog/profiling/collectors/info.rb +59 -4
- data/lib/datadog/profiling/collectors/thread_context.rb +17 -5
- data/lib/datadog/profiling/component.rb +73 -49
- data/lib/datadog/profiling/exporter.rb +48 -13
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/ext.rb +2 -15
- data/lib/datadog/profiling/flush.rb +23 -11
- data/lib/datadog/profiling/http_transport.rb +24 -11
- data/lib/datadog/profiling/load_native_extension.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +31 -12
- data/lib/datadog/profiling/scheduler.rb +12 -10
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/stack_recorder.rb +5 -13
- data/lib/datadog/profiling/tag_builder.rb +42 -38
- data/lib/datadog/profiling/tasks/exec.rb +10 -5
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +4 -2
- data/lib/datadog/profiling.rb +8 -11
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +10 -1
- data/lib/datadog/symbol_database/component.rb +734 -0
- data/lib/datadog/symbol_database/configuration.rb +73 -0
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +1110 -0
- data/lib/datadog/symbol_database/file_hash.rb +48 -0
- data/lib/datadog/symbol_database/logger.rb +43 -0
- data/lib/datadog/symbol_database/remote.rb +180 -0
- data/lib/datadog/symbol_database/scope.rb +102 -0
- data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
- data/lib/datadog/symbol_database/service_version.rb +66 -0
- data/lib/datadog/symbol_database/symbol.rb +69 -0
- data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
- data/lib/datadog/symbol_database/transport/http.rb +45 -0
- data/lib/datadog/symbol_database/transport.rb +54 -0
- data/lib/datadog/symbol_database/uploader.rb +230 -0
- data/lib/datadog/symbol_database.rb +71 -0
- data/lib/datadog/tracing/analytics.rb +1 -1
- data/lib/datadog/tracing/buffer.rb +10 -10
- data/lib/datadog/tracing/component.rb +17 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +6 -8
- data/lib/datadog/tracing/configuration/ext.rb +25 -5
- data/lib/datadog/tracing/configuration/settings.rb +125 -11
- data/lib/datadog/tracing/context.rb +2 -2
- data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
- data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -1
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +29 -11
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +47 -12
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
- data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +4 -1
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/event.rb +8 -8
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +2 -2
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +4 -4
- data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +1 -2
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +3 -3
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +7 -10
- data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/utils.rb +16 -16
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +15 -12
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -4
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +2 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +8 -10
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +8 -10
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +3 -1
- data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
- data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/s3.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/sns.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/sqs.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/states.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/services.rb +7 -7
- data/lib/datadog/tracing/contrib/component.rb +3 -3
- data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/configurable.rb +22 -7
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +4 -4
- data/lib/datadog/tracing/contrib/dalli/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/dalli/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/delayed_job/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +2 -0
- data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +4 -4
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +47 -53
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +7 -7
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +7 -8
- data/lib/datadog/tracing/contrib/ethon/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/ethon/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +1 -8
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/excon/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/excon/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -9
- data/lib/datadog/tracing/contrib/ext.rb +6 -4
- data/lib/datadog/tracing/contrib/extensions.rb +28 -11
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +16 -9
- data/lib/datadog/tracing/contrib/faraday/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +7 -9
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +18 -15
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
- data/lib/datadog/tracing/contrib/grape/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +105 -63
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +11 -15
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -10
- data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/grpc/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/ext.rb +2 -2
- data/lib/datadog/tracing/contrib/hanami/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +10 -11
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +4 -4
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +9 -13
- data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -10
- data/lib/datadog/tracing/contrib/httpclient/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/httprb/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/httprb/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/event.rb +2 -1
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +24 -13
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +39 -8
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +6 -6
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +11 -14
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +19 -12
- data/lib/datadog/tracing/contrib/mysql2/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +1 -2
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +64 -70
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +7 -7
- data/lib/datadog/tracing/contrib/patcher.rb +7 -9
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +3 -6
- data/lib/datadog/tracing/contrib/pg/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/presto/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +9 -11
- data/lib/datadog/tracing/contrib/presto/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +1 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +13 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +6 -2
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +25 -1
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/que/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/racecar/event.rb +2 -6
- data/lib/datadog/tracing/contrib/racecar/events/batch.rb +2 -2
- data/lib/datadog/tracing/contrib/racecar/events/consume.rb +1 -1
- data/lib/datadog/tracing/contrib/racecar/events/message.rb +2 -2
- data/lib/datadog/tracing/contrib/racecar/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +11 -1
- data/lib/datadog/tracing/contrib/rack/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
- data/lib/datadog/tracing/contrib/rack/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +83 -44
- data/lib/datadog/tracing/contrib/rack/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +4 -3
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +126 -2
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +6 -3
- data/lib/datadog/tracing/contrib/rails/ext.rb +3 -1
- data/lib/datadog/tracing/contrib/rails/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +3 -3
- data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -2
- data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +10 -8
- data/lib/datadog/tracing/contrib/rake/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/redis/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/redis/quantize.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/tags.rb +2 -7
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +6 -4
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/registry.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -1
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +8 -9
- data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +8 -3
- data/lib/datadog/tracing/contrib/roda/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/database.rb +6 -5
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +2 -1
- data/lib/datadog/tracing/contrib/sequel/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -6
- data/lib/datadog/tracing/contrib/shoryuken/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +8 -4
- data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +40 -39
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
- data/lib/datadog/tracing/contrib/stripe/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/stripe/request.rb +2 -2
- data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -0
- data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -6
- data/lib/datadog/tracing/contrib/trilogy/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +15 -13
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -6
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
- data/lib/datadog/tracing/contrib.rb +9 -0
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +12 -4
- data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
- data/lib/datadog/tracing/distributed/baggage.rb +133 -12
- data/lib/datadog/tracing/distributed/datadog.rb +17 -16
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +11 -26
- data/lib/datadog/tracing/distributed/helpers.rb +1 -1
- data/lib/datadog/tracing/distributed/none.rb +4 -2
- data/lib/datadog/tracing/distributed/propagation.rb +39 -4
- data/lib/datadog/tracing/distributed/propagation_policy.rb +1 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +107 -78
- data/lib/datadog/tracing/event.rb +5 -7
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/analytics.rb +1 -1
- data/lib/datadog/tracing/metadata/ext.rb +23 -1
- data/lib/datadog/tracing/metadata/tagging.rb +6 -6
- data/lib/datadog/tracing/pipeline/span_filter.rb +3 -1
- data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
- data/lib/datadog/tracing/pipeline.rb +2 -2
- data/lib/datadog/tracing/remote.rb +36 -13
- data/lib/datadog/tracing/sampling/ext.rb +2 -2
- data/lib/datadog/tracing/sampling/priority_sampler.rb +16 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
- data/lib/datadog/tracing/sampling/rule.rb +1 -1
- data/lib/datadog/tracing/sampling/rule_sampler.rb +77 -48
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +3 -3
- data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
- data/lib/datadog/tracing/span.rb +2 -2
- data/lib/datadog/tracing/span_event.rb +12 -12
- data/lib/datadog/tracing/span_link.rb +12 -12
- data/lib/datadog/tracing/span_operation.rb +63 -19
- data/lib/datadog/tracing/sync_writer.rb +1 -2
- data/lib/datadog/tracing/trace_digest.rb +28 -23
- data/lib/datadog/tracing/trace_operation.rb +209 -98
- data/lib/datadog/tracing/trace_segment.rb +2 -2
- data/lib/datadog/tracing/tracer.rb +113 -52
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +4 -52
- data/lib/datadog/tracing/transport/http.rb +15 -9
- data/lib/datadog/tracing/transport/io/client.rb +11 -14
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/statistics.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +22 -0
- data/lib/datadog/tracing/transport/traces.rb +14 -82
- data/lib/datadog/tracing/workers.rb +4 -3
- data/lib/datadog/tracing/writer.rb +1 -1
- data/lib/datadog/tracing.rb +2 -2
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +14 -0
- metadata +201 -40
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
- data/ext/libdatadog_api/macos_development.md +0 -26
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
- data/lib/datadog/appsec/configuration/settings.rb +0 -342
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
- data/lib/datadog/appsec/processor.rb +0 -107
- data/lib/datadog/core/remote/transport/http/api.rb +0 -53
- data/lib/datadog/core/remote/transport/http/client.rb +0 -49
- data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
- data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
- data/lib/datadog/core/transport/http/api/spec.rb +0 -36
- data/lib/datadog/di/configuration/settings.rb +0 -212
- data/lib/datadog/di/transport/http/api.rb +0 -42
- data/lib/datadog/di/transport/http/client.rb +0 -47
- data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
- data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
- data/lib/datadog/tracing/transport/http/api.rb +0 -44
- data/lib/datadog/tracing/workers/trace_writer.rb +0 -199
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'scope'
|
|
4
|
+
require_relative 'symbol'
|
|
5
|
+
require_relative 'file_hash'
|
|
6
|
+
require_relative '../core/utils/enumerable_compat'
|
|
7
|
+
require_relative '../di/fatal_exceptions'
|
|
8
|
+
|
|
9
|
+
module Datadog
|
|
10
|
+
module SymbolDatabase
|
|
11
|
+
# Extracts symbol metadata from loaded Ruby modules and classes via introspection.
|
|
12
|
+
#
|
|
13
|
+
# Instance created by Component with injected dependencies (logger, settings).
|
|
14
|
+
# All methods are instance methods accessing @logger, @settings directly —
|
|
15
|
+
# no parameter threading needed.
|
|
16
|
+
#
|
|
17
|
+
# Uses Ruby's reflection APIs (Module#constants, Class#instance_methods, Method#parameters)
|
|
18
|
+
# to build hierarchical Scope structures representing code organization.
|
|
19
|
+
# Filters to user code only (excludes gems, stdlib, test files).
|
|
20
|
+
#
|
|
21
|
+
# Extraction flow:
|
|
22
|
+
# 1. ObjectSpace.each_object(Module) - Iterate all loaded modules/classes
|
|
23
|
+
# 2. Filter to user code (user_code_module?)
|
|
24
|
+
# 3. Build MODULE or CLASS scope with nested METHOD scopes
|
|
25
|
+
# 4. Extract symbols: constants, class variables, method parameters
|
|
26
|
+
#
|
|
27
|
+
# Called by: Component.extract_and_upload (during upload trigger)
|
|
28
|
+
# Produces: Scope objects passed to ScopeBatcher for batching
|
|
29
|
+
# File hashing: Calls FileHash.compute for MODULE scopes
|
|
30
|
+
#
|
|
31
|
+
# Error handling strategy (defense-in-depth):
|
|
32
|
+
#
|
|
33
|
+
# The extractor introspects arbitrary Ruby objects via ObjectSpace. Ruby's
|
|
34
|
+
# reflection APIs (Module#name, #instance_methods, #const_get, #source_location,
|
|
35
|
+
# #parameters) can fail unpredictably on third-party code: NameError from removed
|
|
36
|
+
# constants, LoadError from autoload, ArgumentError from overridden #name methods,
|
|
37
|
+
# SecurityError in restricted contexts, and more.
|
|
38
|
+
#
|
|
39
|
+
# Rescue blocks are organized in three layers:
|
|
40
|
+
#
|
|
41
|
+
# 1. **Inner per-item rescues** (bare `rescue` in const_get loops, method.name):
|
|
42
|
+
# Skip one constant or name lookup without aborting the enclosing collection.
|
|
43
|
+
# These are expected failures — no logging needed.
|
|
44
|
+
#
|
|
45
|
+
# 2. **Method-level rescues** (`rescue => e` with logging):
|
|
46
|
+
# Catch failures in extract_method_scope, find_source_file, etc. Log at debug
|
|
47
|
+
# for post-hoc diagnosis, return nil or empty array. One bad method/module
|
|
48
|
+
# doesn't kill the entire class extraction.
|
|
49
|
+
#
|
|
50
|
+
# 3. **Top-level entry rescues** (`rescue => e` with logging):
|
|
51
|
+
# extract() and extract_all() are the error boundaries. Any exception that
|
|
52
|
+
# escapes layers 1-2 is caught here and logged.
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
class Extractor
|
|
56
|
+
# Common Ruby core modules to exclude from included_modules extraction.
|
|
57
|
+
# These are ubiquitous mix-ins that don't provide meaningful context about the class structure.
|
|
58
|
+
# Kernel: Mixed into Object, appears in nearly all classes
|
|
59
|
+
# PP: Pretty-printing module, loaded by many tools
|
|
60
|
+
# JSON: JSON serialization module, loaded by many tools
|
|
61
|
+
# Enumerable: Core iteration protocol, extremely common
|
|
62
|
+
# Comparable: Core comparison protocol, extremely common
|
|
63
|
+
# Sentinel for unknown minimum line number. 0 means "available throughout the scope."
|
|
64
|
+
# Defined here (the only runtime consumer) so extractor.rb is self-contained.
|
|
65
|
+
# The parent module (lib/datadog/symbol_database.rb) defines the same values for
|
|
66
|
+
# documentation and external reference, but is not required by this file.
|
|
67
|
+
UNKNOWN_MIN_LINE = 0
|
|
68
|
+
# PostgreSQL signed INT_MAX (2^31 - 1). Means "entire file" or "unknown end."
|
|
69
|
+
UNKNOWN_MAX_LINE = 2147483647
|
|
70
|
+
|
|
71
|
+
EXCLUDED_COMMON_MODULES = ['Kernel', 'PP::', 'JSON::', 'Enumerable', 'Comparable'].freeze
|
|
72
|
+
|
|
73
|
+
# RubyVM::InstructionSequence#trace_points event types included when
|
|
74
|
+
# computing targetable lines on METHOD scopes.
|
|
75
|
+
# :line — any line with executable bytecode (primary line probe target)
|
|
76
|
+
# :return — last expression before method returns (DI instruments return events)
|
|
77
|
+
# :call excluded — method entry is handled by method probes, not line probes
|
|
78
|
+
TARGETABLE_LINE_EVENTS = [:line, :return].freeze
|
|
79
|
+
|
|
80
|
+
# Cached unbound Module#singleton_class? — dispatched explicitly so user classes
|
|
81
|
+
# that define their own `singleton_class?` (e.g. with required arguments) cannot
|
|
82
|
+
# intercept the predicate and cause the module to be silently dropped from
|
|
83
|
+
# extract_all. Cached at load time because build_per_file_index iterates
|
|
84
|
+
# ObjectSpace.each_object(Module) over tens of thousands of modules.
|
|
85
|
+
MODULE_SINGLETON_CLASS_PRED = Module.instance_method(:singleton_class?)
|
|
86
|
+
private_constant :MODULE_SINGLETON_CLASS_PRED
|
|
87
|
+
|
|
88
|
+
# Cached UnboundMethod for Module#name — avoids resolving it on every
|
|
89
|
+
# safe_mod_name call. Some classes override .name (e.g. Faker::Travel::Airport),
|
|
90
|
+
# so we bind the original Module#name to get the real module name safely.
|
|
91
|
+
MODULE_NAME = Module.instance_method(:name)
|
|
92
|
+
|
|
93
|
+
# Cached UnboundMethods for the remaining class/module/object introspection
|
|
94
|
+
# used during extraction. Like MODULE_NAME, these bind the original
|
|
95
|
+
# implementations and are dispatched explicitly so that application code
|
|
96
|
+
# which overrides any of them (per-class, on a subclass, or in a singleton
|
|
97
|
+
# class) can neither intercept extraction nor be executed as a side effect
|
|
98
|
+
# of it. KERNEL_CLASS recovers an object's real class without calling a
|
|
99
|
+
# possibly-overridden #class.
|
|
100
|
+
CLASS_SUPERCLASS = Class.instance_method(:superclass)
|
|
101
|
+
MODULE_INCLUDED_MODULES = Module.instance_method(:included_modules)
|
|
102
|
+
MODULE_ANCESTORS = Module.instance_method(:ancestors)
|
|
103
|
+
MODULE_CLASS_VARIABLES = Module.instance_method(:class_variables)
|
|
104
|
+
MODULE_CONSTANTS = Module.instance_method(:constants)
|
|
105
|
+
MODULE_AUTOLOAD_P = Module.instance_method(:autoload?)
|
|
106
|
+
MODULE_CONST_GET = Module.instance_method(:const_get)
|
|
107
|
+
MODULE_CONST_DEFINED = Module.instance_method(:const_defined?)
|
|
108
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
109
|
+
private_constant :CLASS_SUPERCLASS, :MODULE_INCLUDED_MODULES, :MODULE_ANCESTORS,
|
|
110
|
+
:MODULE_CLASS_VARIABLES, :MODULE_CONSTANTS, :MODULE_AUTOLOAD_P, :MODULE_CONST_GET,
|
|
111
|
+
:MODULE_CONST_DEFINED, :KERNEL_CLASS
|
|
112
|
+
|
|
113
|
+
# @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
|
|
114
|
+
# @param settings [Configuration::Settings] Tracer settings
|
|
115
|
+
def initialize(logger:, settings:)
|
|
116
|
+
@logger = logger
|
|
117
|
+
@settings = settings
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Extract symbols from a single module or class.
|
|
121
|
+
# Returns nil if module should be skipped (anonymous, gem code, stdlib).
|
|
122
|
+
#
|
|
123
|
+
# Returns a FILE scope wrapping the extracted CLASS or MODULE scope.
|
|
124
|
+
# The backend requires root-level scopes to be in ROOT_SCOPES (MODULE, JAR,
|
|
125
|
+
# ASSEMBLY, PACKAGE, FILE). FILE is the natural root for Ruby — one per source file.
|
|
126
|
+
#
|
|
127
|
+
# For full extraction with proper FQN-based nesting and per-file method grouping,
|
|
128
|
+
# use extract_all instead. This method is kept for single-module extraction in tests.
|
|
129
|
+
#
|
|
130
|
+
# @param mod [Module, Class] The module or class to extract from
|
|
131
|
+
# @return [Scope, nil] FILE scope wrapping extracted scope, or nil if filtered out
|
|
132
|
+
def extract(mod)
|
|
133
|
+
return nil unless Module === mod
|
|
134
|
+
mod_name = safe_mod_name(mod)
|
|
135
|
+
return nil unless mod_name
|
|
136
|
+
|
|
137
|
+
return nil unless user_code_module?(mod)
|
|
138
|
+
|
|
139
|
+
source_file = find_source_file(mod)
|
|
140
|
+
return nil unless source_file
|
|
141
|
+
|
|
142
|
+
inner_scope = if Class === mod
|
|
143
|
+
extract_class_scope(mod)
|
|
144
|
+
else
|
|
145
|
+
extract_module_scope(mod)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
wrap_in_file_scope(source_file, [inner_scope])
|
|
149
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
150
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
151
|
+
@logger.debug { "symdb: failed to extract #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Extract symbols from all loaded modules and classes.
|
|
156
|
+
# Returns an array of FILE scopes with proper FQN-based nesting.
|
|
157
|
+
#
|
|
158
|
+
# Two-pass algorithm:
|
|
159
|
+
# Pass 1 (`build_per_file_index`): iterate ObjectSpace once, building
|
|
160
|
+
# `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
|
|
161
|
+
# Stores Symbol method names + Module refs only; no UnboundMethod retention
|
|
162
|
+
# between passes.
|
|
163
|
+
# Pass 2 (`build_file_scope`): for each file in the index, resolve
|
|
164
|
+
# UnboundMethods just-in-time, build the nested MODULE/CLASS scope tree from
|
|
165
|
+
# FQN splitting, and produce one FILE Scope. The per-file working set is
|
|
166
|
+
# released as soon as the FILE scope is yielded (or accumulated into the
|
|
167
|
+
# returned Array, in legacy mode).
|
|
168
|
+
#
|
|
169
|
+
# This is the production path used by Component. Methods are split by source file,
|
|
170
|
+
# so a class reopened across two files produces two FILE scopes, each with only
|
|
171
|
+
# the methods defined in that file.
|
|
172
|
+
#
|
|
173
|
+
# Memory profile (with a block):
|
|
174
|
+
# - Pass 1 builds a per-file index containing only Symbol method names plus
|
|
175
|
+
# Module references. No UnboundMethod objects are retained between passes.
|
|
176
|
+
# - Pass 2 processes one file at a time. The peak per file is bounded by the
|
|
177
|
+
# number of methods that live in that one file across all its modules
|
|
178
|
+
# (typical Rails: tens of methods; pathological case: a single very large
|
|
179
|
+
# source file). Once a FILE scope is yielded and the caller stops referencing
|
|
180
|
+
# it, the entire per-file working set becomes garbage.
|
|
181
|
+
#
|
|
182
|
+
# This is O(largest_file + batch_buffer), not O(total_classes).
|
|
183
|
+
#
|
|
184
|
+
# Without a block, returns the full `Array<Scope>` (legacy form, used by specs).
|
|
185
|
+
# The Array itself still scales with the number of files, so block form is the
|
|
186
|
+
# one to use for production memory bounds.
|
|
187
|
+
#
|
|
188
|
+
# @yieldparam scope [Scope] FILE scope for one source file
|
|
189
|
+
# @return [Array<Scope>, nil] Array of FILE scopes when called without a block; nil when a block is given
|
|
190
|
+
def extract_all
|
|
191
|
+
index = build_per_file_index
|
|
192
|
+
|
|
193
|
+
if block_given?
|
|
194
|
+
# Drain the index destructively so each per-file entry becomes eligible for
|
|
195
|
+
# collection as soon as its FILE scope is yielded and consumed. Hash#shift
|
|
196
|
+
# returns [key, value] on a non-empty hash and nil when empty, so the
|
|
197
|
+
# `while (pair = ...)` form is the drain. Indexing pair[0]/pair[1] rather
|
|
198
|
+
# than destructuring avoids introducing names into method scope that would
|
|
199
|
+
# then shadow the else-branch's block parameters.
|
|
200
|
+
while (pair = index.shift)
|
|
201
|
+
scope = build_file_scope(pair[0], pair[1])
|
|
202
|
+
yield scope if scope
|
|
203
|
+
end
|
|
204
|
+
nil
|
|
205
|
+
else
|
|
206
|
+
# Legacy non-block form for specs. No memory bound — the full Array is
|
|
207
|
+
# materialized.
|
|
208
|
+
result = []
|
|
209
|
+
index.each do |path, file_entries|
|
|
210
|
+
scope = build_file_scope(path, file_entries)
|
|
211
|
+
result << scope if scope
|
|
212
|
+
end
|
|
213
|
+
result
|
|
214
|
+
end
|
|
215
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
216
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
217
|
+
@logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
|
|
218
|
+
block_given? ? nil : []
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# Safe Module#name lookup — some classes override the singleton `name` method
|
|
224
|
+
# (e.g. Faker::Travel::Airport defines `def name(size:, region:)` in class << self,
|
|
225
|
+
# which shadows Module#name and raises ArgumentError when called without args).
|
|
226
|
+
# @param mod [Module] The module
|
|
227
|
+
# @return [String, nil] Module name or nil
|
|
228
|
+
def safe_mod_name(mod)
|
|
229
|
+
MODULE_NAME.bind(mod).call
|
|
230
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
231
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
232
|
+
@logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Verify that mod_name still resolves to mod through Ruby's constant
|
|
237
|
+
# table. Returns false when a Class/Module has been detached from its
|
|
238
|
+
# constant (via remove_const) but still carries the cached Module#name —
|
|
239
|
+
# see build_per_file_index for the failure mode this protects.
|
|
240
|
+
#
|
|
241
|
+
# Walks the namespace path segment-by-segment. For each segment:
|
|
242
|
+
# 1. Check for a pending autoload directly on the current namespace.
|
|
243
|
+
# If present, const_get would trigger it — loading customer code as
|
|
244
|
+
# a side effect of symbol extraction and raising LoadError if the
|
|
245
|
+
# target file is missing (LoadError is ScriptError, not StandardError,
|
|
246
|
+
# and would propagate past the outer rescue in
|
|
247
|
+
# build_per_file_index). Return false instead.
|
|
248
|
+
# 2. Otherwise, require the constant to be directly defined on this
|
|
249
|
+
# namespace (const_defined?(sym, false)) and descend via
|
|
250
|
+
# const_get(sym, false). The direct-only lookup means an ancestor's
|
|
251
|
+
# pending autoload at the same name does not affect the result: a
|
|
252
|
+
# subclass with its own binding resolves through the binding, an
|
|
253
|
+
# inherited autoload triggers nothing.
|
|
254
|
+
#
|
|
255
|
+
# Uses `current.autoload?(sym, false)` (the inherit=false form, added
|
|
256
|
+
# in Ruby 2.7) so the question is strictly "is there an autoload
|
|
257
|
+
# registered directly on this namespace?" and ancestors' pending
|
|
258
|
+
# autoloads at the same name do not affect the result.
|
|
259
|
+
# @param mod_name [String]
|
|
260
|
+
# @param mod [Module]
|
|
261
|
+
# @return [Boolean]
|
|
262
|
+
def resolves_to_same_module?(mod_name, mod)
|
|
263
|
+
current = Object
|
|
264
|
+
mod_name.split('::').each do |seg|
|
|
265
|
+
sym = seg.to_sym
|
|
266
|
+
return false if MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
267
|
+
return false unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
268
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
269
|
+
end
|
|
270
|
+
current.equal?(mod)
|
|
271
|
+
rescue NameError, ArgumentError, TypeError
|
|
272
|
+
# Expected "no" outcome for stale/detached classes — the whole point
|
|
273
|
+
# of this predicate. Per the rescue convention in this file's header
|
|
274
|
+
# comment: inner per-item rescues are expected failures, no logging.
|
|
275
|
+
false
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Check if module is from user code (not gems or stdlib)
|
|
279
|
+
# @param mod [Module] The module to check
|
|
280
|
+
# @return [Boolean] true if user code
|
|
281
|
+
def user_code_module?(mod)
|
|
282
|
+
mod_name = safe_mod_name(mod)
|
|
283
|
+
return false unless mod_name
|
|
284
|
+
|
|
285
|
+
# CRITICAL: Exclude entire Datadog namespace (prevents circular extraction)
|
|
286
|
+
# Matches Java: className.startsWith("com/datadog/")
|
|
287
|
+
# Matches Python: packages.is_user_code() excludes ddtrace.*
|
|
288
|
+
# Note: bare 'Datadog' must be checked separately — start_with?('Datadog::')
|
|
289
|
+
# doesn't match the root module itself.
|
|
290
|
+
return false if mod_name == 'Datadog' || mod_name.start_with?('Datadog::')
|
|
291
|
+
|
|
292
|
+
# Exclude Ruby root classes. These are never user code, but
|
|
293
|
+
# find_source_file can return a user-code path for them via
|
|
294
|
+
# const_source_location (top-level constants like User are
|
|
295
|
+
# Object constants, so Object.const_source_location(:User)
|
|
296
|
+
# points to the user's file).
|
|
297
|
+
return false if mod.equal?(Object) || mod.equal?(BasicObject) ||
|
|
298
|
+
mod.equal?(Kernel) || mod.equal?(Module) || mod.equal?(Class)
|
|
299
|
+
|
|
300
|
+
source_file = find_source_file(mod)
|
|
301
|
+
return false unless source_file
|
|
302
|
+
|
|
303
|
+
user_code_path?(source_file)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Check if path is user code
|
|
307
|
+
# @param path [String] File path
|
|
308
|
+
# @return [Boolean] true if user code
|
|
309
|
+
def user_code_path?(path)
|
|
310
|
+
# Only absolute paths are real source files. Pseudo-paths like '<main>',
|
|
311
|
+
# '<internal:...>', '(eval)' are not user code.
|
|
312
|
+
return false unless path.start_with?('/')
|
|
313
|
+
# Only .rb files are Ruby source. Excludes the Ruby binary
|
|
314
|
+
# (/usr/local/bin/ruby), C extensions (.so/.bundle), and other
|
|
315
|
+
# non-source files that appear in method source_location.
|
|
316
|
+
return false unless path.end_with?('.rb')
|
|
317
|
+
# Exclude gem paths
|
|
318
|
+
return false if path.include?('/gems/')
|
|
319
|
+
# Exclude Ruby stdlib
|
|
320
|
+
return false if path.include?('/ruby/')
|
|
321
|
+
return false if path.start_with?('<internal:')
|
|
322
|
+
return false if path.include?('(eval)')
|
|
323
|
+
# Exclude test code (not application code)
|
|
324
|
+
return false if path.include?('/spec/')
|
|
325
|
+
return false if path.include?('/test/')
|
|
326
|
+
# Exclude Datadog's own library code (e.g., monkey-patched methods from tracing contrib).
|
|
327
|
+
# Without this, stdlib classes like Net::HTTP appear as user code when dd-trace-rb
|
|
328
|
+
# instruments them, because the patched method source points to lib/datadog/tracing/contrib/.
|
|
329
|
+
return false if path.include?('/lib/datadog/')
|
|
330
|
+
|
|
331
|
+
true
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Find source file for a module.
|
|
335
|
+
# Prefers user code paths over gem/stdlib paths. ActiveRecord models have
|
|
336
|
+
# generated methods (autosave callbacks) whose source is in the gem, but
|
|
337
|
+
# user-defined methods point to app/models/. Without this preference,
|
|
338
|
+
# AR models get filtered out as gem code.
|
|
339
|
+
#
|
|
340
|
+
# For namespace-only modules (no instance or singleton methods), falls back to
|
|
341
|
+
# Module#const_source_location to locate the module via its constants.
|
|
342
|
+
# This handles patterns like `module ApplicationCable; class Channel...; end; end`
|
|
343
|
+
# where the namespace module itself has no methods but defines user-code classes.
|
|
344
|
+
#
|
|
345
|
+
# @param mod [Module] The module
|
|
346
|
+
# @return [String, nil] Source file path or nil
|
|
347
|
+
def find_source_file(mod)
|
|
348
|
+
fallback = nil
|
|
349
|
+
|
|
350
|
+
# Try instance methods first
|
|
351
|
+
mod.instance_methods(false).each do |method_name|
|
|
352
|
+
method = mod.instance_method(method_name)
|
|
353
|
+
location = method.source_location
|
|
354
|
+
next unless location
|
|
355
|
+
|
|
356
|
+
path = location[0]
|
|
357
|
+
return path if user_code_path?(path)
|
|
358
|
+
|
|
359
|
+
fallback ||= path # steep:ignore
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Try singleton methods
|
|
363
|
+
mod.singleton_methods(false).each do |method_name|
|
|
364
|
+
method = mod.method(method_name)
|
|
365
|
+
location = method.source_location
|
|
366
|
+
next unless location
|
|
367
|
+
|
|
368
|
+
path = location[0]
|
|
369
|
+
return path if user_code_path?(path)
|
|
370
|
+
|
|
371
|
+
fallback ||= path # steep:ignore
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Use const_source_location to find where this class/module is declared.
|
|
375
|
+
# This handles two cases:
|
|
376
|
+
# 1. Classes with no user-defined methods (e.g. AR models with only associations) whose
|
|
377
|
+
# generated methods point to gem code — we find the `class Foo` declaration instead.
|
|
378
|
+
# 2. Namespace-only modules (`module Foo; class Bar; end; end`) with no methods at all.
|
|
379
|
+
mod_name = safe_mod_name(mod)
|
|
380
|
+
if mod_name
|
|
381
|
+
# Look up the class/module by its last name component in its enclosing namespace.
|
|
382
|
+
parts = mod_name.split('::')
|
|
383
|
+
const_name = parts.last
|
|
384
|
+
namespace = if parts.length > 1
|
|
385
|
+
begin
|
|
386
|
+
MODULE_CONST_GET.bind(Object).call(parts[0..-2].join('::')) # steep:ignore
|
|
387
|
+
rescue NameError
|
|
388
|
+
nil
|
|
389
|
+
end
|
|
390
|
+
else
|
|
391
|
+
Object
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
if namespace
|
|
395
|
+
location = begin
|
|
396
|
+
namespace.const_source_location(const_name)
|
|
397
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
398
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
399
|
+
@logger.debug { "symdb: const_source_location(#{const_name}) failed: #{e.class}: #{e.message}" }
|
|
400
|
+
nil
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if location && !location.empty?
|
|
404
|
+
path = location[0]
|
|
405
|
+
return path if path && !path.empty? && user_code_path?(path)
|
|
406
|
+
fallback ||= ((path && !path.empty?) ? path : nil)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Also scan constants defined by mod itself (namespace-only modules).
|
|
411
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |child_const_name|
|
|
412
|
+
location = begin
|
|
413
|
+
mod.const_source_location(child_const_name)
|
|
414
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
415
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
416
|
+
@logger.debug { "symdb: const_source_location(#{child_const_name}) failed: #{e.class}: #{e.message}" }
|
|
417
|
+
nil
|
|
418
|
+
end
|
|
419
|
+
next unless location && !location.empty?
|
|
420
|
+
|
|
421
|
+
path = location[0]
|
|
422
|
+
next unless path && !path.empty?
|
|
423
|
+
|
|
424
|
+
return path if user_code_path?(path)
|
|
425
|
+
|
|
426
|
+
fallback ||= path
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
fallback
|
|
431
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
432
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
433
|
+
@logger.debug { "symdb: error finding source file for #{safe_mod_name(mod) || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
434
|
+
nil
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Wrap inner scopes in a FILE root scope.
|
|
438
|
+
# FILE is the per-source-file root scope for Ruby uploads, analogous to
|
|
439
|
+
# Python's MODULE-per-file or Java's JAR.
|
|
440
|
+
#
|
|
441
|
+
# @param file_path [String] Source file path
|
|
442
|
+
# @param inner_scopes [Array<Scope>] Child scopes to nest under FILE
|
|
443
|
+
# @return [Scope] FILE scope wrapping the inner scopes
|
|
444
|
+
def wrap_in_file_scope(file_path, inner_scopes)
|
|
445
|
+
file_hash = FileHash.compute(file_path, logger: @logger)
|
|
446
|
+
lang = {}
|
|
447
|
+
lang[:file_hash] = file_hash if file_hash
|
|
448
|
+
|
|
449
|
+
Scope.new(
|
|
450
|
+
scope_type: 'FILE',
|
|
451
|
+
name: file_path,
|
|
452
|
+
source_file: file_path,
|
|
453
|
+
start_line: UNKNOWN_MIN_LINE,
|
|
454
|
+
end_line: UNKNOWN_MAX_LINE,
|
|
455
|
+
language_specifics: lang,
|
|
456
|
+
scopes: inner_scopes
|
|
457
|
+
)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Extract MODULE scope (without file_hash — that belongs on the FILE root scope).
|
|
461
|
+
# Does not include nested classes — nesting is handled by extract_all via FQN splitting.
|
|
462
|
+
# @param mod [Module] The module
|
|
463
|
+
# @return [Scope] The module scope
|
|
464
|
+
def extract_module_scope(mod)
|
|
465
|
+
source_file = find_source_file(mod)
|
|
466
|
+
|
|
467
|
+
Scope.new(
|
|
468
|
+
scope_type: 'MODULE',
|
|
469
|
+
name: safe_mod_name(mod),
|
|
470
|
+
source_file: source_file,
|
|
471
|
+
start_line: UNKNOWN_MIN_LINE,
|
|
472
|
+
end_line: UNKNOWN_MAX_LINE,
|
|
473
|
+
symbols: extract_scope_symbols(mod)
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Extract CLASS scope
|
|
478
|
+
# @param klass [Class] The class
|
|
479
|
+
# @return [Scope] The class scope
|
|
480
|
+
def extract_class_scope(klass)
|
|
481
|
+
methods = klass.instance_methods(false)
|
|
482
|
+
start_line, end_line = calculate_class_line_range(klass, methods)
|
|
483
|
+
source_file = find_source_file(klass)
|
|
484
|
+
|
|
485
|
+
Scope.new(
|
|
486
|
+
scope_type: 'CLASS',
|
|
487
|
+
name: safe_mod_name(klass),
|
|
488
|
+
source_file: source_file,
|
|
489
|
+
start_line: start_line,
|
|
490
|
+
end_line: end_line,
|
|
491
|
+
language_specifics: build_class_language_specifics(klass),
|
|
492
|
+
scopes: extract_method_scopes(klass),
|
|
493
|
+
symbols: extract_scope_symbols(klass)
|
|
494
|
+
)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Calculate class line range from method locations.
|
|
498
|
+
# Start from the earliest method start, end at the latest method end (derived
|
|
499
|
+
# from iseq trace_points so methods spanning multiple lines aren't truncated).
|
|
500
|
+
# @param klass [Class] The class
|
|
501
|
+
# @param methods [Array<Symbol>] Method names
|
|
502
|
+
# @return [Array<Integer, Integer>] [start_line, end_line]
|
|
503
|
+
def calculate_class_line_range(klass, methods)
|
|
504
|
+
starts = []
|
|
505
|
+
ends = []
|
|
506
|
+
methods.each do |method_name|
|
|
507
|
+
method = klass.instance_method(method_name)
|
|
508
|
+
location = method.source_location
|
|
509
|
+
next unless location && location[0]
|
|
510
|
+
starts << location[1]
|
|
511
|
+
_ranges, method_end = extract_targetable_lines(method, location[1])
|
|
512
|
+
ends << method_end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
|
|
516
|
+
|
|
517
|
+
[starts.min, ends.max]
|
|
518
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
519
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
520
|
+
@logger.debug { "symdb: error calculating line range for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
521
|
+
[UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Build language specifics for CLASS
|
|
525
|
+
# @param klass [Class] The class
|
|
526
|
+
# @return [Hash] Language-specific metadata
|
|
527
|
+
def build_class_language_specifics(klass)
|
|
528
|
+
specifics = {}
|
|
529
|
+
|
|
530
|
+
# Superclass chain (exclude Object and BasicObject).
|
|
531
|
+
# Emitted as an array named super_classes — consistent with Java, .NET, and Python.
|
|
532
|
+
# Array allows for multiple entries if future Ruby versions or mixins expand the chain.
|
|
533
|
+
# Anonymous superclasses (class Foo < Class.new { ... }) have nil name; compact to skip.
|
|
534
|
+
superclass = CLASS_SUPERCLASS.bind(klass).call
|
|
535
|
+
if superclass && !superclass.equal?(Object) && !superclass.equal?(BasicObject)
|
|
536
|
+
super_name = safe_mod_name(superclass)
|
|
537
|
+
specifics[:super_classes] = [super_name] if super_name
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Included modules (exclude common ones).
|
|
541
|
+
# included_modules returns the entire ancestor chain's mixins, not only directly
|
|
542
|
+
# included ones. This is intentional: the field reports "modules this class
|
|
543
|
+
# responds to," which is what the consumer (UI navigation, probe context) needs.
|
|
544
|
+
included = MODULE_INCLUDED_MODULES.bind(klass).call.map { |m| safe_mod_name(m) }.reject do |name|
|
|
545
|
+
name.nil? || EXCLUDED_COMMON_MODULES.any? { |prefix| name.start_with?(prefix) }
|
|
546
|
+
end
|
|
547
|
+
specifics[:included_modules] = included unless included.empty?
|
|
548
|
+
|
|
549
|
+
# Prepended modules
|
|
550
|
+
# Take all ancestors before the class itself (prepending inserts modules before the class in ancestor chain).
|
|
551
|
+
# This code path is taken when a class has prepended modules (e.g., class Foo; prepend Bar; end).
|
|
552
|
+
# Single-pass collection avoids the intermediate arrays from take_while.map.compact.
|
|
553
|
+
# Test coverage: spec/datadog/symbol_database/extractor_spec.rb tests prepend behavior.
|
|
554
|
+
prepended = []
|
|
555
|
+
MODULE_ANCESTORS.bind(klass).call.each do |a|
|
|
556
|
+
break if a.equal?(klass)
|
|
557
|
+
name = safe_mod_name(a)
|
|
558
|
+
prepended << name if name
|
|
559
|
+
end
|
|
560
|
+
specifics[:prepended_modules] = prepended unless prepended.empty?
|
|
561
|
+
|
|
562
|
+
specifics
|
|
563
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
564
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
565
|
+
@logger.debug { "symdb: error building language specifics for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
566
|
+
{}
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Extract method scopes from a class
|
|
570
|
+
# @param klass [Class] The class
|
|
571
|
+
# @return [Array<Scope>] Method scopes
|
|
572
|
+
def extract_method_scopes(klass)
|
|
573
|
+
scopes = []
|
|
574
|
+
|
|
575
|
+
# Get all instance methods (public, protected, private)
|
|
576
|
+
all_instance_methods = klass.instance_methods(false) +
|
|
577
|
+
klass.protected_instance_methods(false) +
|
|
578
|
+
klass.private_instance_methods(false)
|
|
579
|
+
all_instance_methods.uniq!
|
|
580
|
+
|
|
581
|
+
all_instance_methods.each do |method_name|
|
|
582
|
+
method_scope = extract_method_scope(klass, method_name, :instance)
|
|
583
|
+
scopes << method_scope if method_scope
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
scopes
|
|
587
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
588
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
589
|
+
@logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
590
|
+
[]
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Extract a single method scope
|
|
594
|
+
# @param klass [Class] The class
|
|
595
|
+
# @param method_name [Symbol] Method name
|
|
596
|
+
# @param method_type [Symbol] :instance or :class
|
|
597
|
+
# @return [Scope, nil] Method scope or nil
|
|
598
|
+
def extract_method_scope(klass, method_name, method_type)
|
|
599
|
+
method = klass.instance_method(method_name)
|
|
600
|
+
location = method.source_location
|
|
601
|
+
|
|
602
|
+
return nil unless location # Skip methods without source location
|
|
603
|
+
|
|
604
|
+
source_file, line = location
|
|
605
|
+
return nil unless user_code_path?(source_file) # Skip gem/stdlib methods
|
|
606
|
+
|
|
607
|
+
targetable_lines, end_line = extract_targetable_lines(method, line)
|
|
608
|
+
|
|
609
|
+
Scope.new(
|
|
610
|
+
scope_type: 'METHOD',
|
|
611
|
+
name: method_name.to_s,
|
|
612
|
+
source_file: source_file,
|
|
613
|
+
start_line: line,
|
|
614
|
+
end_line: end_line,
|
|
615
|
+
targetable_lines: targetable_lines,
|
|
616
|
+
language_specifics: {
|
|
617
|
+
visibility: method_visibility(klass, method_name),
|
|
618
|
+
method_type: method_type.to_s,
|
|
619
|
+
arity: method.arity
|
|
620
|
+
},
|
|
621
|
+
symbols: extract_method_parameters(method)
|
|
622
|
+
)
|
|
623
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
624
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
625
|
+
@logger.debug { "symdb: failed to extract method #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
626
|
+
nil
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Get method visibility
|
|
630
|
+
# @param klass [Class] The class
|
|
631
|
+
# @param method_name [Symbol] Method name
|
|
632
|
+
# @return [String] 'public', 'private', or 'protected'
|
|
633
|
+
def method_visibility(klass, method_name)
|
|
634
|
+
if klass.private_instance_methods(false).include?(method_name)
|
|
635
|
+
'private'
|
|
636
|
+
elsif klass.protected_instance_methods(false).include?(method_name)
|
|
637
|
+
'protected'
|
|
638
|
+
else
|
|
639
|
+
'public'
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Extract targetable lines and end_line from a method's bytecode.
|
|
644
|
+
# Returns [ranges, end_line] where ranges is an array of {start:, end:} hashes
|
|
645
|
+
# or nil if iseq is unavailable (C-extension methods).
|
|
646
|
+
# @param method [Method, UnboundMethod] The method
|
|
647
|
+
# @param start_line [Integer] Fallback end_line if iseq unavailable
|
|
648
|
+
# @return [Array(Array<Hash>, Integer), Array(nil, Integer)]
|
|
649
|
+
def extract_targetable_lines(method, start_line)
|
|
650
|
+
iseq = RubyVM::InstructionSequence.of(method) # steep:ignore
|
|
651
|
+
unless iseq
|
|
652
|
+
@logger.debug { "symdb: no iseq for #{method.name} (C extension or native), skipping targetable lines" }
|
|
653
|
+
return [nil, start_line]
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
lines = iseq.trace_points
|
|
657
|
+
.select { |_, event| TARGETABLE_LINE_EVENTS.include?(event) }
|
|
658
|
+
.map(&:first)
|
|
659
|
+
.uniq
|
|
660
|
+
.sort
|
|
661
|
+
|
|
662
|
+
end_line = lines.max || start_line
|
|
663
|
+
ranges = build_targetable_ranges(lines)
|
|
664
|
+
result = ranges.empty? ? nil : ranges
|
|
665
|
+
@logger.debug { "symdb: #{method.name} targetable lines: #{result ? "#{ranges.size} range(s), lines #{lines.first}..#{lines.last}" : 'none (no matching events)'}" }
|
|
666
|
+
[result, end_line]
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Compress sorted line numbers into consecutive ranges.
|
|
670
|
+
# [4, 5, 6, 8, 10, 11] => [{start: 4, end: 6}, {start: 8, end: 8}, {start: 10, end: 11}]
|
|
671
|
+
# @param lines [Array<Integer>] Sorted, deduplicated line numbers
|
|
672
|
+
# @return [Array<Hash>] Array of {start:, end:} range hashes
|
|
673
|
+
def build_targetable_ranges(lines)
|
|
674
|
+
return [] if lines.empty?
|
|
675
|
+
|
|
676
|
+
ranges = []
|
|
677
|
+
range_start = lines[0]
|
|
678
|
+
prev = range_start
|
|
679
|
+
|
|
680
|
+
lines[1..-1].each do |line| # steep:ignore
|
|
681
|
+
if line == prev + 1
|
|
682
|
+
prev = line
|
|
683
|
+
else
|
|
684
|
+
ranges << {start: range_start, end: prev}
|
|
685
|
+
range_start = line
|
|
686
|
+
prev = line
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
ranges << {start: range_start, end: prev}
|
|
690
|
+
ranges
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
# Extract method parameters as symbols.
|
|
694
|
+
# Does NOT include `self` — Ruby's implicit receiver is not a declared parameter.
|
|
695
|
+
# Java skips slot 0 (this) for the same reason. .NET uploads `this` but the web-ui
|
|
696
|
+
# filters it for dotnet. Ruby follows Java's approach: don't upload it.
|
|
697
|
+
# @param method [UnboundMethod] The method
|
|
698
|
+
# @return [Array<Symbol>] Parameter symbols
|
|
699
|
+
def extract_method_parameters(method)
|
|
700
|
+
method_name = begin
|
|
701
|
+
method.name.to_s
|
|
702
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
703
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
704
|
+
@logger.debug { "symdb: method.name failed: #{e.class}: #{e.message}" }
|
|
705
|
+
'unknown'
|
|
706
|
+
end
|
|
707
|
+
params = method.parameters
|
|
708
|
+
|
|
709
|
+
return [] if params.nil? || params.empty?
|
|
710
|
+
|
|
711
|
+
Core::Utils::EnumerableCompat.filter_map(params) do |param_type, param_name|
|
|
712
|
+
# Skip block parameters for MVP
|
|
713
|
+
next if param_type == :block
|
|
714
|
+
|
|
715
|
+
# Skip if param_name is nil — normal for generated methods (attr_writer, attr_accessor).
|
|
716
|
+
# See pitfall 37 and specs/json-schema.md "Discovered During Implementation".
|
|
717
|
+
next if param_name.nil?
|
|
718
|
+
|
|
719
|
+
Symbol.new(
|
|
720
|
+
symbol_type: 'ARG',
|
|
721
|
+
name: param_name.to_s,
|
|
722
|
+
line: UNKNOWN_MIN_LINE, # Parameters available in entire method
|
|
723
|
+
)
|
|
724
|
+
end
|
|
725
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
726
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
727
|
+
@logger.debug { "symdb: failed to extract parameters from #{method_name}: #{e.class}: #{e.message}" }
|
|
728
|
+
[]
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
# ── extract_all helpers ──────────────────────────────────────────────
|
|
732
|
+
|
|
733
|
+
# Sleep between chunks of modules processed in build_per_file_index so
|
|
734
|
+
# request-handling threads have guaranteed CPU time while extraction is in
|
|
735
|
+
# flight. Unlike Thread.pass (which only offers the GVL among runnable
|
|
736
|
+
# threads and leaves the extractor immediately re-runnable), sleep removes
|
|
737
|
+
# the extractor thread from the runnable set for a fixed duration, capping
|
|
738
|
+
# its CPU share at sleep_work_ratio regardless of GVL scheduling.
|
|
739
|
+
#
|
|
740
|
+
# The cadence is measured in modules that pass the singleton-class fast-path
|
|
741
|
+
# skip — singleton classes are discarded in microseconds and counting them
|
|
742
|
+
# would add wall-clock delay disproportionate to the work being done (e.g.
|
|
743
|
+
# on heavily monkey-patched processes that retain large singleton chains).
|
|
744
|
+
SLEEP_EVERY_N_MODULES = 100
|
|
745
|
+
SLEEP_SECONDS = 0.001
|
|
746
|
+
private_constant :SLEEP_EVERY_N_MODULES, :SLEEP_SECONDS
|
|
747
|
+
|
|
748
|
+
# Pass 1 (memory-bounded form): build a per-file index of
|
|
749
|
+
# `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
|
|
750
|
+
#
|
|
751
|
+
# Stores Symbol method names plus Module references only — no UnboundMethod
|
|
752
|
+
# objects retained between passes. UnboundMethods created here (to read
|
|
753
|
+
# `source_location`) become garbage as the inner loop ends.
|
|
754
|
+
#
|
|
755
|
+
# The Module references are pointer-sized and the modules are already kept
|
|
756
|
+
# alive in ObjectSpace, so adding them to the index costs no extra retention.
|
|
757
|
+
#
|
|
758
|
+
# @return [Hash{String=>Array<Array(String, Module, Array<Symbol>)>}]
|
|
759
|
+
def build_per_file_index
|
|
760
|
+
index = {}
|
|
761
|
+
seen = 0
|
|
762
|
+
|
|
763
|
+
ObjectSpace.each_object(Module) do |mod|
|
|
764
|
+
# Singleton classes (per-object metaclasses) are never user-code classes.
|
|
765
|
+
# They're not const-referenced, DI cannot instrument methods on a singular
|
|
766
|
+
# object instance, so skipping them is both correct and cheap.
|
|
767
|
+
next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
|
|
768
|
+
|
|
769
|
+
seen += 1
|
|
770
|
+
sleep SLEEP_SECONDS if (seen % SLEEP_EVERY_N_MODULES).zero?
|
|
771
|
+
|
|
772
|
+
mod_name = safe_mod_name(mod)
|
|
773
|
+
next unless mod_name
|
|
774
|
+
next unless resolves_to_same_module?(mod_name, mod)
|
|
775
|
+
next unless user_code_module?(mod)
|
|
776
|
+
|
|
777
|
+
file_to_names = collect_method_names_by_file(mod)
|
|
778
|
+
|
|
779
|
+
# Namespace-only modules (no own methods) — use find_source_file as the
|
|
780
|
+
# canonical file so the FILE scope still gets a MODULE entry.
|
|
781
|
+
if file_to_names.empty?
|
|
782
|
+
source_file = find_source_file(mod)
|
|
783
|
+
file_to_names[source_file] = [] if source_file
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
next if file_to_names.empty?
|
|
787
|
+
|
|
788
|
+
file_to_names.each do |file_path, method_names|
|
|
789
|
+
(index[file_path] ||= []) << [mod_name, mod, method_names]
|
|
790
|
+
end
|
|
791
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
792
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
793
|
+
@logger.debug { "symdb: error indexing #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
index
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# For a single module, return `{ file_path => [method_name_symbol, ...] }`.
|
|
800
|
+
# Stores only the method-name symbols and their file paths — UnboundMethod
|
|
801
|
+
# objects allocated to read `source_location` are not retained between
|
|
802
|
+
# passes, so they can be GC'd as soon as the inner loop ends.
|
|
803
|
+
def collect_method_names_by_file(mod)
|
|
804
|
+
result = Hash.new { |h, k| h[k] = [] } # steep:ignore
|
|
805
|
+
|
|
806
|
+
# Module#instance_methods(false) already returns both public and protected
|
|
807
|
+
# methods, so iterating it plus private_instance_methods covers all three
|
|
808
|
+
# visibilities without an intermediate merged array.
|
|
809
|
+
[mod.instance_methods(false), mod.private_instance_methods(false)].each do |method_names|
|
|
810
|
+
method_names.each do |method_name|
|
|
811
|
+
method = mod.instance_method(method_name)
|
|
812
|
+
loc = method.source_location
|
|
813
|
+
next unless loc
|
|
814
|
+
next unless user_code_path?(loc[0])
|
|
815
|
+
|
|
816
|
+
result[loc[0]] << method_name
|
|
817
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
818
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
819
|
+
@logger.debug { "symdb: error indexing method #{method_name}: #{e.class}: #{e.message}" }
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
result
|
|
824
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
825
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
826
|
+
@logger.debug { "symdb: error indexing methods: #{e.class}: #{e.message}" }
|
|
827
|
+
{}
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# Pass 2: build the FILE scope for one source file by walking just the modules
|
|
831
|
+
# that contribute methods to it. Resolves UnboundMethods just-in-time per
|
|
832
|
+
# method; the per-method scratch is collected by GC as each module's loop body
|
|
833
|
+
# ends. The returned Scope is the only thing the caller needs to keep alive
|
|
834
|
+
# — once the caller drops it, the entire per-file working set is collectable.
|
|
835
|
+
#
|
|
836
|
+
# @param file_path [String]
|
|
837
|
+
# @param entries [Array<Array(String, Module, Array<Symbol>)>] tuples produced by build_per_file_index
|
|
838
|
+
# @return [Scope, nil] FILE scope, or nil if nothing extractable
|
|
839
|
+
def build_file_scope(file_path, entries)
|
|
840
|
+
return nil if entries.empty?
|
|
841
|
+
|
|
842
|
+
root = {
|
|
843
|
+
name: file_path, type: 'FILE', children: {},
|
|
844
|
+
methods: [], mod: nil, source_file: file_path, fqn: nil,
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# Sort by FQN depth so parent namespaces are placed before children.
|
|
848
|
+
sorted = entries.sort_by { |(mod_name, _, _)| mod_name.count(':') }
|
|
849
|
+
|
|
850
|
+
sorted.each do |mod_name, mod, method_names|
|
|
851
|
+
# Resolve UnboundMethods for this (mod, file) just-in-time. These objects
|
|
852
|
+
# live only as long as the tree node holds them; they are released when
|
|
853
|
+
# convert_tree_to_scope finishes building the file's Scope.
|
|
854
|
+
method_infos = Core::Utils::EnumerableCompat.filter_map(method_names) do |name|
|
|
855
|
+
method = mod.instance_method(name)
|
|
856
|
+
# Pass 1 (build_per_file_index) recorded this method under file_path.
|
|
857
|
+
# If the method has been redefined in another file between the two
|
|
858
|
+
# passes (e.g. a class reopened during a Rails reload while extract_all
|
|
859
|
+
# is iterating), the resolved UnboundMethod's source_location now
|
|
860
|
+
# points elsewhere. Drop the stale entry — the hot-load TracePoint
|
|
861
|
+
# enqueues the redefined class and the next debounce window extracts
|
|
862
|
+
# it under the new file_path.
|
|
863
|
+
loc = method.source_location
|
|
864
|
+
next nil unless loc && loc[0] == file_path
|
|
865
|
+
{name: name, method: method, type: :instance}
|
|
866
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
867
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
868
|
+
@logger.debug { "symdb: error resolving #{mod_name}##{name}: #{e.class}: #{e.message}" }
|
|
869
|
+
nil
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# If Pass 1 recorded methods for this module but every one of them has
|
|
873
|
+
# moved out of file_path between the passes, drop the entry — otherwise
|
|
874
|
+
# the FILE scope would carry an empty CLASS/MODULE node at a location
|
|
875
|
+
# the module no longer lives in.
|
|
876
|
+
next if method_names.any? && method_infos.empty?
|
|
877
|
+
|
|
878
|
+
parts = mod_name.split('::')
|
|
879
|
+
place_in_tree(root, parts, mod, mod_name, method_infos, file_path)
|
|
880
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
881
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
882
|
+
@logger.debug { "symdb: error placing #{mod_name} in tree: #{e.class}: #{e.message}" }
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
# steep:ignore:start
|
|
886
|
+
# Steep widens root[:children] to the union of all value types declared in
|
|
887
|
+
# the literal (String | Hash | Array | nil), losing the Hash narrowing.
|
|
888
|
+
return nil if root[:children].empty?
|
|
889
|
+
# steep:ignore:end
|
|
890
|
+
|
|
891
|
+
convert_tree_to_scope(file_path, root)
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
# Place a module/class in the file tree at the correct nesting depth.
|
|
895
|
+
# Creates intermediate namespace nodes as needed.
|
|
896
|
+
# mod_name is the safe name (resolved via Module#instance_method bind) —
|
|
897
|
+
# callers must not pass raw mod.name, since classes that override singleton
|
|
898
|
+
# name (e.g. Faker::Travel::Airport) will raise.
|
|
899
|
+
def place_in_tree(root, name_parts, mod, mod_name, methods, file_path)
|
|
900
|
+
current = root
|
|
901
|
+
|
|
902
|
+
# Create/find intermediate nodes for each namespace segment except the last
|
|
903
|
+
name_parts[0..-2].each_with_index do |part, idx| # steep:ignore
|
|
904
|
+
fqn = name_parts[0..idx].join('::') # steep:ignore
|
|
905
|
+
current[:children][part] ||= {
|
|
906
|
+
name: fqn, type: resolve_scope_type(fqn),
|
|
907
|
+
children: {}, methods: [], mod: nil,
|
|
908
|
+
source_file: file_path, fqn: fqn
|
|
909
|
+
}
|
|
910
|
+
current = current[:children][part]
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# Create or find the leaf node
|
|
914
|
+
leaf_name = name_parts.last
|
|
915
|
+
leaf = current[:children][leaf_name]
|
|
916
|
+
if leaf
|
|
917
|
+
# Node exists (was created as intermediate or from another entry).
|
|
918
|
+
# Update type and mod — the actual module object is authoritative.
|
|
919
|
+
leaf[:type] = (Class === mod) ? 'CLASS' : 'MODULE'
|
|
920
|
+
leaf[:mod] = mod
|
|
921
|
+
else
|
|
922
|
+
leaf = {
|
|
923
|
+
name: mod_name,
|
|
924
|
+
type: (Class === mod) ? 'CLASS' : 'MODULE',
|
|
925
|
+
children: {}, methods: [],
|
|
926
|
+
mod: mod, source_file: file_path,
|
|
927
|
+
fqn: mod_name
|
|
928
|
+
}
|
|
929
|
+
current[:children][leaf_name] = leaf
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# Add methods for this file
|
|
933
|
+
leaf[:methods].concat(methods)
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
# Determine scope type (CLASS or MODULE) for a fully-qualified name.
|
|
937
|
+
# Looks up the actual Ruby constant to check if it's a Class.
|
|
938
|
+
# @param fqn [String] Fully-qualified name (e.g. "Authentication::Strategies")
|
|
939
|
+
# @return [String] 'CLASS' or 'MODULE'
|
|
940
|
+
def resolve_scope_type(fqn)
|
|
941
|
+
current = Object
|
|
942
|
+
fqn.split('::').each do |seg|
|
|
943
|
+
sym = seg.to_sym
|
|
944
|
+
pending_autoload = if RubyVersion.is?('>= 2.7')
|
|
945
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
946
|
+
else
|
|
947
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym)
|
|
948
|
+
end
|
|
949
|
+
return 'MODULE' if pending_autoload
|
|
950
|
+
return 'MODULE' unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
951
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
952
|
+
end
|
|
953
|
+
(Class === current) ? 'CLASS' : 'MODULE'
|
|
954
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
955
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
956
|
+
@logger.debug { "symdb: resolve_scope_type(#{fqn}) failed: #{e.class}: #{e.message}, defaulting to MODULE" }
|
|
957
|
+
'MODULE'
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Convert a single file tree (built by build_file_scope) to a FILE Scope.
|
|
961
|
+
# @param file_path [String] Source file path
|
|
962
|
+
# @param root [Hash] Tree node from build_file_scope
|
|
963
|
+
# @return [Scope] FILE scope
|
|
964
|
+
def convert_tree_to_scope(file_path, root)
|
|
965
|
+
file_hash = FileHash.compute(file_path, logger: @logger)
|
|
966
|
+
lang = {}
|
|
967
|
+
lang[:file_hash] = file_hash if file_hash
|
|
968
|
+
|
|
969
|
+
Scope.new(
|
|
970
|
+
scope_type: 'FILE',
|
|
971
|
+
name: file_path,
|
|
972
|
+
source_file: file_path,
|
|
973
|
+
start_line: UNKNOWN_MIN_LINE,
|
|
974
|
+
end_line: UNKNOWN_MAX_LINE,
|
|
975
|
+
language_specifics: lang,
|
|
976
|
+
scopes: root[:children].values.map { |child| convert_node_to_scope(child) },
|
|
977
|
+
)
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
# Convert a single hash node to a Scope object (recursive).
|
|
981
|
+
# @param node [Hash] Tree node
|
|
982
|
+
# @return [Scope] Scope object
|
|
983
|
+
def convert_node_to_scope(node)
|
|
984
|
+
# Build method scopes from collected method entries
|
|
985
|
+
method_scopes = Core::Utils::EnumerableCompat.filter_map(node[:methods]) do |method_info|
|
|
986
|
+
build_instance_method_scope(node[:mod], method_info[:name], method_info[:method])
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
# Recurse into child scopes (nested modules/classes)
|
|
990
|
+
child_scopes = node[:children].values.map { |child| convert_node_to_scope(child) }
|
|
991
|
+
|
|
992
|
+
# Compute line range: start from the earliest method start, end at the latest
|
|
993
|
+
# method end. Using max(start_line) would underreport the class's end_line for
|
|
994
|
+
# classes whose last method spans multiple lines.
|
|
995
|
+
starts = method_scopes.map(&:start_line).reject { |l| l == UNKNOWN_MIN_LINE } # steep:ignore
|
|
996
|
+
ends = method_scopes.map(&:end_line).reject { |l| l == UNKNOWN_MAX_LINE } # steep:ignore
|
|
997
|
+
start_line = starts.empty? ? UNKNOWN_MIN_LINE : starts.min
|
|
998
|
+
end_line = ends.empty? ? UNKNOWN_MAX_LINE : ends.max
|
|
999
|
+
|
|
1000
|
+
# Extract symbols (constants, class variables) if we have the actual module object
|
|
1001
|
+
symbols = node[:mod] ? extract_scope_symbols(node[:mod]) : []
|
|
1002
|
+
|
|
1003
|
+
# Build language specifics
|
|
1004
|
+
lang = if node[:type] == 'CLASS' && node[:mod]
|
|
1005
|
+
build_class_language_specifics(node[:mod])
|
|
1006
|
+
else
|
|
1007
|
+
{}
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
Scope.new(
|
|
1011
|
+
scope_type: node[:type],
|
|
1012
|
+
name: node[:name],
|
|
1013
|
+
source_file: node[:source_file],
|
|
1014
|
+
start_line: start_line,
|
|
1015
|
+
end_line: end_line,
|
|
1016
|
+
language_specifics: lang,
|
|
1017
|
+
scopes: method_scopes + child_scopes,
|
|
1018
|
+
symbols: symbols
|
|
1019
|
+
)
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
# Build a METHOD scope from a pre-resolved instance method.
|
|
1023
|
+
# Used by extract_all path where methods are collected in Pass 1.
|
|
1024
|
+
# @param klass [Module] The class/module (for visibility lookup)
|
|
1025
|
+
# @param method_name [Symbol] Method name
|
|
1026
|
+
# @param method [UnboundMethod] The method object
|
|
1027
|
+
# @return [Scope, nil] Method scope or nil
|
|
1028
|
+
def build_instance_method_scope(klass, method_name, method)
|
|
1029
|
+
location = method.source_location
|
|
1030
|
+
return nil unless location
|
|
1031
|
+
|
|
1032
|
+
source_file, line = location
|
|
1033
|
+
|
|
1034
|
+
targetable_lines, end_line = extract_targetable_lines(method, line)
|
|
1035
|
+
|
|
1036
|
+
Scope.new(
|
|
1037
|
+
scope_type: 'METHOD',
|
|
1038
|
+
name: method_name.to_s,
|
|
1039
|
+
source_file: source_file,
|
|
1040
|
+
start_line: line,
|
|
1041
|
+
end_line: end_line,
|
|
1042
|
+
targetable_lines: targetable_lines,
|
|
1043
|
+
language_specifics: {
|
|
1044
|
+
visibility: klass ? method_visibility(klass, method_name) : 'public', # steep:ignore
|
|
1045
|
+
method_type: 'instance',
|
|
1046
|
+
arity: method.arity
|
|
1047
|
+
},
|
|
1048
|
+
symbols: extract_method_parameters(method)
|
|
1049
|
+
)
|
|
1050
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1051
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1052
|
+
klass_name = klass ? (safe_mod_name(klass) || '<unknown>') : '<unknown>'
|
|
1053
|
+
@logger.debug { "symdb: failed to build method scope #{klass_name}##{method_name}: #{e.class}: #{e.message}" }
|
|
1054
|
+
nil
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# Extract symbols (constants, class variables) from a module or class.
|
|
1058
|
+
# Class variables are emitted only for classes; constants for both.
|
|
1059
|
+
# @param mod [Module] The module or class
|
|
1060
|
+
# @return [Array<Symbol>] Symbols
|
|
1061
|
+
def extract_scope_symbols(mod)
|
|
1062
|
+
symbols = []
|
|
1063
|
+
|
|
1064
|
+
# Class variables (only for classes)
|
|
1065
|
+
if Class === mod
|
|
1066
|
+
MODULE_CLASS_VARIABLES.bind(mod).call(false).each do |var_name|
|
|
1067
|
+
symbols << Symbol.new(
|
|
1068
|
+
symbol_type: 'STATIC_FIELD',
|
|
1069
|
+
name: var_name.to_s,
|
|
1070
|
+
line: UNKNOWN_MIN_LINE
|
|
1071
|
+
)
|
|
1072
|
+
end
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
# Constants (excluding nested modules/classes).
|
|
1076
|
+
# Skip autoloaded constants to avoid triggering loading as a side effect.
|
|
1077
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |const_name|
|
|
1078
|
+
next if MODULE_AUTOLOAD_P.bind(mod).call(const_name)
|
|
1079
|
+
const_value = MODULE_CONST_GET.bind(mod).call(const_name)
|
|
1080
|
+
next if Module === const_value
|
|
1081
|
+
|
|
1082
|
+
symbols << Symbol.new(
|
|
1083
|
+
symbol_type: 'STATIC_FIELD',
|
|
1084
|
+
name: const_name.to_s,
|
|
1085
|
+
line: UNKNOWN_MIN_LINE,
|
|
1086
|
+
type: safe_mod_name(KERNEL_CLASS.bind(const_value).call)
|
|
1087
|
+
)
|
|
1088
|
+
rescue NameError, LoadError, NoMethodError, TypeError => e # standard:disable Lint/ShadowedException
|
|
1089
|
+
# Expected: constant removed/undefined (NameError), autoload failure (LoadError),
|
|
1090
|
+
# or a value whose class cannot be read (NoMethodError/TypeError). Skipping one
|
|
1091
|
+
# constant here keeps the rest of the module's symbols. Logged separately from
|
|
1092
|
+
# unexpected errors so the latter stand out in triage. Lint/ShadowedException
|
|
1093
|
+
# disabled: these descend from StandardError, but Ruby's rescue-clause-order
|
|
1094
|
+
# semantics ensure the catch-all rescue below only catches exceptions not matched here.
|
|
1095
|
+
@logger.debug { "symdb: skipping module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
1096
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1097
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1098
|
+
@logger.debug { "symdb: unexpected error reading module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
symbols
|
|
1102
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1103
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1104
|
+
mod_name = safe_mod_name(mod) || '<unknown>'
|
|
1105
|
+
@logger.debug { "symdb: failed to extract symbols from #{mod_name}: #{e.class}: #{e.message}" }
|
|
1106
|
+
[]
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
end
|