diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac4180a5c4..3c3b96784f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ ## [Unreleased (beta)] +## [0.19.0] - 2019-01-22 + +Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.19.0 + +Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.18.3...v0.19.0 + +### Added + + - Tracer#active_correlation for adding correlation IDs to logs. (#660, #664, #673) + - Opt-in support for `event_sample_rate` tag for some integrations. (#665, #666) + +### Changed + + - Priority sampling enabled by default. (#654) + ## [0.18.3] - 2019-01-17 Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.18.3 @@ -627,8 +642,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.18.3...master -[Unreleased (beta)]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.3...0.19-dev +[Unreleased (stable)]: https://github.com/DataDog/dd-trace-rb/compare/v0.19.0...master +[Unreleased (beta)]: https://github.com/DataDog/dd-trace-rb/compare/v0.19.0...0.20-dev +[0.19.0]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.3...v0.19.0 [0.18.3]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.2...v0.18.3 [0.18.2]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.1...v0.18.2 [0.18.1]: https://github.com/DataDog/dd-trace-rb/compare/v0.18.0...v0.18.1 diff --git a/Rakefile b/Rakefile index 344c9fc9c95..676f354c1bb 100644 --- a/Rakefile +++ b/Rakefile @@ -46,7 +46,7 @@ namespace :spec do end RSpec::Core::RakeTask.new(:contrib) do |t| - t.pattern = 'spec/**/contrib/{configurable,integration,patchable,patcher,registerable,configuration/*}_spec.rb' + t.pattern = 'spec/**/contrib/{configurable,integration,patchable,patcher,registerable,sampling,configuration/*}_spec.rb' end [ diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index a076565664b..940ce69a81e 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -62,6 +62,7 @@ For descriptions of terminology used in APM, take a look at the [official docume - [Processing pipeline](#processing-pipeline) - [Filtering](#filtering) - [Processing](#processing) + - [Trace correlation](#trace-correlation) - [OpenTracing](#opentracing) ## Compatibility @@ -517,6 +518,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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` | @@ -865,6 +867,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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` | @@ -897,6 +900,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | --- | ----------- | ------- | | `application` | Your Rack application. Required for `middleware_names`. | `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 | `false` | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `headers` | Hash of HTTP request or response headers to add as tags to the `rack.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'] }` | | `middleware_names` | Enable this if you want to use the middleware classes as the resource names for `rack` spans. Requires `application` option to use. | `false` | | `quantize` | Hash containing options for quantization. May include `:query` or `:fragment`. | `{}` | @@ -1006,6 +1010,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | | `enabled` | Defines whether Rake tasks should be traced. Useful for temporarily disabling tracing. `true` or `false` | `true` | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | @@ -1107,6 +1112,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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]`) | `[]` | @@ -1198,6 +1204,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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` | @@ -1220,6 +1227,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | | `client_service_name` | Service name used for client-side `sidekiq` instrumentation | `'sidekiq-client'` | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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` | @@ -1271,6 +1279,7 @@ Where `options` is an optional `Hash` that accepts the following parameters: | Key | Description | Default | | --- | ----------- | ------- | +| `event_sample_rate` | Rate which spans should be sampled for search and analytics. | `nil` | | `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` | @@ -1356,15 +1365,7 @@ The sampler can set the priority to the following values: - `Datadog::Ext::Priority::AUTO_REJECT`: the sampler automatically decided to reject the trace. - `Datadog::Ext::Priority::AUTO_KEEP`: the sampler automatically decided to keep the trace. -For now, priority sampling is disabled by default. Enabling it ensures that your sampled distributed traces will be complete. To enable the priority sampling: - -```ruby -Datadog.configure do |c| - c.tracer priority_sampling: true -end -``` - -Once enabled, the sampler will automatically assign a priority of 0 or 1 to traces, depending on their service and volume. +Priority sampling is enabled by default. Enabling it ensures that your sampled distributed traces will be complete. Once enabled, the sampler will automatically assign a priority of 0 or 1 to traces, depending on their service and volume. You can also set this priority manually to either drop a non-interesting trace or to keep an important one. For that, set the `context#sampling_priority` to: @@ -1613,6 +1614,77 @@ Datadog::Pipeline.before_flush( ) ``` +### Trace correlation + +In many cases, such as logging, it may be useful to correlate trace IDs to other events or data streams, for easier cross referencing. The tracer can produce a correlation identifier for the currently active trace via `active_correlation`, which can be used to decorate these other data sources. + +```ruby +# When a trace is active... +Datadog.tracer.trace('correlation.example') do + # Returns # + correlation = Datadog.tracer.active_correlation + correlation.trace_id # => 5963550561812073440 + correlation.span_id # => 2232727802607726424 +end + +# When a trace isn't active... +correlation = Datadog.tracer.active_correlation +# Returns # +correlation = Datadog.tracer.active_correlation +correlation.trace_id # => 0 +correlation.span_id # => 0 +``` + +#### For logging in Ruby applications + +To add correlation IDs to your logger, add a log formatter which retrieves the correlation IDs with `Datadog.tracer.active_correlation`, then add them to the message. + +To properly correlate with Datadog logging, be sure the following is present: + + - `dd.trace_id=`: Where `` is equal to `Datadog.tracer.active_correlation.trace_id` or `0` if no trace is active. + - `dd.span_id=`: Where `` is equal to `Datadog.tracer.active_correlation.span_id` or `0` if no trace is active. + +By default, `Datadog::Correlation::Identifier#to_s` will return `dd.trace_id= dd.span_id=`. + +An example of this in practice: + +```ruby +require 'ddtrace' +require 'logger' + +logger = Logger.new(STDOUT) +logger.progname = 'my_app' +logger.formatter = proc do |severity, datetime, progname, msg| + "[#{datetime}][#{progname}][#{severity}][#{Datadog.tracer.active_correlation}] #{msg}\n" +end + +# When no trace is active +logger.warn('This is an untraced operation.') +# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.trace_id=0 dd.span_id=0] This is an untraced operation. + +# When a trace is active +Datadog.tracer.trace('my.operation') { logger.warn('This is a traced operation.') } +# [2019-01-16 18:38:41 +0000][my_app][WARN][dd.trace_id=8545847825299552251 dd.span_id=3711755234730770098] This is a traced operation. +``` + +#### For logging in Rails applications + +Rails applications which are configured with a `ActiveSupport::TaggedLogging` logger can append correlation IDs as tags to log output. The default Rails logger implements this tagged logging, making it easier to add correlation tags. + +In your Rails environment configuration file, add the following: + +```ruby +Rails.application.configure do + config.log_tags = [proc { Datadog.tracer.active_correlation.to_s }] +end + +# Web requests will produce: +# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Started GET "/articles" for 172.22.0.1 at 2019-01-16 18:50:57 +0000 +# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Processing by ArticlesController#index as */* +# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Article Load (0.5ms) SELECT "articles".* FROM "articles" +# [dd.trace_id=7110975754844687674 dd.span_id=7518426836986654206] Completed 200 OK in 7ms (Views: 5.5ms | ActiveRecord: 0.5ms) +``` + ### OpenTracing For setting up Datadog with OpenTracing, see out [Quickstart for OpenTracing](#quickstart-for-opentracing) section for details. diff --git a/lib/ddtrace/contrib/configuration/settings.rb b/lib/ddtrace/contrib/configuration/settings.rb index c0973e5dc82..d830c7dd0c3 100644 --- a/lib/ddtrace/contrib/configuration/settings.rb +++ b/lib/ddtrace/contrib/configuration/settings.rb @@ -9,6 +9,7 @@ class Settings option :service_name option :tracer, default: Datadog.tracer + option :event_sample_rate def initialize(options = {}) configure(options) diff --git a/lib/ddtrace/contrib/delayed_job/plugin.rb b/lib/ddtrace/contrib/delayed_job/plugin.rb index bef91e6f7b6..fdcbc8211de 100644 --- a/lib/ddtrace/contrib/delayed_job/plugin.rb +++ b/lib/ddtrace/contrib/delayed_job/plugin.rb @@ -1,4 +1,5 @@ require 'delayed/plugin' +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/delayed_job/ext' module Datadog @@ -18,6 +19,7 @@ def self.instrument(job, &block) end tracer.trace(Ext::SPAN_JOB, service: configuration[:service_name], resource: job_name) do |span| + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.set_tag(Ext::TAG_ID, job.id) span.set_tag(Ext::TAG_QUEUE, job.queue) if job.queue span.set_tag(Ext::TAG_PRIORITY, job.priority) diff --git a/lib/ddtrace/contrib/racecar/event.rb b/lib/ddtrace/contrib/racecar/event.rb index 35d8bd4cf67..8110353ed0f 100644 --- a/lib/ddtrace/contrib/racecar/event.rb +++ b/lib/ddtrace/contrib/racecar/event.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/active_support/notifications/event' require 'ddtrace/contrib/racecar/ext' @@ -36,6 +37,7 @@ def process(span, event, _id, payload) span.service = configuration[:service_name] span.resource = payload[:consumer_class] + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.set_tag(Ext::TAG_TOPIC, payload[:topic]) span.set_tag(Ext::TAG_CONSUMER, payload[:consumer_class]) span.set_tag(Ext::TAG_PARTITION, payload[:partition]) diff --git a/lib/ddtrace/contrib/rack/middlewares.rb b/lib/ddtrace/contrib/rack/middlewares.rb index fe7e6d3fbc0..1092f81b1ac 100644 --- a/lib/ddtrace/contrib/rack/middlewares.rb +++ b/lib/ddtrace/contrib/rack/middlewares.rb @@ -1,6 +1,7 @@ require 'ddtrace/ext/app_types' require 'ddtrace/ext/http' require 'ddtrace/propagation/http_propagator' +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/rack/ext' require 'ddtrace/contrib/rack/request_queue' @@ -25,7 +26,7 @@ def initialize(app) end def compute_queue_time(env, tracer) - return unless Datadog.configuration[:rack][:request_queuing] + return unless configuration[:request_queuing] # parse the request queue time request_start = Datadog::Contrib::Rack::QueueTime.get_request_start(env) @@ -34,25 +35,25 @@ def compute_queue_time(env, tracer) tracer.trace( Ext::SPAN_HTTP_SERVER_QUEUE, start_time: request_start, - service: Datadog.configuration[:rack][:web_service_name] + service: configuration[:web_service_name] ) end def call(env) # retrieve integration settings - tracer = Datadog.configuration[:rack][:tracer] + tracer = configuration[:tracer] # [experimental] create a root Span to keep track of frontend web servers # (i.e. Apache, nginx) if the header is properly set frontend_span = compute_queue_time(env, tracer) trace_options = { - service: Datadog.configuration[:rack][:service_name], + service: configuration[:service_name], resource: nil, span_type: Datadog::Ext::HTTP::TYPE } - if Datadog.configuration[:rack][:distributed_tracing] + if configuration[:distributed_tracing] context = HTTPPropagator.extract(env) tracer.provider.context = context if context.trace_id end @@ -114,7 +115,7 @@ def call(env) end def resource_name_for(env, status) - if Datadog.configuration[:rack][:middleware_names] && env['RESPONSE_MIDDLEWARE'] + if configuration[:middleware_names] && env['RESPONSE_MIDDLEWARE'] "#{env['RESPONSE_MIDDLEWARE']}##{env['REQUEST_METHOD']}" else "#{env['REQUEST_METHOD']} #{status}".strip @@ -138,13 +139,19 @@ def set_request_tags!(request_span, env, status, headers, response, original_env response_headers = parse_response_headers(headers || {}) request_span.resource ||= resource_name_for(env, status) + + # Set event sample rate, if available. + Contrib::Sampling.set_event_sample_rate(request_span, configuration[:event_sample_rate]) + if request_span.get_tag(Datadog::Ext::HTTP::METHOD).nil? request_span.set_tag(Datadog::Ext::HTTP::METHOD, env['REQUEST_METHOD']) end + if request_span.get_tag(Datadog::Ext::HTTP::URL).nil? - options = Datadog.configuration[:rack][:quantize] + options = configuration[:quantize] request_span.set_tag(Datadog::Ext::HTTP::URL, Datadog::Quantization::HTTP.url(url, options)) end + if request_span.get_tag(Datadog::Ext::HTTP::BASE_URL).nil? request_obj = ::Rack::Request.new(env) @@ -157,6 +164,7 @@ def set_request_tags!(request_span, env, status, headers, response, original_env request_span.set_tag(Datadog::Ext::HTTP::BASE_URL, base_url) end + if request_span.get_tag(Datadog::Ext::HTTP::STATUS_CODE).nil? && status request_span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, status) end @@ -186,6 +194,10 @@ def set_request_tags!(request_span, env, status, headers, response, original_env If you need the Rack request span, try using `Datadog.tracer.active_span`. This key will be removed in version 1.0).freeze + def configuration + Datadog.configuration[:rack] + end + def add_deprecation_warnings(env) env.instance_eval do unless instance_variable_defined?(:@patched_with_datadog_warnings) @@ -225,7 +237,7 @@ def without_datadog_warnings def parse_request_headers(env) {}.tap do |result| - whitelist = Datadog.configuration[:rack][:headers][:request] || [] + whitelist = configuration[:headers][:request] || [] whitelist.each do |header| rack_header = header_to_rack_header(header) if env.key?(rack_header) @@ -237,7 +249,7 @@ def parse_request_headers(env) def parse_response_headers(headers) {}.tap do |result| - whitelist = Datadog.configuration[:rack][:headers][:response] || [] + whitelist = configuration[:headers][:response] || [] whitelist.each do |header| if headers.key?(header) result[Datadog::Ext::HTTP::ResponseHeaders.to_tag(header)] = headers[header] diff --git a/lib/ddtrace/contrib/rake/instrumentation.rb b/lib/ddtrace/contrib/rake/instrumentation.rb index d2d109baa1c..f1f81961f69 100644 --- a/lib/ddtrace/contrib/rake/instrumentation.rb +++ b/lib/ddtrace/contrib/rake/instrumentation.rb @@ -1,3 +1,4 @@ +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/rake/ext' module Datadog @@ -41,6 +42,7 @@ def shutdown_tracer! def annotate_invoke!(span, args) span.resource = name + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.set_tag(Ext::TAG_TASK_ARG_NAMES, arg_names) span.set_tag(Ext::TAG_INVOKE_ARGS, quantize_args(args)) unless args.nil? rescue StandardError => e diff --git a/lib/ddtrace/contrib/resque/patcher.rb b/lib/ddtrace/contrib/resque/patcher.rb index 3a0d8bb51c9..ebab2584d9f 100644 --- a/lib/ddtrace/contrib/resque/patcher.rb +++ b/lib/ddtrace/contrib/resque/patcher.rb @@ -19,7 +19,6 @@ def patch do_once(:resque) do begin require_relative 'resque_job' - add_pin get_option(:workers).each { |worker| worker.extend(ResqueJob) } rescue StandardError => e Datadog::Tracer.log.error("Unable to apply Resque integration: #{e}") @@ -27,15 +26,6 @@ def patch end end - def add_pin - Pin.new( - get_option(:service_name), - app: Ext::APP, - app_type: Datadog::Ext::AppTypes::WORKER, - tracer: get_option(:tracer) - ).onto(::Resque) - end - def get_option(option) Datadog.configuration[:resque].get_option(option) end diff --git a/lib/ddtrace/contrib/resque/resque_job.rb b/lib/ddtrace/contrib/resque/resque_job.rb index 6d05f36f399..f0d05bd70c6 100644 --- a/lib/ddtrace/contrib/resque/resque_job.rb +++ b/lib/ddtrace/contrib/resque/resque_job.rb @@ -1,5 +1,6 @@ require 'ddtrace/ext/app_types' require 'ddtrace/sync_writer' +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/sidekiq/ext' require 'resque' @@ -9,13 +10,13 @@ module Resque # Uses Resque job hooks to create traces module ResqueJob def around_perform(*_) - pin = Pin.get_from(::Resque) - return yield unless pin && pin.tracer - pin.tracer.trace(Ext::SPAN_JOB, service: pin.service) do |span| + return yield unless datadog_configuration && tracer + + tracer.trace(Ext::SPAN_JOB, span_options) do |span| span.resource = name - span.span_type = pin.app_type + span.span_type = Datadog::Ext::AppTypes::WORKER + Contrib::Sampling.set_event_sample_rate(span, datadog_configuration[:event_sample_rate]) yield - span.service = pin.service end end @@ -28,8 +29,27 @@ def on_failure_shutdown_tracer(*_) end def shutdown_tracer_when_forked! - pin = Datadog::Pin.get_from(Resque) - pin.tracer.shutdown! if pin && pin.tracer && pin.config && pin.config[:forked] + tracer.shutdown! if forked? + end + + private + + def forked? + pin = Datadog::Pin.get_from(::Resque) + return false unless pin + pin.config[:forked] == true + end + + def span_options + { service: datadog_configuration[:service_name] } + end + + def tracer + datadog_configuration.tracer + end + + def datadog_configuration + Datadog.configuration[:resque] end end end @@ -37,12 +57,17 @@ def shutdown_tracer_when_forked! end Resque.after_fork do - # get the current tracer - pin = Datadog::Pin.get_from(Resque) - next unless pin && pin.tracer - pin.config ||= {} - pin.config[:forked] = true - - # clean the state so no CoW happens - pin.tracer.provider.context = nil + configuration = Datadog.configuration[:resque] + next if configuration.nil? + + # Add a pin, marking the job as forked. + # Used to trigger shutdown in forks for performance reasons. + Datadog::Pin.new( + configuration[:service_name], + config: { forked: true } + ).onto(::Resque) + + # Clean the state so no CoW happens + next if configuration[:tracer].nil? + configuration[:tracer].provider.context = nil end diff --git a/lib/ddtrace/contrib/sampling.rb b/lib/ddtrace/contrib/sampling.rb new file mode 100644 index 00000000000..e61ee55909d --- /dev/null +++ b/lib/ddtrace/contrib/sampling.rb @@ -0,0 +1,14 @@ +require 'ddtrace/ext/priority' + +module Datadog + module Contrib + # Defines sampling behavior for integrations + module Sampling + module_function + + def set_event_sample_rate(span, sample_rate) + span.set_metric(Datadog::Ext::Priority::TAG_EVENT_SAMPLE_RATE, sample_rate) unless sample_rate.nil? + end + end + end +end diff --git a/lib/ddtrace/contrib/shoryuken/tracer.rb b/lib/ddtrace/contrib/shoryuken/tracer.rb index 71e6d6aa788..46386073fef 100644 --- a/lib/ddtrace/contrib/shoryuken/tracer.rb +++ b/lib/ddtrace/contrib/shoryuken/tracer.rb @@ -1,16 +1,19 @@ +require 'ddtrace/contrib/sampling' + module Datadog module Contrib module Shoryuken # Tracer is a Shoryuken server-side middleware which traces executed jobs class Tracer def initialize(options = {}) - @tracer = options[:tracer] || Datadog.configuration[:shoryuken][:tracer] - @shoryuken_service = options[:service_name] || Datadog.configuration[:shoryuken][:service_name] + @tracer = options[:tracer] || configuration[:tracer] + @shoryuken_service = options[:service_name] || configuration[:service_name] set_service_info(@shoryuken_service) end def call(worker_instance, queue, sqs_msg, body) @tracer.trace(Ext::SPAN_JOB, service: @shoryuken_service, span_type: Datadog::Ext::AppTypes::WORKER) do |span| + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.resource = resource(worker_instance, body) span.set_tag(Ext::TAG_JOB_ID, sqs_msg.message_id) span.set_tag(Ext::TAG_JOB_QUEUE, queue) @@ -31,8 +34,12 @@ def resource(worker_instance, body) job_class || worker_instance.class.name end + def configuration + Datadog.configuration[:shoryuken] + end + def set_service_info(service) - return if @tracer.services[service] + return if @tracer.nil? || @tracer.services[service] @tracer.set_service_info( service, Ext::APP, diff --git a/lib/ddtrace/contrib/sidekiq/client_tracer.rb b/lib/ddtrace/contrib/sidekiq/client_tracer.rb index 0c6954abeda..0c9535ac6b4 100644 --- a/lib/ddtrace/contrib/sidekiq/client_tracer.rb +++ b/lib/ddtrace/contrib/sidekiq/client_tracer.rb @@ -1,4 +1,5 @@ require 'ddtrace/contrib/sidekiq/tracing' +require 'ddtrace/contrib/sampling' module Datadog module Contrib @@ -9,19 +10,18 @@ class ClientTracer def initialize(options = {}) super - @sidekiq_service = options[:client_service_name] || Datadog.configuration[:sidekiq][:client_service_name] + @sidekiq_service = options[:client_service_name] || configuration[:client_service_name] + set_service_info(@sidekiq_service) end # Client middleware arguments are documented here: # https://github.com/mperham/sidekiq/wiki/Middleware#client-middleware def call(worker_class, job, queue, redis_pool) - service = @sidekiq_service - set_service_info(service) - resource = job_resource(job) - @tracer.trace(Ext::SPAN_PUSH, service: service) do |span| + @tracer.trace(Ext::SPAN_PUSH, service: @sidekiq_service) do |span| span.resource = resource + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.set_tag(Ext::TAG_JOB_ID, job['jid']) span.set_tag(Ext::TAG_JOB_QUEUE, job['queue']) span.set_tag(Ext::TAG_JOB_WRAPPER, job['class']) if job['wrapped'] @@ -29,6 +29,12 @@ def call(worker_class, job, queue, redis_pool) yield end end + + private + + def configuration + Datadog.configuration[:sidekiq] + end end end end diff --git a/lib/ddtrace/contrib/sidekiq/server_tracer.rb b/lib/ddtrace/contrib/sidekiq/server_tracer.rb index f85108c6f22..cfb200e0b29 100644 --- a/lib/ddtrace/contrib/sidekiq/server_tracer.rb +++ b/lib/ddtrace/contrib/sidekiq/server_tracer.rb @@ -1,4 +1,5 @@ require 'ddtrace/contrib/sidekiq/tracing' +require 'ddtrace/contrib/sampling' module Datadog module Contrib @@ -9,7 +10,7 @@ class ServerTracer def initialize(options = {}) super - @sidekiq_service = options[:service_name] || Datadog.configuration[:sidekiq][:service_name] + @sidekiq_service = options[:service_name] || configuration[:service_name] end def call(worker, job, queue) @@ -20,6 +21,7 @@ def call(worker, job, queue) @tracer.trace(Ext::SPAN_JOB, service: service, span_type: Datadog::Ext::AppTypes::WORKER) do |span| span.resource = resource + Contrib::Sampling.set_event_sample_rate(span, configuration[:event_sample_rate]) span.set_tag(Ext::TAG_JOB_ID, job['jid']) span.set_tag(Ext::TAG_JOB_RETRY, job['retry']) span.set_tag(Ext::TAG_JOB_QUEUE, job['queue']) @@ -32,6 +34,10 @@ def call(worker, job, queue) private + def configuration + Datadog.configuration[:sidekiq] + end + def service_from_worker_config(resource) # Try to get the Ruby class from the resource name. worker_klass = begin diff --git a/lib/ddtrace/contrib/sucker_punch/instrumentation.rb b/lib/ddtrace/contrib/sucker_punch/instrumentation.rb index 67d238fb0a8..f8a54e41edf 100644 --- a/lib/ddtrace/contrib/sucker_punch/instrumentation.rb +++ b/lib/ddtrace/contrib/sucker_punch/instrumentation.rb @@ -1,4 +1,5 @@ require 'sucker_punch' +require 'ddtrace/contrib/sampling' require 'ddtrace/contrib/sucker_punch/ext' module Datadog @@ -18,6 +19,7 @@ def __run_perform(*args) __with_instrumentation(Ext::SPAN_PERFORM) do |span| span.resource = "PROCESS #{self}" + Contrib::Sampling.set_event_sample_rate(span, datadog_configuration[:event_sample_rate]) __run_perform_without_datadog(*args) end rescue => e @@ -43,6 +45,10 @@ def perform_in(interval, *args) private + def datadog_configuration + Datadog.configuration[:sucker_punch] + end + def __with_instrumentation(name) pin = Datadog::Pin.get_from(::SuckerPunch) diff --git a/lib/ddtrace/correlation.rb b/lib/ddtrace/correlation.rb new file mode 100644 index 00000000000..a23232bd537 --- /dev/null +++ b/lib/ddtrace/correlation.rb @@ -0,0 +1,32 @@ +module Datadog + # Contains behavior for managing correlations with tracing + # e.g. Retrieve a correlation to the current trace for logging, etc. + module Correlation + # Struct representing correlation + Identifier = Struct.new(:trace_id, :span_id).tap do |struct| + # Do this #class_eval here for Ruby 1.9.3 support. + # Ruby 2.0+ supports passing a block to Struct::new instead. + struct.class_eval do + def initialize(*args) + super + self.trace_id = trace_id || 0 + self.span_id = span_id || 0 + end + + def to_s + "dd.trace_id=#{trace_id} dd.span_id=#{span_id}" + end + end + end.freeze + + NULL_IDENTIFIER = Identifier.new.freeze + + module_function + + # Produces a CorrelationIdentifier from the Context provided + def identifier_from_context(context) + return NULL_IDENTIFIER if context.nil? + Identifier.new(context.trace_id, context.span_id).freeze + end + end +end diff --git a/lib/ddtrace/ext/priority.rb b/lib/ddtrace/ext/priority.rb index 711b597c296..781f015bf4b 100644 --- a/lib/ddtrace/ext/priority.rb +++ b/lib/ddtrace/ext/priority.rb @@ -3,6 +3,8 @@ module Ext # Priority is a hint given to the backend so that it knows which traces to reject or kept. # In a distributed context, it should be set before any context propagation (fork, RPC calls) to be effective. module Priority + # Tag for span sample rate; used by agent to determine whether event is emitted. + TAG_EVENT_SAMPLE_RATE = '_dd1.sr.eausr'.freeze # Use this to explicitely inform the backend that a trace should be rejected and not stored. USER_REJECT = -1 # Used by the builtin sampler to inform the backend that a trace should be rejected and not stored. diff --git a/lib/ddtrace/tracer.rb b/lib/ddtrace/tracer.rb index 9aa97a6cd04..b94afbd752e 100644 --- a/lib/ddtrace/tracer.rb +++ b/lib/ddtrace/tracer.rb @@ -10,6 +10,7 @@ require 'ddtrace/logger' require 'ddtrace/writer' require 'ddtrace/sampler' +require 'ddtrace/correlation' # \Datadog global namespace that includes all tracing functionality for Tracer and Span classes. module Datadog @@ -126,7 +127,7 @@ def configure(options = {}) # Those are rare "power-user" options. sampler = options.fetch(:sampler, nil) - priority_sampling = options[:priority_sampling] + priority_sampling = options.fetch(:priority_sampling, nil) max_spans_before_partial_flush = options.fetch(:max_spans_before_partial_flush, nil) min_spans_before_partial_flush = options.fetch(:min_spans_before_partial_flush, nil) partial_flush_timeout = options.fetch(:partial_flush_timeout, nil) @@ -134,9 +135,15 @@ def configure(options = {}) @enabled = enabled unless enabled.nil? @sampler = sampler unless sampler.nil? - if priority_sampling + # Re-build the sampler and writer if priority sampling is enabled, + # but neither are configured. Verify the sampler isn't already a + # priority sampler too, so we don't wrap one with another. + if priority_sampling != false && !@sampler.is_a?(PrioritySampler) @sampler = PrioritySampler.new(base_sampler: @sampler) @writer = Writer.new(priority_sampler: @sampler) + elsif priority_sampling == false + @sampler = sampler || Datadog::AllSampler.new if @sampler.is_a?(PrioritySampler) + @writer = Writer.new end @writer.transport.hostname = hostname unless hostname.nil? @@ -351,6 +358,11 @@ def active_root_span call_context.current_root_span end + # Return a CorrelationIdentifier for active span + def active_correlation + Datadog::Correlation.identifier_from_context(call_context) + end + # Send the trace to the writer to enqueue the spans list in the agent # sending queue. def write(trace) diff --git a/lib/ddtrace/version.rb b/lib/ddtrace/version.rb index 141ccaa34ee..53b970c36a7 100644 --- a/lib/ddtrace/version.rb +++ b/lib/ddtrace/version.rb @@ -1,8 +1,8 @@ module Datadog module VERSION MAJOR = 0 - MINOR = 18 - PATCH = 3 + MINOR = 19 + PATCH = 0 PRE = nil STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.') diff --git a/spec/ddtrace/configuration/pin_setup_spec.rb b/spec/ddtrace/configuration/pin_setup_spec.rb index ab3bbf732f0..d9d3d89bf0f 100644 --- a/spec/ddtrace/configuration/pin_setup_spec.rb +++ b/spec/ddtrace/configuration/pin_setup_spec.rb @@ -24,7 +24,7 @@ } end - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } it do expect(target.datadog_pin.service).to eq('my-service') diff --git a/spec/ddtrace/context_spec.rb b/spec/ddtrace/context_spec.rb index acf986d598b..677df748037 100644 --- a/spec/ddtrace/context_spec.rb +++ b/spec/ddtrace/context_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Datadog::Context do subject(:context) { described_class.new(options) } let(:options) { {} } - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } describe '#current_root_span' do subject(:current_root_span) { context.current_root_span } diff --git a/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb b/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb index eb17b8d37d2..28ddf0c5ff8 100644 --- a/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb +++ b/spec/ddtrace/contrib/active_model_serializers/patcher_spec.rb @@ -10,7 +10,7 @@ RSpec.describe 'ActiveModelSerializers patcher' do include_context 'AMS serializer' - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } def all_spans tracer.writer.spans(:keep) diff --git a/spec/ddtrace/contrib/active_record/multi_db_spec.rb b/spec/ddtrace/contrib/active_record/multi_db_spec.rb index 18dbf955f70..924b2df1a1c 100644 --- a/spec/ddtrace/contrib/active_record/multi_db_spec.rb +++ b/spec/ddtrace/contrib/active_record/multi_db_spec.rb @@ -6,7 +6,7 @@ require 'sqlite3' RSpec.describe 'ActiveRecord multi-database implementation' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer, service_name: default_db_service_name } } let(:default_db_service_name) { 'default-db' } diff --git a/spec/ddtrace/contrib/active_record/performance_spec.rb b/spec/ddtrace/contrib/active_record/performance_spec.rb index a1b4aac1f3d..aee3eaba788 100644 --- a/spec/ddtrace/contrib/active_record/performance_spec.rb +++ b/spec/ddtrace/contrib/active_record/performance_spec.rb @@ -5,7 +5,7 @@ require 'sqlite3' RSpec.describe 'ActiveRecord tracing performance' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:options) { { tracer: tracer } } let(:spans) { tracer.writer.spans } diff --git a/spec/ddtrace/contrib/active_record/tracer_spec.rb b/spec/ddtrace/contrib/active_record/tracer_spec.rb index 8ea9f479951..decef947731 100644 --- a/spec/ddtrace/contrib/active_record/tracer_spec.rb +++ b/spec/ddtrace/contrib/active_record/tracer_spec.rb @@ -4,7 +4,7 @@ require_relative 'app' RSpec.describe 'ActiveRecord instrumentation' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer } } before(:each) do diff --git a/spec/ddtrace/contrib/active_support/notifications/subscription_spec.rb b/spec/ddtrace/contrib/active_support/notifications/subscription_spec.rb index afee9f296e1..3e6fd6b27db 100644 --- a/spec/ddtrace/contrib/active_support/notifications/subscription_spec.rb +++ b/spec/ddtrace/contrib/active_support/notifications/subscription_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Datadog::Contrib::ActiveSupport::Notifications::Subscription do describe 'instance' do subject(:subscription) { described_class.new(tracer, span_name, options, &block) } - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:span_name) { double('span_name') } let(:options) { {} } let(:payload) { {} } diff --git a/spec/ddtrace/contrib/aws/instrumentation_spec.rb b/spec/ddtrace/contrib/aws/instrumentation_spec.rb index a7eed337119..36a46f61aea 100644 --- a/spec/ddtrace/contrib/aws/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/aws/instrumentation_spec.rb @@ -6,7 +6,7 @@ require 'ddtrace/ext/http' RSpec.describe 'AWS instrumentation' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:client) { ::Aws::S3::Client.new(stub_responses: responses) } let(:responses) { true } diff --git a/spec/ddtrace/contrib/concurrent_ruby/integration_spec.rb b/spec/ddtrace/contrib/concurrent_ruby/integration_spec.rb index b32bae667ad..0ea383c421b 100644 --- a/spec/ddtrace/contrib/concurrent_ruby/integration_spec.rb +++ b/spec/ddtrace/contrib/concurrent_ruby/integration_spec.rb @@ -12,7 +12,7 @@ remove_patch!(:concurrent_ruby) end - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer } } subject(:deferred_execution) do diff --git a/spec/ddtrace/contrib/configuration/settings_spec.rb b/spec/ddtrace/contrib/configuration/settings_spec.rb index cf80e41aeb7..20581b48d13 100644 --- a/spec/ddtrace/contrib/configuration/settings_spec.rb +++ b/spec/ddtrace/contrib/configuration/settings_spec.rb @@ -9,7 +9,23 @@ describe '#options' do subject(:options) { settings.options } - it { is_expected.to include(:service_name) } - it { is_expected.to include(:tracer) } + + describe ':service_name' do + subject(:option) { options[:service_name] } + it { expect(options).to include(:service_name) } + it { expect(option.get).to be nil } + end + + describe ':tracer' do + subject(:option) { options[:tracer] } + it { expect(options).to include(:tracer) } + it { expect(option.get).to be Datadog.tracer } + end + + describe ':event_sample_rate' do + subject(:option) { options[:event_sample_rate] } + it { expect(options).to include(:event_sample_rate) } + it { expect(option.get).to be nil } + end end end diff --git a/spec/ddtrace/contrib/dalli/instrumentation_spec.rb b/spec/ddtrace/contrib/dalli/instrumentation_spec.rb index f6592f73c46..a626ecb6b26 100644 --- a/spec/ddtrace/contrib/dalli/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/dalli/instrumentation_spec.rb @@ -9,7 +9,7 @@ let(:test_port) { ENV.fetch('TEST_MEMCACHED_PORT', '11211') } let(:client) { ::Dalli::Client.new("#{test_host}:#{test_port}") } - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:pin) { ::Dalli.datadog_pin } def all_spans diff --git a/spec/ddtrace/contrib/delayed_job/plugin_spec.rb b/spec/ddtrace/contrib/delayed_job/plugin_spec.rb index 23a70b01f69..530894d7db5 100644 --- a/spec/ddtrace/contrib/delayed_job/plugin_spec.rb +++ b/spec/ddtrace/contrib/delayed_job/plugin_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' + require 'active_record' require 'delayed_job' require 'delayed_job_active_record' @@ -24,14 +26,16 @@ def job_data end) end - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } before do - Datadog.configure { |c| c.use :delayed_job, tracer: tracer } - + Datadog.configure { |c| c.use :delayed_job, configuration_options } Delayed::Worker.delay_jobs = false end + after(:each) { Datadog.registry[:delayed_job].reset_configuration! } + describe 'instrumenting worker execution' do let(:worker) { double(:worker, name: 'worker') } before do @@ -100,6 +104,8 @@ def job_data expect(span.get_tag('delayed_job.attempts')).to eq('0') end + it_behaves_like 'event sample rate' + context 'when queue name is set' do let(:queue_name) { 'queue_name' } let(:job_params) { { queue: queue_name } } diff --git a/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb b/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb index e8798fea60e..2ad06417758 100644 --- a/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb +++ b/spec/ddtrace/contrib/elasticsearch/patcher_spec.rb @@ -9,7 +9,7 @@ let(:client) { Elasticsearch::Client.new(url: server) } let(:pin) { Datadog::Pin.get_from(client) } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } before do Datadog.configure do |c| diff --git a/spec/ddtrace/contrib/elasticsearch/transport_spec.rb b/spec/ddtrace/contrib/elasticsearch/transport_spec.rb index dbe5206a475..e93b747b0f2 100644 --- a/spec/ddtrace/contrib/elasticsearch/transport_spec.rb +++ b/spec/ddtrace/contrib/elasticsearch/transport_spec.rb @@ -22,7 +22,7 @@ let(:server) { "http://#{host}:#{port}" } let(:client) { Elasticsearch::Client.new(url: server) } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:spans) { tracer.writer.spans } let(:span) { spans.first } diff --git a/spec/ddtrace/contrib/excon/instrumentation_spec.rb b/spec/ddtrace/contrib/excon/instrumentation_spec.rb index 9ab1d64abf0..452c2b7f73a 100644 --- a/spec/ddtrace/contrib/excon/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/excon/instrumentation_spec.rb @@ -5,7 +5,7 @@ require 'ddtrace/contrib/excon/middleware' RSpec.describe Datadog::Contrib::Excon::Middleware do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:connection_options) { { mock: true } } let(:middleware_options) { {} } diff --git a/spec/ddtrace/contrib/faraday/middleware_spec.rb b/spec/ddtrace/contrib/faraday/middleware_spec.rb index 04dc8bc6547..469c0dfa2bb 100644 --- a/spec/ddtrace/contrib/faraday/middleware_spec.rb +++ b/spec/ddtrace/contrib/faraday/middleware_spec.rb @@ -5,7 +5,7 @@ require 'ddtrace/ext/distributed' RSpec.describe 'Faraday middleware' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:client) do ::Faraday.new('http://example.com') do |builder| diff --git a/spec/ddtrace/contrib/graphql/tracer_spec.rb b/spec/ddtrace/contrib/graphql/tracer_spec.rb index 32c2e8af07a..9cb6651538f 100644 --- a/spec/ddtrace/contrib/graphql/tracer_spec.rb +++ b/spec/ddtrace/contrib/graphql/tracer_spec.rb @@ -14,7 +14,7 @@ end end - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } def pop_spans tracer.writer.spans(:keep) diff --git a/spec/ddtrace/contrib/http/patcher_spec.rb b/spec/ddtrace/contrib/http/patcher_spec.rb index 69dd7ff040d..e65b596f497 100644 --- a/spec/ddtrace/contrib/http/patcher_spec.rb +++ b/spec/ddtrace/contrib/http/patcher_spec.rb @@ -3,7 +3,7 @@ require 'net/http' RSpec.describe 'net/http patcher' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:host) { 'example.com' } before do diff --git a/spec/ddtrace/contrib/mongodb/client_spec.rb b/spec/ddtrace/contrib/mongodb/client_spec.rb index d3ed752c944..6a51b316dd1 100644 --- a/spec/ddtrace/contrib/mongodb/client_spec.rb +++ b/spec/ddtrace/contrib/mongodb/client_spec.rb @@ -4,7 +4,7 @@ require 'mongo' RSpec.describe 'Mongo::Client instrumentation' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:client) { Mongo::Client.new(["#{host}:#{port}"], client_options) } let(:client_options) { { database: database } } diff --git a/spec/ddtrace/contrib/mysql2/patcher_spec.rb b/spec/ddtrace/contrib/mysql2/patcher_spec.rb index 44924f94219..c68e67f6691 100644 --- a/spec/ddtrace/contrib/mysql2/patcher_spec.rb +++ b/spec/ddtrace/contrib/mysql2/patcher_spec.rb @@ -4,7 +4,7 @@ require 'mysql2' RSpec.describe 'Mysql2::Client patcher' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:client) do Mysql2::Client.new( diff --git a/spec/ddtrace/contrib/racecar/patcher_spec.rb b/spec/ddtrace/contrib/racecar/patcher_spec.rb index 2679b4f87f0..4ee5c04ab25 100644 --- a/spec/ddtrace/contrib/racecar/patcher_spec.rb +++ b/spec/ddtrace/contrib/racecar/patcher_spec.rb @@ -1,11 +1,13 @@ require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' require 'racecar' require 'racecar/cli' require 'active_support' require 'ddtrace' RSpec.describe 'Racecar patcher' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } + let(:configuration_options) { { tracer: tracer } } def all_spans tracer.writer.spans(:keep) @@ -13,10 +15,12 @@ def all_spans before(:each) do Datadog.configure do |c| - c.use :racecar, tracer: tracer + c.use :racecar, configuration_options end end + after(:each) { Datadog.registry[:racecar].reset_configuration! } + describe 'for single message processing' do let(:topic) { 'dd_trace_test_dummy' } let(:consumer) { 'DummyConsumer' } @@ -31,7 +35,7 @@ def all_spans } end - let(:racecar_span) do + let(:span) do all_spans.select { |s| s.name == Datadog::Contrib::Racecar::Ext::SPAN_MESSAGE }.first end @@ -39,7 +43,7 @@ def all_spans it 'is expected to send a span' do ActiveSupport::Notifications.instrument('process_message.racecar', payload) - racecar_span.tap do |span| + span.tap do |span| expect(span).to_not be nil expect(span.service).to eq('racecar') expect(span.name).to eq('racecar.message') @@ -67,7 +71,7 @@ def all_spans nil end - racecar_span.tap do |span| + span.tap do |span| expect(span).to_not be nil expect(span.service).to eq('racecar') expect(span.name).to eq('racecar.message') @@ -81,6 +85,10 @@ def all_spans end end end + + it_behaves_like 'event sample rate' do + before { ActiveSupport::Notifications.instrument('process_message.racecar', payload) } + end end describe 'for batch message processing' do @@ -99,7 +107,7 @@ def all_spans } end - let(:racecar_span) do + let(:span) do all_spans.select { |s| s.name == Datadog::Contrib::Racecar::Ext::SPAN_BATCH }.first end @@ -107,7 +115,7 @@ def all_spans it 'is expected to send a span' do ActiveSupport::Notifications.instrument('process_batch.racecar', payload) - racecar_span.tap do |span| + span.tap do |span| expect(span).to_not be nil expect(span.service).to eq('racecar') expect(span.name).to eq('racecar.batch') @@ -135,7 +143,7 @@ def all_spans nil end - racecar_span.tap do |span| + span.tap do |span| expect(span).to_not be nil expect(span.service).to eq('racecar') expect(span.name).to eq('racecar.batch') @@ -150,5 +158,9 @@ def all_spans end end end + + it_behaves_like 'event sample rate' do + before { ActiveSupport::Notifications.instrument('process_batch.racecar', payload) } + end end end diff --git a/spec/ddtrace/contrib/rack/configuration_spec.rb b/spec/ddtrace/contrib/rack/configuration_spec.rb new file mode 100644 index 00000000000..acfbadea959 --- /dev/null +++ b/spec/ddtrace/contrib/rack/configuration_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' +require 'rack/test' + +require 'rack' +require 'ddtrace' +require 'ddtrace/contrib/rack/middlewares' + +RSpec.describe 'Rack integration configuration' do + include Rack::Test::Methods + + 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 :rack, configuration_options + end + end + + after(:each) { Datadog.registry[:rack].reset_configuration! } + + shared_context 'an incoming HTTP request' do + subject(:response) { get '/' } + + let(:app) do + Rack::Builder.new do + use Datadog::Contrib::Rack::TraceMiddleware + + map '/' do + run(proc { |_env| [200, { 'Content-Type' => 'text/html' }, 'OK'] }) + end + end.to_app + end + end + + it_behaves_like 'event sample rate' do + include_context 'an incoming HTTP request' + before { is_expected.to be_ok } + end + + describe 'request queueing' do + shared_context 'queue header' do + let(:queue_value) { "t=#{queue_time}" } + let(:queue_time) { (Time.now.utc - 5).to_i } + + before(:each) do + header queue_header, queue_value + end + end + + shared_context 'no queue header' do + let(:queue_header) { nil } + let(:queue_value) { nil } + end + + shared_examples_for 'a Rack request with queuing' do + let(:queue_span) { spans.first } + let(:rack_span) { spans.last } + + it 'produces a queued Rack trace' do + is_expected.to be_ok + expect(spans).to have(2).items + + expect(queue_span.name).to eq('http_server.queue') + expect(queue_span.service).to eq(Datadog.configuration[:rack][:web_service_name]) + expect(queue_span.start_time.to_i).to eq(queue_time) + + expect(rack_span.name).to eq('rack.request') + expect(rack_span.span_type).to eq('http') + expect(rack_span.service).to eq(Datadog.configuration[:rack][:service_name]) + expect(rack_span.resource).to eq('GET 200') + expect(rack_span.get_tag('http.method')).to eq('GET') + expect(rack_span.get_tag('http.status_code')).to eq('200') + expect(rack_span.get_tag('http.url')).to eq('/') + expect(rack_span.status).to eq(0) + + expect(queue_span.span_id).to eq(rack_span.parent_id) + end + end + + shared_examples_for 'a Rack request without queuing' do + it 'produces a non-queued Rack trace' do + is_expected.to be_ok + expect(spans).to have(1).items + + expect(span).to_not be nil + expect(span.name).to eq('rack.request') + expect(span.span_type).to eq('http') + expect(span.service).to eq(Datadog.configuration[:rack][:service_name]) + expect(span.resource).to eq('GET 200') + expect(span.get_tag('http.method')).to eq('GET') + expect(span.get_tag('http.status_code')).to eq('200') + expect(span.get_tag('http.url')).to eq('/') + expect(span.status).to eq(0) + + expect(span.parent_id).to eq(0) + end + end + + context 'when enabled' do + let(:configuration_options) { super().merge(request_queuing: true) } + + context 'and a request is received' do + include_context 'an incoming HTTP request' + + context 'with X-Request-Start header' do + include_context 'queue header' do + let(:queue_header) { 'X-Request-Start' } + end + + it_behaves_like 'a Rack request with queuing' + + context 'given a custom web service name' do + let(:configuration_options) { super().merge(web_service_name: web_service_name) } + let(:web_service_name) { 'nginx' } + + it_behaves_like 'a Rack request with queuing' do + it 'sets the custom service name' do + is_expected.to be_ok + expect(queue_span.service).to eq(web_service_name) + end + end + end + end + + context 'with X-Queue-Start header' do + include_context 'queue header' do + let(:queue_header) { 'X-Queue-Start' } + end + + it_behaves_like 'a Rack request with queuing' + end + + # Ensure a queuing Span is NOT created if there is a clock skew + # where the starting time is greater than current host Time.now + context 'with a skewed queue header' do + before(:each) { header 'X-Request-Start', (Time.now.utc + 5).to_i } + it_behaves_like 'a Rack request without queuing' + end + + # Ensure a queuing Span is NOT created if the header is wrong + context 'with a invalid queue header' do + before(:each) { header 'X-Request-Start', 'foobar' } + it_behaves_like 'a Rack request without queuing' + end + + context 'without queue header' do + include_context 'no queue header' + it_behaves_like 'a Rack request without queuing' + end + end + end + + context 'when disabled' do + let(:configuration_options) { super().merge(request_queuing: false) } + + context 'and a request is received' do + include_context 'an incoming HTTP request' + + context 'with X-Request-Start header' do + include_context 'queue header' do + let(:queue_header) { 'X-Request-Start' } + end + + it_behaves_like 'a Rack request without queuing' + end + + context 'with X-Queue-Start header' do + include_context 'queue header' do + let(:queue_header) { 'X-Queue-Start' } + end + + it_behaves_like 'a Rack request without queuing' + end + + context 'without queue header' do + include_context 'no queue header' + it_behaves_like 'a Rack request without queuing' + end + end + end + end +end diff --git a/spec/ddtrace/contrib/rack/distributed_spec.rb b/spec/ddtrace/contrib/rack/distributed_spec.rb index 63bef3fbb5f..de13aa804b4 100644 --- a/spec/ddtrace/contrib/rack/distributed_spec.rb +++ b/spec/ddtrace/contrib/rack/distributed_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Rack integration distributed tracing' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:rack_options) { { tracer: tracer } } let(:spans) { tracer.writer.spans } diff --git a/spec/ddtrace/contrib/rack/integration_spec.rb b/spec/ddtrace/contrib/rack/integration_spec.rb index 900161789cc..e2e9aa1a745 100644 --- a/spec/ddtrace/contrib/rack/integration_spec.rb +++ b/spec/ddtrace/contrib/rack/integration_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Rack integration tests' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:rack_options) { { tracer: tracer } } let(:spans) { tracer.writer.spans } diff --git a/spec/ddtrace/contrib/rack/middleware_spec.rb b/spec/ddtrace/contrib/rack/middleware_spec.rb index b9a2101eb9b..7d965d6d9e4 100644 --- a/spec/ddtrace/contrib/rack/middleware_spec.rb +++ b/spec/ddtrace/contrib/rack/middleware_spec.rb @@ -8,7 +8,7 @@ subject(:middleware) { described_class.new(app) } let(:app) { instance_double(Rack::Builder) } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer } } before(:each) do diff --git a/spec/ddtrace/contrib/rack/request_queuing_spec.rb b/spec/ddtrace/contrib/rack/request_queuing_spec.rb deleted file mode 100644 index c6f9f9b014d..00000000000 --- a/spec/ddtrace/contrib/rack/request_queuing_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -require 'rack' -require 'ddtrace' -require 'ddtrace/contrib/rack/middlewares' - -RSpec.describe 'Rack integration request queuing' do - include Rack::Test::Methods - - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } - let(:rack_options) { { tracer: tracer } } - - let(:spans) { tracer.writer.spans } - let(:span) { spans.first } - - before(:each) do - Datadog.configure do |c| - c.use :rack, rack_options - end - end - - after(:each) { Datadog.registry[:rack].reset_configuration! } - - shared_context 'an incoming HTTP request' do - subject(:response) { get '/' } - - let(:app) do - Rack::Builder.new do - use Datadog::Contrib::Rack::TraceMiddleware - - map '/' do - run(proc { |_env| [200, { 'Content-Type' => 'text/html' }, 'OK'] }) - end - end.to_app - end - end - - shared_context 'queue header' do - let(:queue_value) { "t=#{queue_time}" } - let(:queue_time) { (Time.now.utc - 5).to_i } - - before(:each) do - header queue_header, queue_value - end - end - - shared_context 'no queue header' do - let(:queue_header) { nil } - let(:queue_value) { nil } - end - - shared_examples_for 'a Rack request with queuing' do - let(:queue_span) { spans.first } - let(:rack_span) { spans.last } - - it 'produces a queued Rack trace' do - is_expected.to be_ok - expect(spans).to have(2).items - - expect(queue_span.name).to eq('http_server.queue') - expect(queue_span.service).to eq(Datadog.configuration[:rack][:web_service_name]) - expect(queue_span.start_time.to_i).to eq(queue_time) - - expect(rack_span.name).to eq('rack.request') - expect(rack_span.span_type).to eq('http') - expect(rack_span.service).to eq(Datadog.configuration[:rack][:service_name]) - expect(rack_span.resource).to eq('GET 200') - expect(rack_span.get_tag('http.method')).to eq('GET') - expect(rack_span.get_tag('http.status_code')).to eq('200') - expect(rack_span.get_tag('http.url')).to eq('/') - expect(rack_span.status).to eq(0) - - expect(queue_span.span_id).to eq(rack_span.parent_id) - end - end - - shared_examples_for 'a Rack request without queuing' do - it 'produces a non-queued Rack trace' do - is_expected.to be_ok - expect(spans).to have(1).items - - expect(span).to_not be nil - expect(span.name).to eq('rack.request') - expect(span.span_type).to eq('http') - expect(span.service).to eq(Datadog.configuration[:rack][:service_name]) - expect(span.resource).to eq('GET 200') - expect(span.get_tag('http.method')).to eq('GET') - expect(span.get_tag('http.status_code')).to eq('200') - expect(span.get_tag('http.url')).to eq('/') - expect(span.status).to eq(0) - - expect(span.parent_id).to eq(0) - end - end - - context 'when enabled' do - let(:rack_options) { super().merge(request_queuing: true) } - - context 'and a request is received' do - include_context 'an incoming HTTP request' - - context 'with X-Request-Start header' do - include_context 'queue header' do - let(:queue_header) { 'X-Request-Start' } - end - - it_behaves_like 'a Rack request with queuing' - - context 'given a custom web service name' do - let(:rack_options) { super().merge(web_service_name: web_service_name) } - let(:web_service_name) { 'nginx' } - - it_behaves_like 'a Rack request with queuing' do - it 'sets the custom service name' do - is_expected.to be_ok - expect(queue_span.service).to eq(web_service_name) - end - end - end - end - - context 'with X-Queue-Start header' do - include_context 'queue header' do - let(:queue_header) { 'X-Queue-Start' } - end - - it_behaves_like 'a Rack request with queuing' - end - - # Ensure a queuing Span is NOT created if there is a clock skew - # where the starting time is greater than current host Time.now - context 'with a skewed queue header' do - before(:each) { header 'X-Request-Start', (Time.now.utc + 5).to_i } - it_behaves_like 'a Rack request without queuing' - end - - # Ensure a queuing Span is NOT created if the header is wrong - context 'with a invalid queue header' do - before(:each) { header 'X-Request-Start', 'foobar' } - it_behaves_like 'a Rack request without queuing' - end - - context 'without queue header' do - include_context 'no queue header' - it_behaves_like 'a Rack request without queuing' - end - end - end - - context 'when disabled' do - let(:rack_options) { super().merge(request_queuing: false) } - - context 'and a request is received' do - include_context 'an incoming HTTP request' - - context 'with X-Request-Start header' do - include_context 'queue header' do - let(:queue_header) { 'X-Request-Start' } - end - - it_behaves_like 'a Rack request without queuing' - end - - context 'with X-Queue-Start header' do - include_context 'queue header' do - let(:queue_header) { 'X-Queue-Start' } - end - - it_behaves_like 'a Rack request without queuing' - end - - context 'without queue header' do - include_context 'no queue header' - it_behaves_like 'a Rack request without queuing' - end - end - end -end diff --git a/spec/ddtrace/contrib/rack/resource_name_spec.rb b/spec/ddtrace/contrib/rack/resource_name_spec.rb index f9bc60f650d..190b7e1624d 100644 --- a/spec/ddtrace/contrib/rack/resource_name_spec.rb +++ b/spec/ddtrace/contrib/rack/resource_name_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Rack integration with other middleware' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:rack_options) do { application: app, diff --git a/spec/ddtrace/contrib/rails/action_controller_spec.rb b/spec/ddtrace/contrib/rails/action_controller_spec.rb index f6f65f1ec5e..af852c237aa 100644 --- a/spec/ddtrace/contrib/rails/action_controller_spec.rb +++ b/spec/ddtrace/contrib/rails/action_controller_spec.rb @@ -1,7 +1,7 @@ require 'ddtrace/contrib/rails/rails_helper' RSpec.describe 'ActionController tracing' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:rails_options) { { tracer: tracer } } before(:each) do diff --git a/spec/ddtrace/contrib/rails/middleware_spec.rb b/spec/ddtrace/contrib/rails/middleware_spec.rb index d9baf2dc101..e20fe8b7bfb 100644 --- a/spec/ddtrace/contrib/rails/middleware_spec.rb +++ b/spec/ddtrace/contrib/rails/middleware_spec.rb @@ -15,7 +15,7 @@ def index end) end - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } def all_spans tracer.writer.spans(:keep) diff --git a/spec/ddtrace/contrib/rails/railtie_spec.rb b/spec/ddtrace/contrib/rails/railtie_spec.rb index 29e0c33f9e7..fe134fec204 100644 --- a/spec/ddtrace/contrib/rails/railtie_spec.rb +++ b/spec/ddtrace/contrib/rails/railtie_spec.rb @@ -7,7 +7,7 @@ before(:each) { skip 'Test not compatible with Rails < 4.0' if Rails.version < '4.0' } include_context 'Rails test application' - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:routes) { { '/' => 'test#index' } } let(:controllers) { [controller] } diff --git a/spec/ddtrace/contrib/rake/instrumentation_spec.rb b/spec/ddtrace/contrib/rake/instrumentation_spec.rb index 5e8568f8d59..14d24f96bb1 100644 --- a/spec/ddtrace/contrib/rake/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/rake/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' require 'securerandom' require 'rake' @@ -7,7 +8,7 @@ require 'ddtrace/contrib/rake/patcher' RSpec.describe Datadog::Contrib::Rake::Instrumentation do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer, enabled: true } } let(:spans) { tracer.writer.spans } let(:span) { spans.first } @@ -25,10 +26,11 @@ end after(:each) do + # Reset configuration to defaults + Datadog.registry[:rake].reset_configuration! + # We don't want instrumentation enabled during the rest of the test suite... - Datadog.configure do |c| - c.use :rake, enabled: false - end + Datadog.configure { |c| c.use :rake, enabled: false } end def reset_task!(task_name) @@ -87,6 +89,10 @@ def reset_task!(task_name) expect(invoke_span.resource).to eq(task_name.to_s) expect(invoke_span.parent_id).to eq(0) end + + it_behaves_like 'event sample rate' do + let(:span) { invoke_span } + end end describe '\'rake.execute\' span' do @@ -94,6 +100,7 @@ def reset_task!(task_name) expect(execute_span.name).to eq(Datadog::Contrib::Rake::Ext::SPAN_EXECUTE) expect(execute_span.resource).to eq(task_name.to_s) expect(execute_span.parent_id).to eq(invoke_span.span_id) + expect(execute_span.get_tag(Datadog::Ext::Priority::TAG_EVENT_SAMPLE_RATE)).to be nil end end end diff --git a/spec/ddtrace/contrib/redis/miniapp_spec.rb b/spec/ddtrace/contrib/redis/miniapp_spec.rb index 3c77353b521..1e6e9b674f8 100644 --- a/spec/ddtrace/contrib/redis/miniapp_spec.rb +++ b/spec/ddtrace/contrib/redis/miniapp_spec.rb @@ -6,7 +6,7 @@ require 'ddtrace' RSpec.describe 'Redis mini app test' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } def all_spans tracer.writer.spans(:keep) diff --git a/spec/ddtrace/contrib/redis/redis_spec.rb b/spec/ddtrace/contrib/redis/redis_spec.rb index 8e4314820d4..accdb9e496c 100644 --- a/spec/ddtrace/contrib/redis/redis_spec.rb +++ b/spec/ddtrace/contrib/redis/redis_spec.rb @@ -6,7 +6,7 @@ require 'ddtrace' RSpec.describe 'Redis test' do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } def all_spans tracer.writer.spans(:keep) diff --git a/spec/ddtrace/contrib/resque/instrumentation_spec.rb b/spec/ddtrace/contrib/resque/instrumentation_spec.rb index 67594b55607..70c9768b2a1 100644 --- a/spec/ddtrace/contrib/resque/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/resque/instrumentation_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' require_relative 'job' require 'ddtrace' @@ -6,8 +7,7 @@ RSpec.describe 'Resque instrumentation' do include_context 'Resque job' - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } - let(:pin) { ::Resque.datadog_pin } + let(:tracer) { get_test_tracer } let(:spans) { tracer.writer.spans } let(:span) { spans.first } @@ -15,6 +15,8 @@ let(:host) { ENV.fetch('TEST_REDIS_HOST', '127.0.0.1') } let(:port) { ENV.fetch('TEST_REDIS_PORT', 6379) } + let(:configuration_options) { { tracer: tracer } } + before(:each) do # Setup Resque to use Redis ::Resque.redis = url @@ -22,13 +24,12 @@ # Patch Resque Datadog.configure do |c| - c.use :resque + c.use :resque, configuration_options end - - # Update the Resque pin with the tracer - pin.tracer = tracer end + after(:each) { Datadog.registry[:resque].reset_configuration! } + shared_examples 'job execution tracing' do context 'that succeeds' do before(:each) { perform_job(job_class) } @@ -42,6 +43,8 @@ expect(span.service).to eq('resque') expect(span.status).to_not eq(Datadog::Ext::Errors::STATUS) end + + it_behaves_like 'event sample rate' end context 'that fails' do @@ -71,6 +74,8 @@ expect(span.status).to eq(Datadog::Ext::Errors::STATUS) expect(span.get_tag(Datadog::Ext::Errors::TYPE)).to eq(error_class_name) end + + it_behaves_like 'event 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 b5a73641d5a..6abaf01a14f 100644 --- a/spec/ddtrace/contrib/rest_client/request_patch_spec.rb +++ b/spec/ddtrace/contrib/rest_client/request_patch_spec.rb @@ -5,7 +5,7 @@ require 'restclient/request' RSpec.describe Datadog::Contrib::RestClient::RequestPatch do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:rest_client_options) { {} } before do diff --git a/spec/ddtrace/contrib/sampling_examples.rb b/spec/ddtrace/contrib/sampling_examples.rb new file mode 100644 index 00000000000..e8a96e4972a --- /dev/null +++ b/spec/ddtrace/contrib/sampling_examples.rb @@ -0,0 +1,18 @@ +require 'ddtrace/ext/priority' + +RSpec.shared_examples_for 'event sample rate' do + context 'when not configured' do + it 'is not included in the tags' do + expect(span.get_metric(Datadog::Ext::Priority::TAG_EVENT_SAMPLE_RATE)).to be nil + end + end + + context 'when set' do + let(:configuration_options) { super().merge(event_sample_rate: event_sample_rate) } + let(:event_sample_rate) { 0.5 } + + it 'is included in the tags' do + expect(span.get_metric(Datadog::Ext::Priority::TAG_EVENT_SAMPLE_RATE)).to eq(event_sample_rate) + end + end +end diff --git a/spec/ddtrace/contrib/sampling_spec.rb b/spec/ddtrace/contrib/sampling_spec.rb new file mode 100644 index 00000000000..80d6068c4ce --- /dev/null +++ b/spec/ddtrace/contrib/sampling_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +require 'ddtrace/contrib/sampling' + +RSpec.describe Datadog::Contrib::Sampling do + describe '::set_event_sample_rate' do + subject(:set_event_sample_rate) { described_class.set_event_sample_rate(span, sample_rate) } + let(:span) { instance_double(Datadog::Span) } + + context 'when sample rate is nil' do + let(:sample_rate) { nil } + + it 'does not set the tag' do + expect(span).to_not receive(:set_metric) + set_event_sample_rate + end + end + + context 'when a sample rate is given' do + let(:sample_rate) { 0.5 } + + it 'sets the tag' do + expect(span).to receive(:set_metric) + .with( + Datadog::Ext::Priority::TAG_EVENT_SAMPLE_RATE, + sample_rate + ) + + set_event_sample_rate + end + end + end +end diff --git a/spec/ddtrace/contrib/sequel/configuration_spec.rb b/spec/ddtrace/contrib/sequel/configuration_spec.rb index c94f6423c12..de5e5a1dc50 100644 --- a/spec/ddtrace/contrib/sequel/configuration_spec.rb +++ b/spec/ddtrace/contrib/sequel/configuration_spec.rb @@ -6,7 +6,7 @@ require 'ddtrace/contrib/sequel/patcher' RSpec.describe 'Sequel configuration' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:spans) { tracer.writer.spans } let(:span) { spans.first } diff --git a/spec/ddtrace/contrib/sequel/instrumentation_spec.rb b/spec/ddtrace/contrib/sequel/instrumentation_spec.rb index fc1c890d110..f1f39dba60f 100644 --- a/spec/ddtrace/contrib/sequel/instrumentation_spec.rb +++ b/spec/ddtrace/contrib/sequel/instrumentation_spec.rb @@ -6,7 +6,7 @@ require 'ddtrace/contrib/sequel/integration' RSpec.describe 'Sequel instrumentation' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:configuration_options) { { tracer: tracer } } let(:sequel) do Sequel.sqlite(':memory:').tap do |s| diff --git a/spec/ddtrace/contrib/shoryuken/tracer_spec.rb b/spec/ddtrace/contrib/shoryuken/tracer_spec.rb index 13892002ac3..afa05b21070 100644 --- a/spec/ddtrace/contrib/shoryuken/tracer_spec.rb +++ b/spec/ddtrace/contrib/shoryuken/tracer_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'ddtrace/contrib/sampling_examples' + require 'ddtrace' require 'shoryuken' @@ -56,6 +58,12 @@ def perform(sqs_msg, body); end expect(span.get_tag(Datadog::Contrib::Shoryuken::Ext::TAG_JOB_ATTRIBUTES)).to eq(attributes.to_s) end + it_behaves_like 'event sample rate' do + include_context 'Shoryuken::Worker' + let(:body) { {} } + before { call } + end + context 'with a body' do context 'that is a Hash' do context 'that contains \'job_class\'' do @@ -91,6 +99,12 @@ def perform(sqs_msg, body); end .and_call_original end + # TODO: These expectations do not work because Shoryuken doesn't run middleware in tests + # https://github.com/phstc/shoryuken/issues/541 + # it_behaves_like 'event sample rate' do + # before { perform_async } + # end + it do expect { perform_async }.to_not raise_error # TODO: These expectations do not work because Shoryuken doesn't run middleware in tests diff --git a/spec/ddtrace/contrib/sinatra/activerecord_spec.rb b/spec/ddtrace/contrib/sinatra/activerecord_spec.rb index 5997ceaba49..0092ed0eb7e 100644 --- a/spec/ddtrace/contrib/sinatra/activerecord_spec.rb +++ b/spec/ddtrace/contrib/sinatra/activerecord_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'Sinatra instrumentation with ActiveRecord' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:options) { { tracer: tracer } } let(:span) { spans.first } diff --git a/spec/ddtrace/contrib/sinatra/multi_app_spec.rb b/spec/ddtrace/contrib/sinatra/multi_app_spec.rb index 925f070a740..580f7a63966 100644 --- a/spec/ddtrace/contrib/sinatra/multi_app_spec.rb +++ b/spec/ddtrace/contrib/sinatra/multi_app_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Sinatra instrumentation for multi-apps' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:options) { { tracer: tracer } } let(:span) { spans.first } diff --git a/spec/ddtrace/contrib/sinatra/tracer_spec.rb b/spec/ddtrace/contrib/sinatra/tracer_spec.rb index 38bb8949620..8bd41ec3b36 100644 --- a/spec/ddtrace/contrib/sinatra/tracer_spec.rb +++ b/spec/ddtrace/contrib/sinatra/tracer_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Sinatra instrumentation' do include Rack::Test::Methods - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } let(:options) { { tracer: tracer } } let(:span) { spans.first } @@ -386,7 +386,7 @@ include_context 'app with simple route' subject(:response) { get '/' } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new, enabled: false) } + let(:tracer) { get_test_tracer(enabled: false) } it do is_expected.to be_ok diff --git a/spec/ddtrace/correlation_spec.rb b/spec/ddtrace/correlation_spec.rb new file mode 100644 index 00000000000..de2fbe7cb6a --- /dev/null +++ b/spec/ddtrace/correlation_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +require 'ddtrace/correlation' +require 'ddtrace/context' + +RSpec.describe Datadog::Correlation do + describe '::identifier_from_context' do + subject(:correlation_ids) { described_class.identifier_from_context(context) } + + context 'given nil' do + let(:context) { nil } + + it 'returns an empty Correlation::Identifier' do + is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) + expect(correlation_ids.trace_id).to be 0 + expect(correlation_ids.span_id).to be 0 + expect(correlation_ids.to_s).to eq('dd.trace_id=0 dd.span_id=0') + end + end + + context 'given a Context object' do + let(:context) do + instance_double( + Datadog::Context, + trace_id: trace_id, + span_id: span_id + ) + end + + let(:trace_id) { double('trace id') } + let(:span_id) { double('span id') } + + it 'returns a Correlation::Identifier matching the Context' do + is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) + expect(correlation_ids.trace_id).to eq(trace_id) + expect(correlation_ids.span_id).to eq(span_id) + expect(correlation_ids.to_s).to eq("dd.trace_id=#{trace_id} dd.span_id=#{span_id}") + end + end + end +end diff --git a/spec/ddtrace/integration_spec.rb b/spec/ddtrace/integration_spec.rb index 901e874126d..750c0a1cb0d 100644 --- a/spec/ddtrace/integration_spec.rb +++ b/spec/ddtrace/integration_spec.rb @@ -148,14 +148,8 @@ def agent_receives_span_step3(previous_success) end describe 'sampling priority metrics' do - let(:tracer) do - get_test_tracer.tap do |t| - t.configure(priority_sampling: true) - t.writer = writer - end - end - - let(:writer) { FauxWriter.new(priority_sampler: Datadog::PrioritySampler.new) } + # Sampling priority is enabled by default + let(:tracer) { get_test_tracer } context 'when #sampling_priority is set on a child span' do let(:parent_span) { tracer.start_span('parent span') } @@ -168,7 +162,7 @@ def agent_receives_span_step3(previous_success) end.finish end.finish - try_wait_until { writer.spans(:keep).any? } + try_wait_until { tracer.writer.spans(:keep).any? } end it do diff --git a/spec/ddtrace/pin_spec.rb b/spec/ddtrace/pin_spec.rb index c6d1d80182a..41cbb08e621 100644 --- a/spec/ddtrace/pin_spec.rb +++ b/spec/ddtrace/pin_spec.rb @@ -23,7 +23,7 @@ context 'when given sufficient info' do let(:options) { { app: 'test-app', app_type: 'test-type', tracer: tracer } } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } it 'sets the service info' do expect(tracer.services.key?(service_name)).to be true @@ -35,7 +35,7 @@ context 'when given insufficient info' do let(:options) { { app_type: 'test-type', tracer: tracer } } - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } it 'does not sets the service info' do expect(tracer.services).to be_empty diff --git a/spec/ddtrace/propagation/http_propagator_spec.rb b/spec/ddtrace/propagation/http_propagator_spec.rb index 1bbf84db715..5a868ddc782 100644 --- a/spec/ddtrace/propagation/http_propagator_spec.rb +++ b/spec/ddtrace/propagation/http_propagator_spec.rb @@ -4,7 +4,7 @@ require 'ddtrace/propagation/http_propagator' RSpec.describe Datadog::HTTPPropagator do - let(:tracer) { ::Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } describe '#inject!' do let(:env) { { 'something' => 'alien' } } diff --git a/spec/ddtrace/propagation/propagation_integration_spec.rb b/spec/ddtrace/propagation/propagation_integration_spec.rb index 51197163e8e..72c7384d44d 100644 --- a/spec/ddtrace/propagation/propagation_integration_spec.rb +++ b/spec/ddtrace/propagation/propagation_integration_spec.rb @@ -4,7 +4,7 @@ require 'ddtrace/propagation/http_propagator' RSpec.describe 'Context propagation' do - let(:tracer) { Datadog::Tracer.new(writer: FauxWriter.new) } + let(:tracer) { get_test_tracer } describe 'when max context size is exceeded' do let(:max_size) { 3 } diff --git a/spec/ddtrace/tracer_spec.rb b/spec/ddtrace/tracer_spec.rb index fbc377faa9c..541ba129cf8 100644 --- a/spec/ddtrace/tracer_spec.rb +++ b/spec/ddtrace/tracer_spec.rb @@ -96,4 +96,33 @@ is_expected.to be(span) end end + + describe '#active_correlation' do + subject(:active_correlation) { tracer.active_correlation } + + context 'when a trace is active' do + let(:span) { @span } + + around(:each) do |example| + tracer.trace('test') do |span| + @span = span + example.run + end + end + + it 'produces an Datadog::Correlation::Identifier with data' do + is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) + expect(active_correlation.trace_id).to eq(span.trace_id) + expect(active_correlation.span_id).to eq(span.span_id) + end + end + + context 'when no trace is active' do + it 'produces an empty Datadog::Correlation::Identifier' do + is_expected.to be_a_kind_of(Datadog::Correlation::Identifier) + expect(active_correlation.trace_id).to be 0 + expect(active_correlation.span_id).to be 0 + end + end + end end diff --git a/spec/support/tracer_helpers.rb b/spec/support/tracer_helpers.rb index 706a22ee4ce..3d87f1606dd 100644 --- a/spec/support/tracer_helpers.rb +++ b/spec/support/tracer_helpers.rb @@ -4,8 +4,39 @@ module TracerHelpers # Return a test tracer instance with a faux writer. - def get_test_tracer - Datadog::Tracer.new(writer: FauxWriter.new) + def get_test_tracer(options = {}) + options = { writer: FauxWriter.new }.merge(options) + Datadog::Tracer.new(options).tap do |tracer| + # TODO: Let's try to get rid of this override, which has too much + # knowledge about the internal workings of the tracer. + # It is done to prevent the activation of priority sampling + # from wiping out the configured test writer, by replacing it. + tracer.define_singleton_method(:configure) do |opts = {}| + super(opts) + + # Re-configure the tracer with a new test writer + # since priority sampling will wipe out the old test writer. + unless @writer.is_a?(FauxWriter) + @writer = if @sampler.is_a?(Datadog::PrioritySampler) + FauxWriter.new(priority_sampler: @sampler) + else + FauxWriter.new + end + + hostname = opts.fetch(:hostname, nil) + port = opts.fetch(:port, nil) + + @writer.transport.hostname = hostname unless hostname.nil? + @writer.transport.port = port unless port.nil? + + statsd = opts.fetch(:statsd, nil) + unless statsd.nil? + @writer.statsd = statsd + @writer.transport.statsd = statsd + end + end + end + end end # Return some test traces diff --git a/test/context_flush_test.rb b/test/context_flush_test.rb index c238ba0b411..80446434786 100644 --- a/test/context_flush_test.rb +++ b/test/context_flush_test.rb @@ -346,7 +346,7 @@ def test_tracer_configure assert_nil(tracer.context_flush) # If given a partial_flush option, then uses default context flush. - flush_tracer = Datadog::Tracer.new(writer: FauxWriter.new, partial_flush: true) + flush_tracer = get_test_tracer(partial_flush: true) refute_nil(flush_tracer.context_flush) # If not configured with any flush options, context flush still doesn't exist. diff --git a/test/helper.rb b/test/helper.rb index 3f5fe96dbd1..937432b9e3c 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -35,8 +35,39 @@ class Span end # Return a test tracer instance with a faux writer. -def get_test_tracer - Datadog::Tracer.new(writer: FauxWriter.new) +def get_test_tracer(options = {}) + options = { writer: FauxWriter.new }.merge(options) + Datadog::Tracer.new(options).tap do |tracer| + # TODO: Let's try to get rid of this override, which has too much + # knowledge about the internal workings of the tracer. + # It is done to prevent the activation of priority sampling + # from wiping out the configured test writer, by replacing it. + tracer.define_singleton_method(:configure) do |opts = {}| + super(opts) + + # Re-configure the tracer with a new test writer + # since priority sampling will wipe out the old test writer. + unless @writer.is_a?(FauxWriter) + @writer = if @sampler.is_a?(Datadog::PrioritySampler) + FauxWriter.new(priority_sampler: @sampler) + else + FauxWriter.new + end + + hostname = opts.fetch(:hostname, nil) + port = opts.fetch(:port, nil) + + @writer.transport.hostname = hostname unless hostname.nil? + @writer.transport.port = port unless port.nil? + + statsd = opts.fetch(:statsd, nil) + unless statsd.nil? + @writer.statsd = statsd + @writer.transport.statsd = statsd + end + end + end + end end # Return some test traces diff --git a/test/sampler_test.rb b/test/sampler_test.rb index 84a62974db6..624918674b3 100644 --- a/test/sampler_test.rb +++ b/test/sampler_test.rb @@ -79,7 +79,7 @@ def test_tracer_with_rate_sampler prng = Random.new(123) tracer = get_test_tracer() - tracer.configure(sampler: Datadog::RateSampler.new(0.5)) + tracer.configure(sampler: Datadog::RateSampler.new(0.5), priority_sampling: false) nb_spans = 10000 nb_spans.times do @@ -87,7 +87,7 @@ def test_tracer_with_rate_sampler span.finish() end - spans = tracer.writer.spans() + spans = tracer.writer.spans expected = nb_spans * 0.5 assert_in_delta(spans.length, expected, 0.1 * expected)