newrelic_rpm 8.5.0 → 8.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -3
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +127 -1
  5. data/CONTRIBUTING.md +1 -1
  6. data/LICENSE +0 -6
  7. data/README.md +17 -19
  8. data/Rakefile +26 -21
  9. data/THIRD_PARTY_NOTICES.md +14 -199
  10. data/docker-compose.yml +1 -1
  11. data/lib/new_relic/agent/agent.rb +26 -2
  12. data/lib/new_relic/agent/agent_logger.rb +7 -0
  13. data/lib/new_relic/agent/audit_logger.rb +4 -0
  14. data/lib/new_relic/agent/autostart.rb +13 -10
  15. data/lib/new_relic/agent/configuration/default_source.rb +217 -50
  16. data/lib/new_relic/agent/configuration/environment_source.rb +2 -0
  17. data/lib/new_relic/agent/configuration/event_harvest_config.rb +4 -2
  18. data/lib/new_relic/agent/configuration/server_source.rb +1 -0
  19. data/lib/new_relic/agent/database.rb +5 -5
  20. data/lib/new_relic/agent/datastores/metric_helper.rb +3 -1
  21. data/lib/new_relic/agent/hostname.rb +16 -10
  22. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +23 -20
  23. data/lib/new_relic/agent/instrumentation/active_job.rb +9 -2
  24. data/lib/new_relic/agent/instrumentation/active_merchant.rb +14 -0
  25. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +22 -6
  26. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +16 -7
  27. data/lib/new_relic/agent/instrumentation/active_support_logger/chain.rb +23 -0
  28. data/lib/new_relic/agent/instrumentation/active_support_logger/instrumentation.rb +20 -0
  29. data/lib/new_relic/agent/instrumentation/active_support_logger/prepend.rb +12 -0
  30. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +24 -0
  31. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +10 -0
  32. data/lib/new_relic/agent/instrumentation/authlogic.rb +10 -0
  33. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +9 -4
  34. data/lib/new_relic/agent/instrumentation/curb/chain.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/curb/prepend.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/data_mapper.rb +12 -0
  37. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +19 -0
  38. data/lib/new_relic/agent/instrumentation/logger/instrumentation.rb +18 -18
  39. data/lib/new_relic/agent/instrumentation/logger.rb +4 -3
  40. data/lib/new_relic/agent/instrumentation/rack/helpers.rb +2 -0
  41. data/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb +11 -0
  42. data/lib/new_relic/agent/instrumentation/sidekiq.rb +15 -0
  43. data/lib/new_relic/agent/instrumentation/sinatra.rb +21 -11
  44. data/lib/new_relic/agent/instrumentation/sunspot.rb +10 -0
  45. data/lib/new_relic/agent/instrumentation/thread/chain.rb +24 -0
  46. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +27 -0
  47. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +22 -0
  48. data/lib/new_relic/agent/instrumentation/thread.rb +20 -0
  49. data/lib/new_relic/agent/linking_metadata.rb +45 -0
  50. data/lib/new_relic/agent/local_log_decorator.rb +37 -0
  51. data/lib/new_relic/agent/log_event_aggregator.rb +234 -0
  52. data/lib/new_relic/agent/log_priority.rb +20 -0
  53. data/lib/new_relic/agent/method_tracer.rb +9 -4
  54. data/lib/new_relic/agent/method_tracer_helpers.rb +80 -0
  55. data/lib/new_relic/agent/new_relic_service.rb +27 -23
  56. data/lib/new_relic/agent/pipe_service.rb +5 -2
  57. data/lib/new_relic/agent/samplers/memory_sampler.rb +6 -1
  58. data/lib/new_relic/agent/span_event_primitive.rb +9 -6
  59. data/lib/new_relic/agent/stats.rb +48 -23
  60. data/lib/new_relic/agent/tracer.rb +14 -1
  61. data/lib/new_relic/agent/transaction/abstract_segment.rb +29 -1
  62. data/lib/new_relic/agent/transaction/tracing.rb +8 -3
  63. data/lib/new_relic/agent/transaction.rb +47 -11
  64. data/lib/new_relic/agent/transaction_error_primitive.rb +2 -0
  65. data/lib/new_relic/agent/transaction_metrics.rb +5 -4
  66. data/lib/new_relic/agent/vm/mri_vm.rb +13 -1
  67. data/lib/new_relic/agent.rb +6 -14
  68. data/lib/new_relic/control/instrumentation.rb +31 -0
  69. data/lib/new_relic/dependency_detection.rb +1 -1
  70. data/lib/new_relic/helper.rb +40 -0
  71. data/lib/new_relic/language_support.rb +17 -0
  72. data/lib/new_relic/local_environment.rb +2 -0
  73. data/lib/new_relic/supportability_helper.rb +1 -0
  74. data/lib/new_relic/traced_thread.rb +35 -0
  75. data/lib/new_relic/version.rb +1 -1
  76. data/lib/tasks/config.rake +13 -5
  77. data/newrelic.yml +34 -4
  78. data/newrelic_rpm.gemspec +2 -3
  79. data/test/agent_helper.rb +18 -4
  80. metadata +30 -17
  81. data/ROADMAP.md +0 -24
