newrelic_rpm 3.6.9.171 → 3.7.0.174.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +9 -0
  3. data/lib/new_relic/agent.rb +15 -21
  4. data/lib/new_relic/agent/beacon_configuration.rb +1 -81
  5. data/lib/new_relic/agent/browser_monitoring.rb +82 -49
  6. data/lib/new_relic/agent/configuration/default_source.rb +31 -12
  7. data/lib/new_relic/agent/request_sampler.rb +17 -10
  8. data/lib/new_relic/agent/transaction.rb +10 -17
  9. data/lib/new_relic/coerce.rb +26 -0
  10. data/lib/new_relic/rack/browser_monitoring.rb +43 -39
  11. data/lib/new_relic/version.rb +2 -2
  12. data/lib/tasks/install.rake +1 -0
  13. data/test/agent_helper.rb +2 -2
  14. data/test/environments/rails40/Gemfile +1 -1
  15. data/test/multiverse/lib/multiverse/suite.rb +11 -1
  16. data/test/multiverse/suites/agent_only/rum_instrumentation_test.rb +19 -12
  17. data/test/multiverse/suites/rails/Envfile +1 -5
  18. data/test/multiverse/suites/rails/queue_time_test.rb +3 -2
  19. data/test/multiverse/suites/rails/request_statistics_test.rb +29 -0
  20. data/test/multiverse/suites/sidekiq/Envfile +10 -1
  21. data/test/multiverse/suites/sinatra/Envfile +3 -7
  22. data/test/new_relic/agent/beacon_configuration_test.rb +9 -76
  23. data/test/new_relic/agent/browser_monitoring_test.rb +204 -96
  24. data/test/new_relic/agent/request_sampler_test.rb +41 -1
  25. data/test/new_relic/agent/transaction_test.rb +47 -0
  26. data/test/new_relic/coerce_test.rb +24 -0
  27. data/test/new_relic/rack/browser_monitoring_test.rb +38 -6
  28. data/test/performance/suites/rum_autoinsertion.rb +0 -1
  29. data/test/rum/basic.result.html +2 -2
  30. data/test/rum/comments1.result.html +2 -2
  31. data/test/rum/comments2.result.html +2 -2
  32. data/test/rum/gt_in_quotes1.result.html +2 -2
  33. data/test/rum/gt_in_quotes2.result.html +2 -2
  34. data/test/rum/gt_in_quotes_mismatch.result.html +2 -2
  35. data/test/rum/gt_in_single_quotes1.result.html +2 -2
  36. data/test/rum/gt_in_single_quotes_mismatch.result.html +2 -2
  37. data/test/rum/incomplete_non_meta_tags.result.html +2 -2
  38. data/test/rum/no_header.result.html +2 -2
  39. data/test/rum/no_html_and_no_header.result.html +2 -2
  40. data/test/rum/no_start_header.result.html +2 -2
  41. data/test/rum/script1.result.html +2 -2
  42. data/test/rum/script2.result.html +2 -2
  43. data/test/rum/x_ua_meta_tag.result.html +2 -2
  44. data/test/rum/x_ua_meta_tag_multiline.result.html +2 -2
  45. data/test/rum/x_ua_meta_tag_with_others.result.html +2 -2
  46. data/test/rum/x_ua_meta_tag_with_spaces.result.html +2 -2
  47. metadata +8 -24
  48. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,14 @@
1
1
  # New Relic Ruby Agent Release Notes #
2
2
 
3
+ ## v3.7.0 ##
4
+
5
+ * RUM injection updates
6
+
7
+ The Ruby agent's code for both automatically and manually injecting the Real
8
+ User Monitoring scripts has been updated. This should not require
9
+ application changes, but does result in a different script being injected
10
+ than previous versions of the agent.
11
+
3
12
  ## v3.6.9 ##
4
13
 
5
14
  * Experimental Rubinius 2.x support
@@ -451,29 +451,27 @@ module NewRelic
451
451
  nil # don't return a noticed error datastructure. it can only hurt.
452
452
  end
453
453
 
