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

Implement ActiveRecord integration configuration #451

Merged
merged 7 commits into from
Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
AllCops:
TargetRubyVersion: 2.1
Include:
- 'lib/**/*.rb'
- 'test/**/*.rb'
- 'spec/**/*.rb'
- 'Gemfile'
- 'Rakefile'
Exclude:
- 'Appraisals'
- '*.gemspec'
- 'lib/ddtrace/vendor/**/*.rb'

# 80 characters is a nice goal, but not worth currently changing in existing
# code for the sake of changing it to conform to a length set in 1928 (IBM).
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1.0')
RuboCop::RakeTask.new(:rubocop) do |t|
t.options << ['-D']
t.options << ['-D', '--force-exclusion']
t.patterns = ['lib/**/*.rb', 'test/**/*.rb', 'spec/**/*.rb', 'Gemfile', 'Rakefile']
end
end
Expand Down
39 changes: 39 additions & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,44 @@ Where `options` is an optional `Hash` that accepts the following parameters:
| ``service_name`` | Service name used for database portion of `active_record` instrumentation. | Name of database adapter (e.g. `mysql2`) |
| ``orm_service_name`` | Service name used for the Ruby ORM portion of `active_record` instrumentation. Overrides service name for ORM spans if explicitly set, which otherwise inherit their service from their parent. | ``active_record`` |

**Configuring trace settings per database**

You can provide the `databases` option to configure trace settings by database connection:

```ruby
# Provide a `:describes` option with a connection key.
# Any of the following keys are acceptable, and equivalent to one another.
# If a block is provided, it yields a Settings object that
# accepts any of the configuration options listed above.

Datadog.configure do |c|
# Symbol matching your database connection in config/database.yml
# Only available if you are using Rails with ActiveRecord.
c.use :active_record, describes: :secondary_database, service_name: 'secondary-db'

c.use :active_record, describes: :secondary_database do |second_db|
second_db.service_name = 'secondary-db'
end

# Connection string with the following connection settings:
# Adapter, user, host, port, database
c.use :active_record, describes: 'mysql2://root@127.0.0.1:3306/mysql', service_name: 'secondary-db'

# Hash with following connection settings
# Adapter, user, host, port, database
c.use :active_record, describes: {
adapter: 'mysql2',
host: '127.0.0.1',
port: '3306',
database: 'mysql',
username: 'root'
},
service_name: 'secondary-db'
end
```

If ActiveRecord traces an event that uses a connection described within `databases`, it will use the trace settings assigned to that connection. If the connection does not match any in the `databases` option, it will use settings defined by `c.use :active_record` instead.

### AWS

