Ruby Custom Instrumentation
Incident Management is now generally available! Incident Management is now generally available!

Ruby Custom Instrumentation

If you have not yet read the instructions for auto-instrumentation and setup, please start with the Ruby Setup Instructions.

This page details common use cases for adding and customizing observability with Datadog APM.

Adding Tags

Add custom span tags to your spans to customize your observability within Datadog. The span tags are applied to your incoming traces, allowing you to correlate observed behavior with code-level information such as merchant tier, checkout amount, or user ID.

Add custom span tags

Add custom tags to your spans corresponding to any dynamic value within your application code such as customer.id.

Access the current active span from any method within your code. Note: If the method is called and there is no span currently active, active_span is nil.

require 'ddtrace'

# get '/shopping_cart/:customer_id', to: 'shopping_cart#index'
class ShoppingCartController < ApplicationController
  # GET /shopping_cart
  def index
    # Get the active span
    current_span = Datadog.tracer.active_span
    # customer_id -> 254889
    current_span.set_tag('customer.id', params.permit([:customer_id])) unless current_span.nil?

    # [...]
  end

  # POST /shopping_cart
  def create
    # [...]
  end
end

Add tags directly to Datadog::Span objects by calling #set_tag:

# An example of a Sinatra endpoint,
# with Datadog tracing around the request.
get '/posts' do
  Datadog.tracer.trace('web.request') do |span|
    span.set_tag('http.url', request.path)
    span.set_tag('<TAG_KEY>', '<TAG_VALUE>')
  end
end

Adding tags globally to all spans

Add tags to all spans by configuring the tracer with the tags option:

Datadog.configure do |c|
  c.tags = { 'team' => 'qa' }
end

You can also use the DD_TAGS environment variable to set tags on all spans for an application. For more information on Ruby environment variables, refer to the setup documentation.

Setting Errors on a Span

There are two ways to set an error on a span

  • The first is to simply call span.set_error and pass in the Exception Object.
  • This will automatically extract the error type, message, and backtrace.
require 'ddtrace'
require 'timeout'

def example_method
  span = Datadog.tracer.trace('example.trace')
  puts 'some work'
  sleep(1)
  raise StandardError.new "This is an exception"
rescue StandardError => error
  span = Datadog.tracer.active_span
  span.set_error(error) unless span.nil?
  raise error
ensure
  span.finish
end

example_method()
  • The second is to use tracer.trace which will by default set the error type, message, and backtrace.
  • To configure this behavior you can use the on_error option, which is the Handler invoked when a block is provided to trace, and the block raises an error.
  • The Proc is provided span and error as arguments.
  • By default, on_error Sets error on the span.

Default Behavior: on_error

require 'ddtrace'
require 'timeout'

def example_method
  puts 'some work'
  sleep(1)
  raise StandardError.new "This is a exception"
end

Datadog.tracer.trace('example.trace', on_error: custom_error_handler) do |span|
  example_method()
end

Custom Behavior: on_error

require 'ddtrace'
require 'timeout'

def example_method
  puts 'some work'
  sleep(1)
  raise StandardError.new "This is a special exception"
end

custom_error_handler = proc do |span, error|
  span.set_tag('custom_tag', 'custom_value')
  span.set_error(error) unless error.message.include?("a special exception")
end

Datadog.tracer.trace('example.trace', on_error: custom_error_handler) do |span|
  example_method()
end

Adding Spans

If you aren’t using supported library instrumentation (see library compatibility), you may want to to manually instrument your code. Adding tracing to your code is easy using the Datadog.tracer.trace method, which you can wrap around any Ruby code:

To trace any Ruby code, you can use the Datadog.tracer.trace method:

Datadog.tracer.trace(name, options) do |span|
  # Wrap this block around the code you want to instrument
  # Additionally, you can modify the span here.
  # e.g. Change the resource name, set tags, etc...
end

Where name should be a String that describes the generic kind of operation being done (e.g. 'web.request', or 'request.parse')

And options is an optional Hash that accepts the following parameters:

