Skip to content

Commit

Permalink
Refactored: Moved ActionView instrumentation from Rails into integrat…
Browse files Browse the repository at this point in the history
…ion.
  • Loading branch information
delner committed May 23, 2019
1 parent 75711c0 commit 6928012
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 220 deletions.
1 change: 1 addition & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ Where `options` is an optional `Hash` that accepts the following parameters:
| `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` |
| `service_name` | Service name used for rendering instrumentation. | `action_view` |
| `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` |
| `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/'` |
### Active Model Serializers
Expand Down
1 change: 1 addition & 0 deletions lib/ddtrace/contrib/action_view/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Settings < Contrib::Configuration::Settings
lazy: true

option :service_name, default: Ext::SERVICE_NAME
option :template_base_path, default: 'views/'
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/ddtrace/contrib/action_view/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ module Ext
ENV_ANALYTICS_ENABLED = 'DD_ACTION_VIEW_ANALYTICS_ENABLED'.freeze
ENV_ANALYTICS_SAMPLE_RATE = 'DD_ACTION_VIEW_ANALYTICS_SAMPLE_RATE'.freeze
SERVICE_NAME = 'action_view'.freeze
SPAN_RENDER_PARTIAL = 'rails.render_partial'.freeze
SPAN_RENDER_TEMPLATE = 'rails.render_template'.freeze
TAG_LAYOUT = 'rails.layout'.freeze
TAG_TEMPLATE_NAME = 'rails.template_name'.freeze
end
end
end
Expand Down
2 changes: 0 additions & 2 deletions lib/ddtrace/contrib/action_view/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ module Contrib
module ActionView
# Defines instrumentation for ActionView
module Instrumentation
module_function

# TODO: Add instrumentation here.
end
end
Expand Down
9 changes: 8 additions & 1 deletion lib/ddtrace/contrib/action_view/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ class Integration
register_as :action_view, auto_patch: false

def self.version
Gem.loaded_specs['actionview'] && Gem.loaded_specs['actionview'].version
# ActionView is its own gem in Rails 4.1+
if Gem.loaded_specs['actionview']
Gem.loaded_specs['actionview'].version
# ActionView is embedded in ActionPack in versions < 4.1
elsif Gem.loaded_specs['actionpack']
action_pack_version = Gem.loaded_specs['actionpack'].version
action_pack_version unless action_pack_version >= Gem::Version.new('4.1')
end
end

def self.present?
Expand Down
160 changes: 159 additions & 1 deletion lib/ddtrace/contrib/action_view/patcher.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'ddtrace/ext/http'
require 'ddtrace/contrib/patcher'
require 'ddtrace/contrib/action_view/ext'
require 'ddtrace/contrib/action_view/instrumentation'
require 'ddtrace/contrib/action_view/utils'

module Datadog
module Contrib
module ActionView
# Patcher enables patching of ActionView module.
# rubocop:disable Metrics/ModuleLength
# rubocop:disable Metrics/MethodLength
module Patcher
include Contrib::Patcher

Expand All @@ -18,12 +22,166 @@ def patched?
def patch
do_once(:action_view) do
begin
# TODO: Patch ActionView
patch_renderer
rescue StandardError => e
Datadog::Tracer.log.error("Unable to apply Action View integration: #{e}")
end
end
end

def patch_renderer
do_once(:patch_renderer) do
if defined?(::ActionView::TemplateRenderer) && defined?(::ActionView::PartialRenderer)
patch_template_renderer(::ActionView::TemplateRenderer)
patch_partial_renderer(::ActionView::PartialRenderer)
elsif defined?(::ActionView::Rendering) && defined?(::ActionView::Partials::PartialRenderer)
# NOTE: Rails < 3.1 compatibility: different classes are used
patch_template_renderer(::ActionView::Rendering)
patch_partial_renderer(::ActionView::Partials::PartialRenderer)
else
Datadog::Tracer.log.debug('Expected Template/Partial classes not found; template rendering disabled')
end
end
end

def patch_template_renderer(klass)
# rubocop:disable Metrics/BlockLength
do_once(:patch_template_renderer) do
klass.class_eval do
def render_with_datadog(*args, &block)
# NOTE: This check exists purely for Rails 3.0 compatibility.
# The 'if' part can be removed when support for Rails 3.0 is removed.
if active_datadog_span
render_without_datadog(*args, &block)
else
datadog_tracer.trace(
Ext::SPAN_RENDER_TEMPLATE,
span_type: Datadog::Ext::HTTP::TEMPLATE
) do |span|
with_datadog_span(span) { render_without_datadog(*args, &block) }
end
end
end