@@ -99,4 +99,19 @@ DependencyDetection.defer do
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ executes do
104
+ next unless Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new('5.0.0')
105
+ deprecation_msg = 'Instrumentation for Sidekiq versions below 5.0.0 is deprecated.' \
106
+ 'They will stop being monitored in version 9.0.0. ' \
107
+ 'Please upgrade your Sidekiq version to continue receiving full support. '
108
+
109
+ ::NewRelic::Agent.logger.log_once(
110
+ :warn,
111
+ :deprecated_sidekiq_version,
112
+ deprecation_msg
113
+ )
114
+
115
+ ::NewRelic::Agent.record_metric("Supportability/Deprecated/Sidekiq", 1)
116
+ end
102
117
  end
@@ -32,18 +32,28 @@ DependencyDetection.defer do
32
32
  end
33
33
 
34
34
  executes do
35
- if Sinatra::Base.respond_to?(:build)
36
- # These requires are inside an executes block because they require rack, and
37
- # we can't be sure that rack is available when this file is first required.
38
- require 'new_relic/rack/agent_hooks'
39
- require 'new_relic/rack/browser_monitoring'
40
- if use_prepend?
41
- prepend_instrument ::Sinatra::Base.singleton_class, NewRelic::Agent::Instrumentation::Sinatra::Build::Prepend
42
- else
43
- chain_instrument NewRelic::Agent::Instrumentation::Sinatra::Build::Chain
44
- end
35
+ # These requires are inside an executes block because they require rack, and
36
+ # we can't be sure that rack is available when this file is first required.
37
+ require 'new_relic/rack/agent_hooks'
38
+ require 'new_relic/rack/browser_monitoring'
39
+ if use_prepend?
40
+ prepend_instrument ::Sinatra::Base.singleton_class, NewRelic::Agent::Instrumentation::Sinatra::Build::Prepend
45
41
  else
46
- ::NewRelic::Agent.logger.info("Skipping auto-injection of middleware for Sinatra - requires Sinatra 1.2.1+")
42
+ chain_instrument NewRelic::Agent::Instrumentation::Sinatra::Build::Chain
47
43
  end
48
44
  end
45
+
46
+ executes do
47
+ next unless Gem::Version.new(Sinatra::VERSION) < Gem::Version.new('2.0.0')
48
+ deprecation_msg = 'The Ruby Agent is dropping support for Sinatra versions below 2.0.0 ' \
49
+ 'in version 9.0.0. Please upgrade your Sinatra version to continue receiving full compatibility. ' \
50
+
51
+ ::NewRelic::Agent.logger.log_once(
52
+ :warn,
53
+ :deprecated_sinatra_version,
54
+ deprecation_msg
55
+ )
56
+
57
+ ::NewRelic::Agent.record_metric("Supportability/Deprecated/Sinatra", 1)
58
+ end
49
59
  end
@@ -11,6 +11,16 @@ DependencyDetection.defer do
11
11
 
12
12
  executes do
13
13
  ::NewRelic::Agent.logger.info 'Installing Rails Sunspot instrumentation'
