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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -2
  3. data/README.md +3 -0
  4. data/Rakefile +1 -1
  5. data/lib/bootstrap.rb +105 -0
  6. data/lib/new_relic/agent/agent.rb +4 -1
  7. data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
  8. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
  9. data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
  10. data/lib/new_relic/agent/agent_logger.rb +2 -1
  11. data/lib/new_relic/agent/aws.rb +56 -0
  12. data/lib/new_relic/agent/configuration/default_source.rb +65 -15
  13. data/lib/new_relic/agent/configuration/environment_source.rb +9 -1
  14. data/lib/new_relic/agent/configuration/manager.rb +22 -5
  15. data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
  16. data/lib/new_relic/agent/connect/request_builder.rb +1 -1
  17. data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
  18. data/lib/new_relic/agent/error_collector.rb +23 -0
  19. data/lib/new_relic/agent/harvester.rb +1 -1
  20. data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
  21. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +58 -0
  22. data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
  23. data/lib/new_relic/agent/instrumentation/dynamodb.rb +25 -0
  24. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +6 -1
  25. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
  26. data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +1 -2
  27. data/lib/new_relic/agent/log_event_aggregator.rb +1 -16
  28. data/lib/new_relic/agent/new_relic_service.rb +12 -2
  29. data/lib/new_relic/agent/serverless_handler.rb +171 -0
  30. data/lib/new_relic/agent/span_event_primitive.rb +4 -8
  31. data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
  32. data/lib/new_relic/agent/transaction.rb +2 -6
  33. data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
  34. data/lib/new_relic/agent.rb +12 -8
  35. data/lib/new_relic/constants.rb +2 -0
  36. data/lib/new_relic/control/instance_methods.rb +7 -0
  37. data/lib/new_relic/local_environment.rb +13 -6
  38. data/lib/new_relic/rack/browser_monitoring.rb +9 -1
  39. data/lib/new_relic/version.rb +1 -1
  40. data/lib/tasks/config.rake +5 -3
  41. data/lib/tasks/helpers/config.html.erb +3 -2
  42. data/lib/tasks/helpers/format.rb +1 -1
  43. data/newrelic.yml +15 -1
  44. data/test/agent_helper.rb +3 -1
  45. 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
@@ -38,7 +38,7 @@ module NewRelic
38
38
  end
39
39
 
40
40
  def harvest_thread_enabled?
41
- !NewRelic::Agent.config[:disable_harvest_thread]
41
+ !NewRelic::Agent.config[:disable_harvest_thread] && !NewRelic::Agent.config[:'serverless_mode.enabled']
42
42
  end
43
43
 
44
44
  def restart_harvest_thread
@@ -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
- def perform_request_with_tracing(method, path, params = {}, body = nil, headers = nil)
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
@@ -50,7 +50,6 @@ module NewRelic
50
50
  attributes_hash.each do |attr, value|
51
51
  segment.add_agent_attribute(attr, value)
52
52
  end
53
- segment.record_agent_attributes = true
54
53
  end
55
54
 
56
55
  def grpc_status_and_message_from_exception(exception)
@@ -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
- temperature: (parameters[:temperature] || parameters['temperature'])&.to_f,
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(minimum_log_level)
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
- record_timing_supportability_metrics(method, start_ts, serialize_finish_ts, request_send_ts, response_check_ts)
565
- record_size_supportability_metrics(method, size, options[:item_count]) if size
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 = error_attributes(segment) || {}
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
- if segment.respond_to?(:record_agent_attributes?) && segment.record_agent_attributes?
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 = error_attributes(segment) || {}
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
- ignored = NewRelic::Agent.instance.error_collector.error_is_ignored?(exception)
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'.freeze
20
- TYPE_KEY = 'type'.freeze
21
- ERROR_CLASS_KEY = 'error.class'.freeze
22
- ERROR_MESSAGE_KEY = 'error.message'.freeze
23
- ERROR_EXPECTED_KEY = 'error.expected'.freeze
24
- TIMESTAMP_KEY = 'timestamp'.freeze
25
- PORT_KEY = 'port'.freeze
26
- NAME_KEY = 'transactionName'.freeze
27
- DURATION_KEY = 'duration'.freeze
28
- SAMPLED_KEY = 'sampled'.freeze
29
- GUID_KEY = 'nr.transactionGuid'.freeze
30
- REFERRING_TRANSACTION_GUID_KEY = 'nr.referringTransactionGuid'.freeze
31
- SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'.freeze
32
- SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'.freeze
33
- SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'.freeze
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'.freeze
38
- SPAN_ID_KEY = 'spanId'.freeze
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[GUID_KEY] = payload[:guid] if payload[:guid]
97
- sample[REFERRING_TRANSACTION_GUID_KEY] = payload[:referring_transaction_guid] if payload[:referring_transaction_guid]
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
@@ -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
- if params.is_a?(Hash)
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)