454
- # Add parameters to the current transaction trace (and traced error if any)
455
- # on the call stack.
454
+ # Add parameters to any Transaction Trace, Error or Analytics Events that
455
+ # are recorded during the current transaction.
456
+ #
457
+ # If configured to allow it, these custom parameters will also be present
458
+ # in the RUM script injected into the page.
456
459
  #
457
460
  # @api public
458
461
  #
459
462
  def add_custom_parameters(params)
460
- Transaction.add_custom_parameters(params)
463
+ if params.is_a? Hash
464
+ Transaction.add_custom_parameters(params)
465
+ else
466
+ ::NewRelic::Agent.logger.warn("Bad argument passed to #add_custom_parameters. Expected Hash but got #{params.class}")
467
+ end
461
468
  end
462
469
 
463
- # Set attributes about the user making this request. These attributes will be automatically
464
- # appended to any Transaction Trace or Error that is collected. These attributes
465
- # will also be collected for RUM requests.
466
- #
467
- # Attributes (hash)
468
- # * <tt>:user</tt> => user name or ID
469
- # * <tt>:account</tt> => account name or ID
470
- # * <tt>:product</tt> => product name or level
471
- #
472
- # @api public
473
- #
474
- def set_user_attributes(attributes)
475
- Transaction.set_user_attributes(attributes)
476
- end
470
+ # @deprecated
471
+ alias add_request_parameters add_custom_parameters
472
+
473
+ # @deprecated
474
+ alias set_user_attributes add_custom_parameters
477
475
 
478
476
  # Set the name of the current running transaction. The agent will
479
477
  # apply a reasonable default based on framework routing, but in
@@ -518,10 +516,6 @@ module NewRelic
518
516
  end
519
517
  end
520
518
 
521
- # The #add_request_parameters method is aliased to #add_custom_parameters
522
- # and is now deprecated.
523
- alias add_request_parameters add_custom_parameters #:nodoc:
524
-
525
519
  # Yield to a block that is run with a database metric name
526
520
  # context. This means the Database instrumentation will use this
527
521
  # for the metric name if it does not otherwise know about a model.
@@ -9,41 +9,10 @@ module NewRelic
9
9
  # need to look it up or reconfigure it every request
10
10
  class BeaconConfiguration
11
11
 
12
- # the statically generated header - generated when the beacon
13
- # configuration is created - does not vary per page
14
- attr_reader :browser_timing_header
15
-
16
- # the static portion of the RUM footer - this part does not vary
17
- # by which request is in progress
18
- attr_reader :browser_timing_static_footer
19
-
20
- # RUM footer command used for 'finish' - based on whether JSONP is
21
- # being used. 'nrfj' for JSONP, otherwise 'nrf2'
22
- attr_reader :finish_command
23
-
24
- # A static javascript header that is identical for every account
25
- # and application
26
- JS_HEADER = "<script type=\"text/javascript\">var NREUMQ=NREUMQ||[];NREUMQ.push([\"mark\",\"firstbyte\",new Date().getTime()]);</script>"
27
-
28
12
  # Creates a new browser configuration data. Argument is a hash
29
13
  # of configuration values from the server
30
14
  def initialize
31
- if Agent.config[:js_errors_beta] && Agent.config[:js_agent_loader]
32
- ::NewRelic::Agent.logger.debug("Beta JS errors functionality enabled")
33
- ::NewRelic::Agent.logger.debug("JS agent loader version: #{Agent.config[:js_agent_loader_version]}")
34
- else
35
- @browser_timing_header = build_browser_timing_header
36
- ::NewRelic::Agent.logger.debug("Browser timing header: #{@browser_timing_header.inspect}")
37
- @browser_timing_static_footer = build_load_file_js
38
- ::NewRelic::Agent.logger.debug("Browser timing static footer: #{@browser_timing_static_footer.inspect}")
39
- end
40
-
41
- if Agent.config[:'rum.jsonp']
42
- ::NewRelic::Agent.logger.debug("Real User Monitoring is using JSONP protocol")
43
- @finish_command = 'nrfj'
44
- else
45
- @finish_command = 'nrf2'
46
- end
15
+ ::NewRelic::Agent.logger.debug("JS agent loader version: #{Agent.config[:'browser_monitoring.loader_version']}")
47
16
 