14
+ deprecation_msg = 'The instrumentation for Sunspot is deprecated.' \
15
+ ' It will be removed in version 9.0.0.' \
16
+
17
+ ::NewRelic::Agent.logger.log_once(
18
+ :warn,
19
+ :deprecated_sunspot,
20
+ deprecation_msg
21
+ )
22
+
23
+ ::NewRelic::Agent.record_metric("Supportability/Deprecated/Sunspot", 1)
14
24
  end
15
25
 
16
26
  executes do
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ require_relative 'instrumentation'
6
+
7
+ module NewRelic::Agent::Instrumentation
8
+ module MonitoredThread
9
+ module Chain
10
+ def self.instrument!
11
+ ::Thread.class_eval do
12
+ include NewRelic::Agent::Instrumentation::MonitoredThread
13
+
14
+ alias_method :initialize_without_new_relic, :initialize
15
+
16
+ def initialize(*args, &block)
17
+ traced_block = add_thread_tracing(*args, &block)
18
+ initialize_with_newrelic_tracing { initialize_without_new_relic(*args, &traced_block) }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ module Instrumentation
8
+ module MonitoredThread
9
+ attr_reader :nr_parent_thread_id
10
+
11
+ def initialize_with_newrelic_tracing
12
+ @nr_parent_thread_id = ::Thread.current.object_id
13
+ yield
14
+ end
15
+
16
+ def add_thread_tracing(*args, &block)
17
+ return block if skip_tracing?
18
+ NewRelic::Agent::Tracer.thread_block_with_current_transaction(*args, &block)
19
+ end
20
+
21
+ def skip_tracing?
22
+ !NewRelic::Agent.config[:'instrumentation.thread.tracing']
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ require_relative 'instrumentation'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ module Instrumentation
10
+ module MonitoredThread
11
+ module Prepend
12
+ include NewRelic::Agent::Instrumentation::MonitoredThread
13
+
14
+ def initialize(*args, &block)
15
+ traced_block = add_thread_tracing(*args, &block)
16
+ initialize_with_newrelic_tracing { super(*args, &traced_block) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ require_relative 'thread/chain'
6
+ require_relative 'thread/prepend'
7
+
8
+ DependencyDetection.defer do
9
+ named :thread
10
+
11
+ executes do
12
+ ::NewRelic::Agent.logger.info 'Installing Thread Instrumentation'
13
+
14
+ if use_prepend?
15
+ prepend_instrument ::Thread, ::NewRelic::Agent::Instrumentation::MonitoredThread::Prepend
16
+ else
17
+ chain_instrument ::NewRelic::Agent::Instrumentation::MonitoredThread::Chain
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ module NewRelic
7
+ module Agent
8
+ #
9
+ # This module contains helper methods related to gathering linking
10
+ # metadata for use with logs in context.
11
+ module LinkingMetadata
12
+ extend self
13
+
14
+ def append_service_linking_metadata metadata
15
+ raise ArgumentError, "Missing argument `metadata`" if metadata.nil?
16
+
17
+ config = ::NewRelic::Agent.config
18
+
19
+ metadata[ENTITY_NAME_KEY] = config[:app_name][0]
20
+ metadata[ENTITY_TYPE_KEY] = ENTITY_TYPE
21
+ metadata[HOSTNAME_KEY] = Hostname.get
22
+
23
+ if entity_guid = config[:entity_guid]
24
+ metadata[ENTITY_GUID_KEY] = entity_guid
25
+ end
26
+
27
+ metadata
28
+ end
29
+
30
+ def append_trace_linking_metadata metadata
31
+ raise ArgumentError, "Missing argument `metadata`" if metadata.nil?
32
+
33
+ if trace_id = Tracer.current_trace_id
34
+ metadata[TRACE_ID_KEY] = trace_id
35
+ end
36
+
37
+ if span_id = Tracer.current_span_id
38
+ metadata[SPAN_ID_KEY] = span_id
39
+ end
40
+
41
+ metadata
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ module NewRelic
7
+ module Agent
8
+ # This module contains helper methods related to decorating log messages
9
+ module LocalLogDecorator
10
+ extend self
11
+
12
+ def decorate(message)
13
+ return message unless decorating_enabled?
14
+
15
+ metadata = NewRelic::Agent.linking_metadata
16
+ formatted_metadata = " NR-LINKING|#{metadata[ENTITY_GUID_KEY]}|#{metadata[HOSTNAME_KEY]}|" \
17
+ "#{metadata[TRACE_ID_KEY]}|#{metadata[SPAN_ID_KEY]}|" \
18
+ "#{escape_entity_name(metadata[ENTITY_NAME_KEY])}|"
19
+
20
+ message.partition("\n").insert(1, formatted_metadata).join
21
+ end
22
+
23
+ private
24
+
25
+ def decorating_enabled?
26
+ NewRelic::Agent.config[:'application_logging.enabled'] &&
27
+ NewRelic::Agent::Instrumentation::Logger.enabled? &&
28
+ NewRelic::Agent.config[:'application_logging.local_decorating.enabled']
29
+ end
30
+
31
+ def escape_entity_name(entity_name)
32
+ return unless entity_name
33
+ URI::DEFAULT_PARSER.escape(entity_name)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,234 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/event_aggregator'
6
+ require 'new_relic/agent/log_priority'
7
+
8
+ module NewRelic
9
+ module Agent
10
+ class LogEventAggregator < EventAggregator
11
+ # Per-message keys
12
+ LEVEL_KEY = "level".freeze
13
+ MESSAGE_KEY = "message".freeze
14
+ TIMESTAMP_KEY = "timestamp".freeze
15
+ PRIORITY_KEY = "priority".freeze
16
+
17
+ # Metric keys
18
+ LINES = "Logging/lines".freeze
19
+ DROPPED_METRIC = "Logging/Forwarding/Dropped".freeze
20
+ SEEN_METRIC = "Supportability/Logging/Forwarding/Seen".freeze
21
+ SENT_METRIC = "Supportability/Logging/Forwarding/Sent".freeze
22
+ OVERALL_SUPPORTABILITY_FORMAT = "Supportability/Logging/Ruby/Logger/%s".freeze
23
+ METRICS_SUPPORTABILITY_FORMAT = "Supportability/Logging/Metrics/Ruby/%s".freeze
24
+ FORWARDING_SUPPORTABILITY_FORMAT = "Supportability/Logging/Forwarding/Ruby/%s".freeze
25
+ DECORATING_SUPPORTABILITY_FORMAT = "Supportability/Logging/LocalDecorating/Ruby/%s".freeze
26
+ MAX_BYTES = 32768 # 32 * 1024 bytes (32 kibibytes)
27
+
28
+ named :LogEventAggregator
29
+ buffer_class PrioritySampledBuffer
30
+
31
+ capacity_key :'application_logging.forwarding.max_samples_stored'
32
+ enabled_key :'application_logging.enabled'
33
+
34
+ # Config keys
35
+ OVERALL_ENABLED_KEY = :'application_logging.enabled'
36
+ METRICS_ENABLED_KEY = :'application_logging.metrics.enabled'
37
+ FORWARDING_ENABLED_KEY = :'application_logging.forwarding.enabled'
38
+ DECORATING_ENABLED_KEY = :'application_logging.local_decorating.enabled'
39
+
40
+ def initialize(events)
41
+ super(events)
42
+ @counter_lock = Mutex.new
43
+ @seen = 0
44
+ @seen_by_severity = Hash.new(0)
45
+ @high_security = NewRelic::Agent.config[:high_security]
46
+ @instrumentation_logger_enabled = NewRelic::Agent::Instrumentation::Logger.enabled?
47
+ register_for_done_configuring(events)
48
+ end
49
+
50
+ def capacity
51
+ @buffer.capacity
52
+ end
53
+
54
+ def record(formatted_message, severity)
55
+ return unless enabled?
56
+
57
+ severity = "UNKNOWN" if severity.nil? || severity.empty?
58
+
59
+ if NewRelic::Agent.config[METRICS_ENABLED_KEY]
60
+ @counter_lock.synchronize do
61
+ @seen += 1
62
+ @seen_by_severity[severity] += 1
63
+ end
64
+ end
65
+
66
+ return if formatted_message.nil? || formatted_message.empty?
67
+ return unless NewRelic::Agent.config[:'application_logging.forwarding.enabled']
68
+ return if @high_security
69
+
70
+ txn = NewRelic::Agent::Transaction.tl_current
71
+ priority = LogPriority.priority_for(txn)
72
+
73
+ if txn
74
+ return txn.add_log_event(create_event(priority, formatted_message, severity))
75
+ else
76
+ return @lock.synchronize do
77
+ @buffer.append(priority: priority) do
78
+ create_event(priority, formatted_message, severity)
79
+ end
80
+ end
81
+ end
82
+ rescue
83
+ nil
84
+ end
85
+
86
+ def record_batch txn, logs
87
+ # Ensure we have the same shared priority
88
+ priority = LogPriority.priority_for(txn)
89
+ logs.each do |log|
90
+ log.first[PRIORITY_KEY] = priority
91
+ end
92
+
93
+ @lock.synchronize do
94
+ logs.each do |log|
95
+ @buffer.append(event: log)
96
+ end
97
+ end
98
+ end
99
+
100
+ def create_event priority, formatted_message, severity
101
+ formatted_message = truncate_message(formatted_message)
102
+
103
+ event = LinkingMetadata.append_trace_linking_metadata({
104
+ LEVEL_KEY => severity,
105
+ MESSAGE_KEY => formatted_message,
106
+ TIMESTAMP_KEY => Process.clock_gettime(Process::CLOCK_REALTIME) * 1000
107
+ })
108
+
109
+ [
110
+ {
111
+ PrioritySampledBuffer::PRIORITY_KEY => priority
112
+ },
113
+ event
114
+ ]
115
+ end
116
+
117
+ # Because our transmission format (MELT) is different than historical
118
+ # agent payloads, extract the munging here to keep the service focused
119
+ # on the general harvest + transmit instead of the format.
120
+ #
121
+ # Payload shape matches the publicly documented MELT format.
122
+ # https://docs.newrelic.com/docs/logs/log-api/introduction-log-api
123
+ #
124
+ # We have to keep the aggregated payloads in a separate shape, though, to
125
+ # work with the priority sampling buffers
126
+ def self.payload_to_melt_format(data)
127
+ common_attributes = LinkingMetadata.append_service_linking_metadata({})
128
+
129
+ # To save on unnecessary data transmission, trim the entity.type
130
+ # sent by classic logs-in-context
131
+ common_attributes.delete(ENTITY_TYPE_KEY)
132
+
133
+ _, items = data
134
+ payload = [{
135
+ common: {attributes: common_attributes},
136
+ logs: items.map(&:last)
137
+ }]
138
+
139
+ return [payload, items.size]
140
+ end
141
+
142
+ def harvest!
143
+ record_customer_metrics()
144
+ super
145
+ end
146
+
147
+ def reset!
148
+ @counter_lock.synchronize do
149
+ @seen = 0
150
+ @seen_by_severity.clear
151
+ end
152
+ super
153
+ end
154
+
155
+ def enabled?
156
+ @enabled && @instrumentation_logger_enabled
157
+ end
158
+
159
+ private
160
+
161
+ # We record once-per-connect metrics for enabled/disabled state at the
162
+ # point we consider the configuration stable (i.e. once we've gotten SSC)
163
+ def register_for_done_configuring(events)
164
+ events.subscribe(:server_source_configuration_added) do
165
+ @high_security = NewRelic::Agent.config[:high_security]
166
+
167
+ record_configuration_metric(OVERALL_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
168
+ record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
169
+ record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
170
+ record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
171
+ end
172
+ end
173
+
174
+ def record_configuration_metric(format, key)
175
+ state = NewRelic::Agent.config[key]
176
+ label = if !enabled?
177
+ "disabled"
178
+ else
179
+ state ? "enabled" : "disabled"
180
+ end
181
+ NewRelic::Agent.increment_metric(format % label)
182
+ end
183
+
184
+ def after_harvest metadata
185
+ dropped_count = metadata[:seen] - metadata[:captured]
186
+ note_dropped_events(metadata[:seen], dropped_count)
187
+ record_supportability_metrics(metadata[:seen], metadata[:captured], dropped_count)
188
+ end
189
+
190
+ # To avoid paying the cost of metric recording on every line, we hold
191
+ # these until harvest before recording them
192
+ def record_customer_metrics
193
+ return unless enabled?
194
+ return unless NewRelic::Agent.config[:'application_logging.metrics.enabled']
195
+
196
+ @counter_lock.synchronize do
197
+ return unless @seen > 0
198
+
199
+ NewRelic::Agent.increment_metric(LINES, @seen)
200
+ @seen_by_severity.each do |(severity, count)|
201
+ NewRelic::Agent.increment_metric(line_metric_name_by_severity(severity), count)
202
+ end
203
+
204
+ @seen = 0
205
+ @seen_by_severity.clear
206
+ end
207
+ end
208
+
209
+ def line_metric_name_by_severity(severity)
210
+ @line_metrics ||= {}
211
+ @line_metrics[severity] ||= "Logging/lines/#{severity}".freeze
212
+ end
213
+
214
+ def note_dropped_events total_count, dropped_count
215
+ if dropped_count > 0
216
+ NewRelic::Agent.logger.warn("Dropped #{dropped_count} log events out of #{total_count}.")
217
+ end
218
+ end
219
+
220
+ def record_supportability_metrics total_count, captured_count, dropped_count
221
+ return unless total_count > 0
222
+
223
+ NewRelic::Agent.increment_metric(DROPPED_METRIC, dropped_count)
224
+ NewRelic::Agent.increment_metric(SEEN_METRIC, total_count)
225
+ NewRelic::Agent.increment_metric(SENT_METRIC, captured_count)
226
+ end
227
+
228
+ def truncate_message(message)
229
+ return message if message.bytesize <= MAX_BYTES
230
+ message.byteslice(0...MAX_BYTES)
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/event_aggregator'
6
+
7
+ # Stateless calculation of priority for a given log event
8
+ module NewRelic
9
+ module Agent
10
+ module LogPriority
11
+ extend self
12
+
13
+ def priority_for(txn)
14
+ return txn.priority if txn
15
+
16
+ rand.round(NewRelic::PRIORITY_PRECISION)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -100,7 +100,7 @@ module NewRelic
100
100
  module ClassMethods
101
101
  # contains methods refactored out of the #add_method_tracer method
102
102
  module AddMethodTracer
103
- ALLOWED_KEYS = [:metric, :push_scope, :code_header, :code_footer].freeze
103
+ ALLOWED_KEYS = [:metric, :push_scope, :code_header, :code_information, :code_footer].freeze
104
104
 
105
105
  DEFAULT_SETTINGS = {:push_scope => true, :metric => true, :code_header => "", :code_footer => ""}.freeze
106
106
 
@@ -274,7 +274,8 @@ module NewRelic
274
274
 
275
275
  _nr_define_traced_method(method_name, scoped_metric: scoped_metric, unscoped_metrics: unscoped_metrics,
276
276
  code_header: options[:code_header], code_footer: options[:code_footer],
277
- record_metrics: options[:metric], visibility: visibility)
277
+ record_metrics: options[:metric], visibility: visibility,
278
+ code_information: options[:code_information])
278
279
 
279
280
  prepend(_nr_traced_method_module)
280
281
 
@@ -298,7 +299,8 @@ module NewRelic
298
299
 
299
300
  def _nr_define_traced_method(method_name, scoped_metric: nil, unscoped_metrics: [],
300
301
  code_header: nil, code_footer: nil, record_metrics: true,
301
- visibility: :public)
302
+ visibility: :public, code_information: {})
303
+
302
304
  _nr_traced_method_module.module_eval do
