diff --git a/CHANGELOG.md b/CHANGELOG.md index e2049e02db..eb2e96a95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ ## [Unreleased (beta)] +## [0.21.0] - 2019-03-20 + +Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.21.0 + +Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.20.0...v0.21.0 + +### Added + +- Trace analytics support (#697, #715) +- HTTP after_request span hook (#716, #724) + +### Fixed + +- Distributed traces with IDs in 2^64 range being dropped (#719) +- Custom logger level forced to warning (#681, #721) (@blaines, @ericmustin) + +### Refactored + +- Global configuration for tracing into configuration API (#714) + ## [0.20.0] - 2019-03-07 Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.20.0 @@ -21,7 +41,7 @@ These changes are backwards compatible, but all integration configuration should - Enable distributed tracing by default (#701) -### Fixes +### Fixed - Fix Rack http_server.queue spans missing from distributed traces (#709) @@ -711,8 +731,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1 Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1 -[Unreleased (stable)]: https://github.com/DataDog/dd-trace-rb/compare/v0.20.0...master -[Unreleased (beta)]: https://github.com/DataDog/dd-trace-rb/compare/v0.20.0...0.21-dev +[Unreleased (stable)]: https://github.com/DataDog/dd-trace-rb/compare/v0.21.0...master +[Unreleased (beta)]: https://github.com/DataDog/dd-trace-rb/compare/v0.21.0...0.22-dev +[0.21.0]: https://github.com/DataDog/dd-trace-rb/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/DataDog/dd-trace-rb/compare/v0.19.1...v0.20.0 [0.19.1]: https://github.com/DataDog/dd-trace-rb/compare/v0.19.0...v0.19.1 [0.19.0]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.3...v0.19.0 diff --git a/Rakefile b/Rakefile index 6be983d08a..1925123328 100644 --- a/Rakefile +++ b/Rakefile @@ -47,7 +47,7 @@ namespace :spec do RSpec::Core::RakeTask.new(:contrib) do |t| # rubocop:disable Metrics/LineLength - t.pattern = 'spec/**/contrib/{analytics,configurable,integration,patchable,patcher,registerable,sampling,configuration/*}_spec.rb' + t.pattern = 'spec/**/contrib/{analytics,configurable,integration,patchable,patcher,registerable,registry,configuration/*}_spec.rb' end [ diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index d09f1376ba..9ad786fec6 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -355,6 +355,7 @@ ActiveModelSerializers::SerializableResource.new(test_obj).serializable_hash | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `active_model_serializers` instrumentation. | `'active_model_serializers'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -383,6 +384,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | ---| --- | --- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `orm_service_name` | Service name used for the Ruby ORM portion of `active_record` instrumentation. Overrides service name for ORM spans if explicitly set, which otherwise inherit their service from their parent. | `'active_record'` | | `service_name` | Service name used for database portion of `active_record` instrumentation. | Name of database adapter (e.g. `'mysql2'`) | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -445,6 +447,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `aws` instrumentation | `'aws'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -497,6 +500,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `dalli` instrumentation | `'memcached'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -518,7 +522,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `DelayedJob` instrumentation | `'delayed_job'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -543,6 +547,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `quantize` | Hash containing options for quantization. May include `:show` with an Array of keys to not quantize (or `:all` to skip quantization), or `:exclude` with Array of keys to exclude entirely. | `{}` | | `service_name` | Service name used for `elasticsearch` instrumentation | `'elasticsearch'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -568,6 +573,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `distributed_tracing` | Enables [distributed tracing](#distributed-tracing) | `true` | | `error_handler` | A `Proc` that accepts a `response` parameter. If it evaluates to a *truthy* value, the trace span is marked as an error. By default only sets 5XX responses as errors. | `nil` | | `service_name` | Service name for Excon instrumentation. When provided to middleware for a specific connection, it applies only to that connection object. | `'excon'` | @@ -625,6 +631,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `distributed_tracing` | Enables [distributed tracing](#distributed-tracing) | `true` | | `error_handler` | A `Proc` that accepts a `response` parameter. If it evaluates to a *truthy* value, the trace span is marked as an error. By default only sets 5XX responses as errors. | `nil` | | `service_name` | Service name for Faraday instrumentation. When provided to middleware for a specific connection, it applies only to that connection object. | `'faraday'` | @@ -659,6 +666,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | | `enabled` | Defines whether Grape should be traced. Useful for temporarily disabling tracing. `true` or `false` | `true` | | `service_name` | Service name used for `grape` instrumentation | `'grape'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -683,6 +691,7 @@ The `use :graphql` method accepts the following parameters. Additional options c | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | | `service_name` | Service name used for `graphql` instrumentation | `'ruby-graphql'` | | `schemas` | Required. Array of `GraphQL::Schema` objects which to trace. Tracing will be added to all the schemas listed, using the options provided to this configuration. If you do not provide any, then tracing will not be activated. | `[]` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -742,6 +751,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `grpc` instrumentation | `'grpc'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -788,6 +798,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `quantize` | Hash containing options for quantization. May include `:show` with an Array of keys to not quantize (or `:all` to skip quantization), or `:exclude` with Array of keys to exclude entirely. | `{ show: [:collection, :database, :operation] }` | | `service_name` | Service name used for `mongo` instrumentation | `'mongodb'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -812,6 +823,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `mysql2` instrumentation | `'mysql2'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -839,6 +851,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `distributed_tracing` | Enables [distributed tracing](#distributed-tracing) | `true` | | `service_name` | Service name used for `http` instrumentation | `'net/http'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -868,7 +881,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `racecar` instrumentation | `'racecar'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -964,6 +977,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | | `cache_service` | Cache service name used when tracing cache activity | `'-cache'` | | `controller_service` | Service name used when tracing a Rails action controller | `''` | | `database_service` | Database service name used when tracing database activity | `'-'` | @@ -1010,7 +1024,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `enabled` | Defines whether Rake tasks should be traced. Useful for temporarily disabling tracing. `true` or `false` | `true` | | `quantize` | Hash containing options for quantization of task arguments. See below for more details and examples. | `{}` | | `service_name` | Service name used for `rake` instrumentation | `'rake'` | @@ -1071,6 +1085,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `redis` instrumentation | `'redis'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1113,7 +1128,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `resque` instrumentation | `'resque'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | | `workers` | An array including all worker classes you want to trace (eg `[MyJob]`) | `[]` | @@ -1135,6 +1150,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `distributed_tracing` | Enables [distributed tracing](#distributed-tracing) | `true` | | `service_name` | Service name for `rest_client` instrumentation. | `'rest_client'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1169,6 +1185,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name for `sequel` instrumentation | Name of database adapter (e.g. `'mysql2'`) | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1205,7 +1222,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `shoryuken` instrumentation | `'shoryuken'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1227,7 +1244,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `client_service_name` | Service name used for client-side `sidekiq` instrumentation | `'sidekiq-client'` | | `service_name` | Service name used for server-side `sidekiq` instrumentation | `'sidekiq'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1255,6 +1272,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | | `distributed_tracing` | Enables [distributed tracing](#distributed-tracing) so that this service trace is connected with a trace of another service if tracing headers are received | `true` | | `headers` | Hash of HTTP request or response headers to add as tags to the `sinatra.request`. Accepts `request` and `response` keys with Array values e.g. `['Last-Modified']`. Adds `http.request.headers.*` and `http.response.headers.*` tags respectively. | `{ response: ['Content-Type', 'X-Request-ID'] }` | | `resource_script_names` | Prepend resource names with script name | `false` | @@ -1280,7 +1298,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | -| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `nil` | +| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` | | `service_name` | Service name used for `sucker_punch` instrumentation | `'sucker_punch'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | diff --git a/lib/ddtrace.rb b/lib/ddtrace.rb index eab78d5318..6c480e7236 100644 --- a/lib/ddtrace.rb +++ b/lib/ddtrace.rb @@ -1,6 +1,5 @@ require 'thread' -require 'ddtrace/registry' require 'ddtrace/pin' require 'ddtrace/tracer' require 'ddtrace/error' @@ -14,39 +13,13 @@ # \Datadog global namespace that includes all tracing functionality for Tracer and Span classes. module Datadog extend Augmentation + extend Configuration - @tracer = Tracer.new - @registry = Registry.new - @configuration = Configuration.new(registry: @registry) - - # Default tracer that can be used as soon as +ddtrace+ is required: - # - # require 'ddtrace' - # - # span = Datadog.tracer.trace('web.request') - # span.finish() - # - # If you want to override the default tracer, the recommended way - # is to "pin" your own tracer onto your traced component: - # - # tracer = Datadog::Tracer.new - # pin = Datadog::Pin.get_from(mypatchcomponent) - # pin.tracer = tracer - class << self - attr_reader :tracer, :registry - attr_accessor :configuration - - def configure(target = configuration, opts = {}) - if target.is_a?(Configuration) - yield(target) - else - Configuration::PinSetup.new(target, opts).call - end - end - end + # Load and extend Contrib by default + require 'ddtrace/contrib/extensions' + extend Contrib::Extensions end -require 'ddtrace/contrib/base' require 'ddtrace/contrib/active_model_serializers/integration' require 'ddtrace/contrib/active_record/integration' require 'ddtrace/contrib/aws/integration' diff --git a/lib/ddtrace/configurable.rb b/lib/ddtrace/configurable.rb deleted file mode 100644 index 0bb38211e6..0000000000 --- a/lib/ddtrace/configurable.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Datadog - InvalidOptionError = Class.new(StandardError) - # Configurable provides configuration methods for a given class/module - module Configurable - IDENTITY = ->(x) { x } - - def self.included(base) - base.singleton_class.send(:include, ClassMethods) - end - - # ClassMethods - module ClassMethods - def set_option(name, value) - __assert_valid!(name) - - __options[name][:value] = __options[name][:setter].call(value) - __options[name][:set_flag] = true - end - - def get_option(name) - __assert_valid!(name) - - return __default_value(name) unless __options[name][:set_flag] - - __options[name][:value] - end - - def to_h - __options.each_with_object({}) do |(key, _), hash| - hash[key] = get_option(key) - end - end - - def reset_options! - __options.each do |name, meta| - set_option(name, meta[:default]) - end - end - - def sorted_options - Configuration::Resolver.new(__dependency_graph).call - end - - private - - def option(name, meta = {}, &block) - name = name.to_sym - meta[:setter] ||= (block || IDENTITY) - meta[:depends_on] ||= [] - meta[:lazy] ||= false - __options[name] = meta - end - - def __options - @__options ||= {} - end - - def __assert_valid!(name) - return if __options.key?(name) - raise(InvalidOptionError, "#{__pretty_name} doesn't have the option: #{name}") - end - - def __pretty_name - entry = Datadog.registry.find { |el| el.klass == self } - - return entry.name if entry - - to_s - end - - def __dependency_graph - __options.each_with_object({}) do |(name, meta), graph| - graph[name] = meta[:depends_on] - end - end - - def __default_value(name) - return __options[name][:default].call if __options[name][:lazy] - __options[name][:default] - end - end - end -end diff --git a/lib/ddtrace/configuration.rb b/lib/ddtrace/configuration.rb index 34857473f1..9b5b2baf8b 100644 --- a/lib/ddtrace/configuration.rb +++ b/lib/ddtrace/configuration.rb @@ -1,59 +1,26 @@ -require_relative 'configuration/proxy' -require_relative 'configuration/resolver' -require_relative 'configuration/pin_setup' +require 'ddtrace/configuration/pin_setup' +require 'ddtrace/configuration/settings' module Datadog # Configuration provides a unique access point for configurations - class Configuration - InvalidIntegrationError = Class.new(StandardError) + module Configuration + attr_writer :configuration - def initialize(options = {}) - @registry = options.fetch(:registry) { Datadog.registry } - @wrapped_registry = {} + def configuration + @configuration ||= Settings.new end - def [](integration_name, configuration_name = :default) - integration = fetch_integration(integration_name) - - if integration.class <= Datadog::Contrib::Integration - integration.configuration(configuration_name) + def configure(target = configuration, opts = {}) + if target.is_a?(Settings) + yield(target) else - @wrapped_registry[integration_name] ||= Proxy.new(integration) + PinSetup.new(target, opts).call end end - def use(integration_name, options = {}, &block) - integration = fetch_integration(integration_name) - - if integration.class <= Datadog::Contrib::Integration - configuration_name = options[:describes] || :default - filtered_options = options.reject { |k, _v| k == :describes } - integration.configure(configuration_name, filtered_options, &block) - else - settings = Proxy.new(integration) - integration.sorted_options.each do |name| - settings[name] = options.fetch(name, settings[name]) - end - end - - integration.patch if integration.respond_to?(:patch) - end - - def tracer(options = {}) - instance = options.fetch(:instance, Datadog.tracer) - - instance.configure(options) - instance.class.log = options[:log] if options[:log] - instance.set_tags(options[:tags]) if options[:tags] - instance.set_tags(env: options[:env]) if options[:env] - instance.class.debug_logging = options.fetch(:debug, false) - end - - private - - def fetch_integration(name) - @registry[name] || - raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") + # Helper methods + def tracer + configuration.tracer end end end diff --git a/lib/ddtrace/configuration/resolver.rb b/lib/ddtrace/configuration/dependency_resolver.rb similarity index 89% rename from lib/ddtrace/configuration/resolver.rb rename to lib/ddtrace/configuration/dependency_resolver.rb index c047e669b1..7b6c586da7 100644 --- a/lib/ddtrace/configuration/resolver.rb +++ b/lib/ddtrace/configuration/dependency_resolver.rb @@ -1,9 +1,9 @@ require 'tsort' module Datadog - class Configuration + module Configuration # Resolver performs a topological sort over the dependency graph - class Resolver + class DependencyResolver include TSort def initialize(dependency_graph = {}) diff --git a/lib/ddtrace/configuration/option.rb b/lib/ddtrace/configuration/option.rb new file mode 100644 index 0000000000..93a5d52729 --- /dev/null +++ b/lib/ddtrace/configuration/option.rb @@ -0,0 +1,32 @@ +module Datadog + module Configuration + # Represents an instance of an integration configuration option + class Option + attr_reader \ + :definition + + def initialize(definition, context) + @definition = definition + @context = context + @value = nil + @is_set = false + end + + def set(value) + @value = @context.instance_exec(value, &definition.setter).tap do + @is_set = true + end + end + + def get + return definition.default_value unless @is_set + @value + end + + def reset + @is_set = false + @value = nil + end + end + end +end diff --git a/lib/ddtrace/configuration/option_definition.rb b/lib/ddtrace/configuration/option_definition.rb new file mode 100644 index 0000000000..ab85f3a171 --- /dev/null +++ b/lib/ddtrace/configuration/option_definition.rb @@ -0,0 +1,27 @@ +module Datadog + module Configuration + # Represents a definition for an integration configuration option + class OptionDefinition + IDENTITY = ->(x) { x } + + attr_reader \ + :default, + :depends_on, + :lazy, + :name, + :setter + + def initialize(name, meta = {}, &block) + @default = meta[:default] + @depends_on = meta[:depends_on] || [] + @lazy = meta[:lazy] || false + @name = name.to_sym + @setter = meta[:setter] || block || IDENTITY + end + + def default_value + lazy ? @default.call : @default + end + end + end +end diff --git a/lib/ddtrace/configuration/option_definition_set.rb b/lib/ddtrace/configuration/option_definition_set.rb new file mode 100644 index 0000000000..1267f10807 --- /dev/null +++ b/lib/ddtrace/configuration/option_definition_set.rb @@ -0,0 +1,18 @@ +require 'ddtrace/configuration/dependency_resolver' + +module Datadog + module Configuration + # Represents a set of configuration option definitions for an integration + class OptionDefinitionSet < Hash + def dependency_order + DependencyResolver.new(dependency_graph).call + end + + def dependency_graph + each_with_object({}) do |(name, option), graph| + graph[name] = option.depends_on + end + end + end + end +end diff --git a/lib/ddtrace/configuration/option_set.rb b/lib/ddtrace/configuration/option_set.rb new file mode 100644 index 0000000000..8c0da1d0dc --- /dev/null +++ b/lib/ddtrace/configuration/option_set.rb @@ -0,0 +1,6 @@ +module Datadog + module Configuration + class OptionSet < Hash + end + end +end diff --git a/lib/ddtrace/configuration/options.rb b/lib/ddtrace/configuration/options.rb new file mode 100644 index 0000000000..a59530965e --- /dev/null +++ b/lib/ddtrace/configuration/options.rb @@ -0,0 +1,93 @@ +require 'ddtrace/configuration/option' +require 'ddtrace/configuration/option_set' +require 'ddtrace/configuration/option_definition' +require 'ddtrace/configuration/option_definition_set' + +module Datadog + module Configuration + # Behavior for a configuration object that has options + module Options + def self.included(base) + base.send(:extend, ClassMethods) + base.send(:include, InstanceMethods) + end + + # Class behavior for a configuration object with options + module ClassMethods + def options + @options ||= begin + # Allows for class inheritance of option definitions + superclass <= Options ? superclass.options.dup : OptionDefinitionSet.new + end + end + + protected + + def option(name, meta = {}, &block) + options[name] = OptionDefinition.new(name, meta, &block).tap do + define_option_accessors(name) + end + end + + private + + def define_option_accessors(name) + option_name = name + + define_method(option_name) do + get_option(option_name) + end + + define_method("#{option_name}=") do |value| + set_option(option_name, value) + end + end + end + + # Instance behavior for a configuration object with options + module InstanceMethods + def options + @options ||= OptionSet.new + end + + def set_option(name, value) + add_option(name) unless options.key?(name) + options[name].set(value) + end + + def get_option(name) + add_option(name) unless options.key?(name) + options[name].get + end + + def to_h + options.each_with_object({}) do |(key, _), hash| + hash[key] = get_option(key) + end + end + + def reset_options! + options.values.each(&:reset) + end + + private + + def add_option(name) + assert_valid_option!(name) + definition = self.class.options[name] + Option.new(definition, self).tap do |option| + options[name] = option + end + end + + def assert_valid_option!(name) + unless self.class.options.key?(name) + raise(InvalidOptionError, "#{self.class.name} doesn't define the option: #{name}") + end + end + end + + InvalidOptionError = Class.new(StandardError) + end + end +end diff --git a/lib/ddtrace/configuration/pin_setup.rb b/lib/ddtrace/configuration/pin_setup.rb index 2acacc085a..ace1ca728c 100644 --- a/lib/ddtrace/configuration/pin_setup.rb +++ b/lib/ddtrace/configuration/pin_setup.rb @@ -1,5 +1,5 @@ module Datadog - class Configuration + module Configuration # PinSetup translates a flat hash into a Pin configuration # This class should be removed if we ever remove/refactor the Pin class class PinSetup diff --git a/lib/ddtrace/configuration/proxy.rb b/lib/ddtrace/configuration/proxy.rb deleted file mode 100644 index 019ffe25f3..0000000000 --- a/lib/ddtrace/configuration/proxy.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'forwardable' - -module Datadog - class Configuration - # Proxy provides a hash-like interface for fetching/setting configurations - class Proxy - extend Forwardable - - def initialize(integration) - @integration = integration - end - - def [](param) - @integration.get_option(param) - end - - def []=(param, value) - @integration.set_option(param, value) - end - - def_delegators :@integration, :to_h, :reset_options! - def_delegators :to_h, :to_hash, :merge - end - end -end diff --git a/lib/ddtrace/configuration/settings.rb b/lib/ddtrace/configuration/settings.rb new file mode 100644 index 0000000000..eba16462b3 --- /dev/null +++ b/lib/ddtrace/configuration/settings.rb @@ -0,0 +1,47 @@ +require 'ddtrace/ext/analytics' +require 'ddtrace/environment' +require 'ddtrace/configuration/options' + +module Datadog + module Configuration + # Global configuration settings for the trace library. + class Settings + extend Datadog::Environment::Helpers + include Options + + option :analytics_enabled, + default: -> { env_to_bool(Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED, nil) }, + lazy: true + + option :tracer, default: Tracer.new + + def initialize(options = {}) + configure(options) + end + + def configure(options = {}) + self.class.options.dependency_order.each do |name| + next unless options.key?(name) + respond_to?("#{name}=") ? send("#{name}=", options[name]) : set_option(name, options[name]) + end + + yield(self) if block_given? + end + + # Backwards compatibility for configuring tracer e.g. `c.tracer debug: true` + def tracer(options = nil) + tracer = options && options.key?(:instance) ? set_option(:tracer, options[:instance]) : get_option(:tracer) + + tracer.tap do |t| + unless options.nil? + t.configure(options) + t.class.log = options[:log] if options[:log] + t.set_tags(options[:tags]) if options[:tags] + t.set_tags(env: options[:env]) if options[:env] + t.class.debug_logging = options.fetch(:debug, false) + end + end + end + end + end +end diff --git a/lib/ddtrace/contrib/active_model_serializers/configuration/settings.rb b/lib/ddtrace/contrib/active_model_serializers/configuration/settings.rb index 6cd4686abf..129910ac30 100644 --- a/lib/ddtrace/contrib/active_model_serializers/configuration/settings.rb +++ b/lib/ddtrace/contrib/active_model_serializers/configuration/settings.rb @@ -7,6 +7,14 @@ module ActiveModelSerializers module Configuration # Custom settings for the ActiveModelSerializers integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME option :tracer, default: Datadog.tracer do |value| (value || Datadog.tracer).tap do |v| diff --git a/lib/ddtrace/contrib/active_model_serializers/event.rb b/lib/ddtrace/contrib/active_model_serializers/event.rb index 6d34750095..35fd72b44c 100644 --- a/lib/ddtrace/contrib/active_model_serializers/event.rb +++ b/lib/ddtrace/contrib/active_model_serializers/event.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/http' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/active_support/notifications/event' require 'ddtrace/contrib/active_model_serializers/ext' @@ -30,6 +31,11 @@ def configuration def process(span, event, _id, payload) span.service = configuration[:service_name] + # Set analytics sample rate + if Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate]) + end + # Set the resource name and serializer name res = resource(payload[:serializer]) span.resource = res diff --git a/lib/ddtrace/contrib/active_model_serializers/ext.rb b/lib/ddtrace/contrib/active_model_serializers/ext.rb index 3ec4bdb170..605b6de2ca 100644 --- a/lib/ddtrace/contrib/active_model_serializers/ext.rb +++ b/lib/ddtrace/contrib/active_model_serializers/ext.rb @@ -4,11 +4,11 @@ module ActiveModelSerializers # ActiveModelSerializers integration constants module Ext APP = 'active_model_serializers'.freeze + ENV_ANALYTICS_ENABLED = 'DD_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_ACTIVE_MODEL_SERIALIZERS_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'active_model_serializers'.freeze - SPAN_RENDER = 'active_model_serializers.render'.freeze SPAN_SERIALIZE = 'active_model_serializers.serialize'.freeze - TAG_ADAPTER = 'active_model_serializers.adapter'.freeze TAG_SERIALIZER = 'active_model_serializers.serializer'.freeze end diff --git a/lib/ddtrace/contrib/active_record/configuration/settings.rb b/lib/ddtrace/contrib/active_record/configuration/settings.rb index c0140331b9..b577aeef2a 100644 --- a/lib/ddtrace/contrib/active_record/configuration/settings.rb +++ b/lib/ddtrace/contrib/active_record/configuration/settings.rb @@ -8,6 +8,14 @@ module ActiveRecord module Configuration # Custom settings for the ActiveRecord integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :orm_service_name option :service_name, depends_on: [:tracer] do |value| (value || Utils.adapter_name).tap do |service_name| diff --git a/lib/ddtrace/contrib/active_record/events/instantiation.rb b/lib/ddtrace/contrib/active_record/events/instantiation.rb index 6db609e2ff..95f803282c 100644 --- a/lib/ddtrace/contrib/active_record/events/instantiation.rb +++ b/lib/ddtrace/contrib/active_record/events/instantiation.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/active_record/ext' require 'ddtrace/contrib/active_record/event' @@ -38,6 +39,12 @@ def process(span, event, _id, payload) span.resource = payload.fetch(:class_name) span.span_type = Ext::SPAN_TYPE_INSTANTIATION + + # Set analytics sample rate + if Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate]) + end + span.set_tag(Ext::TAG_INSTANTIATION_CLASS_NAME, payload.fetch(:class_name)) span.set_tag(Ext::TAG_INSTANTIATION_RECORD_COUNT, payload.fetch(:record_count)) rescue StandardError => e diff --git a/lib/ddtrace/contrib/active_record/events/sql.rb b/lib/ddtrace/contrib/active_record/events/sql.rb index d6525a83c8..1ff5eac146 100644 --- a/lib/ddtrace/contrib/active_record/events/sql.rb +++ b/lib/ddtrace/contrib/active_record/events/sql.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/net' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/active_record/ext' require 'ddtrace/contrib/active_record/event' @@ -38,6 +39,11 @@ def process(span, event, _id, payload) span.resource = payload.fetch(:sql) span.span_type = Datadog::Ext::SQL::TYPE + # Set analytics sample rate + if Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate]) + end + # Find out if the SQL query has been cached in this request. This meta is really # helpful to users because some spans may have 0ns of duration because the query # is simply cached from memory, so the notification is fired with start == finish. diff --git a/lib/ddtrace/contrib/active_record/ext.rb b/lib/ddtrace/contrib/active_record/ext.rb index a9b3532551..3422760ee3 100644 --- a/lib/ddtrace/contrib/active_record/ext.rb +++ b/lib/ddtrace/contrib/active_record/ext.rb @@ -4,13 +4,12 @@ module ActiveRecord # ActiveRecord integration constants module Ext APP = 'active_record'.freeze + ENV_ANALYTICS_ENABLED = 'DD_ACTIVE_RECORD_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_ACTIVE_RECORD_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'active_record'.freeze - SPAN_INSTANTIATION = 'active_record.instantiation'.freeze SPAN_SQL = 'active_record.sql'.freeze - SPAN_TYPE_INSTANTIATION = 'custom'.freeze - TAG_DB_CACHED = 'active_record.db.cached'.freeze TAG_DB_NAME = 'active_record.db.name'.freeze TAG_DB_VENDOR = 'active_record.db.vendor'.freeze diff --git a/lib/ddtrace/contrib/analytics.rb b/lib/ddtrace/contrib/analytics.rb index f7c1fba8b1..524ff04529 100644 --- a/lib/ddtrace/contrib/analytics.rb +++ b/lib/ddtrace/contrib/analytics.rb @@ -9,13 +9,12 @@ module Analytics # Checks whether analytics should be enabled. # `flag` is a truthy/falsey value that represents a setting on the integration. def enabled?(flag = nil) - # TODO: Check global flag here. - # (global_flag && flag != false) || flag == true - flag == true + (Datadog.configuration.analytics_enabled && flag != false) || flag == true end def set_sample_rate(span, sample_rate) - span.set_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE, sample_rate) unless sample_rate.nil? + return if span.nil? || sample_rate.nil? + span.set_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE, sample_rate) end end end diff --git a/lib/ddtrace/contrib/aws/configuration/settings.rb b/lib/ddtrace/contrib/aws/configuration/settings.rb index c098f5e64c..87ac664b13 100644 --- a/lib/ddtrace/contrib/aws/configuration/settings.rb +++ b/lib/ddtrace/contrib/aws/configuration/settings.rb @@ -7,6 +7,14 @@ module Aws module Configuration # Custom settings for the AWS integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME end end diff --git a/lib/ddtrace/contrib/aws/ext.rb b/lib/ddtrace/contrib/aws/ext.rb index 639ed08c8b..e9c2257eb5 100644 --- a/lib/ddtrace/contrib/aws/ext.rb +++ b/lib/ddtrace/contrib/aws/ext.rb @@ -4,17 +4,16 @@ module Aws # AWS integration constants module Ext APP = 'aws'.freeze + ENV_ANALYTICS_ENABLED = 'DD_AWS_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_AWS_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'aws'.freeze - SPAN_COMMAND = 'aws.command'.freeze - TAG_AGENT = 'aws.agent'.freeze + TAG_DEFAULT_AGENT = 'aws-sdk-ruby'.freeze + TAG_HOST = 'host'.freeze TAG_OPERATION = 'aws.operation'.freeze - TAG_REGION = 'aws.region'.freeze TAG_PATH = 'path'.freeze - TAG_HOST = 'host'.freeze - - TAG_DEFAULT_AGENT = 'aws-sdk-ruby'.freeze + TAG_REGION = 'aws.region'.freeze end end end diff --git a/lib/ddtrace/contrib/aws/instrumentation.rb b/lib/ddtrace/contrib/aws/instrumentation.rb index 6bd639ccc8..0dee60c836 100644 --- a/lib/ddtrace/contrib/aws/instrumentation.rb +++ b/lib/ddtrace/contrib/aws/instrumentation.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/aws/ext' module Datadog @@ -27,6 +28,12 @@ def annotate!(span, context) span.span_type = Datadog::Ext::AppTypes::WEB span.name = Ext::SPAN_COMMAND span.resource = context.safely(:resource) + + # Set analytics sample rate + if Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate]) + end + span.set_tag(Ext::TAG_AGENT, Ext::TAG_DEFAULT_AGENT) span.set_tag(Ext::TAG_OPERATION, context.safely(:operation)) span.set_tag(Ext::TAG_REGION, context.safely(:region)) diff --git a/lib/ddtrace/contrib/base.rb b/lib/ddtrace/contrib/base.rb deleted file mode 100644 index 76163e9a33..0000000000 --- a/lib/ddtrace/contrib/base.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'ddtrace/configurable' -require 'ddtrace/patcher' -require 'ddtrace/registry/registerable' - -module Datadog - module Contrib - # Base provides features that are shared across all integrations - module Base - def self.included(base) - base.send(:include, Registry::Registerable) - base.send(:include, Datadog::Configurable) - base.send(:include, Datadog::Patcher) - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/option.rb b/lib/ddtrace/contrib/configuration/option.rb deleted file mode 100644 index 168f7afa83..0000000000 --- a/lib/ddtrace/contrib/configuration/option.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Datadog - module Contrib - module Configuration - # Represents an instance of an integration configuration option - class Option - attr_reader \ - :definition - - def initialize(definition, context) - @definition = definition - @context = context - @value = nil - @is_set = false - end - - def set(value) - @value = @context.instance_exec(value, &definition.setter).tap do - @is_set = true - end - end - - def get - return definition.default_value unless @is_set - @value - end - - def reset - set(definition.default_value) - end - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/option_definition.rb b/lib/ddtrace/contrib/configuration/option_definition.rb deleted file mode 100644 index 170f5f1561..0000000000 --- a/lib/ddtrace/contrib/configuration/option_definition.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Datadog - module Contrib - module Configuration - # Represents a definition for an integration configuration option - class OptionDefinition - IDENTITY = ->(x) { x } - - attr_reader \ - :default, - :depends_on, - :lazy, - :name, - :setter - - def initialize(name, meta = {}, &block) - @default = meta[:default] - @depends_on = meta[:depends_on] || [] - @lazy = meta[:lazy] || false - @name = name.to_sym - @setter = meta[:setter] || block || IDENTITY - end - - def default_value - lazy ? @default.call : @default - end - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/option_definition_set.rb b/lib/ddtrace/contrib/configuration/option_definition_set.rb deleted file mode 100644 index 0f0e2c2c43..0000000000 --- a/lib/ddtrace/contrib/configuration/option_definition_set.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'ddtrace/configuration/resolver' - -module Datadog - module Contrib - module Configuration - # Represents a set of configuration option definitions for an integration - class OptionDefinitionSet < Hash - def dependency_order - Datadog::Configuration::Resolver.new(dependency_graph).call - end - - def dependency_graph - each_with_object({}) do |(name, option), graph| - graph[name] = option.depends_on - end - end - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/option_set.rb b/lib/ddtrace/contrib/configuration/option_set.rb deleted file mode 100644 index 4e5dd1a59e..0000000000 --- a/lib/ddtrace/contrib/configuration/option_set.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Datadog - module Contrib - module Configuration - class OptionSet < Hash - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/options.rb b/lib/ddtrace/contrib/configuration/options.rb deleted file mode 100644 index 1aab275d99..0000000000 --- a/lib/ddtrace/contrib/configuration/options.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'ddtrace/contrib/configuration/option' -require 'ddtrace/contrib/configuration/option_set' -require 'ddtrace/contrib/configuration/option_definition' -require 'ddtrace/contrib/configuration/option_definition_set' - -module Datadog - module Contrib - module Configuration - # Behavior for a configuration object that has options - module Options - def self.included(base) - base.send(:extend, ClassMethods) - base.send(:include, InstanceMethods) - end - - # Class behavior for a configuration object with options - module ClassMethods - def options - @options ||= begin - # Allows for class inheritance of option definitions - superclass <= Options ? superclass.options.dup : OptionDefinitionSet.new - end - end - - protected - - def option(name, meta = {}, &block) - options[name] = OptionDefinition.new(name, meta, &block).tap do - define_option_accessors(name) - end - end - - private - - def define_option_accessors(name) - option_name = name - - define_method(option_name) do - get_option(option_name) - end - - define_method("#{option_name}=") do |value| - set_option(option_name, value) - end - end - end - - # Instance behavior for a configuration object with options - module InstanceMethods - def options - @options ||= OptionSet.new - end - - def set_option(name, value) - add_option(name) unless options.key?(name) - options[name].set(value) - end - - def get_option(name) - add_option(name) unless options.key?(name) - options[name].get - end - - def to_h - options.each_with_object({}) do |(key, _), hash| - hash[key] = get_option(key) - end - end - - def reset_options! - options.values.each(&:reset) - end - - private - - def add_option(name) - assert_valid_option!(name) - definition = self.class.options[name] - Option.new(definition, self).tap do |option| - options[name] = option - end - end - - def assert_valid_option!(name) - unless self.class.options.key?(name) - raise(InvalidOptionError, "#{self.class.name} doesn't define the option: #{name}") - end - end - end - - InvalidOptionError = Class.new(StandardError) - end - end - end -end diff --git a/lib/ddtrace/contrib/configuration/settings.rb b/lib/ddtrace/contrib/configuration/settings.rb index 31c4c5c62b..537f78e776 100644 --- a/lib/ddtrace/contrib/configuration/settings.rb +++ b/lib/ddtrace/contrib/configuration/settings.rb @@ -1,11 +1,13 @@ -require 'ddtrace/contrib/configuration/options' +require 'ddtrace/environment' +require 'ddtrace/configuration/options' module Datadog module Contrib module Configuration # Common settings for all integrations class Settings - include Options + extend Datadog::Environment::Helpers + include Datadog::Configuration::Options option :service_name option :tracer, default: Datadog.tracer @@ -31,18 +33,6 @@ def [](name) def []=(name, value) respond_to?("#{name}=") ? send("#{name}=", value) : set_option(name, value) end - - class << self - private - - def env_to_bool(var, default = nil) - ENV.key?(var) ? ENV[var].to_s.downcase == 'true' : default - end - - def env_to_float(var, default = nil) - ENV.key?(var) ? ENV[var].to_f : default - end - end end end end diff --git a/lib/ddtrace/contrib/dalli/configuration/settings.rb b/lib/ddtrace/contrib/dalli/configuration/settings.rb index 613ea07fb6..484c02b2d7 100644 --- a/lib/ddtrace/contrib/dalli/configuration/settings.rb +++ b/lib/ddtrace/contrib/dalli/configuration/settings.rb @@ -7,6 +7,14 @@ module Dalli module Configuration # Custom settings for the Dalli integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME end end diff --git a/lib/ddtrace/contrib/dalli/ext.rb b/lib/ddtrace/contrib/dalli/ext.rb index de2334d8d0..79128e0e57 100644 --- a/lib/ddtrace/contrib/dalli/ext.rb +++ b/lib/ddtrace/contrib/dalli/ext.rb @@ -4,9 +4,10 @@ module Dalli # Dalli integration constants module Ext APP = 'dalli'.freeze - SERVICE_NAME = 'memcached'.freeze - + ENV_ANALYTICS_ENABLED = 'DD_DALLI_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_DALLI_ANALYTICS_SAMPLE_RATE'.freeze QUANTIZE_MAX_CMD_LENGTH = 100 + SERVICE_NAME = 'memcached'.freeze SPAN_COMMAND = 'memcached.command'.freeze TAG_COMMAND = 'memcached.command'.freeze end diff --git a/lib/ddtrace/contrib/dalli/instrumentation.rb b/lib/ddtrace/contrib/dalli/instrumentation.rb index d4433df40e..3e217654ca 100644 --- a/lib/ddtrace/contrib/dalli/instrumentation.rb +++ b/lib/ddtrace/contrib/dalli/instrumentation.rb @@ -1,5 +1,6 @@ require 'ddtrace/ext/app_types' require 'ddtrace/ext/net' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/dalli/quantize' module Datadog @@ -35,6 +36,12 @@ def request(op, *args) span.resource = op.to_s.upcase span.service = datadog_configuration[:service_name] span.span_type = Datadog::Ext::AppTypes::CACHE + + # Set analytics sample rate + if Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, datadog_configuration[:analytics_sample_rate]) + end + span.set_tag(Datadog::Ext::NET::TARGET_HOST, hostname) span.set_tag(Datadog::Ext::NET::TARGET_PORT, port) cmd = Datadog::Contrib::Dalli::Quantize.format_command(op, args) diff --git a/lib/ddtrace/contrib/delayed_job/configuration/settings.rb b/lib/ddtrace/contrib/delayed_job/configuration/settings.rb index a2f3a7a26a..54fe042723 100644 --- a/lib/ddtrace/contrib/delayed_job/configuration/settings.rb +++ b/lib/ddtrace/contrib/delayed_job/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the DelayedJob integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/delayed_job/ext.rb b/lib/ddtrace/contrib/delayed_job/ext.rb index 0522a39fbd..6b2e76dcc8 100644 --- a/lib/ddtrace/contrib/delayed_job/ext.rb +++ b/lib/ddtrace/contrib/delayed_job/ext.rb @@ -4,7 +4,7 @@ module DelayedJob # DelayedJob integration constants module Ext APP = 'delayed_job'.freeze - ENV_ANALYTICS_ENALBED = 'DD_DELAYED_JOB_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_DELAYED_JOB_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_DELAYED_JOB_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'delayed_job'.freeze SPAN_JOB = 'delayed_job'.freeze diff --git a/lib/ddtrace/contrib/elasticsearch/configuration/settings.rb b/lib/ddtrace/contrib/elasticsearch/configuration/settings.rb index 5fd362d095..84686f71d3 100644 --- a/lib/ddtrace/contrib/elasticsearch/configuration/settings.rb +++ b/lib/ddtrace/contrib/elasticsearch/configuration/settings.rb @@ -7,6 +7,14 @@ module Elasticsearch module Configuration # Custom settings for the Elasticsearch integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :quantize, default: {} option :service_name, default: Ext::SERVICE_NAME end diff --git a/lib/ddtrace/contrib/elasticsearch/ext.rb b/lib/ddtrace/contrib/elasticsearch/ext.rb index ae34391d6a..a9ce92310d 100644 --- a/lib/ddtrace/contrib/elasticsearch/ext.rb +++ b/lib/ddtrace/contrib/elasticsearch/ext.rb @@ -4,10 +4,10 @@ module Elasticsearch # Elasticsearch integration constants module Ext APP = 'elasticsearch'.freeze + ENV_ANALYTICS_ENABLED = 'DD_ELASTICSEARCH_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_ELASTICSEARCH_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'elasticsearch'.freeze - SPAN_QUERY = 'elasticsearch.query'.freeze - TAG_BODY = 'elasticsearch.body'.freeze TAG_METHOD = 'elasticsearch.method'.freeze TAG_PARAMS = 'elasticsearch.params'.freeze diff --git a/lib/ddtrace/contrib/elasticsearch/patcher.rb b/lib/ddtrace/contrib/elasticsearch/patcher.rb index cef4046922..633b13de1e 100644 --- a/lib/ddtrace/contrib/elasticsearch/patcher.rb +++ b/lib/ddtrace/contrib/elasticsearch/patcher.rb @@ -1,6 +1,7 @@ require 'ddtrace/contrib/patcher' require 'ddtrace/ext/app_types' require 'ddtrace/ext/net' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/elasticsearch/ext' module Datadog @@ -83,11 +84,16 @@ def perform_request(*args) params = JSON.generate(params) if params && !params.is_a?(String) body = JSON.generate(body) if body && !body.is_a?(String) + # Set analytics sample rate + if Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, datadog_configuration[:analytics_sample_rate]) + end + span.set_tag(Datadog::Contrib::Elasticsearch::Ext::TAG_METHOD, method) span.set_tag(Datadog::Contrib::Elasticsearch::Ext::TAG_URL, url) span.set_tag(Datadog::Contrib::Elasticsearch::Ext::TAG_PARAMS, params) if params if body - quantize_options = Datadog.configuration[:elasticsearch][:quantize] + quantize_options = datadog_configuration[:quantize] quantized_body = Datadog::Contrib::Elasticsearch::Quantize.format_body(body, quantize_options) span.set_tag(Datadog::Contrib::Elasticsearch::Ext::TAG_BODY, quantized_body) end @@ -106,6 +112,10 @@ def perform_request(*args) end response end + + def datadog_configuration + Datadog.configuration[:elasticsearch] + end end end end diff --git a/lib/ddtrace/contrib/excon/configuration/settings.rb b/lib/ddtrace/contrib/excon/configuration/settings.rb index 1e2e59954a..7c51e95a0a 100644 --- a/lib/ddtrace/contrib/excon/configuration/settings.rb +++ b/lib/ddtrace/contrib/excon/configuration/settings.rb @@ -7,6 +7,14 @@ module Excon module Configuration # Custom settings for the Excon integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :distributed_tracing, default: true option :error_handler, default: nil option :service_name, default: Ext::SERVICE_NAME diff --git a/lib/ddtrace/contrib/excon/ext.rb b/lib/ddtrace/contrib/excon/ext.rb index b080436840..5f1f9a8818 100644 --- a/lib/ddtrace/contrib/excon/ext.rb +++ b/lib/ddtrace/contrib/excon/ext.rb @@ -4,8 +4,9 @@ module Excon # Excon integration constants module Ext APP = 'excon'.freeze + ENV_ANALYTICS_ENABLED = 'DD_EXCON_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_EXCON_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'excon'.freeze - SPAN_REQUEST = 'excon.request'.freeze end end diff --git a/lib/ddtrace/contrib/excon/middleware.rb b/lib/ddtrace/contrib/excon/middleware.rb index a1d5500cfe..978d0274fa 100644 --- a/lib/ddtrace/contrib/excon/middleware.rb +++ b/lib/ddtrace/contrib/excon/middleware.rb @@ -3,6 +3,7 @@ require 'ddtrace/ext/net' require 'ddtrace/ext/distributed' require 'ddtrace/propagation/http_propagator' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/excon/ext' module Datadog @@ -82,6 +83,14 @@ def tracer @options[:tracer] end + def analytics_enabled? + Contrib::Analytics.enabled?(@options[:analytics_enabled]) + end + + def analytics_sample_rate + @options[:analytics_sample_rate] + end + def distributed_tracing? @options[:distributed_tracing] == true && tracer.enabled end @@ -98,6 +107,12 @@ def annotate!(span, datum) span.resource = datum[:method].to_s.upcase span.service = service_name(datum) span.span_type = Datadog::Ext::HTTP::TYPE + + # Set analytics sample rate + if analytics_enabled? + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) + end + span.set_tag(Datadog::Ext::HTTP::URL, datum[:path]) span.set_tag(Datadog::Ext::HTTP::METHOD, datum[:method].to_s.upcase) span.set_tag(Datadog::Ext::NET::TARGET_HOST, datum[:host]) diff --git a/lib/ddtrace/contrib/extensions.rb b/lib/ddtrace/contrib/extensions.rb new file mode 100644 index 0000000000..6eabc39d06 --- /dev/null +++ b/lib/ddtrace/contrib/extensions.rb @@ -0,0 +1,62 @@ +require 'ddtrace/contrib/registry' + +module Datadog + module Contrib + # Extensions that can be added to the base library + # Adds registry, configuration access for integrations. + module Extensions + def self.extended(base) + Datadog.send(:extend, Helpers) + Datadog::Configuration::Settings.send(:include, Configuration) + end + + # Helper methods for Datadog module. + module Helpers + def registry + configuration.registry + end + end + + # Extensions for Datadog::Configuration::Settings + module Configuration + InvalidIntegrationError = Class.new(StandardError) + + def self.included(base) + # Add the additional options to the global configuration settings + base.instance_eval do + option :registry, default: Registry.new + end + end + + def initialize(options = {}) + super + set_option(:registry, options[:registry]) if options.key?(:registry) + end + + def [](integration_name, configuration_name = :default) + integration = fetch_integration(integration_name) + integration.configuration(configuration_name) unless integration.nil? + end + + def use(integration_name, options = {}, &block) + integration = fetch_integration(integration_name) + + unless integration.nil? + configuration_name = options[:describes] || :default + filtered_options = options.reject { |k, _v| k == :describes } + integration.configure(configuration_name, filtered_options, &block) + end + + integration.patch if integration.respond_to?(:patch) + end + + private + + def fetch_integration(name) + get_option(:registry)[name] || + raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") + end + end + end + end +end diff --git a/lib/ddtrace/contrib/faraday/configuration/settings.rb b/lib/ddtrace/contrib/faraday/configuration/settings.rb index 6383a702f9..f478183d65 100644 --- a/lib/ddtrace/contrib/faraday/configuration/settings.rb +++ b/lib/ddtrace/contrib/faraday/configuration/settings.rb @@ -12,6 +12,14 @@ class Settings < Contrib::Configuration::Settings Datadog::Ext::HTTP::ERROR_RANGE.cover?(env[:status]) end + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :distributed_tracing, default: true option :error_handler, default: DEFAULT_ERROR_HANDLER option :service_name, default: Ext::SERVICE_NAME diff --git a/lib/ddtrace/contrib/faraday/ext.rb b/lib/ddtrace/contrib/faraday/ext.rb index 8b90d447ae..2ba099ddfb 100644 --- a/lib/ddtrace/contrib/faraday/ext.rb +++ b/lib/ddtrace/contrib/faraday/ext.rb @@ -4,8 +4,9 @@ module Faraday # Faraday integration constants module Ext APP = 'faraday'.freeze + ENV_ANALYTICS_ENABLED = 'DD_FARADAY_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_FARADAY_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'faraday'.freeze - SPAN_REQUEST = 'faraday.request'.freeze end end diff --git a/lib/ddtrace/contrib/faraday/middleware.rb b/lib/ddtrace/contrib/faraday/middleware.rb index 66fbe757fb..95854b2f86 100644 --- a/lib/ddtrace/contrib/faraday/middleware.rb +++ b/lib/ddtrace/contrib/faraday/middleware.rb @@ -2,6 +2,7 @@ require 'ddtrace/ext/http' require 'ddtrace/ext/net' require 'ddtrace/propagation/http_propagator' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/faraday/ext' module Datadog @@ -33,6 +34,12 @@ def annotate!(span, env) span.resource = env[:method].to_s.upcase span.service = service_name(env) span.span_type = Datadog::Ext::HTTP::TYPE + + # Set analytics sample rate + if analytics_enabled? + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) + end + span.set_tag(Datadog::Ext::HTTP::URL, env[:url].path) span.set_tag(Datadog::Ext::HTTP::METHOD, env[:method].to_s.upcase) span.set_tag(Datadog::Ext::NET::TARGET_HOST, env[:url].host) @@ -65,6 +72,14 @@ def service_name(env) options[:service_name] end + def analytics_enabled? + Contrib::Analytics.enabled?(options[:analytics_enabled]) + end + + def analytics_sample_rate + options[:analytics_sample_rate] + end + def setup_service! return if options[:service_name] == datadog_configuration[:service_name] diff --git a/lib/ddtrace/contrib/grape/configuration/settings.rb b/lib/ddtrace/contrib/grape/configuration/settings.rb index 4558df2cbb..a37364f33e 100644 --- a/lib/ddtrace/contrib/grape/configuration/settings.rb +++ b/lib/ddtrace/contrib/grape/configuration/settings.rb @@ -8,6 +8,14 @@ module Grape module Configuration # Custom settings for the Grape integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :enabled, default: true option :service_name, default: Ext::SERVICE_NAME end diff --git a/lib/ddtrace/contrib/grape/endpoint.rb b/lib/ddtrace/contrib/grape/endpoint.rb index abd3b96831..51bda0aa8e 100644 --- a/lib/ddtrace/contrib/grape/endpoint.rb +++ b/lib/ddtrace/contrib/grape/endpoint.rb @@ -1,5 +1,6 @@ require 'ddtrace/ext/http' require 'ddtrace/ext/errors' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/rack/ext' module Datadog @@ -79,6 +80,11 @@ def endpoint_run(name, start, finish, id, payload) request_span.resource = resource end + # Set analytics sample rate + if analytics_enabled? + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) + end + # catch thrown exceptions span.set_error(payload[:exception_object]) unless payload[:exception_object].nil? @@ -145,6 +151,11 @@ def endpoint_run_filters(name, start, finish, id, payload) ) begin + # Set analytics sample rate + if analytics_enabled? + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) + end + # catch thrown exceptions span.set_error(payload[:exception_object]) unless payload[:exception_object].nil? span.set_tag(Ext::TAG_FILTER_TYPE, type.to_s) @@ -166,6 +177,14 @@ def service_name datadog_configuration[:service_name] end + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end + def enabled? datadog_configuration[:enabled] == true end diff --git a/lib/ddtrace/contrib/grape/ext.rb b/lib/ddtrace/contrib/grape/ext.rb index 95f22a41ea..530c31bdbe 100644 --- a/lib/ddtrace/contrib/grape/ext.rb +++ b/lib/ddtrace/contrib/grape/ext.rb @@ -4,12 +4,12 @@ module Grape # Grape integration constants module Ext APP = 'grape'.freeze + ENV_ANALYTICS_ENABLED = 'DD_GRAPE_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_GRAPE_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'grape'.freeze - SPAN_ENDPOINT_RENDER = 'grape.endpoint_render'.freeze SPAN_ENDPOINT_RUN = 'grape.endpoint_run'.freeze SPAN_ENDPOINT_RUN_FILTERS = 'grape.endpoint_run_filters'.freeze - TAG_FILTER_TYPE = 'grape.filter.type'.freeze TAG_ROUTE_ENDPOINT = 'grape.route.endpoint'.freeze TAG_ROUTE_PATH = 'grape.route.path'.freeze diff --git a/lib/ddtrace/contrib/graphql/configuration/settings.rb b/lib/ddtrace/contrib/graphql/configuration/settings.rb index fa47f4c575..68a35ed983 100644 --- a/lib/ddtrace/contrib/graphql/configuration/settings.rb +++ b/lib/ddtrace/contrib/graphql/configuration/settings.rb @@ -8,6 +8,14 @@ module GraphQL module Configuration # Custom settings for the GraphQL integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :schemas option :service_name, default: Ext::SERVICE_NAME, depends_on: [:tracer] do |value| get_option(:tracer).set_service_info(value, Ext::APP, Datadog::Ext::AppTypes::WEB) diff --git a/lib/ddtrace/contrib/graphql/ext.rb b/lib/ddtrace/contrib/graphql/ext.rb index 8bcbd266a6..c30bc40d50 100644 --- a/lib/ddtrace/contrib/graphql/ext.rb +++ b/lib/ddtrace/contrib/graphql/ext.rb @@ -4,6 +4,8 @@ module GraphQL # GraphQL integration constants module Ext APP = 'ruby-graphql'.freeze + ENV_ANALYTICS_ENABLED = 'DD_GRAPHQL_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_GRAPHQL_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'ruby-graphql'.freeze end end diff --git a/lib/ddtrace/contrib/graphql/patcher.rb b/lib/ddtrace/contrib/graphql/patcher.rb index f397ea61c8..8072b914b1 100644 --- a/lib/ddtrace/contrib/graphql/patcher.rb +++ b/lib/ddtrace/contrib/graphql/patcher.rb @@ -30,19 +30,25 @@ def patch def patch_schema!(schema) tracer = get_option(:tracer) service_name = get_option(:service_name) + analytics_enabled = Contrib::Analytics.enabled?(get_option(:analytics_enabled)) + analytics_sample_rate = get_option(:analytics_sample_rate) if schema.respond_to?(:use) schema.use( ::GraphQL::Tracing::DataDogTracing, tracer: tracer, - service: service_name + service: service_name, + analytics_enabled: analytics_enabled, + analytics_sample_rate: analytics_sample_rate ) else schema.define do use( ::GraphQL::Tracing::DataDogTracing, tracer: tracer, - service: service_name + service: service_name, + analytics_enabled: analytics_enabled, + analytics_sample_rate: analytics_sample_rate ) end end diff --git a/lib/ddtrace/contrib/grpc/configuration/settings.rb b/lib/ddtrace/contrib/grpc/configuration/settings.rb index 11947e33f4..724ace1d61 100644 --- a/lib/ddtrace/contrib/grpc/configuration/settings.rb +++ b/lib/ddtrace/contrib/grpc/configuration/settings.rb @@ -7,6 +7,14 @@ module GRPC module Configuration # Custom settings for the gRPC integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME end end diff --git a/lib/ddtrace/contrib/grpc/datadog_interceptor.rb b/lib/ddtrace/contrib/grpc/datadog_interceptor.rb index e3f9f6240c..b2eb41b882 100644 --- a/lib/ddtrace/contrib/grpc/datadog_interceptor.rb +++ b/lib/ddtrace/contrib/grpc/datadog_interceptor.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/app_types' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/grpc/ext' module Datadog @@ -55,6 +56,14 @@ def tracer def service_name (datadog_pin && datadog_pin.service_name) || datadog_configuration[:service_name] end + + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end require_relative 'datadog_interceptor/client' diff --git a/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb b/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb index b30d850d04..6337436475 100644 --- a/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +++ b/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/http' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/grpc/ext' module Datadog @@ -33,6 +34,9 @@ def annotate!(span, metadata) span.set_tag(header, value) end + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + Datadog::GRPCPropagator .inject!(span.context, metadata) rescue StandardError => e diff --git a/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb b/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb index 6e986bee32..c704fd65a9 100644 --- a/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb +++ b/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/http' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/grpc/ext' module Datadog @@ -44,6 +45,9 @@ def annotate!(span, metadata) next if reserved_headers.include?(header) span.set_tag(header, value) end + + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? rescue StandardError => e Datadog::Tracer.log.debug("GRPC client trace failed: #{e}") end diff --git a/lib/ddtrace/contrib/grpc/ext.rb b/lib/ddtrace/contrib/grpc/ext.rb index 372edf262e..0706a004bb 100644 --- a/lib/ddtrace/contrib/grpc/ext.rb +++ b/lib/ddtrace/contrib/grpc/ext.rb @@ -4,8 +4,9 @@ module GRPC # gRPC integration constants module Ext APP = 'grpc'.freeze + ENV_ANALYTICS_ENABLED = 'DD_GRPC_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_GRPC_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'grpc'.freeze - SPAN_CLIENT = 'grpc.client'.freeze SPAN_SERVICE = 'grpc.service'.freeze end diff --git a/lib/ddtrace/contrib/http/configuration/settings.rb b/lib/ddtrace/contrib/http/configuration/settings.rb index c4a90dce88..470d1521d8 100644 --- a/lib/ddtrace/contrib/http/configuration/settings.rb +++ b/lib/ddtrace/contrib/http/configuration/settings.rb @@ -7,6 +7,14 @@ module HTTP module Configuration # Custom settings for the HTTP integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :distributed_tracing, default: true option :service_name, default: Ext::SERVICE_NAME end diff --git a/lib/ddtrace/contrib/http/ext.rb b/lib/ddtrace/contrib/http/ext.rb index fbffd405fc..7cef082d5b 100644 --- a/lib/ddtrace/contrib/http/ext.rb +++ b/lib/ddtrace/contrib/http/ext.rb @@ -4,8 +4,9 @@ module HTTP # HTTP integration constants module Ext APP = 'net/http'.freeze + ENV_ANALYTICS_ENABLED = 'DD_HTTP_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_HTTP_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'net/http'.freeze - SPAN_REQUEST = 'http.request'.freeze end end diff --git a/lib/ddtrace/contrib/http/instrumentation.rb b/lib/ddtrace/contrib/http/instrumentation.rb index 0fae875d8c..fa842d7835 100644 --- a/lib/ddtrace/contrib/http/instrumentation.rb +++ b/lib/ddtrace/contrib/http/instrumentation.rb @@ -4,6 +4,7 @@ require 'ddtrace/ext/http' require 'ddtrace/ext/net' require 'ddtrace/ext/distributed' +require 'ddtrace/contrib/analytics' module Datadog module Contrib @@ -22,6 +23,17 @@ def self.included(base) end end + # Span hook invoked after request is completed. + def self.after_request(&block) + if block_given? + # Set hook + @after_request = block + else + # Get hook + @after_request ||= nil + end + end + # Compatibility shim for Rubies not supporting `.prepend` module InstanceMethodsCompatibility def self.included(base) @@ -38,7 +50,6 @@ def request(*args, &block) # InstanceMethods - implementing instrumentation module InstanceMethods - # rubocop:disable Metrics/MethodLength def request(req, body = nil, &block) # :yield: +response+ pin = datadog_pin return super(req, body, &block) unless pin && pin.tracer @@ -53,12 +64,7 @@ def request(req, body = nil, &block) # :yield: +response+ begin span.service = pin.service span.span_type = Datadog::Ext::HTTP::TYPE - span.resource = req.method - # Using the method as a resource, as URL/path can trigger - # a possibly infinite number of resources. - span.set_tag(Datadog::Ext::HTTP::URL, req.path) - span.set_tag(Datadog::Ext::HTTP::METHOD, req.method) if pin.tracer.enabled && !Datadog::Contrib::HTTP.should_skip_distributed_tracing?(pin) req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID, span.trace_id) @@ -75,26 +81,41 @@ def request(req, body = nil, &block) # :yield: +response+ ensure response = super(req, body, &block) end - span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.code) - if req.respond_to?(:uri) && req.uri - span.set_tag(Datadog::Ext::NET::TARGET_HOST, req.uri.host) - span.set_tag(Datadog::Ext::NET::TARGET_PORT, req.uri.port.to_s) - else - span.set_tag(Datadog::Ext::NET::TARGET_HOST, @address) - span.set_tag(Datadog::Ext::NET::TARGET_PORT, @port.to_s) - end - case response.code.to_i / 100 - when 4 - span.set_error(response) - when 5 - span.set_error(response) + # Add additional tags to the span. + annotate_span!(span, req, response) + + # Invoke hook, if set. + unless Contrib::HTTP::Instrumentation.after_request.nil? + Contrib::HTTP::Instrumentation.after_request.call(span, self, req, response) end response end end + def annotate_span!(span, request, response) + span.set_tag(Datadog::Ext::HTTP::URL, request.path) + span.set_tag(Datadog::Ext::HTTP::METHOD, request.method) + span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.code) + + if request.respond_to?(:uri) && request.uri + span.set_tag(Datadog::Ext::NET::TARGET_HOST, request.uri.host) + span.set_tag(Datadog::Ext::NET::TARGET_PORT, request.uri.port.to_s) + else + span.set_tag(Datadog::Ext::NET::TARGET_HOST, @address) + span.set_tag(Datadog::Ext::NET::TARGET_PORT, @port.to_s) + end + + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + + case response.code.to_i + when 400...599 + span.set_error(response) + end + end + def datadog_pin @datadog_pin ||= begin service = Datadog.configuration[:http][:service_name] @@ -103,6 +124,20 @@ def datadog_pin Datadog::Pin.new(service, app: Ext::APP, app_type: Datadog::Ext::AppTypes::WEB, tracer: tracer) end end + + private + + def datadog_configuration + Datadog.configuration[:http] + end + + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end end end diff --git a/lib/ddtrace/contrib/mongodb/configuration/settings.rb b/lib/ddtrace/contrib/mongodb/configuration/settings.rb index 89289e952a..8023f12716 100644 --- a/lib/ddtrace/contrib/mongodb/configuration/settings.rb +++ b/lib/ddtrace/contrib/mongodb/configuration/settings.rb @@ -9,6 +9,14 @@ module Configuration class Settings < Contrib::Configuration::Settings DEFAULT_QUANTIZE = { show: [:collection, :database, :operation] }.freeze + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :quantize, default: DEFAULT_QUANTIZE option :service_name, default: Ext::SERVICE_NAME end diff --git a/lib/ddtrace/contrib/mongodb/ext.rb b/lib/ddtrace/contrib/mongodb/ext.rb index 61cd119828..3568ddda2e 100644 --- a/lib/ddtrace/contrib/mongodb/ext.rb +++ b/lib/ddtrace/contrib/mongodb/ext.rb @@ -4,11 +4,11 @@ module MongoDB # MongoDB integration constants module Ext APP = 'mongodb'.freeze + ENV_ANALYTICS_ENABLED = 'DD_MONGO_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_MONGO_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'mongodb'.freeze - SPAN_COMMAND = 'mongo.cmd'.freeze SPAN_TYPE_COMMAND = 'mongodb'.freeze - TAG_COLLECTION = 'mongodb.collection'.freeze TAG_DB = 'mongodb.db'.freeze TAG_OPERATION = 'mongodb.operation'.freeze diff --git a/lib/ddtrace/contrib/mongodb/subscribers.rb b/lib/ddtrace/contrib/mongodb/subscribers.rb index e10c36a42c..ad7e278089 100644 --- a/lib/ddtrace/contrib/mongodb/subscribers.rb +++ b/lib/ddtrace/contrib/mongodb/subscribers.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/mongodb/ext' require 'ddtrace/contrib/mongodb/parsers' @@ -23,6 +24,11 @@ def started(event) query = MongoDB.query_builder(event.command_name, event.database_name, event.command) serialized_query = query.to_s + # Set analytics sample rate + if analytics_enabled? + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) + end + # add operation tags; the full query is stored and used as a resource, # since it has been quantized and reduced span.set_tag(Ext::TAG_DB, query['database']) @@ -84,6 +90,18 @@ def clear_span(event) return if Thread.current[:datadog_mongo_span].nil? Thread.current[:datadog_mongo_span].delete(event.request_id) end + + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end + + def datadog_configuration + Datadog.configuration[:mongo] + end end end end diff --git a/lib/ddtrace/contrib/mysql2/configuration/settings.rb b/lib/ddtrace/contrib/mysql2/configuration/settings.rb index 64770964d6..f10236235b 100644 --- a/lib/ddtrace/contrib/mysql2/configuration/settings.rb +++ b/lib/ddtrace/contrib/mysql2/configuration/settings.rb @@ -7,6 +7,14 @@ module Mysql2 module Configuration # Custom settings for the Mysql2 integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME end end diff --git a/lib/ddtrace/contrib/mysql2/ext.rb b/lib/ddtrace/contrib/mysql2/ext.rb index 1e6cfc5055..3ff2538ee1 100644 --- a/lib/ddtrace/contrib/mysql2/ext.rb +++ b/lib/ddtrace/contrib/mysql2/ext.rb @@ -4,10 +4,10 @@ module Mysql2 # Mysql2 integration constants module Ext APP = 'mysql2'.freeze + ENV_ANALYTICS_ENABLED = 'DD_MYSQL2_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_MYSQL2_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'mysql2'.freeze - SPAN_QUERY = 'mysql2.query'.freeze - TAG_DB_NAME = 'mysql2.db.name'.freeze end end diff --git a/lib/ddtrace/contrib/mysql2/client.rb b/lib/ddtrace/contrib/mysql2/instrumentation.rb similarity index 64% rename from lib/ddtrace/contrib/mysql2/client.rb rename to lib/ddtrace/contrib/mysql2/instrumentation.rb index 0a1ea48746..79cf5580b6 100644 --- a/lib/ddtrace/contrib/mysql2/client.rb +++ b/lib/ddtrace/contrib/mysql2/instrumentation.rb @@ -1,20 +1,19 @@ require 'ddtrace/ext/app_types' require 'ddtrace/ext/net' require 'ddtrace/ext/sql' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/mysql2/ext' module Datadog module Contrib module Mysql2 # Mysql2::Client patch module - module Client - module_function - - def included(base) + module Instrumentation + def self.included(base) if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') base.class_eval do - alias_method :aliased_query, :query - remove_method :query + # Instance methods + include InstanceMethodsCompatibility include InstanceMethods end else @@ -24,22 +23,29 @@ def included(base) # Mysql2::Client patch 1.9.3 instance methods module InstanceMethodsCompatibility - def query(*args) - aliased_query(*args) + def self.included(base) + base.class_eval do + alias_method :query_without_datadog, :query + remove_method :query + end + end + + def query(*args, &block) + query_without_datadog(*args, &block) end end # Mysql2::Client patch instance methods module InstanceMethods - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') - include InstanceMethodsCompatibility - end - def query(sql, options = {}) datadog_pin.tracer.trace(Ext::SPAN_QUERY) do |span| span.resource = sql span.service = datadog_pin.service span.span_type = Datadog::Ext::SQL::TYPE + + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + span.set_tag(Ext::TAG_DB_NAME, query_options[:database]) span.set_tag(Datadog::Ext::NET::TARGET_HOST, query_options[:host]) span.set_tag(Datadog::Ext::NET::TARGET_PORT, query_options[:port]) @@ -55,6 +61,20 @@ def datadog_pin tracer: Datadog.configuration[:mysql2][:tracer] ) end + + private + + def datadog_configuration + Datadog.configuration[:mysql2] + end + + def analytics_enabled? + datadog_configuration[:analytics_enabled] + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end end end diff --git a/lib/ddtrace/contrib/mysql2/patcher.rb b/lib/ddtrace/contrib/mysql2/patcher.rb index 24f9aa9e53..b067c9910e 100644 --- a/lib/ddtrace/contrib/mysql2/patcher.rb +++ b/lib/ddtrace/contrib/mysql2/patcher.rb @@ -1,5 +1,5 @@ require 'ddtrace/contrib/patcher' -require 'ddtrace/contrib/mysql2/client' +require 'ddtrace/contrib/mysql2/instrumentation' module Datadog module Contrib @@ -25,7 +25,7 @@ def patch end def patch_mysql2_client - ::Mysql2::Client.send(:include, Client) + ::Mysql2::Client.send(:include, Instrumentation) end end end diff --git a/lib/ddtrace/contrib/racecar/configuration/settings.rb b/lib/ddtrace/contrib/racecar/configuration/settings.rb index f808ebe461..07364cfe88 100644 --- a/lib/ddtrace/contrib/racecar/configuration/settings.rb +++ b/lib/ddtrace/contrib/racecar/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the Racecar integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/racecar/ext.rb b/lib/ddtrace/contrib/racecar/ext.rb index 0ac801285b..d0ee74627b 100644 --- a/lib/ddtrace/contrib/racecar/ext.rb +++ b/lib/ddtrace/contrib/racecar/ext.rb @@ -4,7 +4,7 @@ module Racecar # Racecar integration constants module Ext APP = 'racecar'.freeze - ENV_ANALYTICS_ENALBED = 'DD_RACECAR_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_RACECAR_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_RACECAR_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'racecar'.freeze SPAN_BATCH = 'racecar.batch'.freeze diff --git a/lib/ddtrace/contrib/rack/configuration/settings.rb b/lib/ddtrace/contrib/rack/configuration/settings.rb index c3d157bdb7..00367c240d 100644 --- a/lib/ddtrace/contrib/rack/configuration/settings.rb +++ b/lib/ddtrace/contrib/rack/configuration/settings.rb @@ -15,7 +15,7 @@ class Settings < Contrib::Configuration::Settings }.freeze option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/rack/ext.rb b/lib/ddtrace/contrib/rack/ext.rb index 1ce0c1fec5..0785a9bedd 100644 --- a/lib/ddtrace/contrib/rack/ext.rb +++ b/lib/ddtrace/contrib/rack/ext.rb @@ -4,7 +4,7 @@ module Rack # Rack integration constants module Ext APP = 'rack'.freeze - ENV_ANALYTICS_ENALBED = 'DD_RACK_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_RACK_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_RACK_ANALYTICS_SAMPLE_RATE'.freeze RACK_ENV_REQUEST_SPAN = 'datadog.rack_request_span'.freeze SERVICE_NAME = 'rack'.freeze diff --git a/lib/ddtrace/contrib/rails/action_controller.rb b/lib/ddtrace/contrib/rails/action_controller.rb index e03d1f6b54..b0afecd879 100644 --- a/lib/ddtrace/contrib/rails/action_controller.rb +++ b/lib/ddtrace/contrib/rails/action_controller.rb @@ -50,6 +50,9 @@ def self.finish_processing(payload) rack_request_span.resource = span.resource if rack_request_span end + # Set analytics sample rate + Utils.set_analytics_sample_rate(span) + span.set_tag(Ext::TAG_ROUTE_ACTION, payload.fetch(:action)) span.set_tag(Ext::TAG_ROUTE_CONTROLLER, payload.fetch(:controller)) diff --git a/lib/ddtrace/contrib/rails/active_support.rb b/lib/ddtrace/contrib/rails/active_support.rb index b7ab84a981..a4eec34a7d 100644 --- a/lib/ddtrace/contrib/rails/active_support.rb +++ b/lib/ddtrace/contrib/rails/active_support.rb @@ -53,6 +53,7 @@ def self.finish_trace_cache(payload) normalized_key = ::ActiveSupport::Cache.expand_cache_key(payload.fetch(:key)) cache_key = Datadog::Utils.truncate(normalized_key, Ext::QUANTIZE_CACHE_MAX_KEY_SIZE) span.set_tag(Ext::TAG_CACHE_KEY, cache_key) + span.set_error(payload[:exception]) if payload[:exception] ensure span.finish diff --git a/lib/ddtrace/contrib/rails/configuration/settings.rb b/lib/ddtrace/contrib/rails/configuration/settings.rb index 39ab0a9a90..aa5fe29410 100644 --- a/lib/ddtrace/contrib/rails/configuration/settings.rb +++ b/lib/ddtrace/contrib/rails/configuration/settings.rb @@ -6,6 +6,14 @@ module Rails module Configuration # Custom settings for the Rails integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :cache_service option :controller_service option :database_service, depends_on: [:service_name] do |value| diff --git a/lib/ddtrace/contrib/rails/ext.rb b/lib/ddtrace/contrib/rails/ext.rb index 7fa8381e13..2527172a00 100644 --- a/lib/ddtrace/contrib/rails/ext.rb +++ b/lib/ddtrace/contrib/rails/ext.rb @@ -4,20 +4,17 @@ module Rails # Rails integration constants module Ext APP = 'rails'.freeze - + ENV_ANALYTICS_ENABLED = 'DD_RAILS_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_RAILS_ANALYTICS_SAMPLE_RATE'.freeze QUANTIZE_CACHE_MAX_KEY_SIZE = 300 - RESOURCE_CACHE_DELETE = 'DELETE'.freeze RESOURCE_CACHE_GET = 'GET'.freeze RESOURCE_CACHE_SET = 'SET'.freeze - SPAN_ACTION_CONTROLLER = 'rails.action_controller'.freeze SPAN_CACHE = 'rails.cache'.freeze SPAN_RENDER_PARTIAL = 'rails.render_partial'.freeze SPAN_RENDER_TEMPLATE = 'rails.render_template'.freeze - SPAN_TYPE_CACHE = 'cache'.freeze - TAG_CACHE_BACKEND = 'rails.cache.backend'.freeze TAG_CACHE_KEY = 'rails.cache.key'.freeze TAG_LAYOUT = 'rails.layout'.freeze diff --git a/lib/ddtrace/contrib/rails/utils.rb b/lib/ddtrace/contrib/rails/utils.rb index 5bfda5bbcb..ebd2518263 100644 --- a/lib/ddtrace/contrib/rails/utils.rb +++ b/lib/ddtrace/contrib/rails/utils.rb @@ -1,3 +1,5 @@ +require 'ddtrace/contrib/analytics' + module Datadog module Contrib module Rails @@ -11,7 +13,7 @@ module Utils def self.normalize_template_name(name) return if name.nil? - base_path = Datadog.configuration[:rails][:template_base_path] + base_path = datadog_configuration[:template_base_path] sections_view = name.split(base_path) if sections_view.length == 1 @@ -42,6 +44,20 @@ def self.exception_is_error?(exception) true end end + + def self.set_analytics_sample_rate(span) + if Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, datadog_configuration[:analytics_sample_rate]) + end + end + + class << self + private + + def datadog_configuration + Datadog.configuration[:rails] + end + end end end end diff --git a/lib/ddtrace/contrib/rake/configuration/settings.rb b/lib/ddtrace/contrib/rake/configuration/settings.rb index 63bb82e238..1d0b69975f 100644 --- a/lib/ddtrace/contrib/rake/configuration/settings.rb +++ b/lib/ddtrace/contrib/rake/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the Rake integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/rake/ext.rb b/lib/ddtrace/contrib/rake/ext.rb index bcf588f5be..4267872c92 100644 --- a/lib/ddtrace/contrib/rake/ext.rb +++ b/lib/ddtrace/contrib/rake/ext.rb @@ -4,7 +4,7 @@ module Rake # Rake integration constants module Ext APP = 'rake'.freeze - ENV_ANALYTICS_ENALBED = 'DD_RAKE_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_RAKE_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_RAKE_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'rake'.freeze SPAN_INVOKE = 'rake.invoke'.freeze diff --git a/lib/ddtrace/contrib/redis/configuration/settings.rb b/lib/ddtrace/contrib/redis/configuration/settings.rb index e29ff8c9a2..af5eac3776 100644 --- a/lib/ddtrace/contrib/redis/configuration/settings.rb +++ b/lib/ddtrace/contrib/redis/configuration/settings.rb @@ -7,6 +7,14 @@ module Redis module Configuration # Custom settings for the Redis integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :service_name, default: Ext::SERVICE_NAME end end diff --git a/lib/ddtrace/contrib/redis/ext.rb b/lib/ddtrace/contrib/redis/ext.rb index b20bf7e349..79e9ddb0b2 100644 --- a/lib/ddtrace/contrib/redis/ext.rb +++ b/lib/ddtrace/contrib/redis/ext.rb @@ -4,15 +4,14 @@ module Redis # Redis integration constants module Ext APP = 'redis'.freeze - SERVICE_NAME = 'redis'.freeze - TYPE = 'redis'.freeze - + ENV_ANALYTICS_ENABLED = 'DD_REDIS_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_REDIS_ANALYTICS_SAMPLE_RATE'.freeze METRIC_PIPELINE_LEN = 'redis.pipeline_length'.freeze - + SERVICE_NAME = 'redis'.freeze SPAN_COMMAND = 'redis.command'.freeze - TAG_DB = 'out.redis_db'.freeze TAG_RAW_COMMAND = 'redis.raw_command'.freeze + TYPE = 'redis'.freeze end end end diff --git a/lib/ddtrace/contrib/redis/tags.rb b/lib/ddtrace/contrib/redis/tags.rb index e9881a1d09..9856a39179 100644 --- a/lib/ddtrace/contrib/redis/tags.rb +++ b/lib/ddtrace/contrib/redis/tags.rb @@ -1,4 +1,5 @@ require 'ddtrace/ext/net' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/redis/ext' module Datadog @@ -6,13 +7,30 @@ module Contrib module Redis # Tags handles generic common tags assignment. module Tags - module_function + class << self + def set_common_tags(client, span) + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? - def set_common_tags(client, span) - span.set_tag Datadog::Ext::NET::TARGET_HOST, client.host - span.set_tag Datadog::Ext::NET::TARGET_PORT, client.port - span.set_tag Ext::TAG_DB, client.db - span.set_tag Ext::TAG_RAW_COMMAND, span.resource + span.set_tag Datadog::Ext::NET::TARGET_HOST, client.host + span.set_tag Datadog::Ext::NET::TARGET_PORT, client.port + span.set_tag Ext::TAG_DB, client.db + span.set_tag Ext::TAG_RAW_COMMAND, span.resource + end + + private + + def datadog_configuration + Datadog.configuration[:redis] + end + + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end end end diff --git a/lib/ddtrace/contrib/registerable.rb b/lib/ddtrace/contrib/registerable.rb index ba6d702d5e..269da9a33a 100644 --- a/lib/ddtrace/contrib/registerable.rb +++ b/lib/ddtrace/contrib/registerable.rb @@ -1,4 +1,4 @@ -require 'ddtrace/registry' +require 'ddtrace/contrib/registry' module Datadog module Contrib diff --git a/lib/ddtrace/contrib/registry.rb b/lib/ddtrace/contrib/registry.rb new file mode 100644 index 0000000000..5bba7f74a0 --- /dev/null +++ b/lib/ddtrace/contrib/registry.rb @@ -0,0 +1,42 @@ +module Datadog + module Contrib + # Registry is a collection of integrations. + class Registry + include Enumerable + + Entry = Struct.new(:name, :klass, :auto_patch) + + def initialize + @data = {} + @mutex = Mutex.new + end + + def add(name, klass, auto_patch = false) + @mutex.synchronize do + @data[name] = Entry.new(name, klass, auto_patch).freeze + end + end + + def each + @mutex.synchronize do + @data.each { |_, entry| yield(entry) } + end + end + + def [](name) + @mutex.synchronize do + entry = @data[name] + entry.klass if entry + end + end + + def to_h + @mutex.synchronize do + @data.each_with_object({}) do |(_, entry), hash| + hash[entry.name] = entry.auto_patch + end + end + end + end + end +end diff --git a/lib/ddtrace/contrib/resque/configuration/settings.rb b/lib/ddtrace/contrib/resque/configuration/settings.rb index 12a12d1437..47ba4b49ef 100644 --- a/lib/ddtrace/contrib/resque/configuration/settings.rb +++ b/lib/ddtrace/contrib/resque/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the Resque integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/resque/ext.rb b/lib/ddtrace/contrib/resque/ext.rb index effd603b75..f00f01c716 100644 --- a/lib/ddtrace/contrib/resque/ext.rb +++ b/lib/ddtrace/contrib/resque/ext.rb @@ -4,7 +4,7 @@ module Resque # Resque integration constants module Ext APP = 'resque'.freeze - ENV_ANALYTICS_ENALBED = 'DD_RESQUE_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_RESQUE_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_RESQUE_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'resque'.freeze SPAN_JOB = 'resque.job'.freeze diff --git a/lib/ddtrace/contrib/rest_client/configuration/settings.rb b/lib/ddtrace/contrib/rest_client/configuration/settings.rb index b2193de336..ceed92d9c1 100644 --- a/lib/ddtrace/contrib/rest_client/configuration/settings.rb +++ b/lib/ddtrace/contrib/rest_client/configuration/settings.rb @@ -7,6 +7,14 @@ module RestClient module Configuration # Custom settings for the RestClient integration class Settings < Contrib::Configuration::Settings + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :distributed_tracing, default: true option :service_name, default: Ext::SERVICE_NAME, depends_on: [:tracer] do |value| get_option(:tracer).set_service_info(value, Ext::APP, Datadog::Ext::AppTypes::WEB) diff --git a/lib/ddtrace/contrib/rest_client/ext.rb b/lib/ddtrace/contrib/rest_client/ext.rb index b3a9727fbc..13ec7a430a 100644 --- a/lib/ddtrace/contrib/rest_client/ext.rb +++ b/lib/ddtrace/contrib/rest_client/ext.rb @@ -4,8 +4,9 @@ module RestClient # RestClient integration constants module Ext APP = 'rest_client'.freeze + ENV_ANALYTICS_ENABLED = 'DD_REST_CLIENT_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_REST_CLIENT_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'rest_client'.freeze - SPAN_REQUEST = 'rest_client.request'.freeze end end diff --git a/lib/ddtrace/contrib/rest_client/request_patch.rb b/lib/ddtrace/contrib/rest_client/request_patch.rb index bc4fb47688..2bc82b47bc 100644 --- a/lib/ddtrace/contrib/rest_client/request_patch.rb +++ b/lib/ddtrace/contrib/rest_client/request_patch.rb @@ -47,6 +47,10 @@ def execute(&block) def datadog_tag_request(uri, span) span.resource = method.to_s.upcase + + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + span.set_tag(Datadog::Ext::HTTP::URL, uri.path) span.set_tag(Datadog::Ext::HTTP::METHOD, method.to_s.upcase) span.set_tag(Datadog::Ext::NET::TARGET_HOST, uri.host) @@ -82,9 +86,19 @@ def datadog_trace_request(uri) span.finish end + private + def datadog_configuration Datadog.configuration[:rest_client] end + + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end end end diff --git a/lib/ddtrace/contrib/sequel/configuration/settings.rb b/lib/ddtrace/contrib/sequel/configuration/settings.rb index eb08daff09..1690c1d65c 100644 --- a/lib/ddtrace/contrib/sequel/configuration/settings.rb +++ b/lib/ddtrace/contrib/sequel/configuration/settings.rb @@ -7,7 +7,13 @@ module Sequel module Configuration # Custom settings for the Sequel integration class Settings < Contrib::Configuration::Settings - # Add any custom Sequel settings or behavior here. + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true end end end diff --git a/lib/ddtrace/contrib/sequel/database.rb b/lib/ddtrace/contrib/sequel/database.rb index ad193bd606..df3b59821a 100644 --- a/lib/ddtrace/contrib/sequel/database.rb +++ b/lib/ddtrace/contrib/sequel/database.rb @@ -1,5 +1,6 @@ require 'ddtrace/ext/sql' require 'ddtrace/ext/app_types' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/sequel/ext' require 'ddtrace/contrib/sequel/utils' @@ -23,6 +24,7 @@ def run(sql, options = ::Sequel::OPTS) span.service = datadog_pin.service span.resource = opts[:query] span.span_type = Datadog::Ext::SQL::TYPE + Utils.set_analytics_sample_rate(span) span.set_tag(Ext::TAG_DB_VENDOR, adapter_name) response = super(sql, options) end diff --git a/lib/ddtrace/contrib/sequel/dataset.rb b/lib/ddtrace/contrib/sequel/dataset.rb index f0d6b2b2c7..f4f52dd86f 100644 --- a/lib/ddtrace/contrib/sequel/dataset.rb +++ b/lib/ddtrace/contrib/sequel/dataset.rb @@ -1,5 +1,6 @@ require 'ddtrace/ext/sql' require 'ddtrace/ext/app_types' +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/sequel/ext' require 'ddtrace/contrib/sequel/utils' @@ -44,6 +45,7 @@ def trace_execute(super_method, sql, options, &block) span.service = datadog_pin.service span.resource = opts[:query] span.span_type = Datadog::Ext::SQL::TYPE + Utils.set_analytics_sample_rate(span) span.set_tag(Ext::TAG_DB_VENDOR, adapter_name) response = super_method.call(sql, options, &block) end diff --git a/lib/ddtrace/contrib/sequel/ext.rb b/lib/ddtrace/contrib/sequel/ext.rb index 97a1bded86..ef745aa351 100644 --- a/lib/ddtrace/contrib/sequel/ext.rb +++ b/lib/ddtrace/contrib/sequel/ext.rb @@ -4,10 +4,10 @@ module Sequel # Sequel integration constants module Ext APP = 'sequel'.freeze + ENV_ANALYTICS_ENABLED = 'DD_SEQUEL_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_SEQUEL_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'sequel'.freeze - SPAN_QUERY = 'sequel.query'.freeze - TAG_DB_VENDOR = 'sequel.db.vendor'.freeze end end diff --git a/lib/ddtrace/contrib/sequel/utils.rb b/lib/ddtrace/contrib/sequel/utils.rb index 8e1a3111ef..30554263e6 100644 --- a/lib/ddtrace/contrib/sequel/utils.rb +++ b/lib/ddtrace/contrib/sequel/utils.rb @@ -3,24 +3,42 @@ module Contrib module Sequel # General purpose functions for Sequel module Utils - module_function + class << self + def adapter_name(database) + Datadog::Utils::Database.normalize_vendor(database.adapter_scheme.to_s) + end - def adapter_name(database) - Datadog::Utils::Database.normalize_vendor(database.adapter_scheme.to_s) - end + def parse_opts(sql, opts, db_opts) + if ::Sequel::VERSION >= '4.37.0' && !sql.is_a?(String) + # In 4.37.0, sql was converted to a prepared statement object + sql = sql.prepared_sql unless sql.is_a?(Symbol) + end + + { + name: opts[:type], + query: sql, + database: db_opts[:database], + host: db_opts[:host] + } + end + + def set_analytics_sample_rate(span) + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + end + + private - def parse_opts(sql, opts, db_opts) - if ::Sequel::VERSION >= '4.37.0' && !sql.is_a?(String) - # In 4.37.0, sql was converted to a prepared statement object - sql = sql.prepared_sql unless sql.is_a?(Symbol) + def datadog_configuration + Datadog.configuration[:sequel] end - { - name: opts[:type], - query: sql, - database: db_opts[:database], - host: db_opts[:host] - } + def analytics_enabled? + Contrib::Analytics.enabled?(datadog_configuration[:analytics_enabled]) + end + + def analytics_sample_rate + datadog_configuration[:analytics_sample_rate] + end end end end diff --git a/lib/ddtrace/contrib/shoryuken/configuration/settings.rb b/lib/ddtrace/contrib/shoryuken/configuration/settings.rb index 3966ef266e..8e545dfcbd 100644 --- a/lib/ddtrace/contrib/shoryuken/configuration/settings.rb +++ b/lib/ddtrace/contrib/shoryuken/configuration/settings.rb @@ -7,7 +7,7 @@ module Configuration # Default settings for the Shoryuken integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/shoryuken/ext.rb b/lib/ddtrace/contrib/shoryuken/ext.rb index 2ccbbdd539..8a527c90fa 100644 --- a/lib/ddtrace/contrib/shoryuken/ext.rb +++ b/lib/ddtrace/contrib/shoryuken/ext.rb @@ -4,7 +4,7 @@ module Shoryuken # Shoryuken integration constants module Ext APP = 'shoryuken'.freeze - ENV_ANALYTICS_ENALBED = 'DD_SHORYUKEN_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_SHORYUKEN_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_SHORYUKEN_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'shoryuken'.freeze SPAN_JOB = 'shoryuken.job'.freeze diff --git a/lib/ddtrace/contrib/sidekiq/configuration/settings.rb b/lib/ddtrace/contrib/sidekiq/configuration/settings.rb index dcbf30e41a..8bab25b4c1 100644 --- a/lib/ddtrace/contrib/sidekiq/configuration/settings.rb +++ b/lib/ddtrace/contrib/sidekiq/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the Sidekiq integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/sidekiq/ext.rb b/lib/ddtrace/contrib/sidekiq/ext.rb index f677ea10e8..1e3ee56374 100644 --- a/lib/ddtrace/contrib/sidekiq/ext.rb +++ b/lib/ddtrace/contrib/sidekiq/ext.rb @@ -5,7 +5,7 @@ module Sidekiq module Ext APP = 'sidekiq'.freeze CLIENT_SERVICE_NAME = 'sidekiq-client'.freeze - ENV_ANALYTICS_ENALBED = 'DD_SIDEKIQ_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_SIDEKIQ_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_SIDEKIQ_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'sidekiq'.freeze SPAN_PUSH = 'sidekiq.push'.freeze diff --git a/lib/ddtrace/contrib/sinatra/configuration/settings.rb b/lib/ddtrace/contrib/sinatra/configuration/settings.rb index 30ae3dfb2c..8ebebabc03 100644 --- a/lib/ddtrace/contrib/sinatra/configuration/settings.rb +++ b/lib/ddtrace/contrib/sinatra/configuration/settings.rb @@ -12,6 +12,14 @@ class Settings < Contrib::Configuration::Settings response: %w[Content-Type X-Request-ID] }.freeze + option :analytics_enabled, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }, + lazy: true + + option :analytics_sample_rate, + default: -> { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }, + lazy: true + option :distributed_tracing, default: true option :headers, default: DEFAULT_HEADERS option :resource_script_names, default: false diff --git a/lib/ddtrace/contrib/sinatra/ext.rb b/lib/ddtrace/contrib/sinatra/ext.rb index 0ab38150bc..d081beff80 100644 --- a/lib/ddtrace/contrib/sinatra/ext.rb +++ b/lib/ddtrace/contrib/sinatra/ext.rb @@ -4,13 +4,12 @@ module Sinatra # Sinatra integration constants module Ext APP = 'sinatra'.freeze - SERVICE_NAME = 'sinatra'.freeze - + ENV_ANALYTICS_ENABLED = 'DD_SINATRA_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_SAMPLE_RATE = 'DD_SINATRA_ANALYTICS_SAMPLE_RATE'.freeze RACK_ENV_REQUEST_SPAN = 'datadog.sinatra_request_span'.freeze - + SERVICE_NAME = 'sinatra'.freeze SPAN_RENDER_TEMPLATE = 'sinatra.render_template'.freeze SPAN_REQUEST = 'sinatra.request'.freeze - TAG_ROUTE_PATH = 'sinatra.route.path'.freeze TAG_TEMPLATE_NAME = 'sinatra.template_name'.freeze end diff --git a/lib/ddtrace/contrib/sinatra/tracer.rb b/lib/ddtrace/contrib/sinatra/tracer.rb index bf7fd2cc2e..43355db819 100644 --- a/lib/ddtrace/contrib/sinatra/tracer.rb +++ b/lib/ddtrace/contrib/sinatra/tracer.rb @@ -41,6 +41,7 @@ def render(engine, data, *) # If data is a string, it is a literal template and we don't # want to record it. span.set_tag(Ext::TAG_TEMPLATE_NAME, data) if data.is_a? Symbol + output = super end else diff --git a/lib/ddtrace/contrib/sinatra/tracer_middleware.rb b/lib/ddtrace/contrib/sinatra/tracer_middleware.rb index 984c854eee..05aa5b02f8 100644 --- a/lib/ddtrace/contrib/sinatra/tracer_middleware.rb +++ b/lib/ddtrace/contrib/sinatra/tracer_middleware.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/analytics' require 'ddtrace/contrib/sinatra/ext' require 'ddtrace/contrib/sinatra/env' require 'ddtrace/contrib/sinatra/headers' @@ -37,6 +38,9 @@ def call(env) span.set_tag(name, value) if span.get_tag(name).nil? end + # Set analytics sample rate + Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? + [status, headers, response_body] end end @@ -47,6 +51,14 @@ def tracer configuration[:tracer] end + def analytics_enabled? + Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + end + + def analytics_sample_rate + configuration[:analytics_sample_rate] + end + def configuration Datadog.configuration[:sinatra] end diff --git a/lib/ddtrace/contrib/sucker_punch/configuration/settings.rb b/lib/ddtrace/contrib/sucker_punch/configuration/settings.rb index 4fde21d075..4b79847723 100644 --- a/lib/ddtrace/contrib/sucker_punch/configuration/settings.rb +++ b/lib/ddtrace/contrib/sucker_punch/configuration/settings.rb @@ -8,7 +8,7 @@ module Configuration # Custom settings for the SuckerPunch integration class Settings < Contrib::Configuration::Settings option :analytics_enabled, - default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENALBED, nil) }, + default: -> { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }, lazy: true option :analytics_sample_rate, diff --git a/lib/ddtrace/contrib/sucker_punch/ext.rb b/lib/ddtrace/contrib/sucker_punch/ext.rb index 7568a4ada6..4b91918dad 100644 --- a/lib/ddtrace/contrib/sucker_punch/ext.rb +++ b/lib/ddtrace/contrib/sucker_punch/ext.rb @@ -4,7 +4,7 @@ module SuckerPunch # SuckerPunch integration constants module Ext APP = 'sucker_punch'.freeze - ENV_ANALYTICS_ENALBED = 'DD_SUCKER_PUNCH_ANALYTICS_ENABLED'.freeze + ENV_ANALYTICS_ENABLED = 'DD_SUCKER_PUNCH_ANALYTICS_ENABLED'.freeze ENV_ANALYTICS_SAMPLE_RATE = 'DD_SUCKER_PUNCH_ANALYTICS_SAMPLE_RATE'.freeze SERVICE_NAME = 'sucker_punch'.freeze SPAN_PERFORM = 'sucker_punch.perform'.freeze diff --git a/lib/ddtrace/environment.rb b/lib/ddtrace/environment.rb new file mode 100644 index 0000000000..17e5853a99 --- /dev/null +++ b/lib/ddtrace/environment.rb @@ -0,0 +1,15 @@ +module Datadog + # Namespace for handling application environment + module Environment + # Defines helper methods for environment + module Helpers + def env_to_bool(var, default = nil) + ENV.key?(var) ? ENV[var].to_s.downcase == 'true' : default + end + + def env_to_float(var, default = nil) + ENV.key?(var) ? ENV[var].to_f : default + end + end + end +end diff --git a/lib/ddtrace/ext/analytics.rb b/lib/ddtrace/ext/analytics.rb index e2b127d773..427089db5d 100644 --- a/lib/ddtrace/ext/analytics.rb +++ b/lib/ddtrace/ext/analytics.rb @@ -2,6 +2,7 @@ module Datadog module Ext # Defines constants for trace analytics module Analytics + ENV_TRACE_ANALYTICS_ENABLED = 'DD_TRACE_ANALYTICS_ENABLED'.freeze # Tag for sample rate; used by agent to determine whether analytics event is emitted. TAG_SAMPLE_RATE = '_dd1.sr.eausr'.freeze end diff --git a/lib/ddtrace/opentracer/global_tracer.rb b/lib/ddtrace/opentracer/global_tracer.rb index cecf67180b..083e19cbe1 100644 --- a/lib/ddtrace/opentracer/global_tracer.rb +++ b/lib/ddtrace/opentracer/global_tracer.rb @@ -6,7 +6,7 @@ def global_tracer=(tracer) super.tap do if tracer.class <= Datadog::OpenTracer::Tracer # Update the Datadog global tracer, too. - Datadog.instance_variable_set(:@tracer, tracer.datadog_tracer) + Datadog.configuration.tracer = tracer.datadog_tracer end end end diff --git a/lib/ddtrace/propagation/distributed_headers.rb b/lib/ddtrace/propagation/distributed_headers.rb index d29e0653ff..0503746f4d 100644 --- a/lib/ddtrace/propagation/distributed_headers.rb +++ b/lib/ddtrace/propagation/distributed_headers.rb @@ -16,7 +16,8 @@ def valid? return true if origin == 'synthetics' && trace_id # Sampling priority and origin are optional. - trace_id && parent_id + # DEV: We want to explicitly return true/false here + trace_id && parent_id ? true : false end def trace_id @@ -29,11 +30,19 @@ def parent_id def sampling_priority hdr = header(HTTP_HEADER_SAMPLING_PRIORITY) + # It's important to make a difference between no header, # and a header defined to zero. - return unless hdr + return if hdr.nil? + + # Convert header to an integer value = hdr.to_i - return if value < 0 + + # Ensure the parsed number is the same as the original string value + # e.g. We want to make sure to throw away `'nan'.to_i == 0` + # DEV: Ruby `.to_i` will return `0` if a number could not be parsed + return unless value.to_s == hdr.to_s + value end @@ -53,7 +62,8 @@ def header(name) def id(header) value = header(header).to_i - return if value.zero? || value >= Span::MAX_ID + # Zero or greater than max allowed value of 2**64 + return if value.zero? || value > Span::EXTERNAL_MAX_ID value < 0 ? value + 0x1_0000_0000_0000_0000 : value end end diff --git a/lib/ddtrace/registry.rb b/lib/ddtrace/registry.rb deleted file mode 100644 index 5c8c26a536..0000000000 --- a/lib/ddtrace/registry.rb +++ /dev/null @@ -1,42 +0,0 @@ -require_relative 'registry/registerable' - -module Datadog - # Registry provides insertion/retrieval capabilities for integrations - class Registry - include Enumerable - - Entry = Struct.new(:name, :klass, :auto_patch) - - def initialize - @data = {} - @mutex = Mutex.new - end - - def add(name, klass, auto_patch = false) - @mutex.synchronize do - @data[name] = Entry.new(name, klass, auto_patch).freeze - end - end - - def each - @mutex.synchronize do - @data.each { |_, entry| yield(entry) } - end - end - - def [](name) - @mutex.synchronize do - entry = @data[name] - entry.klass if entry - end - end - - def to_h - @mutex.synchronize do - @data.each_with_object({}) do |(_, entry), hash| - hash[entry.name] = entry.auto_patch - end - end - end - end -end diff --git a/lib/ddtrace/registry/registerable.rb b/lib/ddtrace/registry/registerable.rb deleted file mode 100644 index 4fd63703cf..0000000000 --- a/lib/ddtrace/registry/registerable.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Datadog - class Registry - # Registerable provides a convenience method for self-registering - module Registerable - def self.included(base) - base.singleton_class.send(:include, ClassMethods) - end - - # ClassMethods - module ClassMethods - def register_as(name, options = {}) - registry = options.fetch(:registry, Datadog.registry) - auto_patch = options.fetch(:auto_patch, false) - - registry.add(name, self, auto_patch) - end - end - end - end -end diff --git a/lib/ddtrace/span.rb b/lib/ddtrace/span.rb index b175895311..82cdabd3cd 100644 --- a/lib/ddtrace/span.rb +++ b/lib/ddtrace/span.rb @@ -20,6 +20,10 @@ class Span # and IDs need to be easy to port across various languages and platforms. MAX_ID = 2**63 + # While we only generate 63-bit integers due to limitations in other languages, we support + # parsing 64-bit integers for distributed tracing since an upstream system may generate one + EXTERNAL_MAX_ID = 2**64 + attr_accessor :name, :service, :resource, :span_type, :start_time, :end_time, :span_id, :trace_id, :parent_id, diff --git a/lib/ddtrace/tracer.rb b/lib/ddtrace/tracer.rb index 63f34a1e56..d36980ea40 100644 --- a/lib/ddtrace/tracer.rb +++ b/lib/ddtrace/tracer.rb @@ -53,8 +53,13 @@ def self.log=(logger) end # Activate the debug mode providing more information related to tracer usage + # Default to Warn level unless using custom logger def self.debug_logging=(value) - log.level = value ? Logger::DEBUG : Logger::WARN + if value + log.level = Logger::DEBUG + elsif log.is_a?(Datadog::Logger) + log.level = Logger::WARN + end end # Return if the debug mode is activated or not diff --git a/lib/ddtrace/version.rb b/lib/ddtrace/version.rb index 7b137b7684..699204dc7b 100644 --- a/lib/ddtrace/version.rb +++ b/lib/ddtrace/version.rb @@ -1,7 +1,7 @@ module Datadog module VERSION MAJOR = 0 - MINOR = 20 + MINOR = 21 PATCH = 0 PRE = nil diff --git a/spec/ddtrace/configurable_spec.rb b/spec/ddtrace/configurable_spec.rb deleted file mode 100644 index eaab5eee3f..0000000000 --- a/spec/ddtrace/configurable_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'spec_helper' - -require 'ddtrace' - -RSpec.describe Datadog::Configurable do - shared_examples_for 'a configurable constant' do - describe '#option' do - let(:name) { :foo } - let(:options) { {} } - let(:block) { nil } - before(:each) { configurable.send(:option, name, options, &block) } - - context 'given a default option' do - let(:options) { { default: default_value } } - let(:default_value) { :bar } - it { expect(configurable.get_option(name)).to eq(default_value) } - end - - context 'given a custom setter' do - let(:name) { :shout } - before(:each) { configurable.set_option(name, 'loud') } - - context 'option' do - let(:options) { { setter: ->(v) { v.upcase } } } - it { expect(configurable.get_option(name)).to eq('LOUD') } - end - - context 'block' do - let(:block) { proc { |value| "#{value.upcase}!" } } - it { expect(configurable.get_option(name)).to eq('LOUD!') } - end - end - end - - describe '#get_option' do - subject(:result) { configurable.get_option(name) } - let(:name) { :foo } - let(:options) { {} } - - it { expect(configurable).to respond_to(:get_option) } - - context 'when the option doesn\'t exist' do - it { expect { result }.to raise_error(Datadog::InvalidOptionError) } - end - end - - describe '#set_option' do - let(:name) { :foo } - let(:options) { {} } - let(:value) { :bar } - - before(:each) do - configurable.send(:option, name, options) - configurable.set_option(name, value) - end - - it { expect(configurable).to respond_to(:set_option) } - - context 'when a default has been defined' do - let(:options) { { default: default_value } } - let(:default_value) { :bar } - let(:value) { 'baz!' } - it { expect(configurable.get_option(name)).to eq(value) } - - context 'and the value set is \'false\'' do - let(:default_value) { true } - let(:value) { false } - it { expect(configurable.get_option(name)).to eq(value) } - end - end - - context 'when the option doesn\'t exist' do - subject(:result) { configurable.set_option(:bad_option, value) } - it { expect { result }.to raise_error(Datadog::InvalidOptionError) } - end - end - - describe '#to_h' do - subject(:hash) { configurable.to_h } - - before(:each) do - configurable.send(:option, :x, default: 1) - configurable.send(:option, :y, default: 2) - configurable.set_option(:y, 100) - end - - it { is_expected.to eq(x: 1, y: 100) } - end - - describe '#sorted_options' do - subject(:sorted_options) { configurable.sorted_options } - - before(:each) do - configurable.send(:option, :foo, depends_on: [:bar]) - configurable.send(:option, :bar, depends_on: [:baz]) - configurable.send(:option, :baz) - end - - it { is_expected.to eq([:baz, :bar, :foo]) } - end - end - - describe 'implemented' do - describe 'class' do - subject(:configurable) { Class.new { include(Datadog::Configurable) } } - it_behaves_like 'a configurable constant' - end - - describe 'module' do - subject(:configurable) { Module.new { include(Datadog::Configurable) } } - it_behaves_like 'a configurable constant' - end - end -end diff --git a/spec/ddtrace/configuration/resolver_spec.rb b/spec/ddtrace/configuration/dependency_resolver_spec.rb similarity index 89% rename from spec/ddtrace/configuration/resolver_spec.rb rename to spec/ddtrace/configuration/dependency_resolver_spec.rb index 300a81b0ed..0496c13d49 100644 --- a/spec/ddtrace/configuration/resolver_spec.rb +++ b/spec/ddtrace/configuration/dependency_resolver_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Configuration::Resolver do +RSpec.describe Datadog::Configuration::DependencyResolver do subject(:resolver) { described_class.new(graph) } describe '#call' do diff --git a/spec/ddtrace/contrib/configuration/option_definition_set_spec.rb b/spec/ddtrace/configuration/option_definition_set_spec.rb similarity index 74% rename from spec/ddtrace/contrib/configuration/option_definition_set_spec.rb rename to spec/ddtrace/configuration/option_definition_set_spec.rb index f7cb683151..7d9f7bcd50 100644 --- a/spec/ddtrace/contrib/configuration/option_definition_set_spec.rb +++ b/spec/ddtrace/configuration/option_definition_set_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Contrib::Configuration::OptionDefinitionSet do +RSpec.describe Datadog::Configuration::OptionDefinitionSet do subject(:set) { described_class.new } it { is_expected.to be_a_kind_of(Hash) } @@ -10,17 +10,17 @@ shared_context 'dependent option set' do before(:each) do set[:foo] = instance_double( - Datadog::Contrib::Configuration::OptionDefinition, + Datadog::Configuration::OptionDefinition, depends_on: [:bar] ) set[:bar] = instance_double( - Datadog::Contrib::Configuration::OptionDefinition, + Datadog::Configuration::OptionDefinition, depends_on: [:baz] ) set[:baz] = instance_double( - Datadog::Contrib::Configuration::OptionDefinition, + Datadog::Configuration::OptionDefinition, depends_on: [] ) end @@ -30,10 +30,10 @@ subject(:dependency_order) { set.dependency_order } context 'when invoked' do - let(:resolver) { instance_double(Datadog::Configuration::Resolver) } + let(:resolver) { instance_double(Datadog::Configuration::DependencyResolver) } it do - expect(Datadog::Configuration::Resolver).to receive(:new) + expect(Datadog::Configuration::DependencyResolver).to receive(:new) .with(a_kind_of(Hash)) .and_return(resolver) expect(resolver).to receive(:call) diff --git a/spec/ddtrace/contrib/configuration/option_definition_spec.rb b/spec/ddtrace/configuration/option_definition_spec.rb similarity index 97% rename from spec/ddtrace/contrib/configuration/option_definition_spec.rb rename to spec/ddtrace/configuration/option_definition_spec.rb index 9e856a5a3e..d047086b9f 100644 --- a/spec/ddtrace/contrib/configuration/option_definition_spec.rb +++ b/spec/ddtrace/configuration/option_definition_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Contrib::Configuration::OptionDefinition do +RSpec.describe Datadog::Configuration::OptionDefinition do subject(:definition) { described_class.new(name, meta, &block) } let(:name) { :enabled } diff --git a/spec/ddtrace/contrib/configuration/option_set_spec.rb b/spec/ddtrace/configuration/option_set_spec.rb similarity index 68% rename from spec/ddtrace/contrib/configuration/option_set_spec.rb rename to spec/ddtrace/configuration/option_set_spec.rb index ef0acec1f6..316b7151aa 100644 --- a/spec/ddtrace/contrib/configuration/option_set_spec.rb +++ b/spec/ddtrace/configuration/option_set_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Contrib::Configuration::OptionSet do +RSpec.describe Datadog::Configuration::OptionSet do subject(:set) { described_class.new } it { is_expected.to be_a_kind_of(Hash) } diff --git a/spec/ddtrace/contrib/configuration/option_spec.rb b/spec/ddtrace/configuration/option_spec.rb similarity index 86% rename from spec/ddtrace/contrib/configuration/option_spec.rb rename to spec/ddtrace/configuration/option_spec.rb index 6c09454dd2..78ddac2cfb 100644 --- a/spec/ddtrace/contrib/configuration/option_spec.rb +++ b/spec/ddtrace/configuration/option_spec.rb @@ -2,11 +2,11 @@ require 'ddtrace' -RSpec.describe Datadog::Contrib::Configuration::Option do +RSpec.describe Datadog::Configuration::Option do subject(:option) { described_class.new(definition, context) } let(:definition) do instance_double( - Datadog::Contrib::Configuration::OptionDefinition, + Datadog::Configuration::OptionDefinition, default_value: default_value, setter: setter ) @@ -62,7 +62,11 @@ option.set(value) end - it { is_expected.to be(default_value) } + context 'causes #get' do + subject(:get) { option.get } + before(:each) { reset } + it { is_expected.to be(default_value) } + end end end end diff --git a/spec/ddtrace/contrib/configuration/options_spec.rb b/spec/ddtrace/configuration/options_spec.rb similarity index 90% rename from spec/ddtrace/contrib/configuration/options_spec.rb rename to spec/ddtrace/configuration/options_spec.rb index 91f0495684..63fee51e3a 100644 --- a/spec/ddtrace/contrib/configuration/options_spec.rb +++ b/spec/ddtrace/configuration/options_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Contrib::Configuration::Options do +RSpec.describe Datadog::Configuration::Options do describe 'implemented' do subject(:options_class) do Class.new.tap do |klass| @@ -15,7 +15,7 @@ subject(:options) { options_class.options } context 'for a class directly implementing Options' do - it { is_expected.to be_a_kind_of(Datadog::Contrib::Configuration::OptionDefinitionSet) } + it { is_expected.to be_a_kind_of(Datadog::Configuration::OptionDefinitionSet) } end context 'on class inheriting from a class implementing Options' do @@ -29,7 +29,7 @@ context 'which defines some options' do before(:each) { parent_class.send(:option, :foo) } - it { is_expected.to be_a_kind_of(Datadog::Contrib::Configuration::OptionDefinitionSet) } + it { is_expected.to be_a_kind_of(Datadog::Configuration::OptionDefinitionSet) } it { is_expected.to_not be(parent_class.options) } it { is_expected.to include(:foo) } end @@ -44,7 +44,7 @@ let(:block) { proc {} } it 'creates an option definition' do - is_expected.to be_a_kind_of(Datadog::Contrib::Configuration::OptionDefinition) + is_expected.to be_a_kind_of(Datadog::Configuration::OptionDefinition) expect(options_class.options).to include(name) expect(options_class.new).to respond_to(name) expect(options_class.new).to respond_to("#{name}=") @@ -57,7 +57,7 @@ describe '#options' do subject(:options) { options_object.options } - it { is_expected.to be_a_kind_of(Datadog::Contrib::Configuration::OptionSet) } + it { is_expected.to be_a_kind_of(Datadog::Configuration::OptionSet) } end describe '#set_option' do diff --git a/spec/ddtrace/configuration/proxy_spec.rb b/spec/ddtrace/configuration/proxy_spec.rb deleted file mode 100644 index b30ea1a02d..0000000000 --- a/spec/ddtrace/configuration/proxy_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'spec_helper' - -require 'ddtrace' - -RSpec.describe Datadog::Configuration::Proxy do - subject(:proxy) { described_class.new(configurable_module) } - - let(:configurable_module) do - Module.new do - include Datadog::Configurable - option :x, default: :a - option :y, default: :b - end - end - - describe '#[]' do - before(:each) do - proxy[:x] = 1 - proxy[:y] = 2 - end - - it do - expect(proxy[:x]).to eq(1) - expect(proxy[:y]).to eq(2) - end - end - - describe '#to_h' do - subject(:hash) { proxy.to_h } - it { is_expected.to eq(x: :a, y: :b) } - end - - describe '#to_hash' do - subject(:hash) { proxy.to_hash } - it { is_expected.to eq(x: :a, y: :b) } - end - - describe '#merge' do - subject(:result) { proxy.merge(hash) } - let(:hash) { { z: :c } } - it { is_expected.to eq(x: :a, y: :b, z: :c) } - end -end diff --git a/spec/ddtrace/configuration_spec.rb b/spec/ddtrace/configuration_spec.rb index 0121505daa..d8f94d1614 100644 --- a/spec/ddtrace/configuration_spec.rb +++ b/spec/ddtrace/configuration_spec.rb @@ -2,9 +2,9 @@ require 'ddtrace' -RSpec.describe Datadog::Configuration do +RSpec.describe Datadog::Configuration::Settings do let(:configuration) { described_class.new(registry: registry) } - let(:registry) { Datadog::Registry.new } + let(:registry) { Datadog::Contrib::Registry.new } describe '#use' do subject(:result) { configuration.use(name, options) } @@ -18,101 +18,13 @@ context 'for a generic integration' do before(:each) do - expect(integration).to receive(:sorted_options).and_return([]) + expect(integration).to receive(:configure).with(:default, options).and_return([]) expect(integration).to receive(:patch).and_return(true) end it { expect { result }.to_not raise_error } end - context 'for an integration that includes Datadog::Contrib::Base' do - let(:options) { { option1: :foo!, option2: :bar! } } - let(:integration) do - Module.new do - include Datadog::Contrib::Base - option :option1 - option :option2 - end - end - - it do - expect { result }.to_not raise_error - expect(configuration[name][:option1]).to eq(:foo!) - expect(configuration[name][:option2]).to eq(:bar!) - end - - context 'and has a lazy option' do - let(:integration) do - Module.new do - include Datadog::Contrib::Base - option :option1, default: -> { 1 + 1 }, lazy: true - end - end - - it { expect(configuration[name][:option1]).to eq(2) } - end - - context 'and has dependencies' do - let(:options) { { multiply_by: 5, number: 5 } } - let(:integration) do - Module.new do - include Datadog::Contrib::Base - option :multiply_by, depends_on: [:number] do |value| - get_option(:number) * value - end - - option :number - end - end - - it do - expect { result }.to_not raise_error - expect(configuration[name][:number]).to eq(5) - expect(configuration[name][:multiply_by]).to eq(25) - end - end - - context 'and has a setter' do - let(:array) { [] } - let(:options) { { option1: :foo! } } - let(:integration) do - arr = array - Module.new do - include Datadog::Contrib::Base - option :option1 - option :option2, default: 10 do |value| - arr << value - value - end - end - end - - it 'pass through the setter' do - expect { result }.to_not raise_error - expect(configuration[name][:option1]).to eq(:foo!) - expect(configuration[name][:option2]).to eq(10) - expect(array).to include(10) - end - end - - context 'when a setting is changed' do - before(:each) { configuration[name][:option1] = :foo } - it { expect(configuration[:example][:option1]).to eq(:foo) } - end - - context 'when coerced to a hash' do - let(:integration) do - Module.new do - include Datadog::Contrib::Base - option :option1, default: :foo - option :option2, default: :bar - end - end - - it { expect(configuration[name].to_h).to eq(option1: :foo, option2: :bar) } - end - end - context 'for an integration that includes Datadog::Contrib::Integration' do let(:integration_class) do Class.new do @@ -182,7 +94,7 @@ it 'acts on the default tracer' do previous_state = Datadog.tracer.enabled configuration.tracer(enabled: !previous_state) - expect(Datadog.tracer.enabled).to_not eq(previous_state) + expect(Datadog.tracer.enabled).to eq(!previous_state) configuration.tracer(enabled: previous_state) expect(Datadog.tracer.enabled).to eq(previous_state) end @@ -191,7 +103,9 @@ describe '#[]' do context 'when the integration doesn\'t exist' do it do - expect { configuration[:foobar] }.to raise_error(Datadog::Configuration::InvalidIntegrationError) + expect { configuration[:foobar] }.to raise_error( + Datadog::Contrib::Extensions::Configuration::InvalidIntegrationError + ) end end end diff --git a/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb b/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb index 28ddf0c5ff..e0ee4021df 100644 --- a/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb +++ b/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'spec/ddtrace/contrib/active_model_serializers/helpers' require 'active_support/all' @@ -11,6 +12,7 @@ include_context 'AMS serializer' let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } def all_spans tracer.writer.spans(:keep) @@ -21,7 +23,7 @@ def all_spans ActiveModelSerializersHelpers.disable_logging Datadog.configure do |c| - c.use :active_model_serializers, tracer: tracer + c.use :active_model_serializers, configuration_options end # Make sure to update the subscription tracer, @@ -33,6 +35,13 @@ def all_spans end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:active_model_serializers].reset_configuration! + example.run + Datadog.registry[:active_model_serializers].reset_configuration! + end + describe 'on render' do let(:test_obj) { TestModel.new(name: 'test object') } let(:serializer) { 'TestModelSerializer' } @@ -52,8 +61,20 @@ def all_spans if ActiveModelSerializersHelpers.ams_0_10_or_newer? context 'when adapter is set' do + subject(:render) { ActiveModelSerializers::SerializableResource.new(test_obj).serializable_hash } + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::ActiveModelSerializers::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::ActiveModelSerializers::Ext::ENV_ANALYTICS_SAMPLE_RATE } + + let(:span) do + render + active_model_serializers_span + end + end + it 'is expected to send a span' do - ActiveModelSerializers::SerializableResource.new(test_obj).serializable_hash + render active_model_serializers_span.tap do |span| expect(span).to_not be_nil @@ -70,8 +91,10 @@ def all_spans context 'when adapter is nil' do if ActiveModelSerializersHelpers.ams_0_10_or_newer? + subject(:render) { ActiveModelSerializers::SerializableResource.new(test_obj, adapter: nil).serializable_hash } + it 'is expected to send a span with adapter tag equal to the model name' do - ActiveModelSerializers::SerializableResource.new(test_obj, adapter: nil).serializable_hash + render active_model_serializers_span.tap do |span| expect(span).to_not be_nil @@ -84,8 +107,10 @@ def all_spans end end else + subject(:render) { TestModelSerializer.new(test_obj).as_json } + it 'is expected to send a span with no adapter tag' do - TestModelSerializer.new(test_obj).as_json + render active_model_serializers_span.tap do |span| expect(span).to_not be_nil diff --git a/spec/ddtrace/contrib/active_record/tracer_spec.rb b/spec/ddtrace/contrib/active_record/tracer_spec.rb index decef94773..db58feabcc 100644 --- a/spec/ddtrace/contrib/active_record/tracer_spec.rb +++ b/spec/ddtrace/contrib/active_record/tracer_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'ddtrace' require_relative 'app' @@ -19,46 +20,54 @@ end end - it 'calls the instrumentation when is used standalone' do - Article.count - spans = tracer.writer.spans - services = tracer.writer.services + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:active_record].reset_configuration! + example.run + Datadog.registry[:active_record].reset_configuration! + end - # expect service and trace is sent - expect(spans.size).to eq(1) - expect(services).to_not be_empty - expect(services['mysql2']).to eq('app' => 'active_record', 'app_type' => 'db') + context 'when query is made' do + before(:each) { Article.count } - span = spans[0] - expect(span.service).to eq('mysql2') - expect(span.name).to eq('mysql2.query') - expect(span.span_type).to eq('sql') - expect(span.resource.strip).to eq('SELECT COUNT(*) FROM `articles`') - expect(span.get_tag('active_record.db.vendor')).to eq('mysql2') - expect(span.get_tag('active_record.db.name')).to eq('mysql') - expect(span.get_tag('active_record.db.cached')).to eq(nil) - expect(span.get_tag('out.host')).to eq(ENV.fetch('TEST_MYSQL_HOST', '127.0.0.1')) - expect(span.get_tag('out.port')).to eq(ENV.fetch('TEST_MYSQL_PORT', 3306).to_s) - expect(span.get_tag('sql.query')).to eq(nil) - end + let(:spans) { tracer.writer.spans } + let(:span) { spans.first } + let(:services) { tracer.writer.services } - context 'when service_name' do - subject(:spans) do - Article.count - tracer.writer.spans + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::ActiveRecord::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::ActiveRecord::Ext::ENV_ANALYTICS_SAMPLE_RATE } end - let(:query_span) { spans.first } + it 'calls the instrumentation when is used standalone' do + # expect service and trace is sent + expect(spans.size).to eq(1) + expect(services).to_not be_empty + expect(services['mysql2']).to eq('app' => 'active_record', 'app_type' => 'db') - context 'is not set' do - it { expect(query_span.service).to eq('mysql2') } + expect(span.service).to eq('mysql2') + expect(span.name).to eq('mysql2.query') + expect(span.span_type).to eq('sql') + expect(span.resource.strip).to eq('SELECT COUNT(*) FROM `articles`') + expect(span.get_tag('active_record.db.vendor')).to eq('mysql2') + expect(span.get_tag('active_record.db.name')).to eq('mysql') + expect(span.get_tag('active_record.db.cached')).to eq(nil) + expect(span.get_tag('out.host')).to eq(ENV.fetch('TEST_MYSQL_HOST', '127.0.0.1')) + expect(span.get_tag('out.port')).to eq(ENV.fetch('TEST_MYSQL_PORT', 3306).to_s) + expect(span.get_tag('sql.query')).to eq(nil) end - context 'is set' do - let(:service_name) { 'test_active_record' } - let(:configuration_options) { super().merge(service_name: service_name) } + context 'and service_name' do + context 'is not set' do + it { expect(span.service).to eq('mysql2') } + end + + context 'is set' do + let(:service_name) { 'test_active_record' } + let(:configuration_options) { super().merge(service_name: service_name) } - it { expect(query_span.service).to eq(service_name) } + it { expect(span.service).to eq(service_name) } + end end end end diff --git a/spec/ddtrace/contrib/analytics_examples.rb b/spec/ddtrace/contrib/analytics_examples.rb index ae8cb28e52..3f94d8d7aa 100644 --- a/spec/ddtrace/contrib/analytics_examples.rb +++ b/spec/ddtrace/contrib/analytics_examples.rb @@ -1,9 +1,51 @@ require 'ddtrace/ext/analytics' -RSpec.shared_examples_for 'analytics for integration' do +RSpec.shared_examples_for 'analytics for integration' do |options = { ignore_global_flag: true }| + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.configuration.reset_options! + example.run + Datadog.configuration.reset_options! + end + context 'when not configured' do - it 'is not included in the tags' do - expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil + context 'and the global flag is not set' do + it 'is not included in the tags' do + expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil + end + end + + context 'and the global flag is enabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'true') do + example.run + end + end + + # Most integrations ignore the global flag by default, + # because they aren't considered "key" integrations. + # These integrations will not expect it to be set, despite the global flag. + if options[:ignore_global_flag] + it 'is not included in the tags' do + expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil + end + else + it 'is included in the tags' do + expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(1.0) + end + end + end + + context 'and the global flag is disabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'false') do + example.run + end + end + + it 'is not included in the tags' do + expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil + end end end @@ -15,19 +57,47 @@ end end - context 'and sample rate isn\'t set' do - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(1.0) } + shared_examples_for 'sample rate value' do + context 'isn\'t set' do + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(1.0) } + end + + context 'is set' do + let(:analytics_sample_rate) { 0.5 } + around do |example| + ClimateControl.modify(analytics_sample_rate_var => analytics_sample_rate.to_s) do + example.run + end + end + + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(analytics_sample_rate) } + end end - context 'and sample rate is set' do - let(:analytics_sample_rate) { 0.5 } - around do |example| - ClimateControl.modify(analytics_sample_rate_var => analytics_sample_rate.to_s) do - example.run + context 'and global flag' do + context 'is not set' do + it_behaves_like 'sample rate value' + end + + context 'is explicitly enabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'true') do + example.run + end end + + it_behaves_like 'sample rate value' end - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(analytics_sample_rate) } + context 'is explicitly disabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'false') do + example.run + end + end + + it_behaves_like 'sample rate value' + end end end @@ -38,19 +108,47 @@ end end - context 'and sample rate isn\'t set' do - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + shared_examples_for 'sample rate value' do + context 'isn\'t set' do + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + end + + context 'is set' do + let(:analytics_sample_rate) { 0.5 } + around do |example| + ClimateControl.modify(analytics_sample_rate_var => analytics_sample_rate.to_s) do + example.run + end + end + + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + end end - context 'and sample rate is set' do - let(:analytics_sample_rate) { 0.5 } - around do |example| - ClimateControl.modify(analytics_sample_rate_var => analytics_sample_rate.to_s) do - example.run + context 'and global flag' do + context 'is not set' do + it_behaves_like 'sample rate value' + end + + context 'is explicitly enabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'true') do + example.run + end end + + it_behaves_like 'sample rate value' end - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + context 'is explicitly disabled' do + around do |example| + ClimateControl.modify(Datadog::Ext::Analytics::ENV_TRACE_ANALYTICS_ENABLED => 'false') do + example.run + end + end + + it_behaves_like 'sample rate value' + end end end end @@ -59,28 +157,88 @@ context 'and explicitly enabled' do let(:configuration_options) { super().merge(analytics_enabled: true) } - context 'and sample rate isn\'t set' do - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(1.0) } + shared_examples_for 'sample rate value' do + context 'isn\'t set' do + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(1.0) } + end + + context 'is set' do + let(:configuration_options) { super().merge(analytics_sample_rate: analytics_sample_rate) } + let(:analytics_sample_rate) { 0.5 } + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(analytics_sample_rate) } + end end - context 'and sample rate is set' do - let(:configuration_options) { super().merge(analytics_sample_rate: analytics_sample_rate) } - let(:analytics_sample_rate) { 0.5 } - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to eq(analytics_sample_rate) } + context 'and global flag' do + context 'is not set' do + it_behaves_like 'sample rate value' + end + + context 'is explicitly enabled' do + around do |example| + Datadog.configuration.analytics_enabled = Datadog.configuration.analytics_enabled.tap do + Datadog.configuration.analytics_enabled = true + example.run + end + end + + it_behaves_like 'sample rate value' + end + + context 'is explicitly disabled' do + around do |example| + Datadog.configuration.analytics_enabled = Datadog.configuration.analytics_enabled.tap do + Datadog.configuration.analytics_enabled = false + example.run + end + end + + it_behaves_like 'sample rate value' + end end end context 'and explicitly disabled' do let(:configuration_options) { super().merge(analytics_enabled: false) } - context 'and sample rate isn\'t set' do - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + shared_examples_for 'sample rate value' do + context 'isn\'t set' do + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + end + + context 'is set' do + let(:configuration_options) { super().merge(analytics_sample_rate: analytics_sample_rate) } + let(:analytics_sample_rate) { 0.5 } + it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + end end - context 'and sample rate is set' do - let(:configuration_options) { super().merge(analytics_sample_rate: analytics_sample_rate) } - let(:analytics_sample_rate) { 0.5 } - it { expect(span.get_metric(Datadog::Ext::Analytics::TAG_SAMPLE_RATE)).to be nil } + context 'and global flag' do + context 'is not set' do + it_behaves_like 'sample rate value' + end + + context 'is explicitly enabled' do + around do |example| + Datadog.configuration.analytics_enabled = Datadog.configuration.analytics_enabled.tap do + Datadog.configuration.analytics_enabled = true + example.run + end + end + + it_behaves_like 'sample rate value' + end + + context 'is explicitly disabled' do + around do |example| + Datadog.configuration.analytics_enabled = Datadog.configuration.analytics_enabled.tap do + Datadog.configuration.analytics_enabled = false + example.run + end + end + + it_behaves_like 'sample rate value' + end end end end diff --git a/spec/ddtrace/contrib/aws/instrumentation_spec.rb b/spec/ddtrace/contrib/aws/instrumentation_spec.rb index 36a46f61ae..2eeacade20 100644 --- a/spec/ddtrace/contrib/aws/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/aws/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'aws-sdk' require 'ddtrace' @@ -7,6 +8,7 @@ RSpec.describe 'AWS instrumentation' do let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } let(:client) { ::Aws::S3::Client.new(stub_responses: responses) } let(:responses) { true } @@ -16,10 +18,17 @@ before(:each) do Datadog.configure do |c| - c.use :aws, tracer: tracer + c.use :aws, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:aws].reset_configuration! + example.run + Datadog.registry[:aws].reset_configuration! + end + context 'when the client runs' do describe '#list_buckets' do subject!(:list_buckets) { client.list_buckets } @@ -28,6 +37,11 @@ { list_buckets: { buckets: [{ name: 'bucket1' }] } } end + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Aws::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Aws::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end + it 'generates a span' do expect(span.name).to eq('aws.command') expect(span.service).to eq('aws') diff --git a/spec/ddtrace/contrib/configuration/settings_spec.rb b/spec/ddtrace/contrib/configuration/settings_spec.rb index a840348ef9..75880a2a87 100644 --- a/spec/ddtrace/contrib/configuration/settings_spec.rb +++ b/spec/ddtrace/contrib/configuration/settings_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Datadog::Contrib::Configuration::Settings do subject(:settings) { described_class.new } - it { is_expected.to be_a_kind_of(Datadog::Contrib::Configuration::Options) } + it { is_expected.to be_a_kind_of(Datadog::Configuration::Options) } describe '#options' do subject(:options) { settings.options } diff --git a/spec/ddtrace/contrib/dalli/instrumentation_spec.rb b/spec/ddtrace/contrib/dalli/instrumentation_spec.rb index 41ba485878..2cf326fa8b 100644 --- a/spec/ddtrace/contrib/dalli/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/dalli/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'dalli' require 'ddtrace' @@ -16,6 +17,8 @@ def all_spans tracer.writer.spans(:keep) end + let(:span) { all_spans.first } + # Enable the test tracer before(:each) do Datadog.configure do |c| @@ -23,19 +26,32 @@ def all_spans end end - after(:each) { Datadog.registry[:dalli].reset_configuration! } + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:dalli].reset_configuration! + example.run + Datadog.registry[:dalli].reset_configuration! + end + + describe 'when a client calls #set' do + before(:each) do + client.set('abc', 123) + try_wait_until { all_spans.any? } + end - it 'calls instrumentation' do - client.set('abc', 123) - try_wait_until { all_spans.any? } + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Dalli::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Dalli::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end - span = all_spans.first - expect(all_spans.size).to eq(1) - expect(span.service).to eq('memcached') - expect(span.name).to eq('memcached.command') - expect(span.resource).to eq('SET') - expect(span.get_tag('memcached.command')).to eq('set abc 123 0 0') - expect(span.get_tag('out.host')).to eq(test_host) - expect(span.get_tag('out.port')).to eq(test_port) + it 'calls instrumentation' do + expect(all_spans.size).to eq(1) + expect(span.service).to eq('memcached') + expect(span.name).to eq('memcached.command') + expect(span.resource).to eq('SET') + expect(span.get_tag('memcached.command')).to eq('set abc 123 0 0') + expect(span.get_tag('out.host')).to eq(test_host) + expect(span.get_tag('out.port')).to eq(test_port) + end end end diff --git a/spec/ddtrace/contrib/delayed_job/plugin_spec.rb b/spec/ddtrace/contrib/delayed_job/plugin_spec.rb index 0dea6785a8..4e8920f55b 100644 --- a/spec/ddtrace/contrib/delayed_job/plugin_spec.rb +++ b/spec/ddtrace/contrib/delayed_job/plugin_spec.rb @@ -110,7 +110,7 @@ def job_data end it_behaves_like 'analytics for integration' do - let(:analytics_enabled_var) { Datadog::Contrib::DelayedJob::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::DelayedJob::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::DelayedJob::Ext::ENV_ANALYTICS_SAMPLE_RATE } end diff --git a/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb b/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb index 2ad0641775..35aef41057 100644 --- a/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb +++ b/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'ddtrace' require 'elasticsearch-transport' @@ -8,16 +10,22 @@ let(:server) { "http://#{host}:#{port}" } let(:client) { Elasticsearch::Client.new(url: server) } - let(:pin) { Datadog::Pin.get_from(client) } + let(:configuration_options) { { tracer: tracer } } let(:tracer) { get_test_tracer } before do Datadog.configure do |c| - c.use :elasticsearch + c.use :elasticsearch, configuration_options end wait_http_server(server, 60) - pin.tracer = tracer + end + + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:elasticsearch].reset_configuration! + example.run + Datadog.registry[:elasticsearch].reset_configuration! end describe 'cluster health request' do @@ -105,6 +113,11 @@ before { request } subject(:span) { tracer.writer.spans.first } + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Elasticsearch::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Elasticsearch::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end + it { expect(span.name).to eq('elasticsearch.query') } it { expect(span.service).to eq('elasticsearch') } it { expect(span.resource).to eq('PUT some_index/type/?') } diff --git a/spec/ddtrace/contrib/elasticsearch/transport_spec.rb b/spec/ddtrace/contrib/elasticsearch/transport_spec.rb index e93b747b0f..dec1450059 100644 --- a/spec/ddtrace/contrib/elasticsearch/transport_spec.rb +++ b/spec/ddtrace/contrib/elasticsearch/transport_spec.rb @@ -23,16 +23,19 @@ let(:client) { Elasticsearch::Client.new(url: server) } let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } let(:spans) { tracer.writer.spans } let(:span) { spans.first } before(:each) do Datadog.configure do |c| - c.use :elasticsearch, tracer: tracer + c.use :elasticsearch, configuration_options end end + after(:each) { Datadog.registry[:elasticsearch].reset_configuration! } + context 'when configured with middleware' do let(:client) do Elasticsearch::Client.new url: server do |c| diff --git a/spec/ddtrace/contrib/excon/instrumentation_spec.rb b/spec/ddtrace/contrib/excon/instrumentation_spec.rb index bd64a244d1..5fa2872874 100644 --- a/spec/ddtrace/contrib/excon/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/excon/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'excon' require 'ddtrace' @@ -25,7 +26,11 @@ end end - after(:each) do + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:excon].reset_configuration! + example.run + Datadog.registry[:excon].reset_configuration! Excon.stubs.clear end @@ -74,6 +79,12 @@ context 'when there is successful request' do subject!(:response) { connection.get(path: '/success') } + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Excon::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Excon::Ext::ENV_ANALYTICS_SAMPLE_RATE } + let(:span) { request_span } + end + it do expect(request_span).to_not be nil expect(request_span.service).to eq(Datadog::Contrib::Excon::Ext::SERVICE_NAME) @@ -116,7 +127,7 @@ context 'when the request times out' do subject(:response) { connection.get(path: '/timeout') } it do - expect { subject }.to raise_error + expect { subject }.to raise_error(Excon::Error::Timeout) expect(request_span.finished?).to eq(true) expect(request_span.status).to eq(Datadog::Ext::Errors::STATUS) expect(request_span.get_tag('error.type')).to eq('Excon::Error::Timeout') @@ -125,7 +136,7 @@ context 'when the request is idempotent' do subject(:response) { connection.get(path: '/timeout', idempotent: true, retry_limit: 4) } it 'records separate spans' do - expect { subject }.to raise_error + expect { subject }.to raise_error(Excon::Error::Timeout) expect(all_request_spans.size).to eq(4) expect(all_request_spans.all?(&:finished?)).to eq(true) end diff --git a/spec/ddtrace/contrib/faraday/middleware_spec.rb b/spec/ddtrace/contrib/faraday/middleware_spec.rb index 0b5893c42d..b3ecb1669c 100644 --- a/spec/ddtrace/contrib/faraday/middleware_spec.rb +++ b/spec/ddtrace/contrib/faraday/middleware_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'ddtrace' require 'faraday' @@ -31,6 +32,13 @@ end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:faraday].reset_configuration! + example.run + Datadog.registry[:faraday].reset_configuration! + end + context 'when there is no interference' do subject!(:response) { client.get('/success') } @@ -44,6 +52,12 @@ context 'when there is successful request' do subject!(:response) { client.get('/success') } + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Faraday::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Faraday::Ext::ENV_ANALYTICS_SAMPLE_RATE } + let(:span) { request_span } + end + it do expect(request_span).to_not be nil expect(request_span.service).to eq(Datadog::Contrib::Faraday::Ext::SERVICE_NAME) diff --git a/spec/ddtrace/contrib/graphql/tracer_spec.rb b/spec/ddtrace/contrib/graphql/tracer_spec.rb index 9cb6651538..d328dd7fd6 100644 --- a/spec/ddtrace/contrib/graphql/tracer_spec.rb +++ b/spec/ddtrace/contrib/graphql/tracer_spec.rb @@ -64,6 +64,10 @@ def pop_spans expect(root_span.name).to eq('execute.graphql') expect(root_span.resource).to eq('execute.graphql') + # TODO: Assert GraphQL root span sets analytics sample rate. + # Need to wait on pull request to be merged and GraphQL released. + # See https://github.com/rmosolgo/graphql-ruby/pull/2154 + # Expect each span to be properly named all_spans.each do |span| expect(span.service).to eq('graphql-test') diff --git a/spec/ddtrace/contrib/grpc/datadog_interceptor/client_spec.rb b/spec/ddtrace/contrib/grpc/datadog_interceptor/client_spec.rb index 02c13f1ccc..7c87b0e2a3 100644 --- a/spec/ddtrace/contrib/grpc/datadog_interceptor/client_spec.rb +++ b/spec/ddtrace/contrib/grpc/datadog_interceptor/client_spec.rb @@ -1,21 +1,29 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'grpc' require 'ddtrace' RSpec.describe 'tracing on the client connection' do subject(:client) { Datadog::Contrib::GRPC::DatadogInterceptor::Client.new } let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer, service_name: 'rspec' } } let(:span) { tracer.writer.spans.first } before do Datadog.configure do |c| - c.use :grpc, - tracer: tracer, - service_name: 'rspec' + c.use :grpc, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:grpc].reset_configuration! + example.run + Datadog.registry[:grpc].reset_configuration! + end + context 'using client-specific configurations' do let(:keywords) do { request: instance_double(Object), @@ -52,6 +60,11 @@ specify { expect(span.resource).to eq 'myservice.endpoint' } specify { expect(span.get_tag('error.stack')).to be_nil } specify { expect(span.get_tag(:some)).to eq 'datum' } + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end describe '#request_response' do diff --git a/spec/ddtrace/contrib/grpc/datadog_interceptor/server_spec.rb b/spec/ddtrace/contrib/grpc/datadog_interceptor/server_spec.rb index 28bcdd25ce..170dafff2a 100644 --- a/spec/ddtrace/contrib/grpc/datadog_interceptor/server_spec.rb +++ b/spec/ddtrace/contrib/grpc/datadog_interceptor/server_spec.rb @@ -1,17 +1,27 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'grpc' require 'ddtrace' RSpec.describe 'tracing on the server connection' do subject(:server) { Datadog::Contrib::GRPC::DatadogInterceptor::Server.new } let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer, service_name: 'rspec' } } before do Datadog.configure do |c| - c.use :grpc, tracer: tracer, service_name: 'rspec' + c.use :grpc, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:grpc].reset_configuration! + example.run + Datadog.registry[:grpc].reset_configuration! + end + let(:span) { tracer.writer.spans.first } shared_examples 'span data contents' do @@ -21,6 +31,11 @@ specify { expect(span.resource).to eq 'my.server.endpoint' } specify { expect(span.get_tag('error.stack')).to be_nil } specify { expect(span.get_tag(:some)).to eq 'datum' } + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end describe '#request_response' do diff --git a/spec/ddtrace/contrib/grpc/interception_context_spec.rb b/spec/ddtrace/contrib/grpc/interception_context_spec.rb index b7ebd2b88d..0eb8cfe2c4 100644 --- a/spec/ddtrace/contrib/grpc/interception_context_spec.rb +++ b/spec/ddtrace/contrib/grpc/interception_context_spec.rb @@ -1,22 +1,32 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'grpc' require 'ddtrace' RSpec.describe GRPC::InterceptionContext do subject(:interception_context) { described_class.new } let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer, service_name: 'rspec' } } describe '#intercept!' do let(:span) { tracer.writer.spans.first } before do Datadog.configure do |c| - c.use :grpc, tracer: tracer, service_name: 'rspec' + c.use :grpc, configuration_options end subject.intercept!(type, keywords) {} end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:grpc].reset_configuration! + example.run + Datadog.registry[:grpc].reset_configuration! + end + context 'when intercepting on the client' do shared_examples 'span data contents' do specify { expect(span.name).to eq 'grpc.client' } @@ -25,6 +35,11 @@ specify { expect(span.resource).to eq 'myservice.endpoint' } specify { expect(span.get_tag('error.stack')).to be_nil } specify { expect(span.get_tag(:some)).to eq 'datum' } + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end context 'request response call type' do @@ -90,6 +105,11 @@ specify { expect(span.resource).to eq 'my.server.endpoint' } specify { expect(span.get_tag('error.stack')).to be_nil } specify { expect(span.get_tag(:some)).to eq 'datum' } + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::GRPC::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end context 'request response call type' do diff --git a/spec/ddtrace/contrib/http/request_spec.rb b/spec/ddtrace/contrib/http/request_spec.rb index e0fe213156..10338896c7 100644 --- a/spec/ddtrace/contrib/http/request_spec.rb +++ b/spec/ddtrace/contrib/http/request_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'ddtrace' require 'net/http' require 'time' @@ -17,12 +19,19 @@ let(:client) { Net::HTTP.new(host, port) } let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } let(:spans) { tracer.writer.spans } before(:each) do - Datadog.configure { |c| c.use :http } - Datadog::Pin.get_from(client).tracer = tracer + Datadog.configure { |c| c.use :http, configuration_options } + end + + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:http].reset_configuration! + example.run + Datadog.registry[:http].reset_configuration! end describe '#get' do @@ -48,10 +57,17 @@ expect(span.get_tag('out.port')).to eq(port.to_s) expect(span.status).to eq(0) end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::HTTP::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::HTTP::Ext::ENV_ANALYTICS_SAMPLE_RATE } + before(:each) { response } + end end context 'that returns 404' do - before(:each) { stub_request(:get, "#{uri}#{path}").to_return(status: 404) } + before(:each) { stub_request(:get, "#{uri}#{path}").to_return(status: 404, body: body) } + let(:body) { '{ "code": 404, message": "Not found!" }' } let(:span) { spans.first } it 'generates a well-formed trace' do @@ -67,6 +83,45 @@ expect(span.get_tag('out.port')).to eq(port.to_s) expect(span.status).to eq(1) expect(span.get_tag('error.type')).to eq('Net::HTTPNotFound') + expect(span.get_tag('error.msg')).to be nil + end + + context 'when configured with #after_request hook' do + before(:each) { Datadog::Contrib::HTTP::Instrumentation.after_request(&callback) } + after(:each) { Datadog::Contrib::HTTP::Instrumentation.instance_variable_set(:@after_request, nil) } + + context 'which defines each parameter' do + let(:callback) do + proc do |span, http, request, response| + expect(span).to be_a_kind_of(Datadog::Span) + expect(http).to be_a_kind_of(Net::HTTP) + expect(request).to be_a_kind_of(Net::HTTP::Get) + expect(response).to be_a_kind_of(Net::HTTPNotFound) + end + end + + it { expect(response.code).to eq('404') } + end + + context 'which changes the error status' do + let(:callback) do + proc do |span, _http, _request, response| + case response.code.to_i + when 400...599 + if response.class.body_permitted? && !response.body.nil? + span.set_error([response.class, response.body[0...4095]]) + end + end + end + end + + it 'generates a trace modified by the hook' do + expect(response.code).to eq('404') + expect(span.status).to eq(1) + expect(span.get_tag('error.type')).to eq('Net::HTTPNotFound') + expect(span.get_tag('error.msg')).to eq(body) + end + end end end end diff --git a/spec/ddtrace/contrib/mongodb/client_spec.rb b/spec/ddtrace/contrib/mongodb/client_spec.rb index 6a51b316dd..4d7363b848 100644 --- a/spec/ddtrace/contrib/mongodb/client_spec.rb +++ b/spec/ddtrace/contrib/mongodb/client_spec.rb @@ -1,10 +1,12 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'ddtrace' require 'mongo' RSpec.describe 'Mongo::Client instrumentation' do let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } let(:client) { Mongo::Client.new(["#{host}:#{port}"], client_options) } let(:client_options) { { database: database } } @@ -13,7 +15,6 @@ let(:database) { 'test' } let(:collection) { :artists } - let(:pin) { Datadog::Pin.get_from(client) } let(:spans) { tracer.writer.spans(:keep) } let(:span) { spans.first } @@ -26,18 +27,18 @@ def discard_spans! Mongo::Logger.logger.level = ::Logger::WARN Datadog.configure do |c| - c.use :mongo + c.use :mongo, configuration_options end - - # Have to manually update this because its still - # using global pin instead of configuration. - # Remove this when we remove the pin. - pin.tracer = tracer end # Clear data between tests let(:drop_database?) { true } - after(:each) do + + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:mongo].reset_configuration! + example.run + Datadog.registry[:mongo].reset_configuration! client.database.drop if drop_database? end @@ -45,16 +46,10 @@ def discard_spans! expect { |b| Mongo::Client.new(["#{host}:#{port}"], client_options, &b) }.to yield_control end - context 'pin' do - it 'has the correct attributes' do - expect(pin.service).to eq('mongodb') - expect(pin.app).to eq('mongodb') - expect(pin.app_type).to eq('db') - end - - context 'when the service is changed' do + context 'when the client is configured' do + context 'with a different service name' do let(:service) { 'mongodb-primary' } - before(:each) { pin.service = service } + before(:each) { Datadog.configure(client, service_name: service) } it 'produces spans with the correct service' do client[collection].insert_one(name: 'FKA Twigs') @@ -63,8 +58,8 @@ def discard_spans! end end - context 'when the tracer is disabled' do - before(:each) { pin.tracer.enabled = false } + context 'to disable the tracer' do + before(:each) { tracer.enabled = false } it 'produces spans with the correct service' do client[collection].insert_one(name: 'FKA Twigs') @@ -78,13 +73,18 @@ def discard_spans! shared_examples_for 'a MongoDB trace' do it 'has basic properties' do expect(spans).to have(1).items - expect(span.service).to eq(pin.service) + expect(span.service).to eq('mongodb') expect(span.span_type).to eq('mongodb') expect(span.get_tag('mongodb.db')).to eq(database) expect(span.get_tag('mongodb.collection')).to eq(collection.to_s) expect(span.get_tag('out.host')).to eq(host) expect(span.get_tag('out.port')).to eq(port.to_s) end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::MongoDB::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::MongoDB::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end describe '#insert_one operation' do diff --git a/spec/ddtrace/contrib/mysql2/patcher_spec.rb b/spec/ddtrace/contrib/mysql2/patcher_spec.rb index c68e67f669..bd7649494f 100644 --- a/spec/ddtrace/contrib/mysql2/patcher_spec.rb +++ b/spec/ddtrace/contrib/mysql2/patcher_spec.rb @@ -1,10 +1,13 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'ddtrace' require 'mysql2' RSpec.describe 'Mysql2::Client patcher' do let(:tracer) { get_test_tracer } + let(:service_name) { 'my-sql' } + let(:configuration_options) { { tracer: tracer, service_name: service_name } } let(:client) do Mysql2::Client.new( @@ -22,19 +25,27 @@ let(:username) { ENV.fetch('TEST_MYSQL_USER') { 'root' } } let(:password) { ENV.fetch('TEST_MYSQL_PASSWORD') { 'root' } } - let(:pin) { client.datadog_pin } let(:spans) { tracer.writer.spans(:keep) } let(:span) { spans.first } before(:each) do Datadog.configure do |c| - c.use :mysql2, service_name: 'my-sql', tracer: tracer + c.use :mysql2, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:mysql2].reset_configuration! + example.run + Datadog.registry[:mysql2].reset_configuration! + end + context 'pin' do + subject(:pin) { client.datadog_pin } + it 'has the correct attributes' do - expect(pin.service).to eq('my-sql') + expect(pin.service).to eq(service_name) expect(pin.app).to eq('mysql2') expect(pin.app_type).to eq('db') end @@ -42,7 +53,7 @@ describe 'tracing' do describe '#query' do - describe 'disabled tracer' do + context 'when the tracer is disabled' do before(:each) { tracer.enabled = false } it 'does not write spans' do @@ -51,21 +62,31 @@ end end - it 'traces successful queries' do - client.query('SELECT 1') - expect(spans.count).to eq(1) - expect(span.get_tag('mysql2.db.name')).to eq(database) - expect(span.get_tag('out.host')).to eq(host) - expect(span.get_tag('out.port')).to eq(port) + context 'when a successful query is made' do + before(:each) { client.query('SELECT 1') } + + it 'produces a trace' do + expect(spans.count).to eq(1) + expect(span.get_tag('mysql2.db.name')).to eq(database) + expect(span.get_tag('out.host')).to eq(host) + expect(span.get_tag('out.port')).to eq(port) + end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Mysql2::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Mysql2::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end - it 'traces failed queries' do - expect { client.query('SELECT INVALID') }.to raise_error(Mysql2::Error) + context 'when a failed query is made' do + before(:each) { expect { client.query('SELECT INVALID') }.to raise_error(Mysql2::Error) } - expect(spans.count).to eq(1) - expect(span.status).to eq(1) - expect(span.get_tag('error.msg')) - .to eq("Unknown column 'INVALID' in 'field list'") + it 'traces failed queries' do + expect(spans.count).to eq(1) + expect(span.status).to eq(1) + expect(span.get_tag('error.msg')) + .to eq("Unknown column 'INVALID' in 'field list'") + end end end end diff --git a/spec/ddtrace/contrib/racecar/patcher_spec.rb b/spec/ddtrace/contrib/racecar/patcher_spec.rb index e2e5d60e05..a350b67c1a 100644 --- a/spec/ddtrace/contrib/racecar/patcher_spec.rb +++ b/spec/ddtrace/contrib/racecar/patcher_spec.rb @@ -93,7 +93,7 @@ def all_spans it_behaves_like 'analytics for integration' do before { ActiveSupport::Notifications.instrument('process_message.racecar', payload) } - let(:analytics_enabled_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_SAMPLE_RATE } end end @@ -168,7 +168,7 @@ def all_spans it_behaves_like 'analytics for integration' do before { ActiveSupport::Notifications.instrument('process_batch.racecar', payload) } - let(:analytics_enabled_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Racecar::Ext::ENV_ANALYTICS_SAMPLE_RATE } end end diff --git a/spec/ddtrace/contrib/rack/configuration_spec.rb b/spec/ddtrace/contrib/rack/configuration_spec.rb index 929abab2cd..233d28dfd9 100644 --- a/spec/ddtrace/contrib/rack/configuration_spec.rb +++ b/spec/ddtrace/contrib/rack/configuration_spec.rb @@ -42,10 +42,10 @@ end end - it_behaves_like 'analytics for integration' do + it_behaves_like 'analytics for integration', ignore_global_flag: false do include_context 'an incoming HTTP request' before { is_expected.to be_ok } - let(:analytics_enabled_var) { Datadog::Contrib::Rack::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Rack::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Rack::Ext::ENV_ANALYTICS_SAMPLE_RATE } end diff --git a/spec/ddtrace/contrib/rails/analytics_spec.rb b/spec/ddtrace/contrib/rails/analytics_spec.rb new file mode 100644 index 0000000000..78014d3a16 --- /dev/null +++ b/spec/ddtrace/contrib/rails/analytics_spec.rb @@ -0,0 +1,61 @@ +require 'ddtrace/contrib/analytics_examples' +require 'ddtrace/contrib/rails/rails_helper' + +RSpec.describe 'Rails trace analytics' do + let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } + + before(:each) do + Datadog::RailsActionPatcher.patch_action_controller + Datadog.configure do |c| + c.use :rails, configuration_options + end + end + + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:rails].reset_configuration! + example.run + Datadog.registry[:rails].reset_configuration! + end + + let(:span) { spans.first } + let(:spans) { tracer.writer.spans(:keep) } + + describe 'for a controller action' do + subject(:result) { action.call(env) } + let(:controller) do + stub_const('TestController', Class.new(base_class) do + def index + # Do nothing + end + end) + end + let(:name) { :index } + let(:base_class) { ActionController::Metal } + let(:action) { controller.action(name) } + let(:env) { {} } + + before(:each) do + # ActionController::Metal is only patched in 2.0+ + skip 'Not supported for Ruby < 2.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') + end + + shared_examples_for 'a successful dispatch' do + it do + expect { result }.to_not raise_error + expect(result).to be_a_kind_of(Array) + expect(result).to have(3).items + expect(spans).to have(1).items + expect(span.name).to eq('rails.action_controller') + end + end + + it_behaves_like 'analytics for integration', ignore_global_flag: false do + before { expect { result }.to_not raise_error } + let(:analytics_enabled_var) { Datadog::Contrib::Rails::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Rails::Ext::ENV_ANALYTICS_SAMPLE_RATE } + it_behaves_like 'a successful dispatch' + end + end +end diff --git a/spec/ddtrace/contrib/rake/instrumentation_spec.rb b/spec/ddtrace/contrib/rake/instrumentation_spec.rb index c208e6cbf7..f95f4a4d6f 100644 --- a/spec/ddtrace/contrib/rake/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/rake/instrumentation_spec.rb @@ -94,7 +94,7 @@ def reset_task!(task_name) it_behaves_like 'analytics for integration' do let(:span) { invoke_span } - let(:analytics_enabled_var) { Datadog::Contrib::Rake::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Rake::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Rake::Ext::ENV_ANALYTICS_SAMPLE_RATE } end end diff --git a/spec/ddtrace/contrib/redis/redis_spec.rb b/spec/ddtrace/contrib/redis/redis_spec.rb index accdb9e496..043eb58ae0 100644 --- a/spec/ddtrace/contrib/redis/redis_spec.rb +++ b/spec/ddtrace/contrib/redis/redis_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'time' require 'redis' @@ -7,6 +8,7 @@ RSpec.describe 'Redis test' do let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } def all_spans tracer.writer.spans(:keep) @@ -14,10 +16,17 @@ def all_spans before(:each) do Datadog.configure do |c| - c.use :redis, tracer: tracer + c.use :redis, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:redis].reset_configuration! + example.run + Datadog.registry[:redis].reset_configuration! + end + shared_examples_for 'a Redis driver' do |driver| let(:redis) { Redis.new(host: host, port: port, driver: driver) } let(:host) { ENV.fetch('TEST_REDIS_HOST', '127.0.0.1') } @@ -43,6 +52,11 @@ def all_spans expect(span.get_tag('out.port')).to eq(port.to_s) expect(span.get_tag('out.redis_db')).to eq('0') end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Redis::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Redis::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end context 'roundtrip' do diff --git a/spec/ddtrace/contrib/registerable_spec.rb b/spec/ddtrace/contrib/registerable_spec.rb index d81a3aa695..fc01c92a11 100644 --- a/spec/ddtrace/contrib/registerable_spec.rb +++ b/spec/ddtrace/contrib/registerable_spec.rb @@ -19,7 +19,7 @@ context 'when a registry' do context 'is provided' do let(:options) { { registry: registry } } - let(:registry) { instance_double(Datadog::Registry) } + let(:registry) { instance_double(Datadog::Contrib::Registry) } it do expect(registry).to receive(:add) diff --git a/spec/ddtrace/registry_spec.rb b/spec/ddtrace/contrib/registry_spec.rb similarity index 91% rename from spec/ddtrace/registry_spec.rb rename to spec/ddtrace/contrib/registry_spec.rb index db93b985a0..c328aa75fc 100644 --- a/spec/ddtrace/registry_spec.rb +++ b/spec/ddtrace/contrib/registry_spec.rb @@ -2,7 +2,7 @@ require 'ddtrace' -RSpec.describe Datadog::Registry do +RSpec.describe Datadog::Contrib::Registry do describe 'instance' do subject(:registry) { described_class.new } @@ -17,7 +17,7 @@ context 'when given an entry to the registry' do it do entry = registry.add(name, klass, auto_patch) - expect(entry).to be_an_instance_of(Datadog::Registry::Entry) + expect(entry).to be_an_instance_of(described_class::Entry) expect(registry[name]).to eq(klass) end end @@ -42,7 +42,7 @@ end end - describe Datadog::Registry::Entry do + describe Datadog::Contrib::Registry::Entry do describe 'instance' do subject(:entry) { described_class.new(name, klass, auto_patch) } diff --git a/spec/ddtrace/contrib/resque/instrumentation_spec.rb b/spec/ddtrace/contrib/resque/instrumentation_spec.rb index 43eb5bcf07..4f144dac98 100644 --- a/spec/ddtrace/contrib/resque/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/resque/instrumentation_spec.rb @@ -50,7 +50,7 @@ end it_behaves_like 'analytics for integration' do - let(:analytics_enabled_var) { Datadog::Contrib::Resque::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Resque::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Resque::Ext::ENV_ANALYTICS_SAMPLE_RATE } end end diff --git a/spec/ddtrace/contrib/rest_client/request_patch_spec.rb b/spec/ddtrace/contrib/rest_client/request_patch_spec.rb index fc4d992bd8..2693ddeb82 100644 --- a/spec/ddtrace/contrib/rest_client/request_patch_spec.rb +++ b/spec/ddtrace/contrib/rest_client/request_patch_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' + require 'ddtrace' require 'ddtrace/contrib/rest_client/request_patch' require 'rest_client' @@ -17,7 +19,12 @@ WebMock.enable! end - after(:each) { Datadog.registry[:rest_client].reset_configuration! } + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:rest_client].reset_configuration! + example.run + Datadog.registry[:rest_client].reset_configuration! + end describe 'instrumented request' do let(:path) { '/sample/path' } @@ -78,6 +85,11 @@ it 'has correct service name' do expect(span.service).to eq('rest_client') end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::RestClient::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::RestClient::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end context 'response has internal server error status' do diff --git a/spec/ddtrace/contrib/sequel/instrumentation_spec.rb b/spec/ddtrace/contrib/sequel/instrumentation_spec.rb index f1f39dba60..ce0d7c5d19 100644 --- a/spec/ddtrace/contrib/sequel/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/sequel/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'time' require 'sequel' @@ -19,15 +20,19 @@ before(:each) do skip('Sequel not compatible.') unless Datadog::Contrib::Sequel::Integration.compatible? - # Reset options (that might linger from other tests) - Datadog.configuration[:sequel].reset_options! - # Patch Sequel Datadog.configure do |c| c.use :sequel, configuration_options end end + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:sequel].reset_configuration! + example.run + Datadog.registry[:sequel].reset_configuration! + end + describe 'for a SQLite database' do before(:each) do sequel.create_table(:table) do @@ -51,6 +56,11 @@ expect(span.status).to eq(0) expect(span.parent_id).to eq(0) end + + it_behaves_like 'analytics for integration' do + let(:analytics_enabled_var) { Datadog::Contrib::Sequel::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Sequel::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end describe 'when queried through a Sequel::Dataset' do @@ -115,6 +125,13 @@ expect(command_span.trace_id).to eq(publish_span.trace_id) end end + + it_behaves_like 'analytics for integration' do + # Check one of the command spans at random + let(:span) { spans[2..5].sample } + let(:analytics_enabled_var) { Datadog::Contrib::Sequel::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Sequel::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end end end end diff --git a/spec/ddtrace/contrib/shoryuken/tracer_spec.rb b/spec/ddtrace/contrib/shoryuken/tracer_spec.rb index acb57abfe5..6616ebc84e 100644 --- a/spec/ddtrace/contrib/shoryuken/tracer_spec.rb +++ b/spec/ddtrace/contrib/shoryuken/tracer_spec.rb @@ -66,7 +66,7 @@ def perform(sqs_msg, body); end it_behaves_like 'analytics for integration' do include_context 'Shoryuken::Worker' let(:body) { {} } - let(:analytics_enabled_var) { Datadog::Contrib::Shoryuken::Ext::ENV_ANALYTICS_ENALBED } + let(:analytics_enabled_var) { Datadog::Contrib::Shoryuken::Ext::ENV_ANALYTICS_ENABLED } let(:analytics_sample_rate_var) { Datadog::Contrib::Shoryuken::Ext::ENV_ANALYTICS_SAMPLE_RATE } before { call } end diff --git a/spec/ddtrace/contrib/sinatra/tracer_spec.rb b/spec/ddtrace/contrib/sinatra/tracer_spec.rb index 3f93e4b1d5..e2b739ed5b 100644 --- a/spec/ddtrace/contrib/sinatra/tracer_spec.rb +++ b/spec/ddtrace/contrib/sinatra/tracer_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/analytics_examples' require 'rack/test' require 'sinatra/base' @@ -10,18 +11,23 @@ include Rack::Test::Methods let(:tracer) { get_test_tracer } - let(:options) { { tracer: tracer } } + let(:configuration_options) { { tracer: tracer } } let(:span) { spans.first } let(:spans) { tracer.writer.spans } before(:each) do Datadog.configure do |c| - c.use :sinatra, options + c.use :sinatra, configuration_options end end - after(:each) { Datadog.registry[:sinatra].reset_configuration! } + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:sinatra].reset_configuration! + example.run + Datadog.registry[:sinatra].reset_configuration! + end shared_context 'app with simple route' do let(:app) do @@ -54,6 +60,12 @@ expect(span.parent).to be nil end + it_behaves_like 'analytics for integration', ignore_global_flag: false do + before { is_expected.to be_ok } + let(:analytics_enabled_var) { Datadog::Contrib::Sinatra::Ext::ENV_ANALYTICS_ENABLED } + let(:analytics_sample_rate_var) { Datadog::Contrib::Sinatra::Ext::ENV_ANALYTICS_SAMPLE_RATE } + end + context 'which sets X-Request-Id on the response' do let(:app) do req_id = request_id @@ -278,7 +290,7 @@ end context 'with a custom service name' do - let(:options) { super().merge(service_name: service_name) } + let(:configuration_options) { super().merge(service_name: service_name) } let(:service_name) { 'my-sinatra-app' } context 'and a simple request is made' do @@ -325,7 +337,7 @@ end context 'with distributed tracing disabled' do - let(:options) { super().merge(distributed_tracing: false) } + let(:configuration_options) { super().merge(distributed_tracing: false) } context 'and a simple request is made' do include_context 'app with simple route' @@ -355,7 +367,7 @@ end context 'with header tags' do - let(:options) { super().merge(headers: { request: request_headers, response: response_headers }) } + let(:configuration_options) { super().merge(headers: { request: request_headers, response: response_headers }) } let(:request_headers) { [] } let(:response_headers) { [] } @@ -392,7 +404,7 @@ end context 'with script names' do - let(:options) { super().merge(resource_script_names: true) } + let(:configuration_options) { super().merge(resource_script_names: true) } let(:app) do Class.new(Sinatra::Application) do diff --git a/spec/ddtrace/opentracer/global_tracer_spec.rb b/spec/ddtrace/opentracer/global_tracer_spec.rb index 0954dbc486..2e21addf24 100644 --- a/spec/ddtrace/opentracer/global_tracer_spec.rb +++ b/spec/ddtrace/opentracer/global_tracer_spec.rb @@ -11,7 +11,7 @@ context 'when included into OpenTracing' do describe '#global_tracer=' do subject(:global_tracer) { OpenTracing.global_tracer = tracer } - after(:each) { Datadog.instance_variable_set(:@tracer, Datadog::Tracer.new) } + after(:each) { Datadog.configuration.tracer = Datadog::Tracer.new } context 'when given a Datadog::OpenTracer::Tracer' do let(:tracer) { Datadog::OpenTracer::Tracer.new } diff --git a/spec/ddtrace/propagation/distributed_headers_spec.rb b/spec/ddtrace/propagation/distributed_headers_spec.rb index 56db985ee8..279650ce8f 100644 --- a/spec/ddtrace/propagation/distributed_headers_spec.rb +++ b/spec/ddtrace/propagation/distributed_headers_spec.rb @@ -49,4 +49,243 @@ def env_header(name) end end end + + describe '#trace_id' do + context 'no trace_id header' do + it { expect(headers.trace_id).to be_nil } + end + + context 'incorrect header' do + [ + 'X-DATADOG-TRACE-ID-TYPO', + 'X-DATDOG-TRACE-ID', + 'X-TRACE-ID', + 'TRACE-ID' + ].each do |header| + # '100' is a valid value + let(:env) { { env_header(header) => '100' } } + + it { expect(headers.trace_id).to be_nil } + end + end + + context 'trace_id in header' do + [ + ['123', 123], + ['0', nil], + ['a', nil], + ['-1', 18446744073709551615], + ['-8809075535603237910', 9637668538106313706], + ['ooops', nil], + + # Boundaries of what we generate + [Datadog::Span::MAX_ID.to_s, Datadog::Span::MAX_ID], + [(Datadog::Span::MAX_ID + 1).to_s, Datadog::Span::MAX_ID + 1], + + # Max allowed values + [Datadog::Span::EXTERNAL_MAX_ID.to_s, Datadog::Span::EXTERNAL_MAX_ID], + [(Datadog::Span::EXTERNAL_MAX_ID + 1).to_s, nil] + ].each do |value, expected| + context "set to #{value}" do + let(:env) { { env_header(Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID) => value } } + + it { expect(headers.trace_id).to eq(expected) } + end + end + end + end + + describe '#parent_id' do + context 'no parent_id header' do + it { expect(headers.parent_id).to be_nil } + end + + context 'incorrect header' do + [ + 'X-DATADOG-PARENT-ID-TYPO', + 'X-DATDOG-PARENT-ID', + 'X-PARENT-ID', + 'PARENT-ID' + ].each do |header| + # '100' is a valid value + let(:env) { { env_header(header) => '100' } } + + it { expect(headers.parent_id).to be_nil } + end + end + + context 'parent_id in header' do + [ + ['123', 123], + ['0', nil], + ['a', nil], + ['-1', 18446744073709551615], + ['-8809075535603237910', 9637668538106313706], + ['ooops', nil], + + # Boundaries of what we generate + [Datadog::Span::MAX_ID.to_s, Datadog::Span::MAX_ID], + [(Datadog::Span::MAX_ID + 1).to_s, Datadog::Span::MAX_ID + 1], + + # Max allowed values + [Datadog::Span::EXTERNAL_MAX_ID.to_s, Datadog::Span::EXTERNAL_MAX_ID], + [(Datadog::Span::EXTERNAL_MAX_ID + 1).to_s, nil] + ].each do |value, expected| + context "set to #{value}" do + let(:env) { { env_header(Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID) => value } } + + it { expect(headers.parent_id).to eq(expected) } + end + end + end + end + + describe '#sampling_priority' do + context 'no sampling priorityheader' do + it { expect(headers.sampling_priority).to be_nil } + end + + context 'incorrect header' do + [ + 'X-DATADOG-SAMPLING-PRIORITY-TYPO', + 'X-DATDOG-SAMPLING-PRIORITY', + 'X-SAMPLING-PRIORITY', + 'SAMPLING-PRIORITY' + ].each do |header| + # '100' is a valid value + let(:env) { { env_header(header) => '100' } } + + it { expect(headers.sampling_priority).to be_nil } + end + end + + context 'sampling_priority in header' do + [ + # Allowed values + ['-1', -1], + ['0', 0], + ['1', 1], + ['2', 2], + + # Outside of bounds, but still allowed since a number + ['-2', -2], + ['3', 3], + ['999', 999], + + # Not a number + ['ooops', nil] + ].each do |value, expected| + context "set to #{value}" do + let(:env) { { env_header(Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY) => value } } + + it { expect(headers.sampling_priority).to eq(expected) } + end + end + end + end + + describe '#valid?' do + context 'with headers' do + [ + # Trace id and Parent id with no other headers - valid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456' }, + true + ], + + # All acceptable values for sampling priority - valid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '-1' }, + true + ], + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '0' }, + true + ], + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '1' }, + true + ], + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '2' }, + true + ], + + # Invalid Trace id - invalid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => 'a', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '0' }, + false + ], + + # Invalid Parent id - invalid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => 'a', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '0' }, + false + ], + + # Invalid sampling priority - valid + # DEV: This is valid because sampling priority isn't required + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => 'nan' }, + true + ], + + # Trace id and Parent id both 0 - invalid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '0', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '0', + Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY => '0' }, + false + ], + + # Typos in header names - invalid + [ + { 'X-DATADOG-TRACE-ID-TYPO' => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '456' }, + false + ], + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + 'X-DATADOG-PARENT-ID-TYPO' => '456' }, + false + ], + + # Parent id is not required when origin is 'synthetics' - valid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '0', + Datadog::Ext::DistributedTracing::HTTP_HEADER_ORIGIN => 'synthetics' }, + true + ], + # Invalid when not 'synthetics' - invalid + [ + { Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => '123', + Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => '0', + Datadog::Ext::DistributedTracing::HTTP_HEADER_ORIGIN => 'not-synthetics' }, + false + ] + ].each do |test_headers, expected| + context test_headers.to_s do + let(:env) { Hash[test_headers.map { |k, v| [env_header(k), v] }] } + + it { expect(headers.valid?).to eq(expected) } + end + end + end + end end diff --git a/spec/ddtrace_spec.rb b/spec/ddtrace_spec.rb index 7b3876cefe..a0cd6973b6 100644 --- a/spec/ddtrace_spec.rb +++ b/spec/ddtrace_spec.rb @@ -12,12 +12,12 @@ describe '#registry' do subject { datadog.registry } - it { is_expected.to be_an_instance_of(Datadog::Registry) } + it { is_expected.to be_an_instance_of(Datadog::Contrib::Registry) } end describe '#configuration' do subject { datadog.configuration } - it { is_expected.to be_an_instance_of(Datadog::Configuration) } + it { is_expected.to be_an_instance_of(Datadog::Configuration::Settings) } end describe '#configure' do diff --git a/test/logger_test.rb b/test/logger_test.rb index 4cf352bf22..4809073185 100644 --- a/test/logger_test.rb +++ b/test/logger_test.rb @@ -4,6 +4,18 @@ require 'ddtrace/span' class LoggerTest < Minitest::Test + DEFAULT_LOG = Datadog::Tracer.log + + def setup + @buf = StringIO.new + Datadog::Tracer.log = Datadog::Logger.new(@buf) + Datadog::Tracer.log.level = ::Logger::WARN + end + + def teardown + Datadog::Tracer.log = DEFAULT_LOG + end + def test_tracer_logger # a logger must be available by default assert Datadog::Tracer.log @@ -28,15 +40,18 @@ def test_tracer_set_debug_mode assert_equal(logger.level, Logger::WARN) end - # rubocop:disable Metrics/MethodLength - def test_tracer_logger_override - default_log = Datadog::Tracer.log + def test_tracer_set_debug_custom_noop + # custom logger + custom_buf = StringIO.new + custom_logger = Logger.new(custom_buf) + custom_logger.level = ::Logger::INFO + Datadog::Tracer.log = custom_logger - buf = StringIO.new - - Datadog::Tracer.log = Datadog::Logger.new(buf) - Datadog::Tracer.log.level = ::Logger::WARN + Datadog::Tracer.debug_logging = false + assert_equal(custom_logger.level, ::Logger::INFO) + end + def test_tracer_logger_override assert_equal(false, Datadog::Tracer.log.debug?) assert_equal(false, Datadog::Tracer.log.info?) assert_equal(true, Datadog::Tracer.log.warn?) @@ -50,7 +65,7 @@ def test_tracer_logger_override Datadog::Tracer.log.progname = 'bar' Datadog::Tracer.log.add(Logger::WARN, 'add some warning') - lines = buf.string.lines + lines = @buf.string.lines assert_equal(4, lines.length, 'there should be 4 log messages') if lines.respond_to? :length # Test below iterates on lines, this is required for Ruby 1.9 backward compatibility. @@ -74,16 +89,9 @@ def test_tracer_logger_override end i += 1 end - - Datadog::Tracer.log = default_log end def test_tracer_logger_override_debug - default_log = Datadog::Tracer.log - - buf = StringIO.new - - Datadog::Tracer.log = Datadog::Logger.new(buf) Datadog::Tracer.log.level = ::Logger::DEBUG assert_equal(true, Datadog::Tracer.log.debug?) @@ -95,7 +103,7 @@ def test_tracer_logger_override_debug Datadog::Tracer.log.debug('detailed things') Datadog::Tracer.log.info() { 'more detailed info' } - lines = buf.string.lines + lines = @buf.string.lines # Test below iterates on lines, this is required for Ruby 1.9 backward compatibility. assert_equal(2, lines.length, 'there should be 2 log messages') if lines.respond_to? :length @@ -115,13 +123,9 @@ def test_tracer_logger_override_debug end i += 1 end - - Datadog::Tracer.log = default_log end def test_tracer_logger_override_refuse - default_log = Datadog::Tracer.log - buf = StringIO.new buf_log = Datadog::Logger.new(buf) @@ -133,7 +137,5 @@ def test_tracer_logger_override_refuse assert_equal(buf_log, Datadog::Tracer.log) Datadog::Tracer.log = "this won't work" assert_equal(buf_log, Datadog::Tracer.log) - - Datadog::Tracer.log = default_log end end diff --git a/test/propagation/distributed_headers_test.rb b/test/propagation/distributed_headers_test.rb deleted file mode 100644 index 18bf7f4a4c..0000000000 --- a/test/propagation/distributed_headers_test.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'helper' -require 'ddtrace/tracer' -require 'ddtrace/span' -require 'ddtrace/propagation/distributed_headers' - -class DistributedHeadersTest < Minitest::Test - def test_valid_without_sampling_priority # rubocop:disable Metrics/MethodLength - test_cases = { - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456' } => true, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '0' } => true, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '1' } => true, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '999' } => true, - { 'HTTP_X_DATADOG_TRACE_ID' => 'a', - 'HTTP_X_DATADOG_PARENT_ID' => '456', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '0' } => false, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => 'ooops' } => true, # corner case, 0 is valid for a sampling priority - { 'HTTP_X_DATADOG_TRACE_ID' => '0', - 'HTTP_X_DATADOG_PARENT_ID' => '0' } => false, - { 'HTTP_X_DATADOG_TRACE_TYPO' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '456' } => false, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => 'b', - 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '0' } => false, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_TYPO' => '456' } => false, - # Parent id is not required when origin is synthetics - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '0', - 'HTTP_X_DATADOG_ORIGIN' => 'not-synthetics' } => false, - { 'HTTP_X_DATADOG_TRACE_ID' => '123', - 'HTTP_X_DATADOG_PARENT_ID' => '0', - 'HTTP_X_DATADOG_ORIGIN' => 'synthetics' } => true - } - - test_cases.each do |env, expected| - dh = Datadog::DistributedHeaders.new(env) - if dh.valid? - assert_equal(expected, true, "with #{env} valid? should be true") - else - assert_equal(expected, false, "with #{env} valid? should be false") - end - end - end - - def test_trace_id - test_cases = { - { 'HTTP_X_DATADOG_TRACE_ID' => '123' } => 123, - { 'HTTP_X_DATADOG_TRACE_ID' => '0' } => nil, - { 'HTTP_X_DATADOG_TRACE_ID' => '-1' } => 18446744073709551615, - { 'HTTP_X_DATADOG_TRACE_ID' => '-8809075535603237910' } => 9637668538106313706, - { 'HTTP_X_DATADOG_TRACE_ID' => 'ooops' } => nil, - { 'HTTP_X_DATADOG_TRACE_TYPO' => '1' } => nil, - { 'HTTP_X_DATADOG_TRACE_ID' => Datadog::Span::MAX_ID.to_s } => nil, - { 'HTTP_X_DATADOG_TRACE_ID' => (Datadog::Span::MAX_ID - 1).to_s } => Datadog::Span::MAX_ID - 1 - } - - test_cases.each do |env, expected| - dh = Datadog::DistributedHeaders.new(env) - if expected - assert_equal(expected, dh.trace_id, "with #{env} trace_id should return #{expected}") - else - assert_nil(dh.trace_id, "with #{env} trace_id should return nil") - end - end - end - - def test_parent_id - test_cases = { - { 'HTTP_X_DATADOG_PARENT_ID' => '123' } => 123, - { 'HTTP_X_DATADOG_PARENT_ID' => '0' } => nil, - { 'HTTP_X_DATADOG_PARENT_ID' => 'a' } => nil, - { 'HTTP_X_DATADOG_PARENT_ID' => '' } => nil, - { 'HTTP_X_DATADOG_PARENT_ID' => '-1' } => 18446744073709551615, - { 'HTTP_X_DATADOG_PARENT_ID' => '-8809075535603237910' } => 9637668538106313706, - { 'HTTP_X_DATADOG_PARENT_ID' => 'ooops' } => nil, - { 'HTTP_X_DATADOG_PARENT_TYPO' => '1' } => nil, - { 'HTTP_X_DATADOG_PARENT_ID' => Datadog::Span::MAX_ID.to_s } => nil, - { 'HTTP_X_DATADOG_PARENT_ID' => (Datadog::Span::MAX_ID - 1).to_s } => Datadog::Span::MAX_ID - 1 - } - - test_cases.each do |env, expected| - dh = Datadog::DistributedHeaders.new(env) - if expected - assert_equal(expected, dh.parent_id, "with #{env} parent_id should return #{expected}") - else - assert_nil(dh.parent_id, "with #{env} parent_id should return nil") - end - end - end - - def test_sampling_priority - test_cases = { - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '0' } => 0, - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '1' } => 1, - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '2' } => 2, - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '999' } => 999, - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => '-1' } => nil, - { 'HTTP_X_DATADOG_SAMPLING_PRIORITY' => 'ooops' } => 0, - # nil cases below are very important to test, they are valid real-world use cases - {} => nil, - { 'HTTP_X_DATADOG_SAMPLING_TYPO' => '1' } => nil - } - - test_cases.each do |env, expected| - dh = Datadog::DistributedHeaders.new(env) - if expected - assert_equal(expected, dh.sampling_priority, "with #{env} sampling_priority should return #{expected}") - else - assert_nil(dh.sampling_priority, "with #{env} sampling_priority should return nil") - end - end - end -end