newrelic_rpm 9.8.0 → 9.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -2
- data/README.md +3 -0
- data/Rakefile +1 -1
- data/lib/bootstrap.rb +105 -0
- data/lib/new_relic/agent/agent.rb +4 -1
- data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
- data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
- data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
- data/lib/new_relic/agent/agent_logger.rb +2 -1
- data/lib/new_relic/agent/aws.rb +56 -0
- data/lib/new_relic/agent/configuration/default_source.rb +65 -15
- data/lib/new_relic/agent/configuration/environment_source.rb +9 -1
- data/lib/new_relic/agent/configuration/manager.rb +22 -5
- data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
- data/lib/new_relic/agent/connect/request_builder.rb +1 -1
- data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
- data/lib/new_relic/agent/error_collector.rb +23 -0
- data/lib/new_relic/agent/harvester.rb +1 -1
- data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
- data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +58 -0
- data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
- data/lib/new_relic/agent/instrumentation/dynamodb.rb +25 -0
- data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +6 -1
- data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
- data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +1 -2
- data/lib/new_relic/agent/log_event_aggregator.rb +1 -16
- data/lib/new_relic/agent/new_relic_service.rb +12 -2
- data/lib/new_relic/agent/serverless_handler.rb +171 -0
- data/lib/new_relic/agent/span_event_primitive.rb +4 -8
- data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
- data/lib/new_relic/agent/transaction.rb +2 -6
- data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
- data/lib/new_relic/agent.rb +12 -8
- data/lib/new_relic/constants.rb +2 -0
- data/lib/new_relic/control/instance_methods.rb +7 -0
- data/lib/new_relic/local_environment.rb +13 -6
- data/lib/new_relic/rack/browser_monitoring.rb +9 -1
- data/lib/new_relic/version.rb +1 -1
- data/lib/tasks/config.rake +5 -3
- data/lib/tasks/helpers/config.html.erb +3 -2
- data/lib/tasks/helpers/format.rb +1 -1
- data/newrelic.yml +15 -1
- data/test/agent_helper.rb +3 -1
- metadata +10 -3
@@ -35,7 +35,7 @@ module NewRelic
|
|
35
35
|
|
36
36
|
class << self
|
37
37
|
def for_transaction(transaction)
|
38
|
-
return nil unless connected?
|
38
|
+
return nil unless Agent.instance.connected?
|
39
39
|
|
40
40
|
payload = new
|
41
41
|
payload.version = VERSION
|
@@ -101,10 +101,6 @@ module NewRelic
|
|
101
101
|
transaction.current_segment.guid
|
102
102
|
end
|
103
103
|
end
|
104
|
-
|
105
|
-
def connected?
|
106
|
-
Agent.instance.connected?
|
107
|
-
end
|
108
104
|
end
|
109
105
|
|
110
106
|
attr_accessor :version,
|
@@ -110,6 +110,29 @@ module NewRelic
|
|
110
110
|
false
|
111
111
|
end
|
112
112
|
|
113
|
+
# Neither ignored nor expected errors impact apdex.
|
114
|
+
#
|
115
|
+
# Ignored errors are checked via `#error_is_ignored?`
|
116
|
+
# Expected errors are checked in 2 separate ways:
|
117
|
+
# 1. The presence of an `expected: true` attribute key/value pair in the
|
118
|
+
# options hash, which will be set if that key/value pair was used in
|
119
|
+
# the `notice_error` public API.
|
120
|
+
# 2. By calling `#expected?` which in turn calls `ErrorFilter#expected?`
|
121
|
+
# which checks for 3 things:
|
122
|
+
# - A match for user-defined HTTP status codes to expect
|
123
|
+
# - A match for user-defined error classes to expect
|
124
|
+
# - A match for user-defined error messages to expect
|
125
|
+
def error_affects_apdex?(error, options)
|
126
|
+
return false if error_is_ignored?(error)
|
127
|
+
return false if options[:expected]
|
128
|
+
|
129
|
+
!expected?(error, ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code)
|
130
|
+
rescue => e
|
131
|
+
NewRelic::Agent.logger.error("Could not determine if error '#{error}' should impact Apdex - " \
|
132
|
+
"#{e.class}: #{e.message}. Defaulting to 'true' (it should impact Apdex).")
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
113
136
|
# Calling instance_variable_set on a wrapped Java object in JRuby will
|
114
137
|
# generate a warning unless that object's class has already been marked
|
115
138
|
# as persistent, so we skip tagging of exception objects that are actually
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module NewRelic::Agent::Instrumentation
|
6
|
+
module DynamoDB::Chain
|
7
|
+
def self.instrument!
|
8
|
+
::Aws::DynamoDB::Client.class_eval do
|
9
|
+
include NewRelic::Agent::Instrumentation::DynamoDB
|
10
|
+
|
11
|
+
NewRelic::Agent::Instrumentation::DynamoDB::INSTRUMENTED_METHODS.each do |method_name|
|
12
|
+
alias_method("#{method_name}_without_new_relic".to_sym, method_name.to_sym)
|
13
|
+
|
14
|
+
define_method(method_name) do |*args|
|
15
|
+
instrument_method_with_new_relic(method_name, *args) { send("#{method_name}_without_new_relic".to_sym, *args) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method(:build_request_without_new_relic, :build_request)
|
20
|
+
|
21
|
+
def build_request(*args)
|
22
|
+
build_request_with_new_relic(*args) { build_request_without_new_relic(*args) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module NewRelic::Agent::Instrumentation
|
6
|
+
module DynamoDB
|
7
|
+
INSTRUMENTED_METHODS = %w[
|
8
|
+
create_table
|
9
|
+
delete_item
|
10
|
+
delete_table
|
11
|
+
get_item
|
12
|
+
put_item
|
13
|
+
query
|
14
|
+
scan
|
15
|
+
update_item
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
PRODUCT = 'DynamoDB'
|
19
|
+
DEFAULT_HOST = 'dynamodb.amazonaws.com'
|
20
|
+
|
21
|
+
def instrument_method_with_new_relic(method_name, *args)
|
22
|
+
return yield unless NewRelic::Agent::Tracer.tracing_enabled?
|
23
|
+
|
24
|
+
NewRelic::Agent.record_instrumentation_invocation(PRODUCT)
|
25
|
+
|
26
|
+
segment = NewRelic::Agent::Tracer.start_datastore_segment(
|
27
|
+
product: PRODUCT,
|
28
|
+
operation: method_name,
|
29
|
+
host: config&.endpoint&.host || DEFAULT_HOST,
|
30
|
+
port_path_or_id: config&.endpoint&.port,
|
31
|
+
collection: args[0][:table_name]
|
32
|
+
)
|
33
|
+
|
34
|
+
arn = get_arn(args[0])
|
35
|
+
segment&.add_agent_attribute('cloud.resource_id', arn) if arn
|
36
|
+
|
37
|
+
@nr_captured_request = nil # clear request just in case
|
38
|
+
begin
|
39
|
+
NewRelic::Agent::Tracer.capture_segment_error(segment) { yield }
|
40
|
+
ensure
|
41
|
+
segment&.add_agent_attribute('aws.operation', method_name)
|
42
|
+
segment&.add_agent_attribute('aws.requestId', @nr_captured_request&.context&.http_response&.headers&.[]('x-amzn-requestid'))
|
43
|
+
segment&.add_agent_attribute('aws.region', config&.region)
|
44
|
+
segment&.finish
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_request_with_new_relic(*args)
|
49
|
+
@nr_captured_request = yield
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_arn(params)
|
53
|
+
return unless params[:table_name]
|
54
|
+
|
55
|
+
NewRelic::Agent::Aws.create_arn(PRODUCT.downcase, "table/#{params[:table_name]}", config)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module NewRelic::Agent::Instrumentation
|
6
|
+
module DynamoDB::Prepend
|
7
|
+
include NewRelic::Agent::Instrumentation::DynamoDB
|
8
|
+
|
9
|
+
INSTRUMENTED_METHODS.each do |method_name|
|
10
|
+
define_method(method_name) do |*args|
|
11
|
+
instrument_method_with_new_relic(method_name, *args) { super(*args) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_request(*args)
|
16
|
+
build_request_with_new_relic(*args) { super }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'dynamodb/instrumentation'
|
6
|
+
require_relative 'dynamodb/chain'
|
7
|
+
require_relative 'dynamodb/prepend'
|
8
|
+
|
9
|
+
DependencyDetection.defer do
|
10
|
+
named :dynamodb
|
11
|
+
|
12
|
+
depends_on do
|
13
|
+
defined?(Aws::DynamoDB::Client)
|
14
|
+
end
|
15
|
+
|
16
|
+
executes do
|
17
|
+
NewRelic::Agent.logger.info('Installing DynamoDB instrumentation')
|
18
|
+
|
19
|
+
if use_prepend?
|
20
|
+
prepend_instrument Aws::DynamoDB::Client, NewRelic::Agent::Instrumentation::DynamoDB::Prepend
|
21
|
+
else
|
22
|
+
chain_instrument NewRelic::Agent::Instrumentation::DynamoDB::Chain
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -10,7 +10,11 @@ module NewRelic::Agent::Instrumentation
|
|
10
10
|
OPERATION = 'perform_request'
|
11
11
|
INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
|
12
12
|
|
13
|
-
|
13
|
+
# We need the positional arguments `params` and `body`
|
14
|
+
# to capture the nosql statement
|
15
|
+
# *args protects the instrumented method if new arguments are added to
|
16
|
+
# perform_request
|
17
|
+
def perform_request_with_tracing(_method, _path, params = {}, body = nil, _headers = nil, *_args)
|
14
18
|
return yield unless NewRelic::Agent::Tracer.tracing_enabled?
|
15
19
|
|
16
20
|
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
@@ -22,6 +26,7 @@ module NewRelic::Agent::Instrumentation
|
|
22
26
|
port_path_or_id: nr_hosts[:port],
|
23
27
|
database_name: nr_cluster_name
|
24
28
|
)
|
29
|
+
|
25
30
|
begin
|
26
31
|
NewRelic::Agent::Tracer.capture_segment_error(segment) { yield }
|
27
32
|
ensure
|
@@ -68,7 +68,7 @@ module NewRelic::Agent::Instrumentation
|
|
68
68
|
vendor: VENDOR,
|
69
69
|
request_max_tokens: (parameters[:max_tokens] || parameters['max_tokens'])&.to_i,
|
70
70
|
request_model: parameters[:model] || parameters['model'],
|
71
|
-
|
71
|
+
request_temperature: (parameters[:temperature] || parameters['temperature'])&.to_f,
|
72
72
|
metadata: llm_custom_attributes
|
73
73
|
)
|
74
74
|
end
|
@@ -128,7 +128,6 @@ module NewRelic::Agent::Instrumentation
|
|
128
128
|
def update_chat_completion_messages(messages, response, summary)
|
129
129
|
messages += create_chat_completion_response_messages(response, messages.size, summary.id)
|
130
130
|
response_id = response['id'] || NewRelic::Agent::GuidGenerator.generate_guid
|
131
|
-
|
132
131
|
messages.each do |message|
|
133
132
|
message.id = "#{response_id}-#{message.sequence}"
|
134
133
|
message.request_id = summary.request_id
|
@@ -247,21 +247,6 @@ module NewRelic
|
|
247
247
|
message.byteslice(0...MAX_BYTES)
|
248
248
|
end
|
249
249
|
|
250
|
-
def minimum_log_level
|
251
|
-
if Logger::Severity.constants.include?(configured_log_level_constant)
|
252
|
-
configured_log_level_constant
|
253
|
-
else
|
254
|
-
NewRelic::Agent.logger.log_once(
|
255
|
-
:error,
|
256
|
-
'Invalid application_logging.forwarding.log_level ' \
|
257
|
-
"'#{NewRelic::Agent.config[LOG_LEVEL_KEY]}' specified! " \
|
258
|
-
"Must be one of #{Logger::Severity.constants.join('|')}. " \
|
259
|
-
"Using default level of 'debug'"
|
260
|
-
)
|
261
|
-
:DEBUG
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
250
|
def configured_log_level_constant
|
266
251
|
format_log_level_constant(NewRelic::Agent.config[LOG_LEVEL_KEY])
|
267
252
|
end
|
@@ -275,7 +260,7 @@ module NewRelic
|
|
275
260
|
# always record custom log levels
|
276
261
|
return false unless Logger::Severity.constants.include?(severity_constant)
|
277
262
|
|
278
|
-
Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(
|
263
|
+
Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(configured_log_level_constant)
|
279
264
|
end
|
280
265
|
end
|
281
266
|
end
|
@@ -143,6 +143,9 @@ module NewRelic
|
|
143
143
|
end
|
144
144
|
|
145
145
|
def metric_data(stats_hash)
|
146
|
+
# let the serverless handler handle serialization
|
147
|
+
return NewRelic::Agent.agent.serverless_handler.metric_data(stats_hash) if NewRelic::Agent.agent.serverless?
|
148
|
+
|
146
149
|
timeslice_start = stats_hash.started_at
|
147
150
|
timeslice_end = stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)
|
148
151
|
metric_data_array = build_metric_data_array(stats_hash)
|
@@ -154,6 +157,9 @@ module NewRelic
|
|
154
157
|
end
|
155
158
|
|
156
159
|
def error_data(unsent_errors)
|
160
|
+
# let the serverless handler handle serialization
|
161
|
+
return NewRelic::Agent.agent.serverless_handler.error_data(unsent_errors) if NewRelic::Agent.agent.serverless?
|
162
|
+
|
157
163
|
invoke_remote(:error_data, [@agent_id, unsent_errors],
|
158
164
|
:item_count => unsent_errors.size)
|
159
165
|
end
|
@@ -554,6 +560,8 @@ module NewRelic
|
|
554
560
|
# enough to be worth compressing, and handles any errors the
|
555
561
|
# server may return
|
556
562
|
def invoke_remote(method, payload = [], options = {})
|
563
|
+
return NewRelic::Agent.agent.serverless_handler.store_payload(method, payload) if NewRelic::Agent.agent.serverless?
|
564
|
+
|
557
565
|
start_ts = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
558
566
|
request_send_ts, response_check_ts = nil
|
559
567
|
data, encoding, size, serialize_finish_ts = marshal_payload(method, payload, options)
|
@@ -561,8 +569,10 @@ module NewRelic
|
|
561
569
|
response, request_send_ts, response_check_ts = invoke_remote_send_request(method, payload, data, encoding)
|
562
570
|
@marshaller.load(decompress_response(response))
|
563
571
|
ensure
|
564
|
-
|
565
|
-
|
572
|
+
unless NewRelic::Agent.agent.serverless?
|
573
|
+
record_timing_supportability_metrics(method, start_ts, serialize_finish_ts, request_send_ts, response_check_ts)
|
574
|
+
record_size_supportability_metrics(method, size, options[:item_count]) if size
|
575
|
+
end
|
566
576
|
end
|
567
577
|
|
568
578
|
def handle_serialization_error(method, e)
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
require 'new_relic/base64'
|
7
|
+
|
8
|
+
module NewRelic
|
9
|
+
module Agent
|
10
|
+
class ServerlessHandler
|
11
|
+
ATTRIBUTE_ARN = 'aws.lambda.arn'
|
12
|
+
ATTRIBUTE_COLD_START = 'aws.lambda.coldStart'
|
13
|
+
ATTRIBUTE_REQUEST_ID = 'aws.requestId'
|
14
|
+
AGENT_ATTRIBUTE_DESTINATIONS = NewRelic::Agent::AttributeFilter::DST_TRANSACTION_TRACER |
|
15
|
+
NewRelic::Agent::AttributeFilter::DST_TRANSACTION_EVENTS
|
16
|
+
EXECUTION_ENVIRONMENT = "AWS_Lambda_ruby#{RUBY_VERSION.rpartition('.').first}".freeze
|
17
|
+
LAMBDA_MARKER = 'NR_LAMBDA_MONITORING'
|
18
|
+
LAMBDA_ENVIRONMENT_VARIABLE = 'AWS_LAMBDA_FUNCTION_NAME'
|
19
|
+
METHOD_BLOCKLIST = %i[agent_command_results connect get_agent_commands preconnect profile_data
|
20
|
+
shutdown].freeze
|
21
|
+
NAMED_PIPE = '/tmp/newrelic-telemetry'
|
22
|
+
SUPPORTABILITY_METRIC = 'Supportability/AWSLambda/HandlerInvocation'
|
23
|
+
FUNCTION_NAME = 'lambda_function'
|
24
|
+
PAYLOAD_VERSION = ENV.fetch('NEW_RELIC_SERVERLESS_PAYLOAD_VERSION', 2)
|
25
|
+
|
26
|
+
def self.env_var_set?
|
27
|
+
ENV.key?(LAMBDA_ENVIRONMENT_VARIABLE)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@context = nil
|
32
|
+
@payloads = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def invoke_lambda_function_with_new_relic(event:, context:, method_name:, namespace: nil)
|
36
|
+
NewRelic::Agent.increment_metric(SUPPORTABILITY_METRIC)
|
37
|
+
|
38
|
+
@context = context
|
39
|
+
|
40
|
+
NewRelic::Agent::Tracer.in_transaction(category: :other, name: function_name) do
|
41
|
+
add_agent_attributes
|
42
|
+
|
43
|
+
NewRelic::LanguageSupport.constantize(namespace).send(method_name, event: event, context: context)
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
harvest!
|
47
|
+
write_output
|
48
|
+
reset!
|
49
|
+
end
|
50
|
+
|
51
|
+
def store_payload(method, payload)
|
52
|
+
return if METHOD_BLOCKLIST.include?(method)
|
53
|
+
|
54
|
+
@payloads[method] = payload
|
55
|
+
end
|
56
|
+
|
57
|
+
def metric_data(stats_hash)
|
58
|
+
payload = [nil,
|
59
|
+
stats_hash.started_at,
|
60
|
+
(stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)),
|
61
|
+
[]]
|
62
|
+
stats_hash.each do |metric_spec, stats|
|
63
|
+
next if stats.is_reset?
|
64
|
+
|
65
|
+
hash = {name: metric_spec.name}
|
66
|
+
hash[:scope] = metric_spec.scope unless metric_spec.scope.empty?
|
67
|
+
|
68
|
+
payload.last.push([hash, [
|
69
|
+
stats.call_count,
|
70
|
+
stats.total_call_time,
|
71
|
+
stats.total_exclusive_time,
|
72
|
+
stats.min_call_time,
|
73
|
+
stats.max_call_time,
|
74
|
+
stats.sum_of_squares
|
75
|
+
]])
|
76
|
+
end
|
77
|
+
|
78
|
+
return if payload.last.empty?
|
79
|
+
|
80
|
+
store_payload(:metric_data, payload)
|
81
|
+
end
|
82
|
+
|
83
|
+
def error_data(errors)
|
84
|
+
store_payload(:error_data, [nil, errors.map(&:to_collector_array)])
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def harvest!
|
90
|
+
NewRelic::Agent.instance.harvest_and_send_analytic_event_data
|
91
|
+
NewRelic::Agent.instance.harvest_and_send_custom_event_data
|
92
|
+
NewRelic::Agent.instance.harvest_and_send_data_types
|
93
|
+
end
|
94
|
+
|
95
|
+
def metadata
|
96
|
+
m = {arn: @context.invoked_function_arn,
|
97
|
+
protocol_version: NewRelic::Agent::NewRelicService::PROTOCOL_VERSION,
|
98
|
+
function_version: @context.function_version,
|
99
|
+
execution_environment: EXECUTION_ENVIRONMENT,
|
100
|
+
agent_version: NewRelic::VERSION::STRING}
|
101
|
+
if PAYLOAD_VERSION >= 2
|
102
|
+
m[:metadata_version] = PAYLOAD_VERSION
|
103
|
+
m[:agent_language] = NewRelic::LANGUAGE
|
104
|
+
end
|
105
|
+
m
|
106
|
+
end
|
107
|
+
|
108
|
+
def function_name
|
109
|
+
ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_output
|
113
|
+
string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
|
114
|
+
|
115
|
+
return puts string unless use_named_pipe?
|
116
|
+
|
117
|
+
File.write(NAMED_PIPE, string)
|
118
|
+
|
119
|
+
NewRelic::Agent.logger.debug "Wrote serverless payload to #{NAMED_PIPE}\n" \
|
120
|
+
"BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
|
121
|
+
end
|
122
|
+
|
123
|
+
def payload_v1
|
124
|
+
payload_hash = {'metadata' => metadata, 'data' => @payloads}
|
125
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
|
126
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
127
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
128
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, base64_encoded]
|
129
|
+
::JSON.dump(array)
|
130
|
+
end
|
131
|
+
|
132
|
+
def payload_v2
|
133
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
|
134
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
135
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
136
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, metadata, base64_encoded]
|
137
|
+
::JSON.dump(array)
|
138
|
+
end
|
139
|
+
|
140
|
+
def use_named_pipe?
|
141
|
+
return @use_named_pipe if defined?(@use_named_pipe)
|
142
|
+
|
143
|
+
@use_named_pipe = File.exist?(NAMED_PIPE) && File.writable?(NAMED_PIPE)
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_agent_attributes
|
147
|
+
return unless NewRelic::Agent::Tracer.current_transaction
|
148
|
+
|
149
|
+
add_agent_attribute(ATTRIBUTE_COLD_START, true) if cold?
|
150
|
+
add_agent_attribute(ATTRIBUTE_ARN, @context.invoked_function_arn)
|
151
|
+
add_agent_attribute(ATTRIBUTE_REQUEST_ID, @context.aws_request_id)
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_agent_attribute(attribute, value)
|
155
|
+
NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
|
156
|
+
end
|
157
|
+
|
158
|
+
def cold?
|
159
|
+
return @cold if defined?(@cold)
|
160
|
+
|
161
|
+
@cold = false
|
162
|
+
true
|
163
|
+
end
|
164
|
+
|
165
|
+
def reset!
|
166
|
+
@context = nil
|
167
|
+
@payloads.replace({})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -79,17 +79,13 @@ module NewRelic
|
|
79
79
|
intrinsics[SPAN_KIND_KEY] = CLIENT
|
80
80
|
intrinsics[SERVER_ADDRESS_KEY] = segment.uri.host
|
81
81
|
intrinsics[SERVER_PORT_KEY] = segment.uri.port
|
82
|
-
agent_attributes =
|
82
|
+
agent_attributes = {}
|
83
83
|
|
84
84
|
if allowed?(HTTP_URL_KEY)
|
85
85
|
agent_attributes[HTTP_URL_KEY] = truncate(segment.uri)
|
86
86
|
end
|
87
87
|
|
88
|
-
|
89
|
-
agent_attributes.merge!(agent_attributes(segment))
|
90
|
-
end
|
91
|
-
|
92
|
-
[intrinsics, custom_attributes(segment), agent_attributes]
|
88
|
+
[intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
|
93
89
|
end
|
94
90
|
|
95
91
|
def for_datastore_segment(segment) # rubocop:disable Metrics/AbcSize
|
@@ -99,7 +95,7 @@ module NewRelic
|
|
99
95
|
intrinsics[SPAN_KIND_KEY] = CLIENT
|
100
96
|
intrinsics[CATEGORY_KEY] = DATASTORE_CATEGORY
|
101
97
|
|
102
|
-
agent_attributes =
|
98
|
+
agent_attributes = {}
|
103
99
|
|
104
100
|
if segment.database_name && allowed?(DB_INSTANCE_KEY)
|
105
101
|
agent_attributes[DB_INSTANCE_KEY] = truncate(segment.database_name)
|
@@ -123,7 +119,7 @@ module NewRelic
|
|
123
119
|
agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, 2000)
|
124
120
|
end
|
125
121
|
|
126
|
-
[intrinsics, custom_attributes(segment), agent_attributes]
|
122
|
+
[intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
|
127
123
|
end
|
128
124
|
|
129
125
|
private
|
@@ -23,7 +23,6 @@ module NewRelic
|
|
23
23
|
MISSING_STATUS_CODE = 'MissingHTTPStatusCode'
|
24
24
|
|
25
25
|
attr_reader :library, :uri, :procedure, :http_status_code
|
26
|
-
attr_writer :record_agent_attributes
|
27
26
|
|
28
27
|
def initialize(library, uri, procedure, start_time = nil) # :nodoc:
|
29
28
|
@library = library
|
@@ -32,7 +31,6 @@ module NewRelic
|
|
32
31
|
@host_header = nil
|
33
32
|
@app_data = nil
|
34
33
|
@http_status_code = nil
|
35
|
-
@record_agent_attributes = false
|
36
34
|
super(nil, nil, start_time)
|
37
35
|
end
|
38
36
|
|
@@ -44,14 +42,6 @@ module NewRelic
|
|
44
42
|
@host_header || uri.host
|
45
43
|
end
|
46
44
|
|
47
|
-
# By default external request segments only have errors and the http
|
48
|
-
# url recorded as agent attributes. To have all the agent attributes
|
49
|
-
# recorded, use the attr_writer like so `segment.record_agent_attributes = true`
|
50
|
-
# See: SpanEventPrimitive#for_external_request_segment
|
51
|
-
def record_agent_attributes?
|
52
|
-
@record_agent_attributes
|
53
|
-
end
|
54
|
-
|
55
45
|
# This method adds New Relic request headers to a given request made to an
|
56
46
|
# external API and checks to see if a host header is used for the request.
|
57
47
|
# If a host header is used, it updates the segment name to match the host
|
@@ -816,13 +816,9 @@ module NewRelic
|
|
816
816
|
end
|
817
817
|
|
818
818
|
def had_error_affecting_apdex?
|
819
|
-
@exceptions.each do |exception, options|
|
820
|
-
|
821
|
-
expected = options[:expected]
|
822
|
-
|
823
|
-
return true unless ignored || expected
|
819
|
+
@exceptions.each.any? do |exception, options|
|
820
|
+
NewRelic::Agent.instance.error_collector.error_affects_apdex?(exception, options)
|
824
821
|
end
|
825
|
-
false
|
826
822
|
end
|
827
823
|
|
828
824
|
def apdex_bucket(duration, current_apdex_t)
|
@@ -16,26 +16,27 @@ module NewRelic
|
|
16
16
|
module TransactionErrorPrimitive
|
17
17
|
extend self
|
18
18
|
|
19
|
-
SAMPLE_TYPE = 'TransactionError'
|
20
|
-
TYPE_KEY = 'type'
|
21
|
-
ERROR_CLASS_KEY = 'error.class'
|
22
|
-
ERROR_MESSAGE_KEY = 'error.message'
|
23
|
-
ERROR_EXPECTED_KEY = 'error.expected'
|
24
|
-
TIMESTAMP_KEY = 'timestamp'
|
25
|
-
PORT_KEY = 'port'
|
26
|
-
NAME_KEY = 'transactionName'
|
27
|
-
DURATION_KEY = 'duration'
|
28
|
-
SAMPLED_KEY = 'sampled'
|
29
|
-
|
30
|
-
|
31
|
-
SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'
|
32
|
-
SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'
|
33
|
-
SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'
|
19
|
+
SAMPLE_TYPE = 'TransactionError'
|
20
|
+
TYPE_KEY = 'type'
|
21
|
+
ERROR_CLASS_KEY = 'error.class'
|
22
|
+
ERROR_MESSAGE_KEY = 'error.message'
|
23
|
+
ERROR_EXPECTED_KEY = 'error.expected'
|
24
|
+
TIMESTAMP_KEY = 'timestamp'
|
25
|
+
PORT_KEY = 'port'
|
26
|
+
NAME_KEY = 'transactionName'
|
27
|
+
DURATION_KEY = 'duration'
|
28
|
+
SAMPLED_KEY = 'sampled'
|
29
|
+
CAT_GUID_KEY = 'nr.transactionGuid'
|
30
|
+
CAT_REFERRING_TRANSACTION_GUID_KEY = 'nr.referringTransactionGuid'
|
31
|
+
SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'
|
32
|
+
SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'
|
33
|
+
SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'
|
34
34
|
SYNTHETICS_TYPE_KEY = 'nr.syntheticsType'
|
35
35
|
SYNTHETICS_INITIATOR_KEY = 'nr.syntheticsInitiator'
|
36
36
|
SYNTHETICS_KEY_PREFIX = 'nr.synthetics'
|
37
|
-
PRIORITY_KEY = 'priority'
|
38
|
-
SPAN_ID_KEY = 'spanId'
|
37
|
+
PRIORITY_KEY = 'priority'
|
38
|
+
SPAN_ID_KEY = 'spanId'
|
39
|
+
GUID_KEY = 'guid'
|
39
40
|
|
40
41
|
SYNTHETICS_PAYLOAD_EXPECTED = [:synthetics_resource_id, :synthetics_job_id, :synthetics_monitor_id, :synthetics_type, :synthetics_initiator]
|
41
42
|
|
@@ -57,7 +58,10 @@ module NewRelic
|
|
57
58
|
}
|
58
59
|
|
59
60
|
attrs[SPAN_ID_KEY] = span_id if span_id
|
61
|
+
# don't use safe navigation - leave off keys with missing values
|
62
|
+
# instead of using nil
|
60
63
|
attrs[PORT_KEY] = noticed_error.request_port if noticed_error.request_port
|
64
|
+
attrs[GUID_KEY] = noticed_error.transaction_id if noticed_error.transaction_id
|
61
65
|
|
62
66
|
if payload
|
63
67
|
attrs[NAME_KEY] = payload[:name]
|
@@ -93,8 +97,8 @@ module NewRelic
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def append_cat(payload, sample)
|
96
|
-
sample[
|
97
|
-
sample[
|
100
|
+
sample[CAT_GUID_KEY] = payload[:guid] if payload[:guid]
|
101
|
+
sample[CAT_REFERRING_TRANSACTION_GUID_KEY] = payload[:referring_transaction_guid] if payload[:referring_transaction_guid]
|
98
102
|
end
|
99
103
|
end
|
100
104
|
end
|
data/lib/new_relic/agent.rb
CHANGED
@@ -64,6 +64,7 @@ module NewRelic
|
|
64
64
|
require 'new_relic/agent/linking_metadata'
|
65
65
|
require 'new_relic/agent/local_log_decorator'
|
66
66
|
require 'new_relic/agent/llm'
|
67
|
+
require 'new_relic/agent/aws'
|
67
68
|
|
68
69
|
require 'new_relic/agent/instrumentation/controller_instrumentation'
|
69
70
|
|
@@ -709,16 +710,19 @@ module NewRelic
|
|
709
710
|
def add_custom_attributes(params) # THREAD_LOCAL_ACCESS
|
710
711
|
record_api_supportability_metric(:add_custom_attributes)
|
711
712
|
|
712
|
-
|
713
|
-
Transaction.tl_current&.add_custom_attributes(params)
|
714
|
-
|
715
|
-
segment = ::NewRelic::Agent::Tracer.current_segment
|
716
|
-
if segment
|
717
|
-
add_new_segment_attributes(params, segment)
|
718
|
-
end
|
719
|
-
else
|
713
|
+
unless params.is_a?(Hash)
|
720
714
|
::NewRelic::Agent.logger.warn("Bad argument passed to #add_custom_attributes. Expected Hash but got #{params.class}")
|
715
|
+
return
|
721
716
|
end
|
717
|
+
|
718
|
+
if NewRelic::Agent.agent&.serverless?
|
719
|
+
::NewRelic::Agent.logger.warn('Custom attributes are not supported in serverless mode')
|
720
|
+
return
|
721
|
+
end
|
722
|
+
|
723
|
+
Transaction.tl_current&.add_custom_attributes(params)
|
724
|
+
segment = ::NewRelic::Agent::Tracer.current_segment
|
725
|
+
add_new_segment_attributes(params, segment) if segment
|
722
726
|
end
|
723
727
|
|
724
728
|
def add_new_segment_attributes(params, segment)
|