303
305
  define_method(method_name) do |*args, &block|
304
306
  return super(*args, &block) unless NewRelic::Agent.tl_is_execution_traced?
@@ -325,7 +327,10 @@ module NewRelic
325
327
  # If tracing multiple metrics on this method, nest one unscoped trace inside the scoped trace.
326
328
  begin
327
329
  if scoped_metric_eval
328
- ::NewRelic::Agent::MethodTracer.trace_execution_scoped(scoped_metric_eval, metric: record_metrics, internal: true) do
330
+ ::NewRelic::Agent::MethodTracer.trace_execution_scoped(scoped_metric_eval,
331
+ metric: record_metrics,
332
+ internal: true,
333
+ code_information: code_information) do
329
334
  if unscoped_metrics_eval.empty?
330
335
  super(*args, &block)
331
336
  else
@@ -5,6 +5,13 @@
5
5
  module NewRelic
6
6
  module Agent
7
7
  module MethodTracerHelpers
8
+ # These are code level metrics (CLM) attributes. For Ruby, they map like so:
9
+ # filepath: full path to an .rb file on disk
10
+ # lineno: the line number a Ruby method is defined on within a given .rb file
11
+ # function: the name of the Ruby method
12
+ # namespace: the Ruby class' namespace as a string, ex: 'MyModule::MyClass'
13
+ SOURCE_CODE_INFORMATION_PARAMETERS = %i[filepath lineno function namespace].freeze
14
+ SOURCE_CODE_INFORMATION_FAILURE_METRIC = "Supportabiltiy/CodeLevelMetrics/Ruby/Failure".freeze
8
15
  MAX_ALLOWED_METRIC_DURATION = 1_000_000_000 # roughly 31 years