48
17
  if !Agent.config[:'rum.enabled']
49
18
  ::NewRelic::Agent.logger.debug("Real User Monitoring is disabled for this agent. Edit your configuration to change this.")
@@ -63,55 +32,6 @@ module NewRelic
63
32
  end
64
33
  @license_bytes
65
34
  end
66
-
67
- # returns a snippet of text that does not change
68
- # per-transaction. Is empty when rum is disabled, or we are not
69
- # including the episodes file dynamically (i.e. the user
70
- # includes it themselves)
71
- def build_load_file_js
72
- js = <<-EOS
73
- if (!NREUMQ.f) { NREUMQ.f=function() {
74
- NREUMQ.push(["load",new Date().getTime()]);
75
- EOS
76
-
77
- if Agent.config[:'rum.load_episodes_file'] &&
78
- Agent.config[:'rum.load_episodes_file'] != ''
79
- js << <<-EOS
80
- var e=document.createElement("script");
81
- e.type="text/javascript";
82
- e.src=(("http:"===document.location.protocol)?"http:":"https:") + "//" +
83
- "#{Agent.config[:episodes_file]}";
84
- document.body.appendChild(e);
85
- EOS
86
- end
87
-
88
- js << <<-EOS
89
- if(NREUMQ.a)NREUMQ.a();
90
- };
91
- NREUMQ.a=window.onload;window.onload=NREUMQ.f;
92
- };
93
- EOS
94
- js
95
- end
96
-
97
- # returns a copy of the static javascript header, in case people
98
- # are munging strings somewhere down the line
99
- def javascript_header
100
- JS_HEADER.dup
101
- end
102
-
103
- # Returns the header string, properly html-safed if needed
104
- def build_browser_timing_header
105
- return "" if !enabled?
106
- return "" if Agent.config[:browser_key].nil?
107
-
108
- value = javascript_header
109
- if value.respond_to?(:html_safe)
110
- value.html_safe
111
- else
112
- value
113
- end
114
- end
115
35
  end
116
36
  end
117
37
  end
@@ -13,16 +13,15 @@ module NewRelic
13
13
  #
14
14
  # @api public
15
15
  module BrowserMonitoring
16
+ include NewRelic::Coerce
17
+
16
18
  class DummyTransaction
17
19
 
20
+ attr_reader :custom_parameters
18
21
  attr_accessor :start_time
19
22
 
20
23
  def initialize
21
- @attributes = {}
22
- end
23
-
24
- def user_attributes
25
- @attributes
24
+ @custom_parameters = {}
26
25
  end
27
26
 
28
27
  def queue_time
@@ -142,10 +141,6 @@ module NewRelic
142
141
  end
143
142
  end
144
143
 
145
- def transaction_attribute(key)
146
- current_transaction.user_attributes[key] || ""
147
- end
148
-
149
144
  def tt_guid
150
145
  state = NewRelic::Agent::TransactionState.get
151
146
  return state.request_guid if include_guid?(state)
@@ -161,54 +156,92 @@ module NewRelic
161
156
  return NewRelic::Agent::TransactionState.get.request_token
162
157
  end
163
158
 
164
- def use_beta_js_agent?
165
- return Agent.config[:js_errors_beta] && !Agent.config[:js_agent_loader].to_s.empty?
159
+ def js_agent_loader
160
+ Agent.config[:js_agent_loader].to_s
161
+ end
162
+
163
+ def has_loader?
164
+ !js_agent_loader.empty?
166
165
  end
167
166
 
168
- # NOTE: This method may be overridden for internal prototyping, so should
169
- # remain stable.
167
+ # NOTE: Internal prototyping often overrides this, so leave name stable!
170
168
  def header_js_string
