newrelic_rpm 6.2.0.354 → 6.3.0.355

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +17 -0
  3. data/CHANGELOG.md +35 -6
  4. data/lib/new_relic/agent/agent.rb +38 -130
  5. data/lib/new_relic/agent/configuration/default_source.rb +49 -10
  6. data/lib/new_relic/agent/configuration/event_data.rb +39 -0
  7. data/lib/new_relic/agent/configuration/server_source.rb +11 -1
  8. data/lib/new_relic/agent/connect/request_builder.rb +63 -0
  9. data/lib/new_relic/agent/connect/response_handler.rb +61 -0
  10. data/lib/new_relic/agent/hostname.rb +1 -1
  11. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +3 -7
  12. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +161 -0
  13. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +7 -0
  14. data/lib/new_relic/agent/instrumentation/evented_subscriber.rb +34 -3
  15. data/lib/new_relic/agent/instrumentation/{rails5 → rails_notifications}/action_cable.rb +3 -3
  16. data/lib/new_relic/agent/instrumentation/{rails5 → rails_notifications}/action_controller.rb +3 -3
  17. data/lib/new_relic/agent/instrumentation/{rails5 → rails_notifications}/action_view.rb +3 -3
  18. data/lib/new_relic/agent/parameter_filtering.rb +18 -5
  19. data/lib/new_relic/control/class_methods.rb +7 -1
  20. data/lib/new_relic/control/frameworks/{rails5.rb → rails_notifications.rb} +1 -1
  21. data/lib/new_relic/version.rb +1 -1
  22. data/test/agent_helper.rb +11 -5
  23. metadata +10 -11
  24. data/lib/new_relic/agent/instrumentation/active_record_4.rb +0 -42
  25. data/lib/new_relic/agent/instrumentation/active_record_5.rb +0 -41
  26. data/lib/new_relic/agent/instrumentation/rails4/action_controller.rb +0 -32
  27. data/lib/new_relic/agent/instrumentation/rails4/action_view.rb +0 -27
  28. data/lib/new_relic/control/frameworks/rails6.rb +0 -14
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ module Configuration
8
+ module EventData
9
+
10
+ extend self
11
+
12
+ EVENT_DATA_CONFIG_KEY_MAPPING = {
13
+ :analytic_event_data => :'analytics_events.max_samples_stored',
14
+ :custom_event_data => :'custom_insights_events.max_samples_stored',
15
+ :error_event_data => :'error_collector.max_event_samples_stored'
16
+ }
17
+
18
+ def from_config(config)
19
+ {:harvest_limits => EVENT_DATA_CONFIG_KEY_MAPPING.inject({}) do
20
+ |event_data, (event_data_key, config_key)|
21
+ event_data[event_data_key] = config[config_key]
22
+ event_data
23
+ end
24
+ }
25
+ end
26
+
27
+ def to_config_hash(connect_reply)
28
+ config_hash = EVENT_DATA_CONFIG_KEY_MAPPING.inject({}) do
29
+ |event_data, (event_data_key, config_key)|
30
+ event_data[config_key] = connect_reply['event_data']['harvest_limits'][event_data_key.to_s]
31
+ event_data
32
+ end
33
+ config_hash[:event_report_period] = connect_reply['event_data']['report_period_ms'] / 1000
34
+ config_hash
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -20,7 +20,6 @@ module NewRelic
20
20
  "browser_monitoring.loader_version",
21
21
  "cross_process_id",
22
22
  "data_report_period",
23
- "data_report_periods.analytic_event_data",
24
23
  "encoding_key",
25
24
  "error_beacon",
26
25
  "js_agent_file",
@@ -39,6 +38,7 @@ module NewRelic
39
38
  merge_top_level_keys(merged_settings, connect_reply)
40
39
  merge_agent_config_hash(merged_settings, connect_reply)
41
40
  fix_transaction_threshold(merged_settings)