def render_template_with_datadog(*args)
begin
# arguments based on render_template signature (stable since Rails 3.2)
template = args[0]
layout_name = args[1]

# update the tracing context with computed values before the rendering
template_name = template.try('identifier')
template_name = Utils.normalize_template_name(template_name)
layout = if layout_name.is_a?(String)
# NOTE: Rails < 3.1 compatibility: the second argument is the layout name
layout_name
else
layout_name.try(:[], 'virtual_path')
end
if template_name
active_datadog_span.set_tag(
Ext::TAG_TEMPLATE_NAME,
template_name
)
end

if layout
active_datadog_span.set_tag(
Ext::TAG_LAYOUT,
layout
)
end
rescue StandardError => e
Datadog::Tracer.log.debug(e.message)
end

# execute the original function anyway
render_template_without_datadog(*args)
end

private

attr_accessor :active_datadog_span

def datadog_tracer
Datadog.configuration[:action_view][:tracer]
end

def with_datadog_span(span)
self.active_datadog_span = span
yield
ensure
self.active_datadog_span = nil
end

# method aliasing to patch the class
alias_method :render_without_datadog, :render
alias_method :render, :render_with_datadog

if klass.private_method_defined?(:render_template) || klass.method_defined?(:render_template)
alias_method :render_template_without_datadog, :render_template
alias_method :render_template, :render_template_with_datadog
else
# NOTE: Rails < 3.1 compatibility: the method name is different
alias_method :render_template_without_datadog, :_render_template
alias_method :_render_template, :render_template_with_datadog
end
end
end
end

def patch_partial_renderer(klass)
do_once(:patch_partial_renderer) do
klass.class_eval do
def render_with_datadog(*args, &block)
datadog_tracer.trace(
Ext::SPAN_RENDER_PARTIAL,
span_type: Datadog::Ext::HTTP::TEMPLATE
) do |span|
with_datadog_span(span) { render_without_datadog(*args) }
end
end

def render_partial_with_datadog(*args)
begin
template_name = Utils.normalize_template_name(@template.try('identifier'))
if template_name
active_datadog_span.set_tag(
Ext::TAG_TEMPLATE_NAME,
template_name
)
end
rescue StandardError => e
Datadog::Tracer.log.debug(e.message)
end

# execute the original function anyway
render_partial_without_datadog(*args)
end

private

attr_accessor :active_datadog_span

def datadog_tracer
Datadog.configuration[:action_view][:tracer]
end

def with_datadog_span(span)
self.active_datadog_span = span
yield
ensure
self.active_datadog_span = nil
end

# method aliasing to patch the class
alias_method :render_without_datadog, :render
alias_method :render, :render_with_datadog
alias_method :render_partial_without_datadog, :render_partial
alias_method :render_partial, :render_partial_with_datadog
end
end
end
end
end
end
Expand Down
32 changes: 32 additions & 0 deletions lib/ddtrace/contrib/action_view/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'ddtrace/contrib/analytics'

module Datadog
module Contrib
module ActionView
# common utilities for Rails
module Utils
module_function

# in Rails the template name includes the template full path
# and it's better to avoid storing such information. This method
# returns the relative path from `views/` or the template name
# if a `views/` folder is not in the template full path. A wrong
# usage ensures that this method will not crash the tracing system.
def normalize_template_name(name)
return if name.nil?

base_path = Datadog.configuration[:action_view][:template_base_path]
sections_view = name.split(base_path)

if sections_view.length == 1
name.split('/')[-1]
else
sections_view[-1]
end
rescue
return name.to_s
end
end
end
end
end
19 changes: 0 additions & 19 deletions lib/ddtrace/contrib/rails/action_view.rb

This file was deleted.

6 changes: 5 additions & 1 deletion lib/ddtrace/contrib/rails/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ class Settings < Contrib::Configuration::Settings
option :exception_controller, default: nil
option :middleware, default: true
option :middleware_names, default: false
option :template_base_path, default: 'views/'
option :template_base_path, default: 'views/' do |value|
# Update ActionView template base path too
value.tap { Datadog.configuration[:action_view][:template_base_path] = value }
end

option :tracer, default: Datadog.tracer do |value|
value.tap do
Datadog.configuration[:active_record][:tracer] = value
Datadog.configuration[:active_support][:tracer] = value
Datadog.configuration[:action_view][:tracer] = value
end
end
end
Expand Down
Loading

0 comments on commit 6928012

Please sign in to comment.