171
- if (use_beta_js_agent?)
172
- html_safe_if_needed("\n<script type=\"text/javascript\">#{Agent.config[:js_agent_loader]}</script>")
173
- else
174
- NewRelic::Agent.instance.beacon_configuration.browser_timing_header
175
- end
169
+ return "" unless has_loader?
170
+ html_safe_if_needed("\n<script type=\"text/javascript\">#{js_agent_loader}</script>")
176
171
  end
177
172
 
178
- # NOTE: This method may be overridden for internal prototyping, so should
179
- # remain stable.
173
+ # NOTE: Internal prototyping often overrides this, so leave name stable!
180
174
  def footer_js_string(config)
181
- if (use_beta_js_agent?)
182
- js_data = {
183
- 'txnParam' => config.finish_command,
184
- 'beacon' => NewRelic::Agent.config[:beacon],
185
- 'errorBeacon' => NewRelic::Agent.config[:error_beacon],
186
- 'licenseKey' => NewRelic::Agent.config[:browser_key],
187
- 'applicationID' => NewRelic::Agent.config[:application_id],
188
- 'transactionName' => obfuscate(config, browser_monitoring_transaction_name),
189
- 'queueTime' => current_timings.queue_time_in_millis,
190
- 'applicationTime' => current_timings.app_time_in_millis,
191
- 'ttGuid' => tt_guid,
192
- 'agentToken' => tt_token,
193
- 'user' => obfuscate(config, transaction_attribute(:user)),
194
- 'account' => obfuscate(config, transaction_attribute(:account)),
195
- 'product' => obfuscate(config, transaction_attribute(:product)),
196
- 'agent' => NewRelic::Agent.config[:js_agent_file]
197
- }
198
-
199
- html_safe_if_needed("\n<script type=\"text/javascript\">window.NREUM||(NREUM={});NREUM.info=#{NewRelic.json_dump(js_data)}</script>")
200
- else
201
- obfuscated_transaction_name = obfuscate(config, browser_monitoring_transaction_name)
175
+ data = js_data(config)
176
+ html_safe_if_needed("\n<script type=\"text/javascript\">window.NREUM||(NREUM={});NREUM.info=#{NewRelic.json_dump(data)}</script>")
177
+ end
178
+
179
+ BEACON_KEY = "beacon".freeze
180
+ ERROR_BEACON_KEY = "errorBeacon".freeze
181
+ LICENSE_KEY_KEY = "licenseKey".freeze
182
+ APPLICATIONID_KEY = "applicationID".freeze
183
+ TRANSACTION_NAME_KEY = "transactionName".freeze
184
+ QUEUE_TIME_KEY = "queueTime".freeze
185
+ APPLICATION_TIME_KEY = "applicationTime".freeze
186
+ TT_GUID_KEY = "ttGuid".freeze
187
+ AGENT_TOKEN_KEY = "agentToken".freeze
188
+ AGENT_KEY = "agent".freeze
189
+ EXTRA_KEY = "extra".freeze
202
190
 