41
+ add_event_data(merged_settings, connect_reply)
42
42
  filter_keys(merged_settings)
43
43
 
44
44
  apply_feature_gates(merged_settings, connect_reply, existing_config)
@@ -74,6 +74,16 @@ module NewRelic
74
74
  end
75
75
  end
76
76
 
77
+ def add_event_data(merged_settings, connect_reply)
78
+ if connect_reply['event_data']
79
+ merged_settings.merge! EventData.to_config_hash(connect_reply)
80
+ else
81
+ NewRelic::Agent.logger.warn "No event data configuration found " \
82
+ "in connect response; using default event report period."
83
+ NewRelic::Agent.record_metric('Supportability/Agent/Collector/MissingEventData', 1)
84
+ end
85
+ end
86
+
77
87
  def filter_keys(merged_settings)
78
88
  merged_settings.delete_if do |key, _|
79
89
  setting_spec = DEFAULTS[key.to_sym]
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'new_relic/environment_report'
6
+ require 'new_relic/agent/configuration/event_data'
7
+
8
+ module NewRelic
9
+ module Agent
10
+ module Connect
11
+
12
+ class RequestBuilder
13
+
14
+ def initialize(new_relic_service, config)
15
+ @service = new_relic_service
16
+ @config = config
17
+ end
18
+
19
+
20
+ # Initializes the hash of settings that we send to the
21
+ # server. Returns a literal hash containing the options
22
+ def connect_payload
23
+ {
24
+ :pid => $$,
25
+ :host => local_host,
26
+ :display_host => Agent.config[:'process_host.display_name'],
27
+ :app_name => Agent.config.app_names,
28
+ :language => 'ruby',
29
+ :labels => Agent.config.parsed_labels,
30
+ :agent_version => NewRelic::VERSION::STRING,
31
+ :environment => sanitize_environment_report(environment_report),
32
+ :settings => Agent.config.to_collector_hash,
33
+ :high_security => Agent.config[:high_security],
34
+ :utilization => UtilizationData.new.to_collector_hash,
35
+ :identifier => "ruby:#{local_host}:#{Agent.config.app_names.sort.join(',')}",
36
+ :event_data => Configuration::EventData.from_config(Agent.config)
37
+ }
38
+ end
39
+
40
+ # We've seen objects in the environment report (Rails.env in
41
+ # particular) that can't seralize to JSON. Cope with that here and
42
+ # clear out so downstream code doesn't have to check again.
43
+ def sanitize_environment_report(environment_report)
44
+ return [] unless @service.valid_to_marshal?(environment_report)
45
+ environment_report
46
+ end
47
+
48
+ # Checks whether we should send environment info, and if so,
49
+ # returns the snapshot from the local environment.
50
+ # Generating the EnvironmentReport has the potential to trigger
51
+ # require calls in Rails environments, so this method should only
52
+ # be called synchronously from on the main thread.
53
+ def environment_report
54
+ Agent.config[:send_environment_info] ? Array(EnvironmentReport.new) : []
55
+ end
56
+
57
+ def local_host
58
+ NewRelic::Agent::Hostname.get
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+
6
+ module NewRelic
7
+ module Agent
8
+ module Connect
9
+
10
+ class ResponseHandler
11
+
12
+ def initialize(agent, config)
13
+ @agent = agent
14
+ @config = config
15
+ end
16
+
17
+ # Takes a hash of configuration data returned from the
18
+ # server and uses it to set local variables and to
19
+ # initialize various parts of the agent that are configured
20
+ # separately.
21
+ #
22
+ # Can accommodate most arbitrary data - anything extra is
23
+ # ignored unless we say to do something with it here.
24
+ def configure_agent(config_data)
25
+ return if config_data == nil
26
+
27
+ @agent.agent_id = config_data['agent_run_id']
28
+
29
+ security_policies = config_data.delete('security_policies')
30
+
31
+ add_server_side_config(config_data)
32
+ add_security_policy_config(security_policies) if security_policies
33
+
34
+ @agent.transaction_rules = RulesEngine.create_transaction_rules(config_data)
35
+ @agent.stats_engine.metric_rules = RulesEngine.create_metric_rules(config_data)
36
+
37
+ # If you're adding something else here to respond to the server-side config,
38
+ # use Agent.instance.events.subscribe(:finished_configuring) callback instead!
39
+ end
40
+
41
+ def add_server_side_config(config_data)
42
+ if config_data['agent_config']
43
+ ::NewRelic::Agent.logger.debug "Using config from server"
44
+ end
45
+
46
+ ::NewRelic::Agent.logger.debug "Server provided config: #{config_data.inspect}"
47
+ server_config = NewRelic::Agent::Configuration::ServerSource.new(config_data, @config)
48
+ @config.replace_or_add_config(server_config)
49
+ end
50
+
51
+ def add_security_policy_config(security_policies)
52
+ ::NewRelic::Agent.logger.info 'Installing security policies'
53
+ security_policy_source = NewRelic::Agent::Configuration::SecurityPolicySource.new(security_policies)
54
+ @config.replace_or_add_config(security_policy_source)
55
+ # drop data collected before applying security policies
56
+ @agent.drop_buffered_data
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -13,7 +13,7 @@ module NewRelic
13
13
  dyno_name = "#{matching_prefix}.*" if matching_prefix