The AWS integration will trace every interaction (e.g. API calls) with AWS services (S3, ElastiCache etc.).
Expand Down Expand Up @@ -767,6 +805,7 @@ Where `options` is an optional `Hash` that accepts the following parameters:
| ``middleware_names`` | Enables any short-circuited middleware requests to display the middleware name as resource for the trace. | `false` |
| ``template_base_path`` | Used when the template name is parsed. If you don't store your templates in the ``views/`` folder, you may need to change this value | ``views/`` |
| ``tracer`` | A ``Datadog::Tracer`` instance used to instrument the application. Usually you don't need to set that. | ``Datadog.tracer`` |
| ``databases`` | Hash of tracer settings to use for each database connection. See [ActiveRecord](#activerecord) for more details. | ``{}`` |

### Rake

Expand Down
2 changes: 1 addition & 1 deletion lib/ddtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def configure(target = configuration, opts = {})
require 'ddtrace/contrib/rack/patcher'
require 'ddtrace/contrib/rails/patcher'
require 'ddtrace/contrib/active_model_serializers/patcher'
require 'ddtrace/contrib/active_record/patcher'
require 'ddtrace/contrib/active_record/integration'
require 'ddtrace/contrib/sequel/patcher'
require 'ddtrace/contrib/elasticsearch/patcher'
require 'ddtrace/contrib/faraday/patcher'
Expand Down
46 changes: 46 additions & 0 deletions lib/ddtrace/contrib/active_record/configuration/resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'ddtrace/contrib/configuration/resolver'
require 'ddtrace/vendor/active_record/connection_specification'

module Datadog
module Contrib
module ActiveRecord
module Configuration
# Converts Symbols, Strings, and Hashes to a normalized connection settings Hash.
class Resolver < Contrib::Configuration::Resolver
def initialize(configurations = nil)
@configurations = configurations
end

def configurations
@configurations || ::ActiveRecord::Base.configurations
end

def connection_resolver
@resolver ||= begin
if defined?(::ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver)
::ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(configurations)
else
::Datadog::Vendor::ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(configurations)
end
end
end

def resolve(spec)
return :default if spec == :default
normalize(connection_resolver.resolve(spec).symbolize_keys)
end

def normalize(hash)
{
adapter: hash[:adapter],
host: hash[:host],
port: hash[:port],
database: hash[:database],
username: hash[:username]
}
end
end
end
end
end
end
28 changes: 28 additions & 0 deletions lib/ddtrace/contrib/active_record/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'ddtrace/contrib/configuration/settings'
require 'ddtrace/contrib/active_record/utils'

module Datadog
module Contrib
module ActiveRecord
module Configuration
# Unique settings for ActiveRecord
class Settings < Contrib::Configuration::Settings
option :orm_service_name
option :service_name, depends_on: [:tracer] do |value|
(value || Utils.adapter_name).tap do |service_name|
tracer.set_service_info(service_name, 'active_record', Ext::AppTypes::DB)
end
end

option :tracer, default: Datadog.tracer do |value|
value.tap do
Events.subscriptions.each do |subscription|
subscription.tracer = value
end
end
end
end
end
end
end
end
18 changes: 11 additions & 7 deletions lib/ddtrace/contrib/active_record/events/sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ def span_name
end

def process(span, event, _id, payload)
connection_config = Utils.connection_config(payload[:connection_id])
span.name = "#{connection_config[:adapter_name]}.query"
span.service = configuration[:service_name]
config = Utils.connection_config(payload[:connection_id])
settings = Datadog.configuration[:active_record, config]
adapter_name = Datadog::Utils::Database.normalize_vendor(config[:adapter])
service_name = !settings.nil? ? settings.service_name : configuration[:service_name]

span.name = "#{adapter_name}.query"
span.service = service_name
span.resource = payload.fetch(:sql)
span.span_type = Datadog::Ext::SQL::TYPE

Expand All @@ -33,11 +37,11 @@ def process(span, event, _id, payload)
# is simply cached from memory, so the notification is fired with start == finish.
cached = payload[:cached] || (payload[:name] == 'CACHE')

span.set_tag('active_record.db.vendor', connection_config[:adapter_name])
span.set_tag('active_record.db.name', connection_config[:database_name])
span.set_tag('active_record.db.vendor', adapter_name)
span.set_tag('active_record.db.name', config[:database])
span.set_tag('active_record.db.cached', cached) if cached
span.set_tag('out.host', connection_config[:adapter_host])
span.set_tag('out.port', connection_config[:adapter_port])
span.set_tag('out.host', config[:host]) if config[:host]
span.set_tag('out.port', config[:port]) if config[:port]
rescue StandardError => e
Datadog::Tracer.log.debug(e.message)
end
Expand Down
40 changes: 40 additions & 0 deletions lib/ddtrace/contrib/active_record/integration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'ddtrace/ext/sql'
require 'ddtrace/ext/app_types'
require 'ddtrace/contrib/integration'
require 'ddtrace/contrib/active_record/events'
require 'ddtrace/contrib/active_record/configuration/resolver'
require 'ddtrace/contrib/active_record/configuration/settings'
require 'ddtrace/contrib/active_record/patcher'

module Datadog
module Contrib
module ActiveRecord
# Describes the ActiveRecord integration
class Integration
include Contrib::Integration

register_as :active_record, auto_patch: false

def self.compatible?
super \
&& RUBY_VERSION >= '1.9.3' \
&& Gem.loaded_specs['activerecord'] \
&& Gem.loaded_specs['activerecord'].version >= Gem::Version.new('3.0') \
&& defined?(::ActiveRecord)
end

def default_configuration
Configuration::Settings.new
end

def patcher
ActiveRecord::Patcher
end

def resolver
@resolver ||= Configuration::Resolver.new
end
end
end
end
end
32 changes: 4 additions & 28 deletions lib/ddtrace/contrib/active_record/patcher.rb
Original file line number Diff line number Diff line change
@@ -1,51 +1,27 @@
require 'ddtrace/ext/sql'
require 'ddtrace/ext/app_types'
require 'ddtrace/contrib/active_record/utils'
require 'ddtrace/contrib/patcher'
require 'ddtrace/contrib/active_record/events'

module Datadog
module Contrib
module ActiveRecord
# Patcher enables patching of 'active_record' module.
module Patcher
include Base

register_as :active_record, auto_patch: false
option :service_name, depends_on: [:tracer] do |value|
(value || Utils.adapter_name).tap do |v|
get_option(:tracer).set_service_info(v, 'active_record', Ext::AppTypes::DB)
end
end
option :orm_service_name
option :tracer, default: Datadog.tracer do |value|
(value || Datadog.tracer).tap do |v|
# Make sure to update tracers of all subscriptions
Events.subscriptions.each do |subscription|
subscription.tracer = v
end
end
end

@patched = false
include Contrib::Patcher

module_function

# patched? tells whether patch has been successfully applied
def patched?
@patched
done?(:active_record)
end

def patch
if !@patched && defined?(::ActiveRecord)
do_once(:active_record) do
begin
Events.subscribe!
@patched = true
rescue StandardError => e
Datadog::Tracer.log.error("Unable to apply Active Record integration: #{e}")
end
end

@patched
end
end
end
Expand Down
16 changes: 5 additions & 11 deletions lib/ddtrace/contrib/active_record/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ module ActiveRecord
# Common utilities for Rails
module Utils
def self.adapter_name
connection_config[:adapter_name]
Datadog::Utils::Database.normalize_vendor(connection_config[:adapter])
end

def self.database_name
connection_config[:database_name]
connection_config[:database]
end

def self.adapter_host
connection_config[:adapter_host]
connection_config[:host]
end

def self.adapter_port
connection_config[:adapter_port]
connection_config[:port]
end

def self.connection_config(object_id = nil)
config = object_id.nil? ? default_connection_config : connection_config_by_id(object_id)
{
adapter_name: Datadog::Utils::Database.normalize_vendor(config[:adapter]),
adapter_host: config[:host],
adapter_port: config[:port],
database_name: config[:database]
}
object_id.nil? ? default_connection_config : connection_config_by_id(object_id)
end

# Attempt to retrieve the connection from an object ID.
Expand Down
Loading