From 9fe02216f4336cac9892e1e5128edaf4c65be46e Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Fri, 12 Sep 2025 17:58:28 -0700 Subject: [PATCH 1/4] working --- docs/GettingStarted.md | 2 + gemfiles/ruby_3.3_graphql_2.0.gemfile.lock | 9 + gemfiles/ruby_3.3_graphql_2.3.gemfile.lock | 10 + gemfiles/ruby_3.3_relational_db.gemfile.lock | 7 + .../configuration/supported_configurations.rb | 638 +++++++++--------- .../configuration/capture_variables.rb | 85 +++ .../contrib/graphql/configuration/settings.rb | 17 + lib/datadog/tracing/contrib/graphql/ext.rb | 2 + .../tracing/contrib/graphql/unified_trace.rb | 113 +++- .../configuration/capture_variables_spec.rb | 225 ++++++ .../contrib/graphql/test_schema_examples.rb | 231 ++++++- .../graphql/variable_capture_basic_spec.rb | 119 ++++ supported-configurations.json | 6 + 13 files changed, 1117 insertions(+), 347 deletions(-) create mode 100644 lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb create mode 100644 spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb create mode 100644 spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 9511eb503cb..9a20e703aed 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -913,6 +913,8 @@ The `instrument :graphql` method accepts the following parameters. Additional op | `service_name` | | `String` | Service name used for graphql instrumentation | `'ruby-graphql'` | | `error_extensions` | `DD_TRACE_GRAPHQL_ERROR_EXTENSIONS` | `Array` | List of extension keys to include in the span event reported for GraphQL queries with errors. | `[]` | | `error_tracking` | `DD_TRACE_GRAPHQL_ERROR_TRACKING` | `Bool` | (Recommended) Surface GraphQL errors in Error Tracking. | `false` | +| `capture_variables` | `DD_TRACE_GRAPHQL_CAPTURE_VARIABLES` | `Array` | List of `operationName:variableName` pairs to capture as span tags. Format: `GetUser:id,CreatePost:title`. By default, no variables are captured. | `[]` | +| `capture_variables_except` | `DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT` | `Array` | List of `operationName:variableName` pairs to exclude from capture. When `DD_TRACE_GRAPHQL_CAPTURE_VARIABLES` is `nil`, all variables except those listed in this optiion are captured. | `[]` | Once an instrumentation strategy is selected (`with_unified_tracer: true`, `with_deprecated_tracer: true`, or *no option set* which defaults to `GraphQL::Tracing::DataDogTrace`), it is not possible to change the instrumentation strategy in the same Ruby process. diff --git a/gemfiles/ruby_3.3_graphql_2.0.gemfile.lock b/gemfiles/ruby_3.3_graphql_2.0.gemfile.lock index a2edc67b574..2988f397dcf 100644 --- a/gemfiles/ruby_3.3_graphql_2.0.gemfile.lock +++ b/gemfiles/ruby_3.3_graphql_2.0.gemfile.lock @@ -95,6 +95,7 @@ GEM erubi (1.13.0) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) globalid (1.2.1) activesupport (>= 6.1) @@ -110,10 +111,15 @@ GEM reline (>= 0.4.2) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.1.0-arm64-darwin) + ffi (~> 1.0) + libddwaf (1.24.1.1.0-x86_64-linux) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -148,6 +154,8 @@ GEM nio4r (2.7.3) nokogiri (1.16.6-aarch64-linux) racc (~> 1.4) + nokogiri (1.16.6-arm64-darwin) + racc (~> 1.4) nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) os (1.1.4) @@ -256,6 +264,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock b/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock index 244d848d353..c2b6c075e43 100644 --- a/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock +++ b/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock @@ -97,10 +97,12 @@ GEM erubi (1.13.0) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) globalid (1.2.1) activesupport (>= 6.1) google-protobuf (3.25.3-aarch64-linux) + google-protobuf (3.25.3-arm64-darwin) google-protobuf (3.25.3-x86_64-linux) graphql (2.3.6) base64 @@ -114,10 +116,15 @@ GEM reline (>= 0.4.2) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.1.0-arm64-darwin) + ffi (~> 1.0) + libddwaf (1.24.1.1.0-x86_64-linux) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -152,6 +159,8 @@ GEM nio4r (2.7.3) nokogiri (1.16.6-aarch64-linux) racc (~> 1.4) + nokogiri (1.16.6-arm64-darwin) + racc (~> 1.4) nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) os (1.1.4) @@ -260,6 +269,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/ruby_3.3_relational_db.gemfile.lock b/gemfiles/ruby_3.3_relational_db.gemfile.lock index a7ed4a50fc9..32cb84adc72 100644 --- a/gemfiles/ruby_3.3_relational_db.gemfile.lock +++ b/gemfiles/ruby_3.3_relational_db.gemfile.lock @@ -47,6 +47,7 @@ GEM dogstatsd-ruby (5.6.1) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) google-protobuf (3.24.3) hashdiff (1.0.1) @@ -59,10 +60,15 @@ GEM reline (>= 0.4.2) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.1.0-arm64-darwin) + ffi (~> 1.0) + libddwaf (1.24.1.1.0-x86_64-linux) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -137,6 +143,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/lib/datadog/core/configuration/supported_configurations.rb b/lib/datadog/core/configuration/supported_configurations.rb index 054847a86c7..11ca83f6c5d 100644 --- a/lib/datadog/core/configuration/supported_configurations.rb +++ b/lib/datadog/core/configuration/supported_configurations.rb @@ -7,330 +7,332 @@ module Datadog module Core module Configuration SUPPORTED_CONFIGURATIONS = - {"DD_AGENT_HOST" => {version: ["A"]}, - "DD_API_KEY" => {version: ["A"]}, - "DD_API_SECURITY_ENABLED" => {version: ["A"]}, - "DD_API_SECURITY_REQUEST_SAMPLE_RATE" => {version: ["A"]}, - "DD_API_SECURITY_SAMPLE_DELAY" => {version: ["A"]}, - "DD_APM_TRACING_ENABLED" => {version: ["A"]}, - "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING" => {version: ["A"]}, - "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE" => {version: ["A"]}, - "DD_APPSEC_ENABLED" => {version: ["A"]}, - "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML" => {version: ["A"]}, - "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON" => {version: ["A"]}, - "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_TEXT" => {version: ["A"]}, - "DD_APPSEC_MAX_STACK_TRACES" => {version: ["A"]}, - "DD_APPSEC_MAX_STACK_TRACE_DEPTH" => {version: ["A"]}, - "DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT" => {version: ["A"]}, - "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP" => {version: ["A"]}, - "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP" => {version: ["A"]}, - "DD_APPSEC_RASP_ENABLED" => {version: ["A"]}, - "DD_APPSEC_RULES" => {version: ["A"]}, - "DD_APPSEC_SCA_ENABLED" => {version: ["A"]}, - "DD_APPSEC_STACK_TRACE_ENABLED" => {version: ["A"]}, - "DD_APPSEC_TRACE_RATE_LIMIT" => {version: ["A"]}, - "DD_APPSEC_WAF_DEBUG" => {version: ["A"]}, - "DD_APPSEC_WAF_TIMEOUT" => {version: ["A"]}, - "DD_CRASHTRACKING_ENABLED" => {version: ["A"]}, - "DD_DBM_PROPAGATION_MODE" => {version: ["A"]}, - "DD_DISABLE_DATADOG_RAILS" => {version: ["A"]}, - "DD_DYNAMIC_INSTRUMENTATION_ENABLED" => {version: ["A"]}, - "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE" => {version: ["A"]}, - "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS" => {version: ["A"]}, - "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES" => {version: ["A"]}, - "DD_ENV" => {version: ["A"]}, - "DD_ERROR_TRACKING_HANDLED_ERRORS" => {version: ["A"]}, - "DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE" => {version: ["A"]}, - "DD_GIT_COMMIT_SHA" => {version: ["A"]}, - "DD_GIT_REPOSITORY_URL" => {version: ["A"]}, - "DD_HEALTH_METRICS_ENABLED" => {version: ["A"]}, - "DD_INJECTION_ENABLED" => {version: ["A"]}, - "DD_INJECT_FORCE" => {version: ["A"]}, - "DD_INSTRUMENTATION_INSTALL_ID" => {version: ["A"]}, - "DD_INSTRUMENTATION_INSTALL_TIME" => {version: ["A"]}, - "DD_INSTRUMENTATION_INSTALL_TYPE" => {version: ["A"]}, - "DD_INSTRUMENTATION_TELEMETRY_ENABLED" => {version: ["A"]}, - "DD_INTEGRATION_SERVICE" => {version: ["A"]}, - "DD_LOGS_INJECTION" => {version: ["A"]}, - "DD_METRIC_AGENT_PORT" => {version: ["A"]}, - "DD_PROFILING_ALLOCATION_ENABLED" => {version: ["A"]}, - "DD_PROFILING_DIR_INTERRUPTION_WORKAROUND_ENABLED" => {version: ["A"]}, - "DD_PROFILING_ENABLED" => {version: ["A"]}, - "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED" => {version: ["A"]}, - "DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED" => {version: ["A"]}, - "DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE" => {version: ["A"]}, - "DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED" => {version: ["A"]}, - "DD_PROFILING_GC_ENABLED" => {version: ["A"]}, - "DD_PROFILING_GVL_ENABLED" => {version: ["A"]}, - "DD_PROFILING_HEAP_CLEAN_AFTER_GC_ENABLED" => {version: ["A"]}, - "DD_PROFILING_MAX_FRAMES" => {version: ["A"]}, - "DD_PROFILING_NATIVE_FILENAMES_ENABLED" => {version: ["A"]}, - "DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED" => {version: ["A"]}, - "DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE" => {version: ["A"]}, - "DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED" => {version: ["A"]}, - "DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED" => {version: ["A"]}, - "DD_PROFILING_SKIP_MYSQL2_CHECK" => {version: ["A"]}, - "DD_PROFILING_TIMELINE_ENABLED" => {version: ["A"]}, - "DD_PROFILING_UPLOAD_PERIOD" => {version: ["A"]}, - "DD_PROFILING_UPLOAD_TIMEOUT" => {version: ["A"]}, - "DD_REDIS_COMMAND_ARGS" => {version: ["A"]}, - "DD_REMOTE_CONFIGURATION_ENABLED" => {version: ["A"]}, - "DD_REMOTE_CONFIG_BOOT_TIMEOUT_SECONDS" => {version: ["A"]}, - "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS" => {version: ["A"]}, - "DD_RODA_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_RODA_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_RUNTIME_METRICS_ENABLED" => {version: ["A"]}, - "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED" => {version: ["A"]}, - "DD_SERVICE" => {version: ["B"]}, - "DD_SITE" => {version: ["A"]}, - "DD_SPAN_SAMPLING_RULES" => {version: ["A"]}, - "DD_SPAN_SAMPLING_RULES_FILE" => {version: ["A"]}, - "DD_TAGS" => {version: ["B"]}, - "DD_TELEMETRY_AGENTLESS_URL" => {version: ["A"]}, - "DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED" => {version: ["A"]}, - "DD_TELEMETRY_HEARTBEAT_INTERVAL" => {version: ["A"]}, - "DD_TELEMETRY_LOG_COLLECTION_ENABLED" => {version: ["A"]}, - "DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL" => {version: ["A"]}, - "DD_TELEMETRY_METRICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED" => {version: ["A"]}, - "DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_CABLE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_CABLE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTION_CABLE_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_MAILER_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_MAILER_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTION_MAILER_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_PACK_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_PACK_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTION_PACK_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_VIEW_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTION_VIEW_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTION_VIEW_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_JOB_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_JOB_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTIVE_JOB_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_RECORD_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_RECORD_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTIVE_RECORD_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ACTIVE_SUPPORT_ENABLED" => {version: ["A"]}, - "DD_TRACE_AGENT_PORT" => {version: ["A"]}, - "DD_TRACE_AGENT_TIMEOUT_SECONDS" => {version: ["A"]}, - "DD_TRACE_AGENT_URL" => {version: ["A"]}, - "DD_TRACE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_AWS_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_AWS_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_AWS_ENABLED" => {version: ["A"]}, - "DD_TRACE_AWS_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_AWS_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_BAGGAGE_TAG_KEYS" => {version: ["A"]}, - "DD_TRACE_CLIENT_IP_ENABLED" => {version: ["A"]}, - "DD_TRACE_CLIENT_IP_HEADER" => {version: ["A"]}, - "DD_TRACE_CONCURRENT_RUBY_ENABLED" => {version: ["A"]}, - "DD_TRACE_DALLI_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_DALLI_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_DALLI_ENABLED" => {version: ["A"]}, - "DD_TRACE_DALLI_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_DALLI_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_DEBUG" => {version: ["B"]}, - "DD_TRACE_DELAYED_JOB_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_DELAYED_JOB_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_DELAYED_JOB_ENABLED" => {version: ["A"]}, - "DD_TRACE_ELASTICSEARCH_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ELASTICSEARCH_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ELASTICSEARCH_ENABLED" => {version: ["A"]}, - "DD_TRACE_ELASTICSEARCH_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_ELASTICSEARCH_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_ENABLED" => {version: ["B"]}, - "DD_TRACE_ETHON_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_ETHON_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_ETHON_ENABLED" => {version: ["A"]}, - "DD_TRACE_ETHON_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_ETHON_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_EXCON_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_EXCON_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_EXCON_ENABLED" => {version: ["A"]}, - "DD_TRACE_EXCON_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_EXCON_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_EXCON_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_FARADAY_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_FARADAY_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_FARADAY_ENABLED" => {version: ["A"]}, - "DD_TRACE_FARADAY_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_FARADAY_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_FARADAY_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_GRAPE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRAPE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_GRAPE_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRAPE_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_ERROR_EXTENSIONS" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_ERROR_TRACKING" => {version: ["A"]}, - "DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER" => {version: ["A"]}, - "DD_TRACE_GRPC_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRPC_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_GRPC_ENABLED" => {version: ["A"]}, - "DD_TRACE_GRPC_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_GRPC_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_HANAMI_ENABLED" => {version: ["A"]}, - "DD_TRACE_HEADER_TAGS" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_HTTPCLIENT_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_HTTPRB_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTPRB_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_HTTPRB_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTPRB_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_HTTPRB_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_HTTPRB_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_HTTP_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_HTTP_ENABLED" => {version: ["A"]}, - "DD_TRACE_HTTP_ERROR_STATUS_CODES" => {version: ["A"]}, - "DD_TRACE_KAFKA_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_KAFKA_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_KAFKA_ENABLED" => {version: ["A"]}, - "DD_TRACE_KARAFKA_ENABLED" => {version: ["A"]}, - "DD_TRACE_LOGRAGE_ENABLED" => {version: ["A"]}, - "DD_TRACE_MEMCACHED_COMMAND_ENABLED" => {version: ["A"]}, - "DD_TRACE_MONGO_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_MONGO_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_MONGO_ENABLED" => {version: ["A"]}, - "DD_TRACE_MONGO_JSON_COMMAND" => {version: ["A"]}, - "DD_TRACE_MONGO_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_MONGO_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_MYSQL2_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_MYSQL2_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_MYSQL2_ENABLED" => {version: ["A"]}, - "DD_TRACE_MYSQL2_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_MYSQL2_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_NATIVE_SPAN_EVENTS" => {version: ["A"]}, - "DD_TRACE_NET_HTTP_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_NET_HTTP_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_ENABLED" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_RESOURCE_PATTERN" => {version: ["A"]}, - "DD_TRACE_OPENSEARCH_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED" => {version: ["A"]}, - "DD_TRACE_PEER_SERVICE_MAPPING" => {version: ["A"]}, - "DD_TRACE_PG_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_PG_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_PG_ENABLED" => {version: ["A"]}, - "DD_TRACE_PG_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_PG_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_PRESTO_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_PRESTO_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_PRESTO_ENABLED" => {version: ["A"]}, - "DD_TRACE_PRESTO_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_PRESTO_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_PROPAGATION_EXTRACT_FIRST" => {version: ["A"]}, - "DD_TRACE_PROPAGATION_STYLE" => {version: ["B"]}, - "DD_TRACE_PROPAGATION_STYLE_EXTRACT" => {version: ["A"]}, - "DD_TRACE_PROPAGATION_STYLE_INJECT" => {version: ["A"]}, - "DD_TRACE_QUE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_QUE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_QUE_ENABLED" => {version: ["A"]}, - "DD_TRACE_QUE_TAG_ARGS_ENABLED" => {version: ["A"]}, - "DD_TRACE_QUE_TAG_DATA_ENABLED" => {version: ["A"]}, - "DD_TRACE_RACECAR_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RACECAR_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_RACECAR_ENABLED" => {version: ["A"]}, - "DD_TRACE_RACK_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RACK_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_RACK_ENABLED" => {version: ["A"]}, - "DD_TRACE_RAILS_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RAILS_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_RAILS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RAKE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RAKE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_RAKE_ENABLED" => {version: ["A"]}, - "DD_TRACE_RATE_LIMIT" => {version: ["A"]}, - "DD_TRACE_REDIS_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_REDIS_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_REDIS_ENABLED" => {version: ["A"]}, - "DD_TRACE_REDIS_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_REDIS_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED" => {version: ["A"]}, - "DD_TRACE_REPORT_HOSTNAME" => {version: ["A"]}, - "DD_TRACE_RESQUE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_RESQUE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_RESQUE_ENABLED" => {version: ["A"]}, - "DD_TRACE_REST_CLIENT_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_REST_CLIENT_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_REST_CLIENT_ENABLED" => {version: ["A"]}, - "DD_TRACE_REST_CLIENT_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_REST_CLIENT_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_RODA_ENABLED" => {version: ["A"]}, - "DD_TRACE_SAMPLE_RATE" => {version: ["B"]}, - "DD_TRACE_SAMPLING_RULES" => {version: ["A"]}, - "DD_TRACE_SEMANTIC_LOGGER_ENABLED" => {version: ["A"]}, - "DD_TRACE_SEQUEL_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SEQUEL_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SEQUEL_ENABLED" => {version: ["A"]}, - "DD_TRACE_SHORYUKEN_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SHORYUKEN_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SHORYUKEN_ENABLED" => {version: ["A"]}, - "DD_TRACE_SIDEKIQ_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SIDEKIQ_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SIDEKIQ_ENABLED" => {version: ["A"]}, - "DD_TRACE_SINATRA_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SINATRA_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SINATRA_ENABLED" => {version: ["A"]}, - "DD_TRACE_SNEAKERS_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SNEAKERS_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SNEAKERS_ENABLED" => {version: ["A"]}, - "DD_TRACE_STARTUP_LOGS" => {version: ["A"]}, - "DD_TRACE_STRIPE_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_STRIPE_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_STRIPE_ENABLED" => {version: ["A"]}, - "DD_TRACE_SUCKER_PUNCH_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_SUCKER_PUNCH_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_SUCKER_PUNCH_ENABLED" => {version: ["A"]}, - "DD_TRACE_TEST_MODE_ENABLED" => {version: ["A"]}, - "DD_TRACE_TRILOGY_ANALYTICS_ENABLED" => {version: ["A"]}, - "DD_TRACE_TRILOGY_ANALYTICS_SAMPLE_RATE" => {version: ["A"]}, - "DD_TRACE_TRILOGY_ENABLED" => {version: ["A"]}, - "DD_TRACE_TRILOGY_PEER_SERVICE" => {version: ["A"]}, - "DD_TRACE_TRILOGY_SERVICE_NAME" => {version: ["A"]}, - "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH" => {version: ["A"]}, - "DD_VERSION" => {version: ["A"]}, - "OTEL_TRACES_SAMPLER_ARG" => {version: ["A"]}}.freeze + { 'DD_AGENT_HOST' => { version: ['A'] }, + 'DD_API_KEY' => { version: ['A'] }, + 'DD_API_SECURITY_ENABLED' => { version: ['A'] }, + 'DD_API_SECURITY_REQUEST_SAMPLE_RATE' => { version: ['A'] }, + 'DD_API_SECURITY_SAMPLE_DELAY' => { version: ['A'] }, + 'DD_APM_TRACING_ENABLED' => { version: ['A'] }, + 'DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING' => { version: ['A'] }, + 'DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE' => { version: ['A'] }, + 'DD_APPSEC_ENABLED' => { version: ['A'] }, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML' => { version: ['A'] }, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON' => { version: ['A'] }, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_TEXT' => { version: ['A'] }, + 'DD_APPSEC_MAX_STACK_TRACES' => { version: ['A'] }, + 'DD_APPSEC_MAX_STACK_TRACE_DEPTH' => { version: ['A'] }, + 'DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT' => { version: ['A'] }, + 'DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP' => { version: ['A'] }, + 'DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP' => { version: ['A'] }, + 'DD_APPSEC_RASP_ENABLED' => { version: ['A'] }, + 'DD_APPSEC_RULES' => { version: ['A'] }, + 'DD_APPSEC_SCA_ENABLED' => { version: ['A'] }, + 'DD_APPSEC_STACK_TRACE_ENABLED' => { version: ['A'] }, + 'DD_APPSEC_TRACE_RATE_LIMIT' => { version: ['A'] }, + 'DD_APPSEC_WAF_DEBUG' => { version: ['A'] }, + 'DD_APPSEC_WAF_TIMEOUT' => { version: ['A'] }, + 'DD_CRASHTRACKING_ENABLED' => { version: ['A'] }, + 'DD_DBM_PROPAGATION_MODE' => { version: ['A'] }, + 'DD_DISABLE_DATADOG_RAILS' => { version: ['A'] }, + 'DD_DYNAMIC_INSTRUMENTATION_ENABLED' => { version: ['A'] }, + 'DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE' => { version: ['A'] }, + 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS' => { version: ['A'] }, + 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES' => { version: ['A'] }, + 'DD_ENV' => { version: ['A'] }, + 'DD_ERROR_TRACKING_HANDLED_ERRORS' => { version: ['A'] }, + 'DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE' => { version: ['A'] }, + 'DD_GIT_COMMIT_SHA' => { version: ['A'] }, + 'DD_GIT_REPOSITORY_URL' => { version: ['A'] }, + 'DD_HEALTH_METRICS_ENABLED' => { version: ['A'] }, + 'DD_INJECTION_ENABLED' => { version: ['A'] }, + 'DD_INJECT_FORCE' => { version: ['A'] }, + 'DD_INSTRUMENTATION_INSTALL_ID' => { version: ['A'] }, + 'DD_INSTRUMENTATION_INSTALL_TIME' => { version: ['A'] }, + 'DD_INSTRUMENTATION_INSTALL_TYPE' => { version: ['A'] }, + 'DD_INSTRUMENTATION_TELEMETRY_ENABLED' => { version: ['A'] }, + 'DD_INTEGRATION_SERVICE' => { version: ['A'] }, + 'DD_LOGS_INJECTION' => { version: ['A'] }, + 'DD_METRIC_AGENT_PORT' => { version: ['A'] }, + 'DD_PROFILING_ALLOCATION_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_DIR_INTERRUPTION_WORKAROUND_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE' => { version: ['A'] }, + 'DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_GC_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_GVL_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_HEAP_CLEAN_AFTER_GC_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_MAX_FRAMES' => { version: ['A'] }, + 'DD_PROFILING_NATIVE_FILENAMES_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE' => { version: ['A'] }, + 'DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_SKIP_MYSQL2_CHECK' => { version: ['A'] }, + 'DD_PROFILING_TIMELINE_ENABLED' => { version: ['A'] }, + 'DD_PROFILING_UPLOAD_PERIOD' => { version: ['A'] }, + 'DD_PROFILING_UPLOAD_TIMEOUT' => { version: ['A'] }, + 'DD_REDIS_COMMAND_ARGS' => { version: ['A'] }, + 'DD_REMOTE_CONFIGURATION_ENABLED' => { version: ['A'] }, + 'DD_REMOTE_CONFIG_BOOT_TIMEOUT_SECONDS' => { version: ['A'] }, + 'DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS' => { version: ['A'] }, + 'DD_RODA_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_RODA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_RUNTIME_METRICS_ENABLED' => { version: ['A'] }, + 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => { version: ['A'] }, + 'DD_SERVICE' => { version: ['B'] }, + 'DD_SITE' => { version: ['A'] }, + 'DD_SPAN_SAMPLING_RULES' => { version: ['A'] }, + 'DD_SPAN_SAMPLING_RULES_FILE' => { version: ['A'] }, + 'DD_TAGS' => { version: ['B'] }, + 'DD_TELEMETRY_AGENTLESS_URL' => { version: ['A'] }, + 'DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED' => { version: ['A'] }, + 'DD_TELEMETRY_HEARTBEAT_INTERVAL' => { version: ['A'] }, + 'DD_TELEMETRY_LOG_COLLECTION_ENABLED' => { version: ['A'] }, + 'DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL' => { version: ['A'] }, + 'DD_TELEMETRY_METRICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED' => { version: ['A'] }, + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_CABLE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_CABLE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTION_CABLE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_MAILER_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_MAILER_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTION_MAILER_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_PACK_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_PACK_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTION_PACK_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_VIEW_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTION_VIEW_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTION_VIEW_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_JOB_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_JOB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_JOB_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_RECORD_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ACTIVE_SUPPORT_ENABLED' => { version: ['A'] }, + 'DD_TRACE_AGENT_PORT' => { version: ['A'] }, + 'DD_TRACE_AGENT_TIMEOUT_SECONDS' => { version: ['A'] }, + 'DD_TRACE_AGENT_URL' => { version: ['A'] }, + 'DD_TRACE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_AWS_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_AWS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_AWS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_AWS_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_AWS_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_BAGGAGE_TAG_KEYS' => { version: ['A'] }, + 'DD_TRACE_CLIENT_IP_ENABLED' => { version: ['A'] }, + 'DD_TRACE_CLIENT_IP_HEADER' => { version: ['A'] }, + 'DD_TRACE_CONCURRENT_RUBY_ENABLED' => { version: ['A'] }, + 'DD_TRACE_DALLI_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_DALLI_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_DALLI_ENABLED' => { version: ['A'] }, + 'DD_TRACE_DALLI_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_DALLI_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_DEBUG' => { version: ['B'] }, + 'DD_TRACE_DELAYED_JOB_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_DELAYED_JOB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_DELAYED_JOB_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ELASTICSEARCH_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ELASTICSEARCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ELASTICSEARCH_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ELASTICSEARCH_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_ELASTICSEARCH_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_ENABLED' => { version: ['B'] }, + 'DD_TRACE_ETHON_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ETHON_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_ETHON_ENABLED' => { version: ['A'] }, + 'DD_TRACE_ETHON_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_ETHON_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_EXCON_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_EXCON_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_EXCON_ENABLED' => { version: ['A'] }, + 'DD_TRACE_EXCON_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_EXCON_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_EXCON_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_FARADAY_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_FARADAY_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_FARADAY_ENABLED' => { version: ['A'] }, + 'DD_TRACE_FARADAY_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_FARADAY_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_FARADAY_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_GRAPE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRAPE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_GRAPE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRAPE_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_ERROR_TRACKING' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => { version: ['A'] }, + 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER' => { version: ['A'] }, + 'DD_TRACE_GRPC_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRPC_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_GRPC_ENABLED' => { version: ['A'] }, + 'DD_TRACE_GRPC_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_GRPC_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_HANAMI_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HEADER_TAGS' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_HTTPCLIENT_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_HTTPRB_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_HTTP_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_HTTP_ENABLED' => { version: ['A'] }, + 'DD_TRACE_HTTP_ERROR_STATUS_CODES' => { version: ['A'] }, + 'DD_TRACE_KAFKA_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_KAFKA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_KAFKA_ENABLED' => { version: ['A'] }, + 'DD_TRACE_KARAFKA_ENABLED' => { version: ['A'] }, + 'DD_TRACE_LOGRAGE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MEMCACHED_COMMAND_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MONGO_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MONGO_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_MONGO_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MONGO_JSON_COMMAND' => { version: ['A'] }, + 'DD_TRACE_MONGO_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_MONGO_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_MYSQL2_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MYSQL2_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_MYSQL2_ENABLED' => { version: ['A'] }, + 'DD_TRACE_MYSQL2_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_MYSQL2_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_NATIVE_SPAN_EVENTS' => { version: ['A'] }, + 'DD_TRACE_NET_HTTP_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_NET_HTTP_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_ENABLED' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_RESOURCE_PATTERN' => { version: ['A'] }, + 'DD_TRACE_OPENSEARCH_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_PEER_SERVICE_MAPPING' => { version: ['A'] }, + 'DD_TRACE_PG_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_PG_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_PG_ENABLED' => { version: ['A'] }, + 'DD_TRACE_PG_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_PG_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_PRESTO_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_PRESTO_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_PRESTO_ENABLED' => { version: ['A'] }, + 'DD_TRACE_PRESTO_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_PRESTO_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_PROPAGATION_EXTRACT_FIRST' => { version: ['A'] }, + 'DD_TRACE_PROPAGATION_STYLE' => { version: ['B'] }, + 'DD_TRACE_PROPAGATION_STYLE_EXTRACT' => { version: ['A'] }, + 'DD_TRACE_PROPAGATION_STYLE_INJECT' => { version: ['A'] }, + 'DD_TRACE_QUE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_QUE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_QUE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_QUE_TAG_ARGS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_QUE_TAG_DATA_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RACECAR_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RACECAR_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_RACECAR_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RACK_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RACK_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_RACK_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RAILS_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RAILS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_RAILS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RAKE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RAKE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_RAKE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RATE_LIMIT' => { version: ['A'] }, + 'DD_TRACE_REDIS_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REDIS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_REDIS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REDIS_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_REDIS_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REPORT_HOSTNAME' => { version: ['A'] }, + 'DD_TRACE_RESQUE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_RESQUE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_RESQUE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REST_CLIENT_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REST_CLIENT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_REST_CLIENT_ENABLED' => { version: ['A'] }, + 'DD_TRACE_REST_CLIENT_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_REST_CLIENT_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_RODA_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SAMPLE_RATE' => { version: ['B'] }, + 'DD_TRACE_SAMPLING_RULES' => { version: ['A'] }, + 'DD_TRACE_SEMANTIC_LOGGER_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SEQUEL_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SEQUEL_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SEQUEL_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SHORYUKEN_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SHORYUKEN_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SHORYUKEN_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SIDEKIQ_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SIDEKIQ_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SIDEKIQ_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SINATRA_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SINATRA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SINATRA_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SNEAKERS_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SNEAKERS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SNEAKERS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_STARTUP_LOGS' => { version: ['A'] }, + 'DD_TRACE_STRIPE_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_STRIPE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_STRIPE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_SUCKER_PUNCH_ENABLED' => { version: ['A'] }, + 'DD_TRACE_TEST_MODE_ENABLED' => { version: ['A'] }, + 'DD_TRACE_TRILOGY_ANALYTICS_ENABLED' => { version: ['A'] }, + 'DD_TRACE_TRILOGY_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, + 'DD_TRACE_TRILOGY_ENABLED' => { version: ['A'] }, + 'DD_TRACE_TRILOGY_PEER_SERVICE' => { version: ['A'] }, + 'DD_TRACE_TRILOGY_SERVICE_NAME' => { version: ['A'] }, + 'DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH' => { version: ['A'] }, + 'DD_VERSION' => { version: ['A'] }, + 'OTEL_TRACES_SAMPLER_ARG' => { version: ['A'] } }.freeze ALIASES = - {"DD_DISABLE_DATADOG_RAILS" => ["DISABLE_DATADOG_RAILS"], - "DD_PROFILING_GVL_ENABLED" => ["DD_PROFILING_PREVIEW_GVL_ENABLED"], - "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED" => ["DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED"], - "DD_SERVICE" => ["OTEL_SERVICE_NAME"], - "DD_TAGS" => ["OTEL_RESOURCE_ATTRIBUTES"], - "DD_TRACE_DEBUG" => ["OTEL_LOG_LEVEL"], - "DD_TRACE_ENABLED" => ["OTEL_TRACES_EXPORTER"], - "DD_TRACE_PROPAGATION_STYLE" => ["OTEL_PROPAGATORS"], - "DD_TRACE_SAMPLE_RATE" => ["OTEL_TRACES_SAMPLER"]}.freeze + { 'DD_DISABLE_DATADOG_RAILS' => ['DISABLE_DATADOG_RAILS'], + 'DD_PROFILING_GVL_ENABLED' => ['DD_PROFILING_PREVIEW_GVL_ENABLED'], + 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => ['DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED'], + 'DD_SERVICE' => ['OTEL_SERVICE_NAME'], + 'DD_TAGS' => ['OTEL_RESOURCE_ATTRIBUTES'], + 'DD_TRACE_DEBUG' => ['OTEL_LOG_LEVEL'], + 'DD_TRACE_ENABLED' => ['OTEL_TRACES_EXPORTER'], + 'DD_TRACE_PROPAGATION_STYLE' => ['OTEL_PROPAGATORS'], + 'DD_TRACE_SAMPLE_RATE' => ['OTEL_TRACES_SAMPLER'] }.freeze DEPRECATIONS = - {"DD_PROFILING_PREVIEW_GVL_ENABLED" => "(This doesn't need a deprecation message since there's an alias for it)"}.freeze + { 'DD_PROFILING_PREVIEW_GVL_ENABLED' => "(This doesn't need a deprecation message since there's an alias for it)" }.freeze ALIAS_TO_CANONICAL = - {"DD_PROFILING_PREVIEW_GVL_ENABLED" => "DD_PROFILING_GVL_ENABLED", - "DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED" => "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED", - "DISABLE_DATADOG_RAILS" => "DD_DISABLE_DATADOG_RAILS", - "OTEL_LOG_LEVEL" => "DD_TRACE_DEBUG", - "OTEL_PROPAGATORS" => "DD_TRACE_PROPAGATION_STYLE", - "OTEL_RESOURCE_ATTRIBUTES" => "DD_TAGS", - "OTEL_SERVICE_NAME" => "DD_SERVICE", - "OTEL_TRACES_EXPORTER" => "DD_TRACE_ENABLED", - "OTEL_TRACES_SAMPLER" => "DD_TRACE_SAMPLE_RATE"}.freeze + { 'DD_PROFILING_PREVIEW_GVL_ENABLED' => 'DD_PROFILING_GVL_ENABLED', + 'DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED' => 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED', + 'DISABLE_DATADOG_RAILS' => 'DD_DISABLE_DATADOG_RAILS', + 'OTEL_LOG_LEVEL' => 'DD_TRACE_DEBUG', + 'OTEL_PROPAGATORS' => 'DD_TRACE_PROPAGATION_STYLE', + 'OTEL_RESOURCE_ATTRIBUTES' => 'DD_TAGS', + 'OTEL_SERVICE_NAME' => 'DD_SERVICE', + 'OTEL_TRACES_EXPORTER' => 'DD_TRACE_ENABLED', + 'OTEL_TRACES_SAMPLER' => 'DD_TRACE_SAMPLE_RATE' }.freeze end end end diff --git a/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb new file mode 100644 index 00000000000..0f7e94387b9 --- /dev/null +++ b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'set' + +module Datadog + module Tracing + module Contrib + module GraphQL + module Configuration + # Datadog tracing supports capturing GraphQL operation variables as span tags. + # + # The provided configuration String for this feature has to be pre-processed to + # allow for ease of utilization by the GraphQL integration. + # + # This class processes configuration, stores the result, and provides methods to + # utilize this configuration with O(1) lookups. + class CaptureVariables + # Regex pattern for valid operation and variable names: + # only alphanumeric characters or underscores. + VALID_NAME_PATTERN = /\A[A-Za-z0-9_]+\z/.freeze + + # @param variables [String, Array] Environment variable {String} (unparsed) or {Array} (parsed) + def initialize(variables) + @variables = if variables.is_a?(Array) + variables.join(',') # Programmatic configuration + else + variables # Environment variable configuration + end + + @operation_variables = Hash.new { |h, k| h[k] = Set.new } + + parse_configuration(@variables) unless @variables.nil? || @variables.empty? + end + + # Determines if a variable should be captured based on configuration. + # @param operation_name [String] The GraphQL operation name + # @param variable_name [String] The variable name + # @return [Boolean] true if the variable should be captured + def match?(operation_name, variable_name) + @operation_variables.key?(operation_name) && @operation_variables[operation_name].include?(variable_name) + end + + # Returns true if no variables are configured for capture. + # @return [Boolean] + def empty? + @operation_variables.empty? + end + + # For easy configuration inspection, + # print the original configuration setting. + def to_s + @variables.to_s + end + + private + + # Parses environment variable values for GraphQL variable capture configuration. + # Expected format: "OperationName:variableName,OperationName2:variableName2" + # + # @param values [String] comma-separated list of operation:variable pairs + def parse_configuration(values) + values.split(',').each do |fragment| + fragment.strip! + + next if fragment.empty? + + parts = fragment.split(':') + next unless parts.length == 2 + + operation_name = parts[0] + variable_name = parts[1] + + # Implicitly checks for whitespaces and empty strings + next unless VALID_NAME_PATTERN.match?(operation_name) + next unless VALID_NAME_PATTERN.match?(variable_name) + + @operation_variables[operation_name] << variable_name + end + end + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb index d30b47599c9..e1d98dbd0ae 100644 --- a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +++ b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb @@ -3,6 +3,7 @@ require_relative '../../configuration/settings' require_relative '../ext' require_relative 'error_extension_env_parser' +require_relative 'capture_variables' module Datadog module Tracing @@ -65,6 +66,22 @@ class Settings < Contrib::Configuration::Settings o.type :bool o.default false end + + # Variables to capture in GraphQL operations + option :capture_variables do |o| + o.env Ext::ENV_CAPTURE_VARIABLES + o.type :array, nilable: false + o.default [] + o.setter { |variable_tags, _| CaptureVariables.new(variable_tags) } + end + + # Variables to exclude from capture in GraphQL operations + option :capture_variables_except do |o| + o.env Ext::ENV_CAPTURE_VARIABLES_EXCEPT + o.type :array, nilable: false + o.default [] + o.setter { |variable_tags, _| CaptureVariables.new(variable_tags) } + end end end end diff --git a/lib/datadog/tracing/contrib/graphql/ext.rb b/lib/datadog/tracing/contrib/graphql/ext.rb index 16156d65bf8..45e755e43a5 100644 --- a/lib/datadog/tracing/contrib/graphql/ext.rb +++ b/lib/datadog/tracing/contrib/graphql/ext.rb @@ -14,6 +14,8 @@ module Ext ENV_WITH_UNIFIED_TRACER = 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER' ENV_ERROR_EXTENSIONS = 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS' ENV_ERROR_TRACKING = 'DD_TRACE_GRAPHQL_ERROR_TRACKING' + ENV_CAPTURE_VARIABLES = 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' + ENV_CAPTURE_VARIABLES_EXCEPT = 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' SERVICE_NAME = 'graphql' TAG_COMPONENT = 'graphql' diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index 166517e17e7..ab20b7e49a1 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -76,7 +76,7 @@ def analyze_query(*args, query:, **kwargs) def execute_multiplex(*args, multiplex:, **kwargs) trace(proc { super }, 'execute_multiplex', multiplex_resource(multiplex), multiplex: multiplex) do |span| - span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(", ")}]") + span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(', ')}]") end end @@ -97,9 +97,9 @@ def execute_query(*args, query:, **kwargs) query.selected_operation_name ) end - query.variables.instance_variable_get(:@storage).each do |key, value| - span.set_tag("graphql.variables.#{key}", value) - end + + # Capture operation variables based on configuration + capture_operation_variables(span, query) }, ->(span) { add_query_error_events(span, query.context.errors) }, query: query, @@ -108,10 +108,10 @@ def execute_query(*args, query:, **kwargs) def execute_query_lazy(*args, query:, multiplex:, **kwargs) resource = if query - query.selected_operation_name || fallback_transaction_name(query.context) - else - multiplex_resource(multiplex) - end + query.selected_operation_name || fallback_transaction_name(query.context) + else + multiplex_resource(multiplex) + end trace(proc { super }, 'execute_lazy', resource, query: query, multiplex: multiplex) end @@ -122,6 +122,7 @@ def execute_field_span(callable, span_key, **kwargs) if platform_key trace(callable, span_key, platform_key, **kwargs) do |span| kwargs[:arguments].each do |key, value| + # DEV-3.0: Misnamed tag: fields have arguments, not variables. span.set_tag("graphql.variables.#{key}", value) end end @@ -178,6 +179,68 @@ def platform_resolve_type_key(type, *args, **kwargs) private + # Captures GraphQL operation variables based on configuration settings + def capture_operation_variables(span, query) + config = Datadog.configuration.tracing[:graphql] + capture_config = config[:capture_variables] + capture_except_config = config[:capture_variables_except] + + # If neither configuration is set, don't capture any variables + return if capture_config.empty? && capture_except_config.empty? + + operation_name = query.selected_operation_name + return unless operation_name # Skip anonymous operations + + query.variables.to_h.each do |variable_name, value| + variable_name_str = variable_name.to_s + + should_capture = should_capture_variable?( + operation_name, + variable_name_str, + capture_config, + capture_except_config + ) + + if should_capture + serialized_value = serialize_variable_value(value) + span.set_tag("graphql.operation.variable.#{variable_name_str}", serialized_value) + end + end + end + + # Determines if a variable should be captured based on configuration + def should_capture_variable?(operation_name, variable_name, capture_config, capture_except_config) + # Check if this operation:variable pair is in the capture list (O(1) lookup) + capture_match = capture_config.match?(operation_name, variable_name) + + # Check if this operation:variable pair is in the except list (O(1) lookup) + except_match = capture_except_config.match?(operation_name, variable_name) + + if !capture_config.empty? + # If capture list is set, capture only if in the list AND not in except list + capture_match && !except_match + elsif !capture_except_config.empty? + # If only except list is set, capture everything except what's in the except list + !except_match + else + # If neither configuration is set, don't capture anything + false + end + end + + # Serializes a GraphQL variable value according to the spec + def serialize_variable_value(value) + case value + when TrueClass, FalseClass + value.to_s + when Integer, Float, String + value + else + # For custom types and ID types, convert to string + value.to_s + end + end + # Traces the given callable with the given trace key, resource, and kwargs. # # @param callable [Proc] the original method call @@ -239,22 +302,22 @@ def operation_resource(operation) def add_query_error_events(span, errors) errors.each do |error| attributes = if !@error_extensions_config.empty? && (extensions = error.extensions) - # Capture extensions, ensuring all values are primitives - extensions.each_with_object({}) do |(key, value), hash| - next unless @error_extensions_config.include?(key.to_s) - - value = case value - when TrueClass, FalseClass, Integer, Float - value - else - value.to_s - end - - hash[@extensions_key + key.to_s] = value - end - else - {} - end + # Capture extensions, ensuring all values are primitives + extensions.each_with_object({}) do |(key, value), hash| + next unless @error_extensions_config.include?(key.to_s) + + value = case value + when TrueClass, FalseClass, Integer, Float + value + else + value.to_s + end + + hash[@extensions_key + key.to_s] = value + end + else + {} + end # {::GraphQL::Error#to_h} returns the error formatted in compliance with the GraphQL spec. # This is an unwritten contract in the `graphql` library. @@ -287,7 +350,7 @@ def add_query_error_events(span, errors) # ["3:10", "7:8"] def serialize_error_locations(locations) locations.map do |location| - "#{location["line"]}:#{location["column"]}" + "#{location['line']}:#{location['column']}" end end end diff --git a/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb new file mode 100644 index 00000000000..1e970674952 --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +require 'datadog/tracing/contrib/graphql/configuration/capture_variables' + +RSpec.describe Datadog::Tracing::Contrib::GraphQL::Configuration::CaptureVariables do + describe '.new' do + subject(:config) { described_class.new(input) } + + context 'with empty array input' do + let(:input) { [] } + + it { expect(config.empty?).to be true } + end + + context 'with empty string' do + let(:input) { '' } + + it { expect(config.empty?).to be true } + end + + context 'with valid fragments' do + let(:input) { 'GetAd:id,ListIp:geo,AddKey:val' } + + it 'correctly parses operation and variable configuration' do + expect(config.match?('GetAd', 'id')).to be true + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('AddKey', 'val')).to be true + expect(config.match?('GetAd', 'geo')).to be false + end + + it { expect(config.empty?).to be false } + end + + context 'with array input' do + let(:input) { ['GetAd:id', 'ListIp:geo', 'AddKey:val'] } + + it 'correctly parses operation and variable configuration' do + expect(config.match?('GetAd', 'id')).to be true + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('AddKey', 'val')).to be true + expect(config.match?('GetAd', 'geo')).to be false + end + + it 'converts array to comma-separated string for to_s' do + expect(config.to_s).to eq('GetAd:id,ListIp:geo,AddKey:val') + end + + it { expect(config.empty?).to be false } + end + + context 'with duplicate operation:variable pairs' do + let(:input) { 'GetAd:id,GetAd:id,ListIp:geo' } + + it 'deduplicates entries' do + expect(config.match?('GetAd', 'id')).to be true + expect(config.match?('ListIp', 'geo')).to be true + end + end + + context 'with multiple variables for same operation' do + let(:input) { 'GetUser:id,GetUser:name,GetUser:email' } + + it 'groups variables under same operation' do + expect(config.match?('GetUser', 'id')).to be true + expect(config.match?('GetUser', 'name')).to be true + expect(config.match?('GetUser', 'email')).to be true + expect(config.match?('GetUser', 'other')).to be false + end + end + + context 'with same variable used by multiple operations' do + let(:input) { 'GetUser:id,GetPost:id,GetComment:id' } + + it 'handles same variable used by multiple operations' do + expect(config.match?('GetUser', 'id')).to be true + expect(config.match?('GetPost', 'id')).to be true + expect(config.match?('GetComment', 'id')).to be true + expect(config.match?('GetUser', 'name')).to be false + end + end + + context 'with whitespace around fragments' do + let(:input) { ' GetAd:id , ListIp:geo ' } + + it 'handles whitespace correctly' do + expect(config.match?('GetAd', 'id')).to be true + expect(config.match?('ListIp', 'geo')).to be true + end + end + + context 'with empty fragments' do + let(:input) { ',,GetAd:id,' } + + it 'skips empty fragments' do + expect(config.match?('GetAd', 'id')).to be true + end + end + + context 'with invalid characters in operation name' do + let(:input) { 'Get-Ad:bad,ListIp:geo' } + + it 'skips invalid operation names' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('Get-Ad', 'bad')).to be false + end + end + + context 'with invalid characters in variable name' do + let(:input) { 'GetAd:in-valid,ListIp:geo' } + + it 'skips invalid variable names' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('GetAd', 'in-valid')).to be false + end + end + + context 'with whitespace around colon' do + let(:input) { 'GetAd : bad,ListIp:geo' } + + it 'treats whitespace as invalid' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('GetAd ', 'bad')).to be false + end + end + + context 'with missing colon' do + let(:input) { 'GetAd,ListIp:geo' } + + it 'skips fragments without colon' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('GetAd', '')).to be false + end + end + + context 'with empty operation name' do + let(:input) { ':id,ListIp:geo' } + + it 'skips empty operation names' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('', 'id')).to be false + end + end + + context 'with empty variable name' do + let(:input) { 'GetAd:,ListIp:geo' } + + it 'skips empty variable names' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('GetAd', '')).to be false + end + end + + context 'with valid alphanumeric and underscore names' do + let(:input) { 'Get_Ad:var_1,Load_Ip:key_2' } + + it 'accepts valid names with underscores and numbers' do + expect(config.match?('Get_Ad', 'var_1')).to be true + expect(config.match?('Load_Ip', 'key_2')).to be true + end + end + + context 'with multiple colons' do + let(:input) { 'GetAd:id:bad,ListIp:geo' } + + it 'skips fragments with multiple colons' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?('GetAd', 'id:bad')).to be false + end + end + end + + describe '#match?' do + subject(:config) { described_class.new('GetUser:id,GetUser:name,GetPost:title') } + + context 'with configured operation and variable' do + it 'returns true for GetUser:id' do + expect(config.match?('GetUser', 'id')).to be true + end + + it 'returns true for GetUser:name' do + expect(config.match?('GetUser', 'name')).to be true + end + + it 'returns true for GetPost:title' do + expect(config.match?('GetPost', 'title')).to be true + end + end + + context 'with unconfigured combinations' do + it 'returns false for GetUser:title' do + expect(config.match?('GetUser', 'title')).to be false + end + + it 'returns false for GetPost:id' do + expect(config.match?('GetPost', 'id')).to be false + end + + it 'returns false for UnknownOperation:id' do + expect(config.match?('UnknownOperation', 'id')).to be false + end + end + end + + describe '#to_s' do + subject(:config) { described_class.new('GetUser:id,GetUser:name') } + + it 'returns the original configuration string' do + expect(config.to_s).to eq('GetUser:id,GetUser:name') + end + end + + describe '#empty?' do + context 'with no configuration' do + subject(:config) { described_class.new([]) } + + it { is_expected.to be_empty } + end + + context 'with configuration' do + subject(:config) { described_class.new('GetUser:id') } + + it { is_expected.not_to be_empty } + end + end +end diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index cbfe781af06..7bed4a0e5a7 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -7,6 +7,12 @@ def load_test_schema(prefix: '') # rubocop:disable Security/Eval # rubocop:disable Style/DocumentDynamicEvalDefinition eval <<-RUBY, binding, __FILE__, __LINE__ + 1 + class #{prefix}TestUserFilterInput < ::GraphQL::Schema::InputObject + argument :name, ::GraphQL::Types::String, required: false + argument :active, ::GraphQL::Types::Boolean, required: false + argument :min_age, ::GraphQL::Types::Int, required: false + end + class #{prefix}TestUserType < ::GraphQL::Schema::Object field :id, ::GraphQL::Types::ID, null: false field :name, ::GraphQL::Types::String, null: true @@ -28,6 +34,43 @@ def user(id:) def graphql_error raise 'GraphQL error' end + + field :user_with_org, #{prefix}TestUserType, null: false, description: 'Find user with org' do + argument :id, ::GraphQL::Types::ID, required: true + argument :org, ::GraphQL::Types::ID, required: true + end + + def user_with_org(id:, org:) + OpenStruct.new(id: id, name: 'Bits', org: org) + end + + field :user_with_filter, #{prefix}TestUserType, null: false, description: 'Find user with filter' do + argument :id, ::GraphQL::Types::ID, required: true + argument :active, ::GraphQL::Types::Boolean, required: true + end + + def user_with_filter(id:, active:) + OpenStruct.new(id: id, name: 'Bits', active: active) + end + + field :user_with_details, #{prefix}TestUserType, null: false, description: 'Find user with details' do + argument :id, ::GraphQL::Types::ID, required: true + argument :name, ::GraphQL::Types::String, required: false + argument :count, ::GraphQL::Types::Int, required: false + end + + def user_with_details(id:, name: nil, count: nil) + OpenStruct.new(id: id, name: name || 'Bits', count: count) + end + + field :user_with_input_filter, #{prefix}TestUserType, null: false, description: 'Find user with input filter' do + argument :id, ::GraphQL::Types::ID, required: true + argument :filter, #{prefix}TestUserFilterInput, required: true + end + + def user_with_input_filter(id:, filter:) + OpenStruct.new(id: id, name: 'Bits', filter: filter) + end end class #{prefix}TestGraphQLSchema < ::GraphQL::Schema @@ -51,6 +94,7 @@ class #{prefix}TestGraphQLSchema < ::GraphQL::Schema end def unload_test_schema(prefix: '') + Object.send(:remove_const, :"#{prefix}TestUserFilterInput") Object.send(:remove_const, :"#{prefix}TestUserType") Object.send(:remove_const, :"#{prefix}TestGraphQLQuery") Object.send(:remove_const, :"#{prefix}TestGraphQLSchema") @@ -141,15 +185,16 @@ def unload_test_schema(prefix: '') if name == 'graphql.execute' expect(span.get_tag('graphql.operation.type')).to eq('query') expect(span.get_tag('graphql.operation.name')).to eq('Users') - # graphql.variables.* in graphql.execute span are the ones defined outside the query - # (variables part in JSON for example) - expect(span.get_tag('graphql.variables.var')).to eq(1) + # Variables are now only captured when explicitly configured + # By default, no variables are captured + expect(span.get_tag('graphql.variables.var')).to be_nil expect(span.get_tag('span.kind')).to eq('server') end if name == 'graphql.resolve' && resource == 'TestGraphQLQuery.user' - # During graphql.resolve, it converts it to string (as it builds an SQL query for example) + # Field arguments are still captured with legacy graphql.variables.* tags + # This is different from operation variables which use graphql.operation.variable.* tags expect(span.get_tag('graphql.variables.id')).to eq('1') end end @@ -258,4 +303,182 @@ def unload_test_schema(prefix: '') end end end + + describe 'operation variable capture' do + let(:graphql_execute) { spans.find { |s| s.name == 'graphql.execute' } } + + context 'with no configuration (default behavior)' do + it 'does not capture any variables' do + result = schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + + expect(graphql_execute.get_tag('graphql.operation.variable.var')).to be_nil + end + end + + context 'with DD_TRACE_GRAPHQL_CAPTURE_VARIABLES configured' do + around do |ex| + ClimateControl.modify('DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => 'Users:var,GetUser:id') do + # Reset configuration to pick up environment variable + Datadog.configuration.tracing[:graphql].reset! + ex.run + end + end + + it 'captures configured variables' do + result = schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + + expect(graphql_execute.get_tag('graphql.operation.variable.var')).to eq(1) + end + + it 'does not capture unconfigured variables' do + result = schema.execute( + query: 'query GetUser($id: ID!, $org: ID!){ userWithOrg(id: $id, org: $org) { name } }', + variables: { id: 1, org: 2 } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.id')).to eq(1) + expect(graphql_execute.get_tag('graphql.operation.variable.org')).to be_nil + end + + it 'does not capture variables for different operations' do + result = schema.execute(query: 'query DifferentOp($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + + expect(graphql_execute.get_tag('graphql.operation.variable.var')).to be_nil + end + end + + context 'with DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT configured' do + around do |ex| + ClimateControl.modify( + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => 'Users:var,Users:org', + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => 'Users:org' + ) do + Datadog.configuration.tracing[:graphql].reset! + ex.run + end + end + + it 'captures variables except those in the except list' do + result = schema.execute( + query: 'query Users($var: ID!, $org: ID!){ userWithOrg(id: $var, org: $org) { name } }', + variables: { var: 1, org: 2 } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.var')).to eq(1) + expect(graphql_execute.get_tag('graphql.operation.variable.org')).to be_nil + end + end + + context 'with only DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT configured' do + around do |ex| + ClimateControl.modify('DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => 'Users:org') do + Datadog.configuration.tracing[:graphql].reset! + ex.run + end + end + + it 'captures all variables except those in the except list' do + result = schema.execute( + query: 'query Users($var: ID!, $org: ID!){ userWithOrg(id: $var, org: $org) { name } }', + variables: { var: 1, org: 2 } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.var')).to eq(1) + expect(graphql_execute.get_tag('graphql.operation.variable.org')).to be_nil + end + end + + context 'with anonymous operations' do + around do |ex| + ClimateControl.modify('DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => '') do + Datadog.configuration.tracing[:graphql].reset! + ex.run + end + end + + it 'never captures variables for anonymous operations' do + result = schema.execute(query: '{ user(id: "1") { name } }') + + expect(graphql_execute.resource).to eq('anonymous') + # No variables to check since query has no variables, but operation name is nil + end + end + + context 'variable serialization' do + around do |ex| + ClimateControl.modify('DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => 'TestIntQuery:intVar,TestStringQuery:stringVar,TestBoolQuery:boolVar,TestIdQuery:idVar,TestInputQuery:inputVar') do + Datadog.configuration.tracing[:graphql].reset! + ex.run + end + end + + it 'serializes integer variables correctly' do + result = schema.execute( + query: 'query TestIntQuery($intVar: Int!){ userWithDetails(id: "1", count: $intVar) { name } }', + variables: { intVar: 42 } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.intVar')).to eq(42) + end + + it 'serializes string variables correctly' do + result = schema.execute( + query: 'query TestStringQuery($stringVar: String!){ userWithDetails(id: "1", name: $stringVar) { name } }', + variables: { stringVar: 'hello' } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.stringVar')).to eq('hello') + end + + it 'serializes true boolean correctly' do + result = schema.execute( + query: 'query TestBoolQuery($boolVar: Boolean!){ userWithFilter(id: "1", active: $boolVar) { name } }', + variables: { boolVar: true } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.boolVar')).to eq('true') + end + + it 'serializes false boolean correctly' do + result = schema.execute( + query: 'query TestBoolQuery($boolVar: Boolean!){ userWithFilter(id: "1", active: $boolVar) { name } }', + variables: { boolVar: false } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.boolVar')).to eq('false') + end + + it 'serializes ID variables correctly' do + result = schema.execute( + query: 'query TestIdQuery($idVar: ID!){ user(id: $idVar) { name } }', + variables: { idVar: 'user123' } + ) + + expect(graphql_execute.get_tag('graphql.operation.variable.idVar')).to eq('user123') + end + + it 'serializes custom input object variables correctly' do + # Create a Ruby hash that represents a GraphQL input object + # Use camelCase for GraphQL field names + input_object = { name: 'John', active: true, minAge: 18 } + + # For schemas without prefix, use the base type name + input_type_name = if defined?(TraceWithTestUserFilterInput) + 'TraceWithTestUserFilterInput' + else + 'TestUserFilterInput' + end + + result = schema.execute( + query: "query TestInputQuery($inputVar: #{input_type_name}!){ userWithInputFilter(id: \"1\", filter: $inputVar) { name } }", + variables: { inputVar: input_object } + ) + + # Custom input objects should be serialized as strings using to_s + # GraphQL converts hash keys from symbols to strings, so we expect the string key version + expected_serialized = { 'name' => 'John', 'active' => true, 'minAge' => 18 }.to_s + expect(graphql_execute.get_tag('graphql.operation.variable.inputVar')).to eq(expected_serialized) + end + end + end end diff --git a/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb b/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb new file mode 100644 index 00000000000..b7b1b0b2ded --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/graphql/test_schema_examples' +require 'datadog/tracing/contrib/graphql/unified_trace_patcher' +require 'datadog' + +RSpec.describe 'GraphQL variable capture basic functionality', + skip: Gem::Version.new(::GraphQL::VERSION) < Gem::Version.new('2.0.19') do + + before(:context) { load_test_schema } + after(:context) do + unload_test_schema + remove_patch!(:graphql) + end + + around do |example| + # Remove the previously set environment variables + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: nil, + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: nil + ) do + example.run + end + end + + let(:all_spans) { spans } + let(:execute_span) { all_spans.find { |s| s.name == 'graphql.execute' } } + + describe 'with unified tracer' do + before do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + end + + context 'when no capture configuration is set' do + it 'does not capture any variables' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '1' }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to be_nil + end + end + + context 'when capture variables is configured for GetUser:id' do + around do |example| + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: 'GetUser:id' + ) do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + example.run + end + end + + it 'captures the id variable' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '42' }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('42') + end + + it 'captures boolean variables correctly' do + # Since we can only test with the schema we have, let's use the id field + # and test different variable types when they're passed as id + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: 123 }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq(123) + end + end + + context 'when capture variables except is configured' do + around do |example| + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: 'GetUser:secret' + ) do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + example.run + end + end + + it 'captures all variables except those in the except list' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '1' }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + # Since id is not in the except list, it should be captured + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('1') + end + end + end +end diff --git a/supported-configurations.json b/supported-configurations.json index fa5503ea345..c86a5265b7e 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -496,6 +496,12 @@ "DD_TRACE_GRAPHQL_ERROR_TRACKING": { "version": ["A"] }, + "DD_TRACE_GRAPHQL_CAPTURE_VARIABLES": { + "version": ["A"] + }, + "DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT": { + "version": ["A"] + }, "DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER": { "version": ["A"] }, From 011bb3b5eb40233b8fcf0879aecde239d6faca0b Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Tue, 16 Sep 2025 12:44:21 -0700 Subject: [PATCH 2/4] wip --- .../configuration/capture_variables.rb | 34 +--- .../tracing/contrib/graphql/unified_trace.rb | 12 +- .../configuration/capture_variables_spec.rb | 84 +++------ .../contrib/graphql/test_schema_examples.rb | 49 +++-- .../graphql/variable_capture_basic_spec.rb | 175 +++++++++--------- 5 files changed, 141 insertions(+), 213 deletions(-) diff --git a/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb index 0f7e94387b9..5a0882f4d81 100644 --- a/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb +++ b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb @@ -7,57 +7,38 @@ module Tracing module Contrib module GraphQL module Configuration - # Datadog tracing supports capturing GraphQL operation variables as span tags. - # - # The provided configuration String for this feature has to be pre-processed to - # allow for ease of utilization by the GraphQL integration. - # - # This class processes configuration, stores the result, and provides methods to - # utilize this configuration with O(1) lookups. + # Processes GraphQL variable capture configuration for O(1) lookups. class CaptureVariables - # Regex pattern for valid operation and variable names: - # only alphanumeric characters or underscores. + # Valid operation and variable names: alphanumeric characters or underscores only VALID_NAME_PATTERN = /\A[A-Za-z0-9_]+\z/.freeze - # @param variables [String, Array] Environment variable {String} (unparsed) or {Array} (parsed) + # @param variables [String, Array] Configuration string or array of operation:variable pairs def initialize(variables) - @variables = if variables.is_a?(Array) - variables.join(',') # Programmatic configuration - else - variables # Environment variable configuration - end + @variables = variables.is_a?(Array) ? variables.join(',') : variables @operation_variables = Hash.new { |h, k| h[k] = Set.new } parse_configuration(@variables) unless @variables.nil? || @variables.empty? end - # Determines if a variable should be captured based on configuration. - # @param operation_name [String] The GraphQL operation name - # @param variable_name [String] The variable name # @return [Boolean] true if the variable should be captured def match?(operation_name, variable_name) @operation_variables.key?(operation_name) && @operation_variables[operation_name].include?(variable_name) end - # Returns true if no variables are configured for capture. - # @return [Boolean] + # @return [Boolean] true if no variables are configured for capture def empty? @operation_variables.empty? end - # For easy configuration inspection, - # print the original configuration setting. + # Returns the original configuration for inspection def to_s @variables.to_s end private - # Parses environment variable values for GraphQL variable capture configuration. - # Expected format: "OperationName:variableName,OperationName2:variableName2" - # - # @param values [String] comma-separated list of operation:variable pairs + # Parses comma-separated operation:variable pairs def parse_configuration(values) values.split(',').each do |fragment| fragment.strip! @@ -70,7 +51,6 @@ def parse_configuration(values) operation_name = parts[0] variable_name = parts[1] - # Implicitly checks for whitespaces and empty strings next unless VALID_NAME_PATTERN.match?(operation_name) next unless VALID_NAME_PATTERN.match?(variable_name) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index ab20b7e49a1..144e40309be 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -185,11 +185,10 @@ def capture_operation_variables(span, query) capture_config = config[:capture_variables] capture_except_config = config[:capture_variables_except] - # If neither configuration is set, don't capture any variables return if capture_config.empty? && capture_except_config.empty? operation_name = query.selected_operation_name - return unless operation_name # Skip anonymous operations + return unless operation_name query.variables.to_h.each do |variable_name, value| variable_name_str = variable_name.to_s @@ -208,27 +207,19 @@ def capture_operation_variables(span, query) end end - # Determines if a variable should be captured based on configuration def should_capture_variable?(operation_name, variable_name, capture_config, capture_except_config) - # Check if this operation:variable pair is in the capture list (O(1) lookup) capture_match = capture_config.match?(operation_name, variable_name) - - # Check if this operation:variable pair is in the except list (O(1) lookup) except_match = capture_except_config.match?(operation_name, variable_name) if !capture_config.empty? - # If capture list is set, capture only if in the list AND not in except list capture_match && !except_match elsif !capture_except_config.empty? - # If only except list is set, capture everything except what's in the except list !except_match else - # If neither configuration is set, don't capture anything false end end - # Serializes a GraphQL variable value according to the spec def serialize_variable_value(value) case value when TrueClass, FalseClass @@ -236,7 +227,6 @@ def serialize_variable_value(value) when Integer, Float, String value else - # For custom types and ID types, convert to string value.to_s end end diff --git a/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb index 1e970674952..35fe64acffc 100644 --- a/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb @@ -96,57 +96,25 @@ end end - context 'with invalid characters in operation name' do - let(:input) { 'Get-Ad:bad,ListIp:geo' } + context 'with invalid configurations' do + let(:valid_fragment) { 'ListIp:geo' } - it 'skips invalid operation names' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('Get-Ad', 'bad')).to be false - end - end - - context 'with invalid characters in variable name' do - let(:input) { 'GetAd:in-valid,ListIp:geo' } - - it 'skips invalid variable names' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('GetAd', 'in-valid')).to be false - end - end + [ + { input: 'Get-Ad:bad,ListIp:geo', desc: 'invalid characters in operation name', invalid: ['Get-Ad', 'bad'] }, + { input: 'GetAd:in-valid,ListIp:geo', desc: 'invalid characters in variable name', invalid: ['GetAd', 'in-valid'] }, + { input: 'GetAd : bad,ListIp:geo', desc: 'whitespace around colon', invalid: ['GetAd ', 'bad'] }, + { input: 'GetAd,ListIp:geo', desc: 'missing colon', invalid: ['GetAd', ''] }, + { input: ':id,ListIp:geo', desc: 'empty operation name', invalid: ['', 'id'] }, + { input: 'GetAd:,ListIp:geo', desc: 'empty variable name', invalid: ['GetAd', ''] } + ].each do |test_case| + context "with #{test_case[:desc]}" do + let(:input) { test_case[:input] } - context 'with whitespace around colon' do - let(:input) { 'GetAd : bad,ListIp:geo' } - - it 'treats whitespace as invalid' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('GetAd ', 'bad')).to be false - end - end - - context 'with missing colon' do - let(:input) { 'GetAd,ListIp:geo' } - - it 'skips fragments without colon' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('GetAd', '')).to be false - end - end - - context 'with empty operation name' do - let(:input) { ':id,ListIp:geo' } - - it 'skips empty operation names' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('', 'id')).to be false - end - end - - context 'with empty variable name' do - let(:input) { 'GetAd:,ListIp:geo' } - - it 'skips empty variable names' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('GetAd', '')).to be false + it 'skips invalid fragments but keeps valid ones' do + expect(config.match?('ListIp', 'geo')).to be true + expect(config.match?(test_case[:invalid][0], test_case[:invalid][1])).to be false + end + end end end @@ -187,16 +155,14 @@ end context 'with unconfigured combinations' do - it 'returns false for GetUser:title' do - expect(config.match?('GetUser', 'title')).to be false - end - - it 'returns false for GetPost:id' do - expect(config.match?('GetPost', 'id')).to be false - end - - it 'returns false for UnknownOperation:id' do - expect(config.match?('UnknownOperation', 'id')).to be false + [ + ['GetUser', 'title'], + ['GetPost', 'id'], + ['UnknownOperation', 'id'] + ].each do |operation, variable| + it "returns false for #{operation}:#{variable}" do + expect(config.match?(operation, variable)).to be false + end end end end diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index 7bed4a0e5a7..b0de208931a 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -309,7 +309,7 @@ def unload_test_schema(prefix: '') context 'with no configuration (default behavior)' do it 'does not capture any variables' do - result = schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) expect(graphql_execute.get_tag('graphql.operation.variable.var')).to be_nil end @@ -325,13 +325,13 @@ def unload_test_schema(prefix: '') end it 'captures configured variables' do - result = schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) expect(graphql_execute.get_tag('graphql.operation.variable.var')).to eq(1) end it 'does not capture unconfigured variables' do - result = schema.execute( + schema.execute( query: 'query GetUser($id: ID!, $org: ID!){ userWithOrg(id: $id, org: $org) { name } }', variables: { id: 1, org: 2 } ) @@ -341,7 +341,7 @@ def unload_test_schema(prefix: '') end it 'does not capture variables for different operations' do - result = schema.execute(query: 'query DifferentOp($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + schema.execute(query: 'query DifferentOp($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) expect(graphql_execute.get_tag('graphql.operation.variable.var')).to be_nil end @@ -359,7 +359,7 @@ def unload_test_schema(prefix: '') end it 'captures variables except those in the except list' do - result = schema.execute( + schema.execute( query: 'query Users($var: ID!, $org: ID!){ userWithOrg(id: $var, org: $org) { name } }', variables: { var: 1, org: 2 } ) @@ -378,7 +378,7 @@ def unload_test_schema(prefix: '') end it 'captures all variables except those in the except list' do - result = schema.execute( + schema.execute( query: 'query Users($var: ID!, $org: ID!){ userWithOrg(id: $var, org: $org) { name } }', variables: { var: 1, org: 2 } ) @@ -397,10 +397,9 @@ def unload_test_schema(prefix: '') end it 'never captures variables for anonymous operations' do - result = schema.execute(query: '{ user(id: "1") { name } }') + schema.execute(query: '{ user(id: "1") { name } }') expect(graphql_execute.resource).to eq('anonymous') - # No variables to check since query has no variables, but operation name is nil end end @@ -413,7 +412,7 @@ def unload_test_schema(prefix: '') end it 'serializes integer variables correctly' do - result = schema.execute( + schema.execute( query: 'query TestIntQuery($intVar: Int!){ userWithDetails(id: "1", count: $intVar) { name } }', variables: { intVar: 42 } ) @@ -422,7 +421,7 @@ def unload_test_schema(prefix: '') end it 'serializes string variables correctly' do - result = schema.execute( + schema.execute( query: 'query TestStringQuery($stringVar: String!){ userWithDetails(id: "1", name: $stringVar) { name } }', variables: { stringVar: 'hello' } ) @@ -430,26 +429,22 @@ def unload_test_schema(prefix: '') expect(graphql_execute.get_tag('graphql.operation.variable.stringVar')).to eq('hello') end - it 'serializes true boolean correctly' do - result = schema.execute( - query: 'query TestBoolQuery($boolVar: Boolean!){ userWithFilter(id: "1", active: $boolVar) { name } }', - variables: { boolVar: true } - ) - - expect(graphql_execute.get_tag('graphql.operation.variable.boolVar')).to eq('true') - end - - it 'serializes false boolean correctly' do - result = schema.execute( - query: 'query TestBoolQuery($boolVar: Boolean!){ userWithFilter(id: "1", active: $boolVar) { name } }', - variables: { boolVar: false } - ) + [ + { value: true, expected: 'true' }, + { value: false, expected: 'false' } + ].each do |test_case| + it "serializes #{test_case[:value]} boolean correctly" do + schema.execute( + query: 'query TestBoolQuery($boolVar: Boolean!){ userWithFilter(id: "1", active: $boolVar) { name } }', + variables: { boolVar: test_case[:value] } + ) - expect(graphql_execute.get_tag('graphql.operation.variable.boolVar')).to eq('false') + expect(graphql_execute.get_tag('graphql.operation.variable.boolVar')).to eq(test_case[:expected]) + end end it 'serializes ID variables correctly' do - result = schema.execute( + schema.execute( query: 'query TestIdQuery($idVar: ID!){ user(id: $idVar) { name } }', variables: { idVar: 'user123' } ) @@ -469,7 +464,7 @@ def unload_test_schema(prefix: '') 'TestUserFilterInput' end - result = schema.execute( + schema.execute( query: "query TestInputQuery($inputVar: #{input_type_name}!){ userWithInputFilter(id: \"1\", filter: $inputVar) { name } }", variables: { inputVar: input_object } ) diff --git a/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb b/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb index b7b1b0b2ded..ed5800606a8 100644 --- a/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb @@ -7,113 +7,110 @@ RSpec.describe 'GraphQL variable capture basic functionality', skip: Gem::Version.new(::GraphQL::VERSION) < Gem::Version.new('2.0.19') do + before(:context) { load_test_schema } + after(:context) do + unload_test_schema + remove_patch!(:graphql) + end - before(:context) { load_test_schema } - after(:context) do - unload_test_schema - remove_patch!(:graphql) - end - - around do |example| - # Remove the previously set environment variables - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: nil, - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: nil - ) do - example.run + around do |example| + # Remove the previously set environment variables + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: nil, + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: nil + ) do + example.run + end end - end - let(:all_spans) { spans } - let(:execute_span) { all_spans.find { |s| s.name == 'graphql.execute' } } + let(:all_spans) { spans } + let(:execute_span) { all_spans.find { |s| s.name == 'graphql.execute' } } - describe 'with unified tracer' do - before do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true + describe 'with unified tracer' do + before do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end end - end - context 'when no capture configuration is set' do - it 'does not capture any variables' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '1' }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to be_nil + context 'when no capture configuration is set' do + it 'does not capture any variables' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '1' }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to be_nil + end end - end - context 'when capture variables is configured for GetUser:id' do - around do |example| - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: 'GetUser:id' - ) do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true + context 'when capture variables is configured for GetUser:id' do + around do |example| + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: 'GetUser:id' + ) do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + example.run end - example.run end - end - it 'captures the id variable' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '42' }, - operation_name: 'GetUser' - ) + it 'captures the id variable' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '42' }, + operation_name: 'GetUser' + ) - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('42') - end + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('42') + end + + it 'captures numeric variables correctly' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: 123 }, + operation_name: 'GetUser' + ) - it 'captures boolean variables correctly' do - # Since we can only test with the schema we have, let's use the id field - # and test different variable types when they're passed as id - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: 123 }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq(123) + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq(123) + end end - end - context 'when capture variables except is configured' do - around do |example| - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: 'GetUser:secret' - ) do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true + context 'when capture variables except is configured' do + around do |example| + ClimateControl.modify( + DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: 'GetUser:secret' + ) do + Datadog.configuration.tracing[:graphql].reset! + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + example.run end - example.run end - end - it 'captures all variables except those in the except list' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '1' }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - # Since id is not in the except list, it should be captured - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('1') + it 'captures all variables except those in the except list' do + result = TestGraphQLSchema.execute( + 'query GetUser($id: ID!) { user(id: $id) { name } }', + variables: { id: '1' }, + operation_name: 'GetUser' + ) + + expect(result.to_h['errors']).to be_nil + expect(execute_span).not_to be_nil + + expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('1') + end end end end -end From 02bdf1e0e0a8dfbddb35d74597aa658f2ebf791a Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Wed, 17 Sep 2025 13:46:56 -0700 Subject: [PATCH 3/4] =?UTF-8?q?Sacrifice=20for=20the=20linting=20gods=20?= =?UTF-8?q?=E2=98=A0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/supported_configurations.rb | 640 +++++++++--------- .../tracing/contrib/graphql/unified_trace.rb | 44 +- 2 files changed, 342 insertions(+), 342 deletions(-) diff --git a/lib/datadog/core/configuration/supported_configurations.rb b/lib/datadog/core/configuration/supported_configurations.rb index 11ca83f6c5d..6fb3a1478db 100644 --- a/lib/datadog/core/configuration/supported_configurations.rb +++ b/lib/datadog/core/configuration/supported_configurations.rb @@ -7,332 +7,332 @@ module Datadog module Core module Configuration SUPPORTED_CONFIGURATIONS = - { 'DD_AGENT_HOST' => { version: ['A'] }, - 'DD_API_KEY' => { version: ['A'] }, - 'DD_API_SECURITY_ENABLED' => { version: ['A'] }, - 'DD_API_SECURITY_REQUEST_SAMPLE_RATE' => { version: ['A'] }, - 'DD_API_SECURITY_SAMPLE_DELAY' => { version: ['A'] }, - 'DD_APM_TRACING_ENABLED' => { version: ['A'] }, - 'DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING' => { version: ['A'] }, - 'DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE' => { version: ['A'] }, - 'DD_APPSEC_ENABLED' => { version: ['A'] }, - 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML' => { version: ['A'] }, - 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON' => { version: ['A'] }, - 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_TEXT' => { version: ['A'] }, - 'DD_APPSEC_MAX_STACK_TRACES' => { version: ['A'] }, - 'DD_APPSEC_MAX_STACK_TRACE_DEPTH' => { version: ['A'] }, - 'DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT' => { version: ['A'] }, - 'DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP' => { version: ['A'] }, - 'DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP' => { version: ['A'] }, - 'DD_APPSEC_RASP_ENABLED' => { version: ['A'] }, - 'DD_APPSEC_RULES' => { version: ['A'] }, - 'DD_APPSEC_SCA_ENABLED' => { version: ['A'] }, - 'DD_APPSEC_STACK_TRACE_ENABLED' => { version: ['A'] }, - 'DD_APPSEC_TRACE_RATE_LIMIT' => { version: ['A'] }, - 'DD_APPSEC_WAF_DEBUG' => { version: ['A'] }, - 'DD_APPSEC_WAF_TIMEOUT' => { version: ['A'] }, - 'DD_CRASHTRACKING_ENABLED' => { version: ['A'] }, - 'DD_DBM_PROPAGATION_MODE' => { version: ['A'] }, - 'DD_DISABLE_DATADOG_RAILS' => { version: ['A'] }, - 'DD_DYNAMIC_INSTRUMENTATION_ENABLED' => { version: ['A'] }, - 'DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE' => { version: ['A'] }, - 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS' => { version: ['A'] }, - 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES' => { version: ['A'] }, - 'DD_ENV' => { version: ['A'] }, - 'DD_ERROR_TRACKING_HANDLED_ERRORS' => { version: ['A'] }, - 'DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE' => { version: ['A'] }, - 'DD_GIT_COMMIT_SHA' => { version: ['A'] }, - 'DD_GIT_REPOSITORY_URL' => { version: ['A'] }, - 'DD_HEALTH_METRICS_ENABLED' => { version: ['A'] }, - 'DD_INJECTION_ENABLED' => { version: ['A'] }, - 'DD_INJECT_FORCE' => { version: ['A'] }, - 'DD_INSTRUMENTATION_INSTALL_ID' => { version: ['A'] }, - 'DD_INSTRUMENTATION_INSTALL_TIME' => { version: ['A'] }, - 'DD_INSTRUMENTATION_INSTALL_TYPE' => { version: ['A'] }, - 'DD_INSTRUMENTATION_TELEMETRY_ENABLED' => { version: ['A'] }, - 'DD_INTEGRATION_SERVICE' => { version: ['A'] }, - 'DD_LOGS_INJECTION' => { version: ['A'] }, - 'DD_METRIC_AGENT_PORT' => { version: ['A'] }, - 'DD_PROFILING_ALLOCATION_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_DIR_INTERRUPTION_WORKAROUND_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE' => { version: ['A'] }, - 'DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_GC_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_GVL_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_HEAP_CLEAN_AFTER_GC_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_MAX_FRAMES' => { version: ['A'] }, - 'DD_PROFILING_NATIVE_FILENAMES_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE' => { version: ['A'] }, - 'DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_SKIP_MYSQL2_CHECK' => { version: ['A'] }, - 'DD_PROFILING_TIMELINE_ENABLED' => { version: ['A'] }, - 'DD_PROFILING_UPLOAD_PERIOD' => { version: ['A'] }, - 'DD_PROFILING_UPLOAD_TIMEOUT' => { version: ['A'] }, - 'DD_REDIS_COMMAND_ARGS' => { version: ['A'] }, - 'DD_REMOTE_CONFIGURATION_ENABLED' => { version: ['A'] }, - 'DD_REMOTE_CONFIG_BOOT_TIMEOUT_SECONDS' => { version: ['A'] }, - 'DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS' => { version: ['A'] }, - 'DD_RODA_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_RODA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_RUNTIME_METRICS_ENABLED' => { version: ['A'] }, - 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => { version: ['A'] }, - 'DD_SERVICE' => { version: ['B'] }, - 'DD_SITE' => { version: ['A'] }, - 'DD_SPAN_SAMPLING_RULES' => { version: ['A'] }, - 'DD_SPAN_SAMPLING_RULES_FILE' => { version: ['A'] }, - 'DD_TAGS' => { version: ['B'] }, - 'DD_TELEMETRY_AGENTLESS_URL' => { version: ['A'] }, - 'DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED' => { version: ['A'] }, - 'DD_TELEMETRY_HEARTBEAT_INTERVAL' => { version: ['A'] }, - 'DD_TELEMETRY_LOG_COLLECTION_ENABLED' => { version: ['A'] }, - 'DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL' => { version: ['A'] }, - 'DD_TELEMETRY_METRICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED' => { version: ['A'] }, - 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_CABLE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_CABLE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTION_CABLE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_MAILER_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_MAILER_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTION_MAILER_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_PACK_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_PACK_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTION_PACK_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_VIEW_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTION_VIEW_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTION_VIEW_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_JOB_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_JOB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_JOB_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_RECORD_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ACTIVE_SUPPORT_ENABLED' => { version: ['A'] }, - 'DD_TRACE_AGENT_PORT' => { version: ['A'] }, - 'DD_TRACE_AGENT_TIMEOUT_SECONDS' => { version: ['A'] }, - 'DD_TRACE_AGENT_URL' => { version: ['A'] }, - 'DD_TRACE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_AWS_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_AWS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_AWS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_AWS_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_AWS_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_BAGGAGE_TAG_KEYS' => { version: ['A'] }, - 'DD_TRACE_CLIENT_IP_ENABLED' => { version: ['A'] }, - 'DD_TRACE_CLIENT_IP_HEADER' => { version: ['A'] }, - 'DD_TRACE_CONCURRENT_RUBY_ENABLED' => { version: ['A'] }, - 'DD_TRACE_DALLI_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_DALLI_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_DALLI_ENABLED' => { version: ['A'] }, - 'DD_TRACE_DALLI_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_DALLI_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_DEBUG' => { version: ['B'] }, - 'DD_TRACE_DELAYED_JOB_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_DELAYED_JOB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_DELAYED_JOB_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ELASTICSEARCH_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ELASTICSEARCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ELASTICSEARCH_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ELASTICSEARCH_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_ELASTICSEARCH_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_ENABLED' => { version: ['B'] }, - 'DD_TRACE_ETHON_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ETHON_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_ETHON_ENABLED' => { version: ['A'] }, - 'DD_TRACE_ETHON_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_ETHON_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_EXCON_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_EXCON_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_EXCON_ENABLED' => { version: ['A'] }, - 'DD_TRACE_EXCON_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_EXCON_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_EXCON_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_FARADAY_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_FARADAY_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_FARADAY_ENABLED' => { version: ['A'] }, - 'DD_TRACE_FARADAY_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_FARADAY_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_FARADAY_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_GRAPE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRAPE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_GRAPE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRAPE_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_ERROR_TRACKING' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => { version: ['A'] }, - 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER' => { version: ['A'] }, - 'DD_TRACE_GRPC_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRPC_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_GRPC_ENABLED' => { version: ['A'] }, - 'DD_TRACE_GRPC_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_GRPC_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_HANAMI_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HEADER_TAGS' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_HTTPCLIENT_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_HTTPRB_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_HTTP_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_HTTP_ENABLED' => { version: ['A'] }, - 'DD_TRACE_HTTP_ERROR_STATUS_CODES' => { version: ['A'] }, - 'DD_TRACE_KAFKA_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_KAFKA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_KAFKA_ENABLED' => { version: ['A'] }, - 'DD_TRACE_KARAFKA_ENABLED' => { version: ['A'] }, - 'DD_TRACE_LOGRAGE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MEMCACHED_COMMAND_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MONGO_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MONGO_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_MONGO_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MONGO_JSON_COMMAND' => { version: ['A'] }, - 'DD_TRACE_MONGO_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_MONGO_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_MYSQL2_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MYSQL2_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_MYSQL2_ENABLED' => { version: ['A'] }, - 'DD_TRACE_MYSQL2_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_MYSQL2_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_NATIVE_SPAN_EVENTS' => { version: ['A'] }, - 'DD_TRACE_NET_HTTP_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_NET_HTTP_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_ENABLED' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_RESOURCE_PATTERN' => { version: ['A'] }, - 'DD_TRACE_OPENSEARCH_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_PEER_SERVICE_MAPPING' => { version: ['A'] }, - 'DD_TRACE_PG_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_PG_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_PG_ENABLED' => { version: ['A'] }, - 'DD_TRACE_PG_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_PG_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_PRESTO_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_PRESTO_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_PRESTO_ENABLED' => { version: ['A'] }, - 'DD_TRACE_PRESTO_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_PRESTO_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_PROPAGATION_EXTRACT_FIRST' => { version: ['A'] }, - 'DD_TRACE_PROPAGATION_STYLE' => { version: ['B'] }, - 'DD_TRACE_PROPAGATION_STYLE_EXTRACT' => { version: ['A'] }, - 'DD_TRACE_PROPAGATION_STYLE_INJECT' => { version: ['A'] }, - 'DD_TRACE_QUE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_QUE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_QUE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_QUE_TAG_ARGS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_QUE_TAG_DATA_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RACECAR_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RACECAR_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_RACECAR_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RACK_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RACK_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_RACK_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RAILS_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RAILS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_RAILS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RAKE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RAKE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_RAKE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RATE_LIMIT' => { version: ['A'] }, - 'DD_TRACE_REDIS_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REDIS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_REDIS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REDIS_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_REDIS_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REPORT_HOSTNAME' => { version: ['A'] }, - 'DD_TRACE_RESQUE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_RESQUE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_RESQUE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REST_CLIENT_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REST_CLIENT_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_REST_CLIENT_ENABLED' => { version: ['A'] }, - 'DD_TRACE_REST_CLIENT_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_REST_CLIENT_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_RODA_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SAMPLE_RATE' => { version: ['B'] }, - 'DD_TRACE_SAMPLING_RULES' => { version: ['A'] }, - 'DD_TRACE_SEMANTIC_LOGGER_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SEQUEL_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SEQUEL_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SEQUEL_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SHORYUKEN_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SHORYUKEN_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SHORYUKEN_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SIDEKIQ_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SIDEKIQ_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SIDEKIQ_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SINATRA_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SINATRA_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SINATRA_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SNEAKERS_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SNEAKERS_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SNEAKERS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_STARTUP_LOGS' => { version: ['A'] }, - 'DD_TRACE_STRIPE_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_STRIPE_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_STRIPE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_SUCKER_PUNCH_ENABLED' => { version: ['A'] }, - 'DD_TRACE_TEST_MODE_ENABLED' => { version: ['A'] }, - 'DD_TRACE_TRILOGY_ANALYTICS_ENABLED' => { version: ['A'] }, - 'DD_TRACE_TRILOGY_ANALYTICS_SAMPLE_RATE' => { version: ['A'] }, - 'DD_TRACE_TRILOGY_ENABLED' => { version: ['A'] }, - 'DD_TRACE_TRILOGY_PEER_SERVICE' => { version: ['A'] }, - 'DD_TRACE_TRILOGY_SERVICE_NAME' => { version: ['A'] }, - 'DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH' => { version: ['A'] }, - 'DD_VERSION' => { version: ['A'] }, - 'OTEL_TRACES_SAMPLER_ARG' => { version: ['A'] } }.freeze + {'DD_AGENT_HOST' => {version: ['A']}, + 'DD_API_KEY' => {version: ['A']}, + 'DD_API_SECURITY_ENABLED' => {version: ['A']}, + 'DD_API_SECURITY_REQUEST_SAMPLE_RATE' => {version: ['A']}, + 'DD_API_SECURITY_SAMPLE_DELAY' => {version: ['A']}, + 'DD_APM_TRACING_ENABLED' => {version: ['A']}, + 'DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING' => {version: ['A']}, + 'DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE' => {version: ['A']}, + 'DD_APPSEC_ENABLED' => {version: ['A']}, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML' => {version: ['A']}, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON' => {version: ['A']}, + 'DD_APPSEC_HTTP_BLOCKED_TEMPLATE_TEXT' => {version: ['A']}, + 'DD_APPSEC_MAX_STACK_TRACES' => {version: ['A']}, + 'DD_APPSEC_MAX_STACK_TRACE_DEPTH' => {version: ['A']}, + 'DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT' => {version: ['A']}, + 'DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP' => {version: ['A']}, + 'DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP' => {version: ['A']}, + 'DD_APPSEC_RASP_ENABLED' => {version: ['A']}, + 'DD_APPSEC_RULES' => {version: ['A']}, + 'DD_APPSEC_SCA_ENABLED' => {version: ['A']}, + 'DD_APPSEC_STACK_TRACE_ENABLED' => {version: ['A']}, + 'DD_APPSEC_TRACE_RATE_LIMIT' => {version: ['A']}, + 'DD_APPSEC_WAF_DEBUG' => {version: ['A']}, + 'DD_APPSEC_WAF_TIMEOUT' => {version: ['A']}, + 'DD_CRASHTRACKING_ENABLED' => {version: ['A']}, + 'DD_DBM_PROPAGATION_MODE' => {version: ['A']}, + 'DD_DISABLE_DATADOG_RAILS' => {version: ['A']}, + 'DD_DYNAMIC_INSTRUMENTATION_ENABLED' => {version: ['A']}, + 'DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE' => {version: ['A']}, + 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS' => {version: ['A']}, + 'DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES' => {version: ['A']}, + 'DD_ENV' => {version: ['A']}, + 'DD_ERROR_TRACKING_HANDLED_ERRORS' => {version: ['A']}, + 'DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE' => {version: ['A']}, + 'DD_GIT_COMMIT_SHA' => {version: ['A']}, + 'DD_GIT_REPOSITORY_URL' => {version: ['A']}, + 'DD_HEALTH_METRICS_ENABLED' => {version: ['A']}, + 'DD_INJECTION_ENABLED' => {version: ['A']}, + 'DD_INJECT_FORCE' => {version: ['A']}, + 'DD_INSTRUMENTATION_INSTALL_ID' => {version: ['A']}, + 'DD_INSTRUMENTATION_INSTALL_TIME' => {version: ['A']}, + 'DD_INSTRUMENTATION_INSTALL_TYPE' => {version: ['A']}, + 'DD_INSTRUMENTATION_TELEMETRY_ENABLED' => {version: ['A']}, + 'DD_INTEGRATION_SERVICE' => {version: ['A']}, + 'DD_LOGS_INJECTION' => {version: ['A']}, + 'DD_METRIC_AGENT_PORT' => {version: ['A']}, + 'DD_PROFILING_ALLOCATION_ENABLED' => {version: ['A']}, + 'DD_PROFILING_DIR_INTERRUPTION_WORKAROUND_ENABLED' => {version: ['A']}, + 'DD_PROFILING_ENABLED' => {version: ['A']}, + 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED' => {version: ['A']}, + 'DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED' => {version: ['A']}, + 'DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE' => {version: ['A']}, + 'DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED' => {version: ['A']}, + 'DD_PROFILING_GC_ENABLED' => {version: ['A']}, + 'DD_PROFILING_GVL_ENABLED' => {version: ['A']}, + 'DD_PROFILING_HEAP_CLEAN_AFTER_GC_ENABLED' => {version: ['A']}, + 'DD_PROFILING_MAX_FRAMES' => {version: ['A']}, + 'DD_PROFILING_NATIVE_FILENAMES_ENABLED' => {version: ['A']}, + 'DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED' => {version: ['A']}, + 'DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE' => {version: ['A']}, + 'DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED' => {version: ['A']}, + 'DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED' => {version: ['A']}, + 'DD_PROFILING_SKIP_MYSQL2_CHECK' => {version: ['A']}, + 'DD_PROFILING_TIMELINE_ENABLED' => {version: ['A']}, + 'DD_PROFILING_UPLOAD_PERIOD' => {version: ['A']}, + 'DD_PROFILING_UPLOAD_TIMEOUT' => {version: ['A']}, + 'DD_REDIS_COMMAND_ARGS' => {version: ['A']}, + 'DD_REMOTE_CONFIGURATION_ENABLED' => {version: ['A']}, + 'DD_REMOTE_CONFIG_BOOT_TIMEOUT_SECONDS' => {version: ['A']}, + 'DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS' => {version: ['A']}, + 'DD_RODA_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_RODA_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_RUNTIME_METRICS_ENABLED' => {version: ['A']}, + 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => {version: ['A']}, + 'DD_SERVICE' => {version: ['B']}, + 'DD_SITE' => {version: ['A']}, + 'DD_SPAN_SAMPLING_RULES' => {version: ['A']}, + 'DD_SPAN_SAMPLING_RULES_FILE' => {version: ['A']}, + 'DD_TAGS' => {version: ['B']}, + 'DD_TELEMETRY_AGENTLESS_URL' => {version: ['A']}, + 'DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED' => {version: ['A']}, + 'DD_TELEMETRY_HEARTBEAT_INTERVAL' => {version: ['A']}, + 'DD_TELEMETRY_LOG_COLLECTION_ENABLED' => {version: ['A']}, + 'DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL' => {version: ['A']}, + 'DD_TELEMETRY_METRICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED' => {version: ['A']}, + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_CABLE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_CABLE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTION_CABLE_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_MAILER_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_MAILER_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTION_MAILER_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_PACK_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_PACK_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTION_PACK_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_VIEW_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTION_VIEW_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTION_VIEW_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_JOB_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_JOB_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTIVE_JOB_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTIVE_MODEL_SERIALIZERS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_RECORD_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTIVE_RECORD_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ACTIVE_SUPPORT_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ACTIVE_SUPPORT_ENABLED' => {version: ['A']}, + 'DD_TRACE_AGENT_PORT' => {version: ['A']}, + 'DD_TRACE_AGENT_TIMEOUT_SECONDS' => {version: ['A']}, + 'DD_TRACE_AGENT_URL' => {version: ['A']}, + 'DD_TRACE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_AWS_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_AWS_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_AWS_ENABLED' => {version: ['A']}, + 'DD_TRACE_AWS_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_AWS_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_BAGGAGE_TAG_KEYS' => {version: ['A']}, + 'DD_TRACE_CLIENT_IP_ENABLED' => {version: ['A']}, + 'DD_TRACE_CLIENT_IP_HEADER' => {version: ['A']}, + 'DD_TRACE_CONCURRENT_RUBY_ENABLED' => {version: ['A']}, + 'DD_TRACE_DALLI_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_DALLI_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_DALLI_ENABLED' => {version: ['A']}, + 'DD_TRACE_DALLI_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_DALLI_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_DEBUG' => {version: ['B']}, + 'DD_TRACE_DELAYED_JOB_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_DELAYED_JOB_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_DELAYED_JOB_ENABLED' => {version: ['A']}, + 'DD_TRACE_ELASTICSEARCH_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ELASTICSEARCH_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ELASTICSEARCH_ENABLED' => {version: ['A']}, + 'DD_TRACE_ELASTICSEARCH_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_ELASTICSEARCH_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_ENABLED' => {version: ['B']}, + 'DD_TRACE_ETHON_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_ETHON_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_ETHON_ENABLED' => {version: ['A']}, + 'DD_TRACE_ETHON_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_ETHON_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_EXCON_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_EXCON_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_EXCON_ENABLED' => {version: ['A']}, + 'DD_TRACE_EXCON_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_EXCON_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_EXCON_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_FARADAY_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_FARADAY_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_FARADAY_ENABLED' => {version: ['A']}, + 'DD_TRACE_FARADAY_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_FARADAY_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_FARADAY_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_GRAPE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRAPE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_GRAPE_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRAPE_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_ERROR_TRACKING' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' => {version: ['A']}, + 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER' => {version: ['A']}, + 'DD_TRACE_GRPC_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRPC_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_GRPC_ENABLED' => {version: ['A']}, + 'DD_TRACE_GRPC_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_GRPC_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_HANAMI_ENABLED' => {version: ['A']}, + 'DD_TRACE_HEADER_TAGS' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_HTTPCLIENT_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_HTTPRB_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTPRB_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_HTTPRB_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTPRB_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_HTTPRB_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_HTTPRB_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_HTTP_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_HTTP_ENABLED' => {version: ['A']}, + 'DD_TRACE_HTTP_ERROR_STATUS_CODES' => {version: ['A']}, + 'DD_TRACE_KAFKA_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_KAFKA_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_KAFKA_ENABLED' => {version: ['A']}, + 'DD_TRACE_KARAFKA_ENABLED' => {version: ['A']}, + 'DD_TRACE_LOGRAGE_ENABLED' => {version: ['A']}, + 'DD_TRACE_MEMCACHED_COMMAND_ENABLED' => {version: ['A']}, + 'DD_TRACE_MONGO_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_MONGO_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_MONGO_ENABLED' => {version: ['A']}, + 'DD_TRACE_MONGO_JSON_COMMAND' => {version: ['A']}, + 'DD_TRACE_MONGO_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_MONGO_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_MYSQL2_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_MYSQL2_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_MYSQL2_ENABLED' => {version: ['A']}, + 'DD_TRACE_MYSQL2_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_MYSQL2_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_NATIVE_SPAN_EVENTS' => {version: ['A']}, + 'DD_TRACE_NET_HTTP_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_NET_HTTP_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_ENABLED' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_RESOURCE_PATTERN' => {version: ['A']}, + 'DD_TRACE_OPENSEARCH_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED' => {version: ['A']}, + 'DD_TRACE_PEER_SERVICE_MAPPING' => {version: ['A']}, + 'DD_TRACE_PG_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_PG_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_PG_ENABLED' => {version: ['A']}, + 'DD_TRACE_PG_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_PG_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_PRESTO_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_PRESTO_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_PRESTO_ENABLED' => {version: ['A']}, + 'DD_TRACE_PRESTO_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_PRESTO_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_PROPAGATION_EXTRACT_FIRST' => {version: ['A']}, + 'DD_TRACE_PROPAGATION_STYLE' => {version: ['B']}, + 'DD_TRACE_PROPAGATION_STYLE_EXTRACT' => {version: ['A']}, + 'DD_TRACE_PROPAGATION_STYLE_INJECT' => {version: ['A']}, + 'DD_TRACE_QUE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_QUE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_QUE_ENABLED' => {version: ['A']}, + 'DD_TRACE_QUE_TAG_ARGS_ENABLED' => {version: ['A']}, + 'DD_TRACE_QUE_TAG_DATA_ENABLED' => {version: ['A']}, + 'DD_TRACE_RACECAR_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RACECAR_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_RACECAR_ENABLED' => {version: ['A']}, + 'DD_TRACE_RACK_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RACK_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_RACK_ENABLED' => {version: ['A']}, + 'DD_TRACE_RAILS_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RAILS_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_RAILS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RAKE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RAKE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_RAKE_ENABLED' => {version: ['A']}, + 'DD_TRACE_RATE_LIMIT' => {version: ['A']}, + 'DD_TRACE_REDIS_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_REDIS_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_REDIS_ENABLED' => {version: ['A']}, + 'DD_TRACE_REDIS_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_REDIS_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED' => {version: ['A']}, + 'DD_TRACE_REPORT_HOSTNAME' => {version: ['A']}, + 'DD_TRACE_RESQUE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_RESQUE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_RESQUE_ENABLED' => {version: ['A']}, + 'DD_TRACE_REST_CLIENT_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_REST_CLIENT_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_REST_CLIENT_ENABLED' => {version: ['A']}, + 'DD_TRACE_REST_CLIENT_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_REST_CLIENT_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_RODA_ENABLED' => {version: ['A']}, + 'DD_TRACE_SAMPLE_RATE' => {version: ['B']}, + 'DD_TRACE_SAMPLING_RULES' => {version: ['A']}, + 'DD_TRACE_SEMANTIC_LOGGER_ENABLED' => {version: ['A']}, + 'DD_TRACE_SEQUEL_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SEQUEL_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SEQUEL_ENABLED' => {version: ['A']}, + 'DD_TRACE_SHORYUKEN_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SHORYUKEN_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SHORYUKEN_ENABLED' => {version: ['A']}, + 'DD_TRACE_SIDEKIQ_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SIDEKIQ_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SIDEKIQ_ENABLED' => {version: ['A']}, + 'DD_TRACE_SINATRA_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SINATRA_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SINATRA_ENABLED' => {version: ['A']}, + 'DD_TRACE_SNEAKERS_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SNEAKERS_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SNEAKERS_ENABLED' => {version: ['A']}, + 'DD_TRACE_STARTUP_LOGS' => {version: ['A']}, + 'DD_TRACE_STRIPE_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_STRIPE_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_STRIPE_ENABLED' => {version: ['A']}, + 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_SUCKER_PUNCH_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_SUCKER_PUNCH_ENABLED' => {version: ['A']}, + 'DD_TRACE_TEST_MODE_ENABLED' => {version: ['A']}, + 'DD_TRACE_TRILOGY_ANALYTICS_ENABLED' => {version: ['A']}, + 'DD_TRACE_TRILOGY_ANALYTICS_SAMPLE_RATE' => {version: ['A']}, + 'DD_TRACE_TRILOGY_ENABLED' => {version: ['A']}, + 'DD_TRACE_TRILOGY_PEER_SERVICE' => {version: ['A']}, + 'DD_TRACE_TRILOGY_SERVICE_NAME' => {version: ['A']}, + 'DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH' => {version: ['A']}, + 'DD_VERSION' => {version: ['A']}, + 'OTEL_TRACES_SAMPLER_ARG' => {version: ['A']}}.freeze ALIASES = - { 'DD_DISABLE_DATADOG_RAILS' => ['DISABLE_DATADOG_RAILS'], - 'DD_PROFILING_GVL_ENABLED' => ['DD_PROFILING_PREVIEW_GVL_ENABLED'], - 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => ['DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED'], - 'DD_SERVICE' => ['OTEL_SERVICE_NAME'], - 'DD_TAGS' => ['OTEL_RESOURCE_ATTRIBUTES'], - 'DD_TRACE_DEBUG' => ['OTEL_LOG_LEVEL'], - 'DD_TRACE_ENABLED' => ['OTEL_TRACES_EXPORTER'], - 'DD_TRACE_PROPAGATION_STYLE' => ['OTEL_PROPAGATORS'], - 'DD_TRACE_SAMPLE_RATE' => ['OTEL_TRACES_SAMPLER'] }.freeze + {'DD_DISABLE_DATADOG_RAILS' => ['DISABLE_DATADOG_RAILS'], + 'DD_PROFILING_GVL_ENABLED' => ['DD_PROFILING_PREVIEW_GVL_ENABLED'], + 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED' => ['DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED'], + 'DD_SERVICE' => ['OTEL_SERVICE_NAME'], + 'DD_TAGS' => ['OTEL_RESOURCE_ATTRIBUTES'], + 'DD_TRACE_DEBUG' => ['OTEL_LOG_LEVEL'], + 'DD_TRACE_ENABLED' => ['OTEL_TRACES_EXPORTER'], + 'DD_TRACE_PROPAGATION_STYLE' => ['OTEL_PROPAGATORS'], + 'DD_TRACE_SAMPLE_RATE' => ['OTEL_TRACES_SAMPLER']}.freeze DEPRECATIONS = - { 'DD_PROFILING_PREVIEW_GVL_ENABLED' => "(This doesn't need a deprecation message since there's an alias for it)" }.freeze + {'DD_PROFILING_PREVIEW_GVL_ENABLED' => "(This doesn't need a deprecation message since there's an alias for it)"}.freeze ALIAS_TO_CANONICAL = - { 'DD_PROFILING_PREVIEW_GVL_ENABLED' => 'DD_PROFILING_GVL_ENABLED', - 'DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED' => 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED', - 'DISABLE_DATADOG_RAILS' => 'DD_DISABLE_DATADOG_RAILS', - 'OTEL_LOG_LEVEL' => 'DD_TRACE_DEBUG', - 'OTEL_PROPAGATORS' => 'DD_TRACE_PROPAGATION_STYLE', - 'OTEL_RESOURCE_ATTRIBUTES' => 'DD_TAGS', - 'OTEL_SERVICE_NAME' => 'DD_SERVICE', - 'OTEL_TRACES_EXPORTER' => 'DD_TRACE_ENABLED', - 'OTEL_TRACES_SAMPLER' => 'DD_TRACE_SAMPLE_RATE' }.freeze + {'DD_PROFILING_PREVIEW_GVL_ENABLED' => 'DD_PROFILING_GVL_ENABLED', + 'DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED' => 'DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED', + 'DISABLE_DATADOG_RAILS' => 'DD_DISABLE_DATADOG_RAILS', + 'OTEL_LOG_LEVEL' => 'DD_TRACE_DEBUG', + 'OTEL_PROPAGATORS' => 'DD_TRACE_PROPAGATION_STYLE', + 'OTEL_RESOURCE_ATTRIBUTES' => 'DD_TAGS', + 'OTEL_SERVICE_NAME' => 'DD_SERVICE', + 'OTEL_TRACES_EXPORTER' => 'DD_TRACE_ENABLED', + 'OTEL_TRACES_SAMPLER' => 'DD_TRACE_SAMPLE_RATE'}.freeze end end end diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index 144e40309be..6f7a1c74c9d 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -76,7 +76,7 @@ def analyze_query(*args, query:, **kwargs) def execute_multiplex(*args, multiplex:, **kwargs) trace(proc { super }, 'execute_multiplex', multiplex_resource(multiplex), multiplex: multiplex) do |span| - span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(', ')}]") + span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(", ")}]") end end @@ -108,10 +108,10 @@ def execute_query(*args, query:, **kwargs) def execute_query_lazy(*args, query:, multiplex:, **kwargs) resource = if query - query.selected_operation_name || fallback_transaction_name(query.context) - else - multiplex_resource(multiplex) - end + query.selected_operation_name || fallback_transaction_name(query.context) + else + multiplex_resource(multiplex) + end trace(proc { super }, 'execute_lazy', resource, query: query, multiplex: multiplex) end @@ -292,22 +292,22 @@ def operation_resource(operation) def add_query_error_events(span, errors) errors.each do |error| attributes = if !@error_extensions_config.empty? && (extensions = error.extensions) - # Capture extensions, ensuring all values are primitives - extensions.each_with_object({}) do |(key, value), hash| - next unless @error_extensions_config.include?(key.to_s) - - value = case value - when TrueClass, FalseClass, Integer, Float - value - else - value.to_s - end - - hash[@extensions_key + key.to_s] = value - end - else - {} - end + # Capture extensions, ensuring all values are primitives + extensions.each_with_object({}) do |(key, value), hash| + next unless @error_extensions_config.include?(key.to_s) + + value = case value + when TrueClass, FalseClass, Integer, Float + value + else + value.to_s + end + + hash[@extensions_key + key.to_s] = value + end + else + {} + end # {::GraphQL::Error#to_h} returns the error formatted in compliance with the GraphQL spec. # This is an unwritten contract in the `graphql` library. @@ -340,7 +340,7 @@ def add_query_error_events(span, errors) # ["3:10", "7:8"] def serialize_error_locations(locations) locations.map do |location| - "#{location['line']}:#{location['column']}" + "#{location["line"]}:#{location["column"]}" end end end From c9be9604c2a1b0a7094ae1dd4196e5f3195c75f5 Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Fri, 19 Sep 2025 18:31:22 -0700 Subject: [PATCH 4/4] working --- gemfiles/ruby_2.7_graphql_2.3.gemfile.lock | 15 ++- gemfiles/ruby_3.1_graphql_2.0.gemfile.lock | 8 ++ gemfiles/ruby_3.3_graphql_2.3.gemfile.lock | 4 +- gemfiles/ruby_3.4_graphql_2.0.gemfile.lock | 17 ++- .../configuration/capture_variables.rb | 50 +++++--- .../tracing/contrib/graphql/unified_trace.rb | 56 ++++---- .../configuration/capture_variables_spec.rb | 120 +++++++++--------- .../graphql/configuration/settings_spec.rb | 48 +++++++ .../contrib/graphql/test_schema_examples.rb | 47 +++---- .../graphql/variable_capture_basic_spec.rb | 116 ----------------- 10 files changed, 227 insertions(+), 254 deletions(-) delete mode 100644 spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb diff --git a/gemfiles/ruby_2.7_graphql_2.3.gemfile.lock b/gemfiles/ruby_2.7_graphql_2.3.gemfile.lock index 2c83a2becfa..94ca0330c9d 100644 --- a/gemfiles/ruby_2.7_graphql_2.3.gemfile.lock +++ b/gemfiles/ruby_2.7_graphql_2.3.gemfile.lock @@ -94,10 +94,13 @@ GEM erubi (1.13.0) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) globalid (1.2.1) activesupport (>= 6.1) - google-protobuf (3.25.4) + google-protobuf (3.25.4-aarch64-linux) + google-protobuf (3.25.4-arm64-darwin) + google-protobuf (3.25.4-x86_64-linux) graphql (2.3.6) base64 hashdiff (1.1.0) @@ -105,10 +108,13 @@ GEM concurrent-ruby (~> 1.0) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.2.1-arm64-darwin) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -129,6 +135,7 @@ GEM memory_profiler (0.9.14) method_source (1.1.0) mini_mime (1.1.5) + mini_portile2 (2.8.9) minitest (5.24.1) msgpack (1.8.0) net-imap (0.4.14) @@ -141,9 +148,8 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.15.6-aarch64-linux) - racc (~> 1.4) - nokogiri (1.15.6-x86_64-linux) + nokogiri (1.15.6) + mini_portile2 (~> 2.8.2) racc (~> 1.4) os (1.1.4) pry (0.14.2) @@ -239,6 +245,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/ruby_3.1_graphql_2.0.gemfile.lock b/gemfiles/ruby_3.1_graphql_2.0.gemfile.lock index 17cec805e4a..ddb8490b975 100644 --- a/gemfiles/ruby_3.1_graphql_2.0.gemfile.lock +++ b/gemfiles/ruby_3.1_graphql_2.0.gemfile.lock @@ -95,9 +95,11 @@ GEM erubi (1.13.0) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) globalid (1.2.1) activesupport (>= 6.1) + google-protobuf (3.25.2) google-protobuf (3.25.2-aarch64-linux) google-protobuf (3.25.2-x86_64-linux) graphql (2.0.28) @@ -111,10 +113,13 @@ GEM reline (>= 0.4.2) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.2.1-arm64-darwin) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -149,6 +154,8 @@ GEM nio4r (2.7.3) nokogiri (1.16.6-aarch64-linux) racc (~> 1.4) + nokogiri (1.16.6-arm64-darwin) + racc (~> 1.4) nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) os (1.1.4) @@ -257,6 +264,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock b/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock index c2b6c075e43..609a4cee174 100644 --- a/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock +++ b/gemfiles/ruby_3.3_graphql_2.3.gemfile.lock @@ -119,12 +119,12 @@ GEM libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) - libddwaf (1.24.1.2.1-aarch64-linux) - ffi (~> 1.0) libddwaf (1.24.1.1.0-arm64-darwin) ffi (~> 1.0) libddwaf (1.24.1.1.0-x86_64-linux) ffi (~> 1.0) + libddwaf (1.24.1.2.1-aarch64-linux) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) diff --git a/gemfiles/ruby_3.4_graphql_2.0.gemfile.lock b/gemfiles/ruby_3.4_graphql_2.0.gemfile.lock index f3d332b812a..580f9e7d7b2 100644 --- a/gemfiles/ruby_3.4_graphql_2.0.gemfile.lock +++ b/gemfiles/ruby_3.4_graphql_2.0.gemfile.lock @@ -97,10 +97,13 @@ GEM erubi (1.13.0) extlz4 (0.3.4) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) globalid (1.2.1) activesupport (>= 6.1) - google-protobuf (3.25.3) + google-protobuf (3.25.3-aarch64-linux) + google-protobuf (3.25.3-arm64-darwin) + google-protobuf (3.25.3-x86_64-linux) graphql (2.0.31) base64 hashdiff (1.1.0) @@ -113,10 +116,13 @@ GEM reline (>= 0.4.2) json-schema (2.8.1) addressable (>= 2.4) + libdatadog (18.1.0.1.0) libdatadog (18.1.0.1.0-aarch64-linux) libdatadog (18.1.0.1.0-x86_64-linux) libddwaf (1.24.1.2.1-aarch64-linux) ffi (~> 1.0) + libddwaf (1.24.1.2.1-arm64-darwin) + ffi (~> 1.0) libddwaf (1.24.1.2.1-x86_64-linux) ffi (~> 1.0) logger (1.7.0) @@ -137,7 +143,6 @@ GEM memory_profiler (0.9.14) method_source (1.1.0) mini_mime (1.1.5) - mini_portile2 (2.8.7) minitest (5.24.1) msgpack (1.8.0) mutex_m (0.2.0) @@ -150,8 +155,11 @@ GEM timeout net-smtp (0.5.0) nio4r (2.7.3) - nokogiri (1.16.6) - mini_portile2 (~> 2.8.2) + nokogiri (1.16.6-aarch64-linux) + racc (~> 1.4) + nokogiri (1.16.6-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) os (1.1.4) ostruct (0.6.1) @@ -260,6 +268,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb index 5a0882f4d81..3fc3863a03f 100644 --- a/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb +++ b/lib/datadog/tracing/contrib/graphql/configuration/capture_variables.rb @@ -7,40 +7,53 @@ module Tracing module Contrib module GraphQL module Configuration - # Processes GraphQL variable capture configuration for O(1) lookups. + # Processes GraphQL variable capture configuration for + # sanitization and fast matching. class CaptureVariables - # Valid operation and variable names: alphanumeric characters or underscores only + # Valid operation and variable names: alphanumeric characters or underscores VALID_NAME_PATTERN = /\A[A-Za-z0-9_]+\z/.freeze - # @param variables [String, Array] Configuration string or array of operation:variable pairs - def initialize(variables) - @variables = variables.is_a?(Array) ? variables.join(',') : variables + EMPTY_MATCHER = Set.new.freeze - @operation_variables = Hash.new { |h, k| h[k] = Set.new } + # @param variables [String] Matching pairs from environment variable + # @param variables [Array] Matching pairs from programmatic configuration + def initialize(variables) + @variables = variables.is_a?(Array) ? variables.map(&:dup) : variables.split(',') + @variables_str = variables.is_a?(Array) ? variables.join(',') : variables.to_s - parse_configuration(@variables) unless @variables.nil? || @variables.empty? + @operation_vars = parse_config(@variables) end - # @return [Boolean] true if the variable should be captured - def match?(operation_name, variable_name) - @operation_variables.key?(operation_name) && @operation_variables[operation_name].include?(variable_name) + # Get a variable matcher for a specific operation + # Returns frozen empty set if operation has no configured variables + # Returns a Set of variable names if operation has configured variables + # @param operation_name [String] GraphQL operation name + # @return [Set] set of variable names or empty set if no match + def matcher_for(operation_name) + @operation_vars[operation_name] || EMPTY_MATCHER end # @return [Boolean] true if no variables are configured for capture def empty? - @operation_variables.empty? + @operation_vars.empty? end - # Returns the original configuration for inspection + # Returns the original configuration for inspection. + # Matches the environment variable format. def to_s - @variables.to_s + @variables_str end private - # Parses comma-separated operation:variable pairs - def parse_configuration(values) - values.split(',').each do |fragment| + # Parses comma-separated operation:variable pairs. + # Ignores invalid entries. + # + # @param values [Array] List of operation:variable pairs + # @return [Hash{String => Set}] Parsed configuration + def parse_config(values) + hash = {} + values.each do |fragment| fragment.strip! next if fragment.empty? @@ -51,11 +64,14 @@ def parse_configuration(values) operation_name = parts[0] variable_name = parts[1] + # Whitespaces left after splitting are invalid next unless VALID_NAME_PATTERN.match?(operation_name) next unless VALID_NAME_PATTERN.match?(variable_name) - @operation_variables[operation_name] << variable_name + hash[operation_name] ||= Set.new + hash[operation_name] << variable_name end + hash end end end diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index 6f7a1c74c9d..dc593f701cd 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -23,6 +23,17 @@ def initialize(*args, **kwargs) @analytics_sample_rate = config[:analytics_sample_rate] @error_extensions_config = config[:error_extensions] + @capture_variables = config[:capture_variables] + @capture_variables_except = config[:capture_variables_except] + + @capture_mode = if !@capture_variables.empty? + :allowlist + elsif !@capture_variables_except.empty? + :denylist + else + nil # No variable capture configured + end + load_error_event_attributes(config[:error_tracking]) super @@ -98,7 +109,6 @@ def execute_query(*args, query:, **kwargs) ) end - # Capture operation variables based on configuration capture_operation_variables(span, query) }, ->(span) { add_query_error_events(span, query.context.errors) }, @@ -179,44 +189,31 @@ def platform_resolve_type_key(type, *args, **kwargs) private - # Captures GraphQL operation variables based on configuration settings def capture_operation_variables(span, query) - config = Datadog.configuration.tracing[:graphql] - capture_config = config[:capture_variables] - capture_except_config = config[:capture_variables_except] - - return if capture_config.empty? && capture_except_config.empty? + return unless @capture_mode operation_name = query.selected_operation_name return unless operation_name - query.variables.to_h.each do |variable_name, value| - variable_name_str = variable_name.to_s + capture_vars = @capture_variables.matcher_for(operation_name) + except_vars = @capture_variables_except.matcher_for(operation_name) - should_capture = should_capture_variable?( - operation_name, - variable_name_str, - capture_config, - capture_except_config - ) - - if should_capture - serialized_value = serialize_variable_value(value) - span.set_tag("graphql.operation.variable.#{variable_name_str}", serialized_value) + query.variables.to_h.each do |name, value| + if capture_variable?(name, capture_vars, except_vars) + # GraphQL variable `name` is always a valid span tag name, as per GraphQL spec + span.set_tag( + "graphql.operation.variable.#{name}", + serialize_variable_value(value) + ) end end end - def should_capture_variable?(operation_name, variable_name, capture_config, capture_except_config) - capture_match = capture_config.match?(operation_name, variable_name) - except_match = capture_except_config.match?(operation_name, variable_name) - - if !capture_config.empty? - capture_match && !except_match - elsif !capture_except_config.empty? - !except_match - else - false + def capture_variable?(name, capture_vars, except_vars) + if @capture_mode == :allowlist # Is in capture list and not in except list + capture_vars.member?(name) && !except_vars.member?(name) + elsif @capture_mode == :denylist # Capture if not in except list + !except_vars.member?(name) end end @@ -227,6 +224,7 @@ def serialize_variable_value(value) when Integer, Float, String value else + # Fallback for string representation for other types value.to_s end end diff --git a/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb index 35fe64acffc..ec5714d4567 100644 --- a/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/configuration/capture_variables_spec.rb @@ -22,10 +22,10 @@ let(:input) { 'GetAd:id,ListIp:geo,AddKey:val' } it 'correctly parses operation and variable configuration' do - expect(config.match?('GetAd', 'id')).to be true - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('AddKey', 'val')).to be true - expect(config.match?('GetAd', 'geo')).to be false + expect(config.matcher_for('GetAd')).to include('id') + expect(config.matcher_for('ListIp')).to include('geo') + expect(config.matcher_for('AddKey')).to include('val') + expect(config.matcher_for('GetAd')).not_to include('geo') end it { expect(config.empty?).to be false } @@ -35,10 +35,10 @@ let(:input) { ['GetAd:id', 'ListIp:geo', 'AddKey:val'] } it 'correctly parses operation and variable configuration' do - expect(config.match?('GetAd', 'id')).to be true - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('AddKey', 'val')).to be true - expect(config.match?('GetAd', 'geo')).to be false + expect(config.matcher_for('GetAd')).to include('id') + expect(config.matcher_for('ListIp')).to include('geo') + expect(config.matcher_for('AddKey')).to include('val') + expect(config.matcher_for('GetAd')).not_to include('geo') end it 'converts array to comma-separated string for to_s' do @@ -52,8 +52,8 @@ let(:input) { 'GetAd:id,GetAd:id,ListIp:geo' } it 'deduplicates entries' do - expect(config.match?('GetAd', 'id')).to be true - expect(config.match?('ListIp', 'geo')).to be true + expect(config.matcher_for('GetAd')).to include('id') + expect(config.matcher_for('ListIp')).to include('geo') end end @@ -61,10 +61,9 @@ let(:input) { 'GetUser:id,GetUser:name,GetUser:email' } it 'groups variables under same operation' do - expect(config.match?('GetUser', 'id')).to be true - expect(config.match?('GetUser', 'name')).to be true - expect(config.match?('GetUser', 'email')).to be true - expect(config.match?('GetUser', 'other')).to be false + matcher = config.matcher_for('GetUser') + expect(matcher).to include('id', 'name', 'email') + expect(matcher).not_to include('other') end end @@ -72,10 +71,10 @@ let(:input) { 'GetUser:id,GetPost:id,GetComment:id' } it 'handles same variable used by multiple operations' do - expect(config.match?('GetUser', 'id')).to be true - expect(config.match?('GetPost', 'id')).to be true - expect(config.match?('GetComment', 'id')).to be true - expect(config.match?('GetUser', 'name')).to be false + expect(config.matcher_for('GetUser')).to include('id') + expect(config.matcher_for('GetPost')).to include('id') + expect(config.matcher_for('GetComment')).to include('id') + expect(config.matcher_for('GetUser')).not_to include('name') end end @@ -83,8 +82,8 @@ let(:input) { ' GetAd:id , ListIp:geo ' } it 'handles whitespace correctly' do - expect(config.match?('GetAd', 'id')).to be true - expect(config.match?('ListIp', 'geo')).to be true + expect(config.matcher_for('GetAd')).to include('id') + expect(config.matcher_for('ListIp')).to include('geo') end end @@ -92,7 +91,7 @@ let(:input) { ',,GetAd:id,' } it 'skips empty fragments' do - expect(config.match?('GetAd', 'id')).to be true + expect(config.matcher_for('GetAd')).to include('id') end end @@ -100,19 +99,24 @@ let(:valid_fragment) { 'ListIp:geo' } [ - { input: 'Get-Ad:bad,ListIp:geo', desc: 'invalid characters in operation name', invalid: ['Get-Ad', 'bad'] }, - { input: 'GetAd:in-valid,ListIp:geo', desc: 'invalid characters in variable name', invalid: ['GetAd', 'in-valid'] }, - { input: 'GetAd : bad,ListIp:geo', desc: 'whitespace around colon', invalid: ['GetAd ', 'bad'] }, - { input: 'GetAd,ListIp:geo', desc: 'missing colon', invalid: ['GetAd', ''] }, - { input: ':id,ListIp:geo', desc: 'empty operation name', invalid: ['', 'id'] }, - { input: 'GetAd:,ListIp:geo', desc: 'empty variable name', invalid: ['GetAd', ''] } + {input: 'Get-Ad:bad,ListIp:geo', desc: 'invalid characters in operation name', invalid: ['Get-Ad', 'bad']}, + {input: 'GetAd:in-valid,ListIp:geo', desc: 'invalid characters in variable name', invalid: ['GetAd', 'in-valid']}, + {input: 'GetAd : bad,ListIp:geo', desc: 'whitespace around colon', invalid: ['GetAd ', 'bad']}, + {input: 'GetAd,ListIp:geo', desc: 'missing colon', invalid: ['GetAd', '']}, + {input: ':id,ListIp:geo', desc: 'empty operation name', invalid: ['', 'id']}, + {input: 'GetAd:,ListIp:geo', desc: 'empty variable name', invalid: ['GetAd', '']} ].each do |test_case| context "with #{test_case[:desc]}" do let(:input) { test_case[:input] } it 'skips invalid fragments but keeps valid ones' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?(test_case[:invalid][0], test_case[:invalid][1])).to be false + expect(config.matcher_for('ListIp')).to include('geo') + matcher = config.matcher_for(test_case[:invalid][0]) + if matcher.empty? + expect(matcher).to be_empty + else + expect(matcher).not_to include(test_case[:invalid][1]) + end end end end @@ -122,8 +126,8 @@ let(:input) { 'Get_Ad:var_1,Load_Ip:key_2' } it 'accepts valid names with underscores and numbers' do - expect(config.match?('Get_Ad', 'var_1')).to be true - expect(config.match?('Load_Ip', 'key_2')).to be true + expect(config.matcher_for('Get_Ad')).to include('var_1') + expect(config.matcher_for('Load_Ip')).to include('key_2') end end @@ -131,47 +135,43 @@ let(:input) { 'GetAd:id:bad,ListIp:geo' } it 'skips fragments with multiple colons' do - expect(config.match?('ListIp', 'geo')).to be true - expect(config.match?('GetAd', 'id:bad')).to be false + expect(config.matcher_for('ListIp')).to include('geo') + expect(config.matcher_for('GetAd')).to be_empty end end end - describe '#match?' do - subject(:config) { described_class.new('GetUser:id,GetUser:name,GetPost:title') } + describe '#to_s' do + subject(:config) { described_class.new('GetUser:id,GetUser:name') } - context 'with configured operation and variable' do - it 'returns true for GetUser:id' do - expect(config.match?('GetUser', 'id')).to be true - end + it 'returns the original configuration string' do + expect(config.to_s).to eq('GetUser:id,GetUser:name') + end + end - it 'returns true for GetUser:name' do - expect(config.match?('GetUser', 'name')).to be true - end + describe '#matcher_for' do + subject(:config) { described_class.new('GetUser:id,GetUser:name,GetPost:title') } - it 'returns true for GetPost:title' do - expect(config.match?('GetPost', 'title')).to be true + context 'with configured operation' do + it 'returns set of variable names for GetUser' do + matcher = config.matcher_for('GetUser') + expect(matcher).to be_a(Set) + expect(matcher).to include('id', 'name') + expect(matcher).not_to include('title') end - end - context 'with unconfigured combinations' do - [ - ['GetUser', 'title'], - ['GetPost', 'id'], - ['UnknownOperation', 'id'] - ].each do |operation, variable| - it "returns false for #{operation}:#{variable}" do - expect(config.match?(operation, variable)).to be false - end + it 'returns set of variable names for GetPost' do + matcher = config.matcher_for('GetPost') + expect(matcher).to be_a(Set) + expect(matcher).to include('title') + expect(matcher).not_to include('id', 'name') end end - end - - describe '#to_s' do - subject(:config) { described_class.new('GetUser:id,GetUser:name') } - it 'returns the original configuration string' do - expect(config.to_s).to eq('GetUser:id,GetUser:name') + context 'with unconfigured operation' do + it 'returns empty set for unknown operation' do + expect(config.matcher_for('UnknownOperation')).to be_empty + end end end diff --git a/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb b/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb index a1312d06e52..eb980331cd6 100644 --- a/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb @@ -138,4 +138,52 @@ end end end + + shared_examples 'capture variables configuration' do |option_name, env_var| + subject(:config) { settings.public_send(option_name) } + let(:settings) { described_class.new } + + context 'when default' do + it { is_expected.to be_a(Datadog::Tracing::Contrib::GraphQL::Configuration::CaptureVariables) } + it { is_expected.to be_empty } + end + + context 'when given an array' do + let(:settings) { described_class.new(option_name => ['GetUser:id', 'GetPost:title']) } + + it 'configures the capture variables correctly' do + expect(config.matcher_for('GetUser')).to contain_exactly('id') + expect(config.matcher_for('GetPost')).to contain_exactly('title') + end + end + + context 'via the environment variable' do + it 'configures from environment variable' do + ClimateControl.modify(env_var => 'GetUser:id,GetPost:title') do + expect(config.matcher_for('GetUser')).to contain_exactly('id') + expect(config.matcher_for('GetPost')).to contain_exactly('title') + end + end + + context 'with empty string' do + it 'handles empty environment variable' do + ClimateControl.modify(env_var => '') do + expect(config.empty?).to be true + end + end + end + end + end + + describe 'capture_variables' do + include_examples 'capture variables configuration', + :capture_variables, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES' + end + + describe 'capture_variables_except' do + include_examples 'capture variables configuration', + :capture_variables_except, + 'DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT' + end end diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index b0de208931a..e0e52887f5f 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -2,6 +2,9 @@ require 'ostruct' require_relative 'test_helpers' +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/graphql/unified_trace_patcher' +require 'datadog' def load_test_schema(prefix: '') # rubocop:disable Security/Eval @@ -142,7 +145,7 @@ def unload_test_schema(prefix: '') let(:service) { defined?(super) ? super() : tracer.default_service } describe 'query trace' do - subject(:result) { schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) } + subject(:result) { schema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: {var: 1}) } matrix = [ ['graphql.analyze', 'query Users($var: ID!){ user(id: $var) { name } }'], @@ -168,7 +171,7 @@ def unload_test_schema(prefix: '') matrix.each_with_index do |(name, resource), index| it "creates #{name} span with #{resource} resource" do expect(result.to_h['errors']).to be nil - expect(result.to_h['data']).to eq({ 'user' => { 'name' => 'Bits' } }) + expect(result.to_h['data']).to eq({'user' => {'name' => 'Bits'}}) expect(spans).to have(matrix.length).items span = spans[index] @@ -266,7 +269,7 @@ def unload_test_schema(prefix: '') 'extensions.bool' => true, 'extensions.str' => '1', 'extensions.array-1-2' => '[1, "2"]', - 'extensions.hash-a-b' => { a: 'b' }.to_s, # Hash#to_s changes per Ruby version: 3.3: '{:a=>1}', 3.4: '{a: 1}' + 'extensions.hash-a-b' => {a: 'b'}.to_s, # Hash#to_s changes per Ruby version: 3.3: '{:a=>1}', 3.4: '{a: 1}' 'extensions.object' => start_with('# 'John', 'active' => true, 'minAge' => 18 }.to_s + expected_serialized = {'name' => 'John', 'active' => true, 'minAge' => 18}.to_s expect(graphql_execute.get_tag('graphql.operation.variable.inputVar')).to eq(expected_serialized) end end diff --git a/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb b/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb deleted file mode 100644 index ed5800606a8..00000000000 --- a/spec/datadog/tracing/contrib/graphql/variable_capture_basic_spec.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -require 'datadog/tracing/contrib/support/spec_helper' -require 'datadog/tracing/contrib/graphql/test_schema_examples' -require 'datadog/tracing/contrib/graphql/unified_trace_patcher' -require 'datadog' - -RSpec.describe 'GraphQL variable capture basic functionality', - skip: Gem::Version.new(::GraphQL::VERSION) < Gem::Version.new('2.0.19') do - before(:context) { load_test_schema } - after(:context) do - unload_test_schema - remove_patch!(:graphql) - end - - around do |example| - # Remove the previously set environment variables - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: nil, - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: nil - ) do - example.run - end - end - - let(:all_spans) { spans } - let(:execute_span) { all_spans.find { |s| s.name == 'graphql.execute' } } - - describe 'with unified tracer' do - before do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true - end - end - - context 'when no capture configuration is set' do - it 'does not capture any variables' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '1' }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to be_nil - end - end - - context 'when capture variables is configured for GetUser:id' do - around do |example| - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES: 'GetUser:id' - ) do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true - end - example.run - end - end - - it 'captures the id variable' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '42' }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('42') - end - - it 'captures numeric variables correctly' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: 123 }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq(123) - end - end - - context 'when capture variables except is configured' do - around do |example| - ClimateControl.modify( - DD_TRACE_GRAPHQL_CAPTURE_VARIABLES_EXCEPT: 'GetUser:secret' - ) do - Datadog.configuration.tracing[:graphql].reset! - Datadog.configure do |c| - c.tracing.instrument :graphql, with_unified_tracer: true - end - example.run - end - end - - it 'captures all variables except those in the except list' do - result = TestGraphQLSchema.execute( - 'query GetUser($id: ID!) { user(id: $id) { name } }', - variables: { id: '1' }, - operation_name: 'GetUser' - ) - - expect(result.to_h['errors']).to be_nil - expect(execute_span).not_to be_nil - - expect(execute_span.get_tag('graphql.operation.variable.id')).to eq('1') - end - end - end - end