203
- user = obfuscate(config, transaction_attribute(:user))
204
- account = obfuscate(config, transaction_attribute(:account))
205
- product = obfuscate(config, transaction_attribute(:product))
191
+ # NOTE: Internal prototyping may override this, so leave name stable!
192
+ def js_data(config)
193
+ {
194
+ BEACON_KEY => NewRelic::Agent.config[:beacon],
195
+ ERROR_BEACON_KEY => NewRelic::Agent.config[:error_beacon],
196
+ LICENSE_KEY_KEY => NewRelic::Agent.config[:browser_key],
197
+ APPLICATIONID_KEY => NewRelic::Agent.config[:application_id],
198
+ TRANSACTION_NAME_KEY => obfuscate(config, browser_monitoring_transaction_name),
199
+ QUEUE_TIME_KEY => current_timings.queue_time_in_millis,
200
+ APPLICATION_TIME_KEY => current_timings.app_time_in_millis,
201
+ TT_GUID_KEY => tt_guid,
202
+ AGENT_TOKEN_KEY => tt_token,
203
+ AGENT_KEY => NewRelic::Agent.config[:js_agent_file],
204
+ EXTRA_KEY => obfuscate(config, format_extra_data(extra_data))
205
+ }
206
+ end
207
+
208
+ ANALYTICS_ENABLED = :'analytics_events.enabled'
209
+ ANALYTICS_TXN_ENABLED = :'analytics_events.transactions.enabled'
210
+ ANALYTICS_TXN_IN_PAGE = :'capture_attributes.page_view_events'
211
+
212
+ # NOTE: Internal prototyping may override this, so leave name stable!
213
+ def extra_data
214
+ return {} unless include_custom_parameters_in_extra?
215
+ current_transaction.custom_parameters.dup
216
+ end
217
+
218
+ def include_custom_parameters_in_extra?
219
+ NewRelic::Agent.config[ANALYTICS_ENABLED] &&
220
+ NewRelic::Agent.config[ANALYTICS_TXN_ENABLED] &&
221
+ NewRelic::Agent.config[ANALYTICS_TXN_IN_PAGE]
222
+ end
223
+
224
+ # Format the props using semicolon separated pairs separated by '=':
225
+ # product=pro;user=bill@microsoft.com
226
+ def format_extra_data(extra_props)
227
+ extra_props = event_params(extra_props)
228
+ extra_props.map do |k, v|
229
+ key = escape_special_characters(k)
230
+ value = format_value(v)
231
+ "#{key}=#{value}"
232
+ end.join(';')
233
+ end
206
234
 
207
- # This is slightly varied from other agents' RUM footer to ensure that
208
- # NREUMQ is defined. Our experimental header placement has some holes
209
- # where it could end up in a comment and not define NREUMQ as the footer
210
- # assumes. We protect against that here.
211
- html_safe_if_needed(%'<script type="text/javascript">if (typeof NREUMQ !== "undefined") { #{config.browser_timing_static_footer}NREUMQ.push(["#{config.finish_command}","#{Agent.config[:beacon]}","#{Agent.config[:browser_key]}","#{Agent.config[:application_id]}","#{obfuscated_transaction_name}",#{current_timings.queue_time_in_millis},#{current_timings.app_time_in_millis},new Date().getTime(),"#{tt_guid}","#{tt_token}","#{user}","#{account}","#{product}"]);}</script>')
235
+ def escape_special_characters(string)
236
+ string.to_s.tr("\";=", "':-" )
237
+ end
238
+
239
+ def format_value(v)
240
+ # flag numeric values with `#' prefix
241
+ if v.is_a? Numeric
242
+ value = "##{v}"
243
+ else
244
+ value = escape_special_characters(v)
212
245
  end
213
246
  end
214
247
 
@@ -98,6 +98,13 @@ module NewRelic
98
98
  Proc.new { self[:'rum.enabled'] }
99
99
  end
100
100
 
101
+ # This check supports the js_errors_beta key we've asked clients to
102
+ # set. Once JS errors are GA, browser_monitoring.loader can stop
103
+ # being dynamic.
104
+ def self.browser_monitoring_loader
105
+ Proc.new { self[:js_errors_beta] ? "full" : "rum"}
106
+ end
107
+
101
108
  def self.slow_sql_record_sql
102
109
  Proc.new { self[:'transaction_tracer.record_sql'] }
103
110
  end
@@ -640,12 +647,6 @@ module NewRelic
640
647
  :type => Boolean,
641
648
  :description => 'Enable or disable real user monitoring.'
642
649
  },
643
- :'rum.jsonp' => {
644
- :default => true,
645
- :public => false,
646
- :type => Boolean,
647
- :description => 'Enable or disable jsonp as the default means of communicating with the beacon.'
648
- },
649
650
  :'rum.load_episodes_file' => {
650
651
  :default => true,
651
652
  :public => false,
@@ -658,19 +659,25 @@ module NewRelic
658
659
  :type => Boolean,
659
660
  :description => 'Enable or disable automatic insertion of the real user monitoring header and footer into outgoing responses.'
660
661
  },