9
16
 
10
17
  extend self
@@ -26,12 +33,85 @@ module NewRelic
26
33
  segment.record_metrics = false
27
34
  end
28
35
 
36
+ unless !options.key?(:code_information) || options[:code_information].nil? || options[:code_information].empty?
37
+ segment.code_information = options[:code_information]
38
+ end
39
+
29
40
  begin
30
41
  Tracer.capture_segment_error(segment) { yield }
31
42
  ensure
32
43
  segment.finish if segment
33
44
  end
34
45
  end
46
+
47
+ def code_information(object, method_name)
48
+ unless NewRelic::Agent.config[:'code_level_metrics.enabled'] && object && method_name
49
+ return NewRelic::EMPTY_HASH
50
+ end
51
+
52
+ @code_information ||= {}
53
+ cache_key = "#{object.object_id}#{method_name}"
54
+ return @code_information[cache_key] if @code_information.key?(cache_key)
55
+
56
+ namespace, location, is_class_method = namespace_and_location(object, method_name.to_sym)
57
+
58
+ @code_information[cache_key] = {filepath: location.first,
59
+ lineno: location.last,
60
+ function: "#{'self.' if is_class_method}#{method_name}",
61
+ namespace: namespace}
62
+ rescue => e
63
+ ::NewRelic::Agent.logger.warn("Unable to determine source code info for '#{object}', " \
64
+ "method '#{method_name}' - #{e.class}: #{e.message}")
65
+ ::NewRelic::Agent.increment_metric(SOURCE_CODE_INFORMATION_FAILURE_METRIC, 1)
66
+ ::NewRelic::EMPTY_HASH
67
+ end
68
+
69
+ private
70
+
71
+ # The string representation of a singleton class looks like
72
+ # '#<Class:MyModule::MyClass>'. Return the 'MyModule::MyClass' part of
73
+ # that string
74
+ def klass_name(object)
75
+ name = Regexp.last_match(1) if object.to_s =~ /^#<Class:(.*)>$/
76
+ return name if name
77
+
78
+ raise "Unable to glean a class name from string '#{object}'" unless name
79
+ end
80
+
81
+ # get at the underlying class from the singleton class
82
+ #
83
+ # note: even with the regex hit from klass_name(), `Object.const_get`
84
+ # is more performant than iterating through `ObjectSpace`
85
+ def klassify_singleton(object)
86
+ Object.const_get(klass_name(object))
87
+ end
88
+
89
+ # determine the namespace (class name including all module names in scope)
90
+ # and source code location (file path and line number) for the given
91
+ # object and method name
92
+ #
93
+ # traced class methods:
94
+ # * object is a singleton class, `#<Class::MyClass>`
95
+ # * get at the underlying non-singleton class
96
+ #
97
+ # traced instance methods and Rails controller methods:
98
+ # * object is a class, `MyClass`
99
+ #
100
+ # anonymous class based methods (`c = Class.new { def method; end; }`:
101
+ # * `#name` returns `nil`, so use '(Anonymous)' instead
102
+ #
103
+ def namespace_and_location(object, method_name)
104
+ klass = object.singleton_class? ? klassify_singleton(object) : object
105
+ name = klass.name || '(Anonymous)'
106
+ is_class_method = false
107
+ method = if klass.instance_methods.include?(method_name)
108
+ klass.instance_method(method_name)
109
+ else
110
+ is_class_method = true
111
+ klass.method(method_name)
112
+ end
113
+ [name, method.source_location, is_class_method]
114
+ end
35
115
  end
36
116
  end
37
117
  end