KeyTypeDescriptionDefault
serviceStringThe service name which this span belongs (e.g. 'my-web-service')Tracer default-service, $PROGRAM_NAME or 'ruby'
resourceStringName of the resource or action being operated on. Traces with the same resource value will be grouped together for the purpose of metrics (but still independently viewable.) Usually domain specific, such as a URL, query, request, etc. (e.g. 'Article#submit', http://example.com/articles/list.)name of Span.
span_typeStringThe type of the span (such as 'http', 'db', etc.)nil
child_ofDatadog::Span / Datadog::ContextParent for this span. If not provided, will automatically become current active span.nil
start_timeIntegerWhen the span actually starts. Useful when tracing events that have already happened.Time.now.utc
tagsHashExtra tags which should be added to the span.{}
on_errorProcHandler invoked when a block is provided to trace, and it raises an error. Provided span and error as arguments. Sets error on the span by default.proc { |span, error| span.set_error(error) unless span.nil? }

It’s highly recommended you set both service and resource at a minimum. Spans without a service or resource as nil will be discarded by the Datadog agent.

Manually creating a new span

Programmatically create spans around any block of code. Spans created in this manner integrate with other tracing mechanisms automatically. In other words, if a trace has already started, the manual span will have its caller as its parent span. Similarly, any traced methods called from the wrapped block of code will have the manual span as its parent.

# An example of a Sinatra endpoint,
# with Datadog tracing around the request,
# database query, and rendering steps.
get '/posts' do
  Datadog.tracer.trace('web.request', service: '<SERVICE_NAME>', resource: 'GET /posts') do |span|
    # Trace the activerecord call
    Datadog.tracer.trace('posts.fetch') do
      @posts = Posts.order(created_at: :desc).limit(10)
    end

    # Add some APM tags
    span.set_tag('http.method', request.request_method)
    span.set_tag('posts.count', @posts.length)

    # Trace the template rendering
    Datadog.tracer.trace('template.render') do
      erb :index
    end
  end
end

Post-Processing Traces

Some applications might require that traces be altered or filtered out before they are sent upstream. The processing pipeline allows users to create processors to define such behavior.

Processors can be any object that responds to #call accepting trace as an argument (which is an Array of Datadog::Spans.)

For example:

lambda_processor = ->(trace) do
  # Processing logic...
  trace
end

class MyCustomProcessor
  def call(trace)
    # Processing logic...
    trace
  end
end
custom_processor = MyFancyProcessor.new

#call blocks of processors must return the trace object; this return value will be passed to the next processor in the pipeline.

These processors must then be added to the pipeline via Datadog::Pipeline.before_flush:

Datadog::Pipeline.before_flush(lambda_processor, custom_processor)

You can also define processors using the short-hand block syntax for Datadog::Pipeline.before_flush:

Datadog::Pipeline.before_flush do |trace|
  trace.delete_if { |span| span.name =~ /forbidden/ }
end

Filtering

You can use the Datadog::Pipeline::SpanFilter processor to remove spans, when the block evaluates as truthy:

Datadog::Pipeline.before_flush(
  # Remove spans that match a particular resource
  Datadog::Pipeline::SpanFilter.new { |span| span.resource =~ /PingController/ },
  # Remove spans that are trafficked to localhost
  Datadog::Pipeline::SpanFilter.new { |span| span.get_tag('host') == 'localhost' }
)

Processing

You can use the Datadog::Pipeline::SpanProcessor processor to modify spans:

Datadog::Pipeline.before_flush(
  # Strip matching text from the resource field
  Datadog::Pipeline::SpanProcessor.new { |span| span.resource.gsub!(/password=.*/, '') }
)

Trace Client & Agent Configuration

There are additional configurations possible for both the tracing client and Datadog Agent for context propagation with B3 Headers, as well as to exclude specific Resources from sending traces to Datadog in the event these traces are not wanted to count in metrics calculated, such as Health Checks.

B3 Headers Extraction and Injection

Datadog APM tracer supports B3 headers extraction and injection for distributed tracing.

Distributed headers injection and extraction is controlled by configuring injection/extraction styles. Currently two styles are supported:

  • Datadog: Datadog
  • B3: B3

Injection styles can be configured using:

  • Environment Variable: DD_PROPAGATION_STYLE_INJECT=Datadog,B3

The value of the environment variable is a comma separated list of header styles that are enabled for injection. By default only Datadog injection style is enabled.

Extraction styles can be configured using:

  • Environment Variable: DD_PROPAGATION_STYLE_EXTRACT=Datadog,B3

The value of the environment variable is a comma separated list of header styles that are enabled for extraction. By default only Datadog extraction style is enabled.

If multiple extraction styles are enabled extraction attempt is done on the order those styles are configured and first successful extracted value is used.

Resource Filtering

Traces can be excluded based on their resource name, to remove synthetic traffic such as health checks from reporting traces to Datadog. This and other security and fine-tuning configurations can be found on the Security page.

Further Reading