Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenTracing support #517

Merged
merged 10 commits into from
Sep 18, 2018
10 changes: 9 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ namespace :spec do

RSpec::Core::RakeTask.new(:main) do |t|
t.pattern = 'spec/**/*_spec.rb'
t.exclude_pattern = 'spec/**/{contrib,benchmark,redis}/**/*_spec.rb'
t.exclude_pattern = 'spec/**/{contrib,benchmark,redis,opentracer}/**/*_spec.rb'
end

RSpec::Core::RakeTask.new(:opentracer) do |t|
t.pattern = 'spec/ddtrace/opentracer/**/*_spec.rb'
end

RSpec::Core::RakeTask.new(:rails) do |t|
Expand Down Expand Up @@ -311,6 +315,7 @@ task :ci do
sh 'bundle exec rake test:main'
sh 'bundle exec rake spec:main'
sh 'bundle exec rake spec:contrib'
sh 'bundle exec rake spec:opentracer'

if RUBY_PLATFORM != 'java'
# Contrib minitests
Expand Down Expand Up @@ -366,6 +371,7 @@ task :ci do
sh 'bundle exec rake test:main'
sh 'bundle exec rake spec:main'
sh 'bundle exec rake spec:contrib'
sh 'bundle exec rake spec:opentracer'

if RUBY_PLATFORM != 'java'
# Contrib minitests
Expand Down Expand Up @@ -432,6 +438,7 @@ task :ci do
sh 'bundle exec rake test:main'
sh 'bundle exec rake spec:main'
sh 'bundle exec rake spec:contrib'
sh 'bundle exec rake spec:opentracer'

if RUBY_PLATFORM != 'java'
# Contrib minitests
Expand Down Expand Up @@ -497,6 +504,7 @@ task :ci do
sh 'bundle exec rake test:main'
sh 'bundle exec rake spec:main'
sh 'bundle exec rake spec:contrib'
sh 'bundle exec rake spec:opentracer'

if RUBY_PLATFORM != 'java'
# Contrib minitests
Expand Down
2 changes: 2 additions & 0 deletions ddtrace.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']

spec.add_dependency 'msgpack'
# TODO: Move this to Appraisals?
spec.add_dependency 'opentracing', '>= 0.4.1'

