class Mmtrix::Rack::BrowserMonitoring

This middleware is used by the agent for the Real user monitoring (RUM) feature, and will usually be automatically injected in the middleware chain. If automatic injection is not working, you may manually use it in your middleware chain instead.

@api public

Constants

ALREADY_INSTRUMENTED_KEY
ATTACHMENT
BODY_START
CHARSET_RE
CONTENT_DISPOSITION
CONTENT_TYPE
GT
HEAD_START
SCAN_LIMIT

The maximum number of bytes of the response body that we will examine in order to look for a RUM insertion point.

TEXT_HTML
X_UA_COMPATIBLE_RE

Public Instance Methods

autoinstrument_source(response, headers, js_to_inject) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 78
def autoinstrument_source(response, headers, js_to_inject)
  source = gather_source(response)
  close_old_response(response)
  return nil unless source

  # Only scan the first 50k (roughly) then give up.
  beginning_of_source = source[0..SCAN_LIMIT]

  if body_start = find_body_start(beginning_of_source)
    meta_tag_positions = [
      find_x_ua_compatible_position(beginning_of_source),
      find_charset_position(beginning_of_source)
    ].compact

    if !meta_tag_positions.empty?
      insertion_index = meta_tag_positions.max
    else
      insertion_index = find_end_of_head_open(beginning_of_source) || body_start
    end

    if insertion_index
      source = source[0...insertion_index] <<
        js_to_inject <<
        source[insertion_index..-1]
    else
      Mmtrix::Agent.logger.debug "Skipping RUM instrumentation. Could not properly determine location to inject script."
    end
  else
    msg = "Skipping RUM instrumentation. Unable to find <body> tag in first #{SCAN_LIMIT} bytes of document."
    Mmtrix::Agent.logger.log_once(:warn, :rum_insertion_failure, msg)
    Mmtrix::Agent.logger.debug(msg)
  end

  source
rescue => e
  Mmtrix::Agent.logger.debug "Skipping RUM instrumentation on exception.", e
  nil
end
close_old_response(response) click to toggle source

Per “The Response > The Body” section of Rack spec, we should close if our response is able. rack.rubyforge.org/doc/SPEC.html

# File lib/mmtrix/rack/browser_monitoring.rb, line 125
def close_old_response(response)
  if response.respond_to?(:close)
    response.close
  end
end
find_body_start(beginning_of_source) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 131
def find_body_start(beginning_of_source)
  beginning_of_source.index(BODY_START)
end
find_charset_position(beginning_of_source) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 140
def find_charset_position(beginning_of_source)
  match = CHARSET_RE.match(beginning_of_source)
  match.end(0) if match
end
find_end_of_head_open(beginning_of_source) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 145
def find_end_of_head_open(beginning_of_source)
  head_open = beginning_of_source.index(HEAD_START)
  beginning_of_source.index(GT, head_open) + 1 if head_open
end
find_x_ua_compatible_position(beginning_of_source) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 135
def find_x_ua_compatible_position(beginning_of_source)
  match = X_UA_COMPATIBLE_RE.match(beginning_of_source)
  match.end(0) if match
end
gather_source(response) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 117
def gather_source(response)
  source = nil
  response.each {|fragment| source ? (source << fragment.to_s) : (source = fragment.to_s)}
  source
end
is_attachment?(headers) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 65
def is_attachment?(headers)
  headers[CONTENT_DISPOSITION] && headers[CONTENT_DISPOSITION].include?(ATTACHMENT)
end
is_html?(headers) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 61
def is_html?(headers)
  headers[CONTENT_TYPE] && headers[CONTENT_TYPE].include?(TEXT_HTML)
end
is_streaming?(env) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 69
def is_streaming?(env)
  return false unless defined?(ActionController::Live)

  env['action_controller.instance'].class.included_modules.include?(ActionController::Live)
end
should_instrument?(env, status, headers) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 52
def should_instrument?(env, status, headers)
  Mmtrix::Agent.config[:'browser_monitoring.auto_instrument'] &&
    status == 200 &&
    !env[ALREADY_INSTRUMENTED_KEY] &&
    is_html?(headers) &&
    !is_attachment?(headers) &&
    !is_streaming?(env)
end
traced_call(env) click to toggle source
# File lib/mmtrix/rack/browser_monitoring.rb, line 31
def traced_call(env)
  result = @app.call(env)   # [status, headers, response]

  js_to_inject = Mmtrix::Agent.browser_timing_header
  if (js_to_inject != "") && should_instrument?(env, result[0], result[1])
    response_string = autoinstrument_source(result[2], result[1], js_to_inject)

    env[ALREADY_INSTRUMENTED_KEY] = true
    if response_string
      response = Rack::Response.new(response_string, result[0], result[1])
      response.finish
    else
      result
    end
  else
    result
  end
end