14
14
  dyno_name
15
15
  else
16
- Socket.gethostname
16
+ Socket.gethostname.force_encoding(Encoding::UTF_8)
17
17
  end
18
18
  end
19
19
 
@@ -48,7 +48,9 @@ module NewRelic
48
48
  category: :controller,
49
49
  options: {
50
50
  request: event.request,
51
- filtered_params: filter(event.payload[:params]),
51
+ filtered_params: NewRelic::Agent::ParameterFiltering.filter_using_rails(
52
+ event.payload[:params],
53
+ Rails.application.config.filter_parameters),
52
54
  apdex_start_time: event.queue_start,
53
55
  ignore_apdex: event.apdex_ignored?,
54
56
  ignore_enduser: event.enduser_ignored?
@@ -59,12 +61,6 @@ module NewRelic
59
61
  def stop_transaction(event)
60
62
  (finishable = event.payload[:finishable]) && finishable.finish
61
63
  end
62
-
63
- def filter(params)
64
- munged_params = NewRelic::Agent::ParameterFiltering.filter_rails_request_parameters(params)
65
- filters = Rails.application.config.filter_parameters
66
- ActionDispatch::Http::ParameterFilter.new(filters).filter(munged_params)
67
- end
68
64
  end
69
65
 
70
66
  class ControllerEvent < Event
@@ -0,0 +1,161 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/instrumentation/active_record_subscriber'
6
+ require 'new_relic/agent/instrumentation/active_record_prepend'
7
+
8
+
9
+ # Provides a way to send :connection through ActiveSupport notifications to avoid
10
+ # looping through connection handlers to locate a connection by connection_id
11
+ # This is not needed in Rails 6+: https://github.com/rails/rails/pull/34602
12
+ module NewRelic
13
+ module Agent
14
+ module Instrumentation
15
+ module ActiveRecordNotifications
16
+ module BaseExtensions41
17
+ # https://github.com/rails/rails/blob/4-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L371
18
+ def log(sql, name = "SQL", binds = [], statement_name = nil)
19
+ @instrumenter.instrument(
20
+ "sql.active_record",
21
+ :sql => sql,
22
+ :name => name,
23
+ :connection_id => object_id,
24
+ :connection => self,
25
+ :statement_name => statement_name,
26
+ :binds => binds) { yield }
27
+ rescue => e
28
+ raise translate_exception(e, sql)
29
+ end
30
+ end
31
+
32
+ module BaseExtensions50
33
+ # https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L582
34
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil)
35
+ @instrumenter.instrument(
36
+ "sql.active_record",
37
+ sql: sql,
38
+ name: name,
39
+ binds: binds,
40
+ type_casted_binds: type_casted_binds,
41
+ statement_name: statement_name,
42
+ connection_id: object_id,
43
+ connection: self) { yield }
44
+ rescue => e
45
+ raise translate_exception_class(e, sql)
46
+ end
47
+ end
48
+
49
+ module BaseExtensions51
50
+ # https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L603
51
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
52
+ @instrumenter.instrument(
53
+ "sql.active_record",
54
+ sql: sql,
55
+ name: name,
56
+ binds: binds,
57
+ type_casted_binds: type_casted_binds,
58
+ statement_name: statement_name,
59
+ connection_id: object_id,
60
+ connection: self) do
61
+ @lock.synchronize do
62
+ yield
63
+ end
64
+ end
65
+ rescue => e
66
+ raise translate_exception_class(e, sql)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ DependencyDetection.defer do
75
+ named :active_record_notifications
76
+
77
+ depends_on do
78
+ defined?(::ActiveRecord) && defined?(::ActiveRecord::Base) &&
79
+ defined?(::ActiveRecord::VERSION) &&
80
+ ::ActiveRecord::VERSION::MAJOR.to_i >= 4
81
+ end
82
+
83
+ depends_on do
84
+ !NewRelic::Agent.config[:disable_activerecord_instrumentation] &&
85
+ !NewRelic::Agent::Instrumentation::ActiveRecordSubscriber.subscribed?
86
+ end
87
+
88
+
89
+ depends_on do
90
+ # If the deprecated :disable_active_record_4 setting is true, and
91
+ # the active record version is four, disable!
92
+ NewRelic::Agent.config[:disable_active_record_4] == false \
93
+ || ::ActiveRecord::VERSION::MAJOR.to_i != 4
94
+ end
95
+
96
+ depends_on do
97
+ # If the deprecated :disable_active_record_5 setting is true, and
98
+ # the active record version is five, disable!
99
+ NewRelic::Agent.config[:disable_active_record_5] == false \
100
+ || ::ActiveRecord::VERSION::MAJOR.to_i != 5
101
+ end
102
+
103
+
104
+ executes do
105
+ ::NewRelic::Agent.logger.info 'Installing notifications based Active Record instrumentation'
106
+ end
107
+
108
+ executes do
109
+ ActiveSupport::Notifications.subscribe('sql.active_record',
110
+ NewRelic::Agent::Instrumentation::ActiveRecordSubscriber.new)
111
+ end
112
+
113
+ executes do
114
+ ActiveSupport.on_load(:active_record) do
115
+ ::NewRelic::Agent::PrependSupportability.record_metrics_for(
116
+ ::ActiveRecord::Base,
117
+ ::ActiveRecord::Relation)
118
+
119
+ # Default to .prepending, unless the ActiveRecord version is <=4
120
+ # **AND** the :prepend_active_record_instrumentation config is false
121
+ if ::ActiveRecord::VERSION::MAJOR > 4 \
122
+ || ::NewRelic::Agent.config[:prepend_active_record_instrumentation]
123
+
124
+ ::ActiveRecord::Base.send(:prepend,
125
+ ::NewRelic::Agent::Instrumentation::ActiveRecordPrepend::BaseExtensions)
126
+ ::ActiveRecord::Relation.send(:prepend,
127
+ ::NewRelic::Agent::Instrumentation::ActiveRecordPrepend::RelationExtensions)
128
+ else
129
+ ::NewRelic::Agent::Instrumentation::ActiveRecordHelper.instrument_additional_methods
130
+ end
131
+ end
132
+ end
133
+
134
+ executes do
135
+ if NewRelic::Agent.config[:backport_fast_active_record_connection_lookup]
136
+
137
+ activerecord_extension = if ::ActiveRecord::VERSION::MAJOR.to_i == 4
138
+ ::NewRelic::Agent::Instrumentation::ActiveRecordNotifications::BaseExtensions41
139
+ elsif ::ActiveRecord::VERSION::MAJOR.to_i == 5
140
+ if ::ActiveRecord::VERSION::MINOR.to_i == 0
141
+ ::NewRelic::Agent::Instrumentation::ActiveRecordNotifications::BaseExtensions50
142
+ elsif ::ActiveRecord::VERSION::MINOR.to_i >= 1
143
+ ::NewRelic::Agent::Instrumentation::ActiveRecordNotifications::BaseExtensions51
144
+ end
145
+ end
146
+
147
+ unless activerecord_extension.nil?
148
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:prepend, activerecord_extension)
149
+ end
150
+ end
151
+ end
152
+
153
+ executes do
154
+ if ::ActiveRecord::VERSION::MAJOR == 5 \
155
+ && ::ActiveRecord::VERSION::MINOR.to_i == 1 \
156
+ && ::ActiveRecord::VERSION::TINY.to_i >= 6
157
+
158
+ ::ActiveRecord::Base.prepend ::NewRelic::Agent::Instrumentation::ActiveRecordPrepend::BaseExtensions516
159
+ end
160
+ end
161
+ end
@@ -78,6 +78,13 @@ module NewRelic
78
78
  def active_record_config(payload)