661
- :'js_agent_loader_version' => {
662
- :default => '',
663
- :public => false,
662
+ :'browser_monitoring.loader' => {
663
+ :default => DefaultSource.browser_monitoring_loader,
664
+ :public => private,
664
665
  :type => String,
665
- :description => 'Version of the JavaScript agent loader retrieved by the collector. This is only informational, setting the value does nothing.'
666
+ :description => 'Type of JavaScript agent loader to use for browser monitoring instrumentation'
667
+ },
668
+ :'browser_monitoring.debug' => {
669
+ :default => false,
670
+ :public => false,
671
+ :type => Boolean,
672
+ :description => 'Enable or disable debugging version of JavaScript agent loader for browser monitoring instrumentation.'
666
673
  },
667
- :'js_agent_loader' => {
674
+ :js_agent_loader => {
668
675
  :default => '',
669
676
  :public => false,
670
677
  :type => String,
671
678
  :description => 'JavaScript agent loader content.'
672
679
  },
673
- :'js_errors_beta' => {
680
+ :js_errors_beta => {
674
681
  :default => false,
675
682
  :public => false,
676
683
  :type => Boolean,
@@ -748,6 +755,18 @@ module NewRelic
748
755
  :type => Fixnum,
749
756
  :description => 'Maximum number of request events recorded by the analytics event sampling in a single harvest.'
750
757
  },
758
+ :'capture_attributes.transaction_events' => {
759
+ :default => true,
760
+ :public => true,
761
+ :type => Boolean,
762
+ :description => 'Include TT custom params in analytics event data.'
763
+ },
764
+ :'capture_attributes.page_view_events' => {
765
+ :default => false,
766
+ :public => false,
767
+ :type => Boolean,
768
+ :description => 'Include TT custom params in real user monitoring script in outgoing responses.'
769
+ },
751
770
  }.freeze
752
771
 
753
772
  end
@@ -13,9 +13,10 @@ class NewRelic::Agent::RequestSampler
13
13
  MonitorMixin
14
14
 
15
15
  # The namespace and keys of config values
16
- MAX_SAMPLES_KEY = :'analytics_events.max_samples_stored'
17
- ENABLED_KEY = :'analytics_events.enabled'
18
- ENABLED_TXN_KEY = :'analytics_events.transactions.enabled'
16
+ MAX_SAMPLES_KEY = :'analytics_events.max_samples_stored'
17
+ ENABLED_KEY = :'analytics_events.enabled'
18
+ ENABLED_TXN_KEY = :'analytics_events.transactions.enabled'
19
+ INCLUDE_CUSTOM_PARAMS_KEY = :'capture_attributes.transaction_events'
19
20
 
20
21
  # The type field of the sample
21
22
  SAMPLE_TYPE = 'Transaction'
@@ -124,13 +125,19 @@ class NewRelic::Agent::RequestSampler
124
125
  def on_transaction_finished(payload)
125
126
  return unless @enabled
126
127
  return unless NewRelic::Agent::Transaction.transaction_type_is_web?(payload[:type])
127
- sample = {
128
- TIMESTAMP_KEY => float(payload[:start_timestamp]),
129
- NAME_KEY => string(payload[:name]),
130
- DURATION_KEY => float(payload[:duration]),
131
- TYPE_KEY => SAMPLE_TYPE
132
- }.merge((payload[:overview_metrics] || {}))
133
-
128
+ # The order in which these are merged is important. We want to ensure that
129
+ # custom parameters can't override required fields (e.g. type)
130
+ sample = {}
131
+ if ::NewRelic::Agent.config[INCLUDE_CUSTOM_PARAMS_KEY]
132
+ sample.merge!(event_params(payload[:custom_params] || {}))
133
+ end
134
+ sample.merge!(payload[:overview_metrics] || {})
135
+ sample.merge!({
136
+ TIMESTAMP_KEY => float(payload[:start_timestamp]),
137
+ NAME_KEY => string(payload[:name]),
138
+ DURATION_KEY => float(payload[:duration]),
139
+ TYPE_KEY => SAMPLE_TYPE,
140
+ })
134
141
  is_full = self.synchronize { @samples.append(sample) }
135
142
  notify_full if is_full && !@notified_full
136
143
  end