# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
require 'base64'
require 'new_relic/agent/beacon_configuration'
require 'new_relic/agent/transaction_timings'
module NewRelic
module Agent
# This module contains support for Real User Monitoring - the
# javascript generation and configuration
module BrowserMonitoring
class DummyTransaction
attr_accessor :start_time
def initialize
@attributes = {}
end
def user_attributes
@attributes
end
def queue_time
0.0
end
def timings
NewRelic::Agent::TransactionTimings.new(0.0, NewRelic::Agent::TransactionState.get)
end
def name
::NewRelic::Agent::UNKNOWN_METRIC
end
end
@@dummy_txn = DummyTransaction.new
# This method returns a string suitable for inclusion in a page
# - known as 'manual instrumentation' for Real User
# Monitoring. Can return either a script tag with associated
# javascript, or in the case of disabled Real User Monitoring,
# an empty string
#
# This is the header string - it should be placed as high in the
# page as is reasonably possible - that is, before any style or
# javascript inclusions, but after any header-related meta tags
def browser_timing_header
insert_js? ? header_js_string : ""
end
# This method returns a string suitable for inclusion in a page
# - known as 'manual instrumentation' for Real User
# Monitoring. Can return either a script tag with associated
# javascript, or in the case of disabled Real User Monitoring,
# an empty string
#
# This is the footer string - it should be placed as low in the
# page as is reasonably possible.
def browser_timing_footer
if insert_js?
NewRelic::Agent::Transaction.freeze_name
generate_footer_js(NewRelic::Agent.instance.beacon_configuration)
else
""
end
end
module_function
def obfuscate(config, text)
obfuscated = ""
if defined?(::Encoding::ASCII_8BIT)
obfuscated.force_encoding(::Encoding::ASCII_8BIT)
end
key_bytes = config.license_bytes
index = 0
text.each_byte{|byte|
obfuscated.concat((byte ^ key_bytes[index % 13].to_i))
index+=1
}
[obfuscated].pack("m0").gsub("\n", '')
end
def browser_monitoring_transaction_name
current_timings.transaction_name || ::NewRelic::Agent::UNKNOWN_METRIC
end
def current_transaction
NewRelic::Agent::TransactionState.get.transaction || @@dummy_txn
end
def current_timings
NewRelic::Agent::TransactionState.get.timings
end
private
# Check whether RUM header and footer should be generated. Log the
# reason if they shouldn't.
def insert_js?
if NewRelic::Agent.instance.beacon_configuration.nil?
::NewRelic::Agent.logger.debug "Beacon configuration is nil. Skipping browser instrumentation."
false
elsif ! NewRelic::Agent.instance.beacon_configuration.enabled?
::NewRelic::Agent.logger.debug "Beacon configuration is disabled. Skipping browser instrumentation."
::NewRelic::Agent.logger.debug NewRelic::Agent.instance.beacon_configuration.inspect
false
elsif Agent.config[:browser_key].nil? || Agent.config[:browser_key].empty?
::NewRelic::Agent.logger.debug "Browser key is not set. Skipping browser instrumentation."
false
elsif ! NewRelic::Agent.is_transaction_traced?
::NewRelic::Agent.logger.debug "Transaction is not traced. Skipping browser instrumentation."
false
elsif ! NewRelic::Agent.is_execution_traced?
::NewRelic::Agent.logger.debug "Execution is not traced. Skipping browser instrumentation."
false
elsif NewRelic::Agent::TransactionState.get.request_ignore_enduser
::NewRelic::Agent.logger.debug "Ignore end user for this transaction is set. Skipping browser instrumentation."
false
else
true
end
end
def generate_footer_js(config)
if current_transaction.start_time
footer_js_string(config)
else
''
end
end
def transaction_attribute(key)
current_transaction.user_attributes[key] || ""
end
def tt_guid
state = NewRelic::Agent::TransactionState.get
return state.request_guid if include_guid?(state)
""
end
def include_guid?(state)
state.request_token &&
state.timings.app_time_in_seconds > state.transaction.apdex_t
end
def tt_token
return NewRelic::Agent::TransactionState.get.request_token
end
def use_beta_js_agent?
return Agent.config[:js_errors_beta] && Agent.config[:js_agent_loader]
end
# NOTE: This method may be overridden for internal prototyping, so should
# remain stable.
def header_js_string
if (use_beta_js_agent?)
html_safe_if_needed("\n")
else
NewRelic::Agent.instance.beacon_configuration.browser_timing_header
end
end
# NOTE: This method may be overridden for internal prototyping, so should
# remain stable.
def footer_js_string(config)
if (use_beta_js_agent?)
js_data = {
'txnParam' => config.finish_command,
'beacon' => NewRelic::Agent.config[:beacon],
'errorBeacon' => NewRelic::Agent.config[:error_beacon],
'licenseKey' => NewRelic::Agent.config[:browser_key],
'applicationID' => NewRelic::Agent.config[:application_id],
'transactionName' => obfuscate(config, browser_monitoring_transaction_name),
'queueTime' => current_timings.queue_time_in_millis,
'applicationTime' => current_timings.app_time_in_millis,
'ttGuid' => tt_guid,
'agentToken' => tt_token,
'user' => obfuscate(config, transaction_attribute(:user)),
'account' => obfuscate(config, transaction_attribute(:account)),
'product' => obfuscate(config, transaction_attribute(:product)),
'agent' => NewRelic::Agent.config[:js_agent_file]
}
html_safe_if_needed("\n")
else
obfuscated_transaction_name = obfuscate(config, browser_monitoring_transaction_name)
user = obfuscate(config, transaction_attribute(:user))
account = obfuscate(config, transaction_attribute(:account))
product = obfuscate(config, transaction_attribute(:product))
# This is slightly varied from other agents' RUM footer to ensure that
# NREUMQ is defined. Our experimental header placement has some holes
# where it could end up in a comment and not define NREUMQ as the footer
# assumes. We protect against that here.
html_safe_if_needed(%'')
end
end
def html_safe_if_needed(string)
if string.respond_to?(:html_safe)
string.html_safe
else
string
end
end
end
end
end