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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -3
- data/.yardopts +1 -0
- data/CHANGELOG.md +127 -1
- data/CONTRIBUTING.md +1 -1
- data/LICENSE +0 -6
- data/README.md +17 -19
- data/Rakefile +26 -21
- data/THIRD_PARTY_NOTICES.md +14 -199
- data/docker-compose.yml +1 -1
- data/lib/new_relic/agent/agent.rb +26 -2
- data/lib/new_relic/agent/agent_logger.rb +7 -0
- data/lib/new_relic/agent/audit_logger.rb +4 -0
- data/lib/new_relic/agent/autostart.rb +13 -10
- data/lib/new_relic/agent/configuration/default_source.rb +217 -50
- data/lib/new_relic/agent/configuration/environment_source.rb +2 -0
- data/lib/new_relic/agent/configuration/event_harvest_config.rb +4 -2
- data/lib/new_relic/agent/configuration/server_source.rb +1 -0
- data/lib/new_relic/agent/database.rb +5 -5
- data/lib/new_relic/agent/datastores/metric_helper.rb +3 -1
- data/lib/new_relic/agent/hostname.rb +16 -10
- data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +23 -20
- data/lib/new_relic/agent/instrumentation/active_job.rb +9 -2
- data/lib/new_relic/agent/instrumentation/active_merchant.rb +14 -0
- data/lib/new_relic/agent/instrumentation/active_record_helper.rb +22 -6
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +16 -7
- data/lib/new_relic/agent/instrumentation/active_support_logger/chain.rb +23 -0
- data/lib/new_relic/agent/instrumentation/active_support_logger/instrumentation.rb +20 -0
- data/lib/new_relic/agent/instrumentation/active_support_logger/prepend.rb +12 -0
- data/lib/new_relic/agent/instrumentation/active_support_logger.rb +24 -0
- data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +10 -0
- data/lib/new_relic/agent/instrumentation/authlogic.rb +10 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +9 -4
- data/lib/new_relic/agent/instrumentation/curb/chain.rb +1 -1
- data/lib/new_relic/agent/instrumentation/curb/prepend.rb +1 -1
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +12 -0
- data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +19 -0
- data/lib/new_relic/agent/instrumentation/logger/instrumentation.rb +18 -18
- data/lib/new_relic/agent/instrumentation/logger.rb +4 -3
- data/lib/new_relic/agent/instrumentation/rack/helpers.rb +2 -0
- data/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb +11 -0
- data/lib/new_relic/agent/instrumentation/sidekiq.rb +15 -0
- data/lib/new_relic/agent/instrumentation/sinatra.rb +21 -11
- data/lib/new_relic/agent/instrumentation/sunspot.rb +10 -0
- data/lib/new_relic/agent/instrumentation/thread/chain.rb +24 -0
- data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +27 -0
- data/lib/new_relic/agent/instrumentation/thread/prepend.rb +22 -0
- data/lib/new_relic/agent/instrumentation/thread.rb +20 -0
- data/lib/new_relic/agent/linking_metadata.rb +45 -0
- data/lib/new_relic/agent/local_log_decorator.rb +37 -0
- data/lib/new_relic/agent/log_event_aggregator.rb +234 -0
- data/lib/new_relic/agent/log_priority.rb +20 -0
- data/lib/new_relic/agent/method_tracer.rb +9 -4
- data/lib/new_relic/agent/method_tracer_helpers.rb +80 -0
- data/lib/new_relic/agent/new_relic_service.rb +27 -23
- data/lib/new_relic/agent/pipe_service.rb +5 -2
- data/lib/new_relic/agent/samplers/memory_sampler.rb +6 -1
- data/lib/new_relic/agent/span_event_primitive.rb +9 -6
- data/lib/new_relic/agent/stats.rb +48 -23
- data/lib/new_relic/agent/tracer.rb +14 -1
- data/lib/new_relic/agent/transaction/abstract_segment.rb +29 -1
- data/lib/new_relic/agent/transaction/tracing.rb +8 -3
- data/lib/new_relic/agent/transaction.rb +47 -11
- data/lib/new_relic/agent/transaction_error_primitive.rb +2 -0
- data/lib/new_relic/agent/transaction_metrics.rb +5 -4
- data/lib/new_relic/agent/vm/mri_vm.rb +13 -1
- data/lib/new_relic/agent.rb +6 -14
- data/lib/new_relic/control/instrumentation.rb +31 -0
- data/lib/new_relic/dependency_detection.rb +1 -1
- data/lib/new_relic/helper.rb +40 -0
- data/lib/new_relic/language_support.rb +17 -0
- data/lib/new_relic/local_environment.rb +2 -0
- data/lib/new_relic/supportability_helper.rb +1 -0
- data/lib/new_relic/traced_thread.rb +35 -0
- data/lib/new_relic/version.rb +1 -1
- data/lib/tasks/config.rake +13 -5
- data/newrelic.yml +34 -4
- data/newrelic_rpm.gemspec +2 -3
- data/test/agent_helper.rb +18 -4
- metadata +30 -17
- 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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
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,
|
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
|