spec.add_development_dependency 'rake', '>= 10.5'
spec.add_development_dependency 'rubocop', '= 0.49.1' if RUBY_VERSION >= '2.1.0'
Expand Down
75 changes: 71 additions & 4 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For descriptions of terminology used in APM, take a look at the [official docume
- [Installation](#installation)
- [Quickstart for Rails applications](#quickstart-for-rails-applications)
- [Quickstart for Ruby applications](#quickstart-for-ruby-applications)
- [Quickstart for OpenTracing](#quickstart-for-opentracing)
- [Manual instrumentation](#manual-instrumentation)
- [Integration instrumentation](#integration-instrumentation)
- [Active Record](#active-record)
Expand Down Expand Up @@ -59,6 +60,7 @@ For descriptions of terminology used in APM, take a look at the [official docume
- [Processing pipeline](#processing-pipeline)
- [Filtering](#filtering)
- [Processing](#processing)
- [OpenTracing](#opentracing)

## Compatibility

Expand All @@ -75,10 +77,6 @@ For descriptions of terminology used in APM, take a look at the [official docume
| | | 2.4 | Full |
| JRuby | http://jruby.org/ | 9.1.5 | Experimental |

*Full* support indicates all tracer features are available.

*Experimental* indicates most features should be available, but unverified.

**Supported web servers**:

| Type | Documentation | Version | Support type |
Expand All @@ -87,6 +85,16 @@ For descriptions of terminology used in APM, take a look at the [official docume
| Unicorn | https://bogomips.org/unicorn/ | 4.8+ / 5.1+ | Full |
| Passenger | https://www.phusionpassenger.com/ | 5.0+ | Full |

**Supported tracing frameworks**:

| Type | Documentation | Version | Support type |
| ----------- | ----------------------------------------------- | --------------------- | ------------ |
| OpenTracing | https://github.com/opentracing/opentracing-ruby | 0.4.1+ (w/ Ruby 2.1+) | Experimental |

*Full* support indicates all tracer features are available.

*Experimental* indicates most features should be available, but unverified.

## Installation

The following steps will help you quickly start tracing your Ruby application.
Expand Down Expand Up @@ -136,6 +144,36 @@ The Ruby APM tracer sends trace data through the Datadog Agent.
1. Activate integration instrumentation (see [Integration instrumentation](#integration-instrumentation))
2. Add manual instrumentation around your code (see [Manual instrumentation](#manual-instrumentation))

### Quickstart for OpenTracing

1. Install the gem with `gem install ddtrace`
2. To your OpenTracing configuration file, add the following:

```ruby
require 'opentracing'
require 'ddtrace'
require 'ddtrace/opentracer'

# Activate the Datadog tracer for OpenTracing
OpenTracing.global_tracer = Datadog::OpenTracer::Tracer.new
```

3. (Optional) Add a configuration block to your Ruby application to configure Datadog with:

```ruby
Datadog.configure do |c|
# Configure the Datadog tracer here.
# Activate integrations, change tracer settings, etc...
# By default without additional configuration,
# no additional integrations will be traced, only
# what you have instrumented with OpenTracing.
end
```

4. (Optional) Add or activate additional instrumentation by doing either of the following:
1. Activate Datadog integration instrumentation (see [Integration instrumentation](#integration-instrumentation))
2. Add Datadog manual instrumentation around your code (see [Manual instrumentation](#manual-instrumentation))

### Final steps for installation

After setting up, your services will appear on the [APM services page](https://app.datadoghq.com/apm/services) within a few minutes. Learn more about [using the APM UI][visualization docs].
Expand Down Expand Up @@ -1510,3 +1548,32 @@ Datadog::Pipeline.before_flush(
Datadog::Pipeline::SpanProcessor.new { |span| span.resource.gsub!(/password=.*/, '') }
)
```

### OpenTracing

For setting up Datadog with OpenTracing, see out [Quickstart for OpenTracing](#quickstart-for-opentracing) section for details.

**Configuring Datadog tracer settings**

The underlying Datadog tracer can be configured by passing options (which match `Datadog::Tracer`) when configuring the global tracer:

```ruby
# Where `options` is a Hash of options provided to Datadog::Tracer
OpenTracing.global_tracer = Datadog::OpenTracer::Tracer.new(options)
```

It can also be configured by using `Datadog.configure` described in the [Tracer settings](#tracer-settings) section.

**Activating and configuring integrations**

By default, configuring OpenTracing with Datadog will not automatically activate any additional instrumentation provided by Datadog. You will only receive spans and traces from OpenTracing instrumentation you have in your application.

However, additional instrumentation provided by Datadog can be activated alongside OpenTracing using `Datadog.configure`, which can be used to further enhance your tracing. To activate this, see [Integration instrumentation](#integration-instrumentation) for more details.

**Supported serialization formats**

| Type | Supported? | Additional information |
| ------------------------------ | ---------- | ---------------------- |
| `OpenTracing::FORMAT_TEXT_MAP` | Yes | |
| `OpenTracing::FORMAT_RACK` | Yes | Because of the loss of resolution in the Rack format, please note that baggage items with names containing either upper case characters or `-` will be converted to lower case and `_` in a round-trip respectively. We recommend avoiding these characters, or accommodating accordingly on the receiving end. |
| `OpenTracing::FORMAT_BINARY` | No | |
40 changes: 40 additions & 0 deletions lib/ddtrace/opentracer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Datadog
# Namespace for ddtrace OpenTracing implementation
module OpenTracer
module_function

def supported?
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1')
end

def load_opentracer
require 'opentracing'
require 'opentracing/carrier'
require 'ddtrace'
require 'ddtrace/opentracer/carrier'
require 'ddtrace/opentracer/tracer'
require 'ddtrace/opentracer/span'
require 'ddtrace/opentracer/span_context'
require 'ddtrace/opentracer/span_context_factory'
require 'ddtrace/opentracer/scope'
require 'ddtrace/opentracer/scope_manager'
require 'ddtrace/opentracer/thread_local_scope'
require 'ddtrace/opentracer/thread_local_scope_manager'
require 'ddtrace/opentracer/distributed_headers'
require 'ddtrace/opentracer/propagator'
require 'ddtrace/opentracer/text_map_propagator'
require 'ddtrace/opentracer/binary_propagator'
require 'ddtrace/opentracer/rack_propagator'
require 'ddtrace/opentracer/global_tracer'

# Modify the OpenTracing module functions
OpenTracing.module_eval do
class << self
prepend Datadog::OpenTracer::GlobalTracer
end
end
end

load_opentracer if supported?
end
end
24 changes: 24 additions & 0 deletions lib/ddtrace/opentracer/binary_propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module BinaryPropagator
extend Propagator

# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of Binary type
def self.inject(span_context, carrier)
nil
end

# Extract a SpanContext in Binary format from the given carrier.
#
# @param carrier [Carrier] A carrier object of Binary type
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def self.extract(carrier)
SpanContext::NOOP_INSTANCE
end
end
end
end
6 changes: 6 additions & 0 deletions lib/ddtrace/opentracer/carrier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Datadog
module OpenTracer
class Carrier < ::OpenTracing::Carrier
end
end
end
46 changes: 46 additions & 0 deletions lib/ddtrace/opentracer/distributed_headers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'ddtrace/span'
require 'ddtrace/ext/distributed'

module Datadog
module OpenTracer
# DistributedHeaders provides easy access and validation to headers
class DistributedHeaders
include Datadog::Ext::DistributedTracing

def initialize(carrier)
@carrier = carrier
end

def valid?
# Sampling priority is optional.
!trace_id.nil? && !parent_id.nil?
end

def trace_id
id HTTP_HEADER_TRACE_ID
end

def parent_id
id HTTP_HEADER_PARENT_ID
end

def sampling_priority
hdr = @carrier[HTTP_HEADER_SAMPLING_PRIORITY]
# It's important to make a difference between no header,
# and a header defined to zero.
return unless hdr
value = hdr.to_i
return if value < 0
value
end

private

def id(header)
value = @carrier[header].to_i
return if value.zero? || value >= Datadog::Span::MAX_ID
value < 0 ? value + 0x1_0000_0000_0000_0000 : value
end
end
end
end
15 changes: 15 additions & 0 deletions lib/ddtrace/opentracer/global_tracer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Datadog
module OpenTracer
# Patch for OpenTracing module
module GlobalTracer
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)
end
end
end
end
end
end
22 changes: 22 additions & 0 deletions lib/ddtrace/opentracer/propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module Propagator
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
def inject(span_context, carrier)
raise NotImplementedError
end

# Extract a SpanContext in the given format from the given carrier.
#
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(carrier)
raise NotImplementedError
end
end
end
end
60 changes: 60 additions & 0 deletions lib/ddtrace/opentracer/rack_propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'ddtrace/propagation/http_propagator'

module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module RackPropagator
extend Propagator
extend Datadog::Ext::DistributedTracing
include Datadog::Ext::DistributedTracing

BAGGAGE_PREFIX = 'ot-baggage-'.freeze
BAGGAGE_PREFIX_FORMATTED = 'HTTP_OT_BAGGAGE_'.freeze

class << self
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of Rack type
def inject(span_context, carrier)
# Inject Datadog trace properties
Datadog::HTTPPropagator.inject!(span_context.datadog_context, carrier)

# Inject baggage
span_context.baggage.each do |key, value|
carrier[BAGGAGE_PREFIX + key] = value
end

nil
end

# Extract a SpanContext in Rack format from the given carrier.
#
# @param carrier [Carrier] A carrier object of Rack type
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(carrier)
# First extract & build a Datadog context
datadog_context = Datadog::HTTPPropagator.extract(carrier)

# Then extract any other baggage
baggage = {}
carrier.each do |key, value|
baggage[header_to_baggage(key)] = value if baggage_header?(key)
end

SpanContextFactory.build(datadog_context: datadog_context, baggage: baggage)
end

private

def baggage_header?(header)
header.to_s.start_with?(BAGGAGE_PREFIX_FORMATTED)
end

def header_to_baggage(key)
key[BAGGAGE_PREFIX_FORMATTED.length, key.length].downcase
end
end
end
end
end
Loading