79
79
  return unless payload[:connection_id]
80
80
 
81
+ # handle if the notification payload provides the AR connection
82
+ # available in Rails 6+ & our ActiveRecordNotifications#log extension
83
+ if payload[:connection]
84
+ connection_config = payload[:connection].instance_variable_get(:@config)
85
+ return connection_config if connection_config
86
+ end
87
+
81
88
  connection = nil
82
89
  connection_id = payload[:connection_id]
83
90
 
@@ -11,10 +11,33 @@ module NewRelic
11
11
  end
12
12
 
13
13
  def self.subscribed?
14
+ find_all_subscribers.find{|s| s.instance_variable_get(:@delegate).class == self }
15
+ end
16
+
17
+ def self.find_all_subscribers
14
18
  # TODO: need to talk to Rails core about an API for this,
15
19
  # rather than digging through Listener ivars
16
- ActiveSupport::Notifications.notifier.instance_variable_get(:@subscribers) \
17
- .find{|s| s.instance_variable_get(:@delegate).class == self }
20
+ instance_variable_names = [:@subscribers, :@string_subscribers, :@other_subscribers]
21
+ all_subscribers = []
22
+
23
+ notifier = ActiveSupport::Notifications.notifier
24
+
25
+ instance_variable_names.each do |name|
26
+ if notifier.instance_variable_defined?(name)
27
+ subscribers = notifier.instance_variable_get(name)
28
+ if subscribers.is_a? Array
29
+ # Rails 5 @subscribers, and Rails 6 @other_subscribers is a
30
+ # plain array of subscriber objects
31
+ all_subscribers += subscribers
32
+ elsif subscribers.is_a? Hash
33
+ # Rails 6 @string_subscribers is a Hash mapping the pattern
34
+ # string of a subscriber to an array of subscriber objects
35
+ subscribers.values.each { |array| all_subscribers += array }
36
+ end
37
+ end
38
+ end
39
+
40
+ all_subscribers
18
41
  end
19
42
 
20
43
  def self.subscribe(pattern)
@@ -51,7 +74,15 @@ module NewRelic
51
74
 
52
75
  def pop_event(transaction_id)
53
76
  event = event_stack[transaction_id].pop
54
- event.end = Time.now
77
+
78
+ if event.respond_to?(:finish!)
79
+ # ActiveSupport version 6 and greater use a finish! method rather
80
+ # that allowing us to set the end directly
81
+ event.finish!
82
+ else
83
+ event.end = Time.now
84
+ end
85
+
55
86
  return event
56
87
  end
57
88