newrelic_rpm 9.11.0 → 9.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -0
  3. data/README.md +16 -20
  4. data/lib/new_relic/agent/agent_logger.rb +1 -0
  5. data/lib/new_relic/agent/configuration/default_source.rb +136 -5
  6. data/lib/new_relic/agent/configuration/manager.rb +8 -0
  7. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  8. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  9. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  10. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -19
  11. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -1
  12. data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
  13. data/lib/new_relic/agent/instrumentation/grape.rb +3 -1
  14. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  15. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  16. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  17. data/lib/new_relic/agent/instrumentation/logstasher.rb +27 -0
  18. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  19. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  20. data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
  21. data/lib/new_relic/agent/instrumentation/opensearch.rb +25 -0
  22. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  23. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  24. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  25. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  26. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  27. data/lib/new_relic/agent/instrumentation/redis.rb +11 -4
  28. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
  29. data/lib/new_relic/agent/instrumentation/sinatra.rb +0 -13
  30. data/lib/new_relic/agent/local_log_decorator.rb +8 -1
  31. data/lib/new_relic/agent/log_event_aggregator.rb +91 -26
  32. data/lib/new_relic/agent/serverless_handler.rb +241 -12
  33. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  34. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  35. data/lib/new_relic/agent/system_info.rb +14 -0
  36. data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
  37. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  38. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  39. data/lib/new_relic/control/frameworks/rails4.rb +4 -2
  40. data/lib/new_relic/control/instance_methods.rb +1 -0
  41. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  42. data/lib/new_relic/control/security_interface.rb +57 -0
  43. data/lib/new_relic/control.rb +1 -1
  44. data/lib/new_relic/environment_report.rb +6 -2
  45. data/lib/new_relic/language_support.rb +7 -1
  46. data/lib/new_relic/local_environment.rb +1 -4
  47. data/lib/new_relic/rack/browser_monitoring.rb +11 -7
  48. data/lib/new_relic/version.rb +1 -1
  49. data/lib/tasks/config.rake +5 -2
  50. data/lib/tasks/helpers/config.html.erb +1 -1
  51. data/lib/tasks/helpers/format.rb +1 -1
  52. data/lib/tasks/helpers/newrelicyml.rb +76 -13
  53. data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -0
  54. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +3 -3
  55. data/newrelic.yml +209 -137
  56. data/test/agent_helper.rb +9 -0
  57. metadata +16 -2
@@ -20,7 +20,8 @@ module NewRelic
20
20
  DROPPED_METRIC = 'Logging/Forwarding/Dropped'.freeze
21
21
  SEEN_METRIC = 'Supportability/Logging/Forwarding/Seen'.freeze
22
22
  SENT_METRIC = 'Supportability/Logging/Forwarding/Sent'.freeze
23
- OVERALL_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
23
+ LOGGER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
24
+ LOGSTASHER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/LogStasher/%s'.freeze
24
25
  METRICS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Metrics/Ruby/%s'.freeze
25
26
  FORWARDING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Forwarding/Ruby/%s'.freeze
26
27
  DECORATING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/LocalDecorating/Ruby/%s'.freeze
@@ -58,38 +59,71 @@ module NewRelic
58
59
  end
59
60
 
60
61
  def record(formatted_message, severity)
61
- return unless enabled?
62
+ return unless logger_enabled?
62
63
 
63
64
  severity = 'UNKNOWN' if severity.nil? || severity.empty?
65
+ increment_event_counters(severity)
66
+
67
+ return if formatted_message.nil? || formatted_message.empty?
68
+ return unless monitoring_conditions_met?(severity)
69
+
70
+ txn = NewRelic::Agent::Transaction.tl_current
71
+ priority = LogPriority.priority_for(txn)
64
72
 
65
- if NewRelic::Agent.config[METRICS_ENABLED_KEY]
66
- @counter_lock.synchronize do
67
- @seen += 1
68
- @seen_by_severity[severity] += 1
73
+ return txn.add_log_event(create_event(priority, formatted_message, severity)) if txn
74
+
75
+ @lock.synchronize do
76
+ @buffer.append(priority: priority) do
77
+ create_event(priority, formatted_message, severity)
69
78
  end
70
79
  end
80
+ rescue
81
+ nil
82
+ end
71
83
 
72
- return if severity_too_low?(severity)
73
- return if formatted_message.nil? || formatted_message.empty?
74
- return unless NewRelic::Agent.config[FORWARDING_ENABLED_KEY]
75
- return if @high_security
84
+ def record_logstasher_event(log)
85
+ return unless logstasher_enabled?
86
+
87
+ # LogStasher logs do not inherently include a message key, so most logs are recorded.
88
+ # But when the key exists, we should not record the log if the message value is nil or empty.
89
+ return if log.key?('message') && (log['message'].nil? || log['message'].empty?)
90
+
91
+ severity = determine_severity(log)
92
+ increment_event_counters(severity)
93
+
94
+ return unless monitoring_conditions_met?(severity)
76
95
 
77
96
  txn = NewRelic::Agent::Transaction.tl_current
78
97
  priority = LogPriority.priority_for(txn)
79
98
 
80
- if txn
81
- return txn.add_log_event(create_event(priority, formatted_message, severity))
82
- else
83
- return @lock.synchronize do
84
- @buffer.append(priority: priority) do
85
- create_event(priority, formatted_message, severity)
86
- end
99
+ return txn.add_log_event(create_logstasher_event(priority, severity, log)) if txn
100
+
101
+ @lock.synchronize do
102
+ @buffer.append(priority: priority) do
103
+ create_logstasher_event(priority, severity, log)
87
104
  end
88
105
  end
89
106
  rescue
90
107
  nil
91
108
  end
92
109
 
110
+ def monitoring_conditions_met?(severity)
111
+ !severity_too_low?(severity) && NewRelic::Agent.config[FORWARDING_ENABLED_KEY] && !@high_security
112
+ end
113
+
114
+ def determine_severity(log)
115
+ log['level'] ? log['level'].to_s.upcase : 'UNKNOWN'
116
+ end
117
+
118
+ def increment_event_counters(severity)
119
+ return unless NewRelic::Agent.config[METRICS_ENABLED_KEY]
120
+
121
+ @counter_lock.synchronize do
122
+ @seen += 1
123
+ @seen_by_severity[severity] += 1
124
+ end
125
+ end
126
+
93
127
  def record_batch(txn, logs)
94
128
  # Ensure we have the same shared priority
95
129
  priority = LogPriority.priority_for(txn)
@@ -104,15 +138,17 @@ module NewRelic
104
138
  end
105
139
  end
106
140
 
107
- def create_event(priority, formatted_message, severity)
108
- formatted_message = truncate_message(formatted_message)
109
-
110
- event = LinkingMetadata.append_trace_linking_metadata({
141
+ def add_event_metadata(formatted_message, severity)
142
+ metadata = {
111
143
  LEVEL_KEY => severity,
112
- MESSAGE_KEY => formatted_message,
113
144
  TIMESTAMP_KEY => Process.clock_gettime(Process::CLOCK_REALTIME) * 1000
114
- })
145
+ }
146
+ metadata[MESSAGE_KEY] = formatted_message unless formatted_message.nil?
147
+
148
+ LinkingMetadata.append_trace_linking_metadata(metadata)
149
+ end
115
150
 
151
+ def create_prioritized_event(priority, event)
116
152
  [
117
153
  {
118
154
  PrioritySampledBuffer::PRIORITY_KEY => priority
@@ -121,6 +157,31 @@ module NewRelic
121
157
  ]
122
158
  end
123
159
 
160
+ def create_event(priority, formatted_message, severity)
161
+ formatted_message = truncate_message(formatted_message)
162
+ event = add_event_metadata(formatted_message, severity)
163
+
164
+ create_prioritized_event(priority, event)
165
+ end
166
+
167
+ def create_logstasher_event(priority, severity, log)
168
+ formatted_message = log['message'] ? truncate_message(log['message']) : nil
169
+ event = add_event_metadata(formatted_message, severity)
170
+ add_logstasher_event_attributes(event, log)
171
+
172
+ create_prioritized_event(priority, event)
173
+ end
174
+
175
+ def add_logstasher_event_attributes(event, log)
176
+ log_copy = log.dup
177
+ # Delete previously reported attributes
178
+ log_copy.delete('message')
179
+ log_copy.delete('level')
180
+ log_copy.delete('@timestamp')
181
+
182
+ event['attributes'] = log_copy
183
+ end
184
+
124
185
  def add_custom_attributes(custom_attributes)
125
186
  attributes.add_custom_attributes(custom_attributes)
126
187
  end
@@ -166,10 +227,14 @@ module NewRelic
166
227
  super
167
228
  end
168
229
 
169
- def enabled?
230
+ def logger_enabled?
170
231
  @enabled && @instrumentation_logger_enabled
171
232
  end
172
233
 
234
+ def logstasher_enabled?
235
+ @enabled && NewRelic::Agent::Instrumentation::LogStasher.enabled?
236
+ end
237
+
173
238
  private
174
239
 
175
240
  # We record once-per-connect metrics for enabled/disabled state at the
@@ -177,8 +242,8 @@ module NewRelic
177
242
  def register_for_done_configuring(events)
178
243
  events.subscribe(:server_source_configuration_added) do
179
244
  @high_security = NewRelic::Agent.config[:high_security]
180
-
181
- record_configuration_metric(OVERALL_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
245
+ record_configuration_metric(LOGGER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
246
+ record_configuration_metric(LOGSTASHER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
182
247
  record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
183
248
  record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
184
249
  record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
@@ -4,13 +4,13 @@
4
4
 
5
5
  require 'json'
6
6
  require 'new_relic/base64'
7
+ require 'uri'
8
+
9
+ require_relative 'serverless_handler_event_sources'
7
10
 
8
11
  module NewRelic
9
12
  module Agent
10
13
  class ServerlessHandler
11
- ATTRIBUTE_ARN = 'aws.lambda.arn'
12
- ATTRIBUTE_COLD_START = 'aws.lambda.coldStart'
13
- ATTRIBUTE_REQUEST_ID = 'aws.requestId'
14
14
  AGENT_ATTRIBUTE_DESTINATIONS = NewRelic::Agent::AttributeFilter::DST_TRANSACTION_TRACER |
15
15
  NewRelic::Agent::AttributeFilter::DST_TRANSACTION_EVENTS
16
16
  EXECUTION_ENVIRONMENT = "AWS_Lambda_ruby#{RUBY_VERSION.rpartition('.').first}".freeze
@@ -22,12 +22,15 @@ module NewRelic
22
22
  SUPPORTABILITY_METRIC = 'Supportability/AWSLambda/HandlerInvocation'
23
23
  FUNCTION_NAME = 'lambda_function'
24
24
  PAYLOAD_VERSION = ENV.fetch('NEW_RELIC_SERVERLESS_PAYLOAD_VERSION', 2)
25
+ DIGIT = /\d/
26
+ EVENT_SOURCES = NewRelic::Agent::ServerlessHandlerEventSources.to_hash
25
27
 
26
28
  def self.env_var_set?
27
29
  ENV.key?(LAMBDA_ENVIRONMENT_VARIABLE)
28
30
  end
29
31
 
30
32
  def initialize
33
+ @event = nil
31
34
  @context = nil
32
35
  @payloads = {}
33
36
  end
@@ -35,12 +38,13 @@ module NewRelic
35
38
  def invoke_lambda_function_with_new_relic(event:, context:, method_name:, namespace: nil)
36
39
  NewRelic::Agent.increment_metric(SUPPORTABILITY_METRIC)
37
40
 
38
- @context = context
41
+ @event, @context = event, context
39
42
 
40
- NewRelic::Agent::Tracer.in_transaction(category: :other, name: function_name) do
41
- add_agent_attributes
43
+ NewRelic::Agent::Tracer.in_transaction(category: category, name: function_name) do
44
+ prep_transaction
42
45
 
43
- NewRelic::LanguageSupport.constantize(namespace).send(method_name, event: event, context: context)
46
+ process_response(NewRelic::LanguageSupport.constantize(namespace)
47
+ .send(method_name, event: event, context: context))
44
48
  end
45
49
  ensure
46
50
  harvest!
@@ -86,6 +90,12 @@ module NewRelic
86
90
 
87
91
  private
88
92
 
93
+ def prep_transaction
94
+ process_api_gateway_info
95
+ process_headers
96
+ add_agent_attributes
97
+ end
98
+
89
99
  def harvest!
90
100
  NewRelic::Agent.instance.harvest_and_send_analytic_event_data
91
101
  NewRelic::Agent.instance.harvest_and_send_custom_event_data
@@ -109,6 +119,11 @@ module NewRelic
109
119
  ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
110
120
  end
111
121
 
122
+ def category
123
+ @category ||=
124
+ @event&.dig('requestContext', 'http', 'method') || @event&.fetch('httpMethod', nil) ? :web : :other
125
+ end
126
+
112
127
  def write_output
113
128
  string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
114
129
 
@@ -120,7 +135,7 @@ module NewRelic
120
135
  "BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
121
136
  end
122
137
 
123
- def payload_v1
138
+ def payload_v1 # New Relic serverless payload v1
124
139
  payload_hash = {'metadata' => metadata, 'data' => @payloads}
125
140
  json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
126
141
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
@@ -129,7 +144,7 @@ module NewRelic
129
144
  ::JSON.dump(array)
130
145
  end
131
146
 
132
- def payload_v2
147
+ def payload_v2 # New Relic serverless payload v2
133
148
  json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
134
149
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
135
150
  base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
@@ -137,6 +152,88 @@ module NewRelic
137
152
  ::JSON.dump(array)
138
153
  end
139
154
 
155
+ def determine_api_gateway_version
156
+ return unless @event
157
+
158
+ version = @event.fetch('version', '')
159
+ if version.start_with?('2.')
160
+ return 2
161
+ elsif version.start_with?('1.')
162
+ return 1
163
+ end
164
+
165
+ headers = headers_from_event
166
+ return unless headers
167
+
168
+ if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
169
+ 2
170
+ elsif @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
171
+ 1
172
+ end
173
+ end
174
+
175
+ def process_api_gateway_info
176
+ api_v = determine_api_gateway_version
177
+ return unless api_v
178
+
179
+ info = api_v == 2 ? info_for_api_gateway_v2 : info_for_api_gateway_v1
180
+ info[:query_parameters] = @event.fetch('queryStringParameters', nil)
181
+
182
+ @http_method = info[:method]
183
+ @http_uri = http_uri(info)
184
+ end
185
+
186
+ def http_uri(info)
187
+ return unless info[:host] && info[:path]
188
+
189
+ url_str = "https://#{info[:host]}"
190
+ url_str += ":#{info[:port]}" unless info[:host].match?(':')
191
+ url_str += "#{info[:path]}"
192
+
193
+ if info[:query_parameters]
194
+ qp = info[:query_parameters].map { |k, v| "#{k}=#{v}" }.join('&')
195
+ url_str += "?#{qp}"
196
+ end
197
+
198
+ URI.parse(url_str)
199
+ rescue StandardError => e
200
+ NewRelic::Agent.logger.error "ServerlessHandler failed to parse the source HTTP URI: #{e}"
201
+ end
202
+
203
+ def info_for_api_gateway_v2
204
+ ctx = @event.fetch('requestContext', nil)
205
+ return {} unless ctx
206
+
207
+ {method: ctx.dig('http', 'method'),
208
+ path: ctx.dig('http', 'path'),
209
+ host: ctx.fetch('domainName', @event.dig('headers', 'Host')),
210
+ port: @event.dig('headers', 'X-Forwarded-Port') || 443}
211
+ end
212
+
213
+ def info_for_api_gateway_v1
214
+ headers = headers_from_event
215
+ {method: @event.fetch('httpMethod', nil),
216
+ path: @event.fetch('path', nil),
217
+ host: headers.fetch('Host', nil),
218
+ port: headers.fetch('X-Forwarded-Port', 443)}
219
+ end
220
+
221
+ def process_headers
222
+ return unless ::NewRelic::Agent.config[:'distributed_tracing.enabled']
223
+
224
+ headers = headers_from_event
225
+ return unless headers && !headers.empty?
226
+
227
+ dt_headers = headers.fetch(NewRelic::NEWRELIC_KEY, nil)
228
+ return unless dt_headers
229
+
230
+ ::NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(dt_headers, 'Other')
231
+ end
232
+
233
+ def headers_from_event
234
+ @headers ||= @event&.dig('requestContext', 'http') || @event&.dig('headers')
235
+ end
236
+
140
237
  def use_named_pipe?
141
238
  return @use_named_pipe if defined?(@use_named_pipe)
142
239
 
@@ -146,15 +243,142 @@ module NewRelic
146
243
  def add_agent_attributes
147
244
  return unless NewRelic::Agent::Tracer.current_transaction
148
245
 
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)
246
+ add_agent_attribute('aws.lambda.coldStart', true) if cold?
247
+ add_agent_attribute('aws.lambda.arn', @context.invoked_function_arn)
248
+ add_agent_attribute('aws.requestId', @context.aws_request_id)
249
+
250
+ add_event_source_attributes
251
+ add_http_attributes if api_gateway_event?
252
+ end
253
+
254
+ def add_http_attributes
255
+ return unless category == :web
256
+
257
+ if @http_uri
258
+ add_agent_attribute('uri.host', @http_uri.host)
259
+ add_agent_attribute('uri.port', @http_uri.port)
260
+ if NewRelic::Agent.instance.attribute_filter.allows_key?('http.url', AttributeFilter::DST_SPAN_EVENTS)
261
+ add_agent_attribute('http.url', @http_uri.to_s)
262
+ end
263
+ end
264
+
265
+ if @http_method
266
+ add_agent_attribute('http.method', @http_method)
267
+ add_agent_attribute('http.request.method', @http_method)
268
+ end
269
+ end
270
+
271
+ def api_gateway_event?
272
+ return false unless @event
273
+
274
+ # '1.0' for API Gateway V1, '2.0' for API Gateway V2
275
+ return true if @event.fetch('version', '').start_with?(DIGIT)
276
+
277
+ return false unless headers_from_event
278
+
279
+ # API Gateway V1 - look for toplevel 'path' and 'httpMethod' keys if a version is unset
280
+ return true if @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
281
+
282
+ # API Gateway V2 - look for 'requestContext/http' inner nested 'path' and 'method' keys if a version is unset
283
+ return true if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
284
+
285
+ false
286
+ end
287
+
288
+ def add_event_source_attributes
289
+ arn = event_source_arn
290
+ add_agent_attribute('aws.lambda.eventSource.arn', arn) if arn
291
+
292
+ info = event_source_event_info
293
+ return unless info
294
+
295
+ add_agent_attribute('aws.lambda.eventSource.eventType', info['name'])
296
+
297
+ info['attributes'].each do |name, elements|
298
+ next if elements.empty?
299
+
300
+ size = false
301
+ if elements.last.eql?('#size')
302
+ elements = elements.dup
303
+ elements.pop
304
+ size = true
305
+ end
306
+ value = @event.dig(*elements)
307
+ value = value.size if size
308
+ next unless value
309
+
310
+ add_agent_attribute(name, value)
311
+ end
312
+ end
313
+
314
+ def event_source_arn
315
+ return unless @event
316
+
317
+ # SQS/Kinesis Stream/DynamoDB/CodeCommit/S3/SNS
318
+ return event_source_arn_for_records if @event.fetch('Records', nil)
319
+
320
+ # Kinesis Firehose
321
+ ds_arn = @event.fetch('deliveryStreamArn', nil) if @event.fetch('records', nil)
322
+ return ds_arn if ds_arn
323
+
324
+ # ELB
325
+ elb_arn = @event.dig('requestContext', 'elb', 'targetGroupArn')
326
+ return elb_arn if elb_arn
327
+
328
+ # (other)
329
+ es_arn = @event.dig('resources', 0)
330
+ return es_arn if es_arn
331
+
332
+ NewRelic::Agent.logger.debug 'Unable to determine an event source arn'
333
+
334
+ nil
335
+ end
336
+
337
+ def event_source_event_info
338
+ return unless @event
339
+
340
+ # if every required key for a source is found, consider that source
341
+ # to be a match
342
+ EVENT_SOURCES.each_value do |info|
343
+ return info unless info['required_keys'].detect { |r| @event.dig(*r).nil? }
344
+ end
345
+
346
+ nil
347
+ end
348
+
349
+ def event_source_arn_for_records
350
+ record = @event['Records'].first
351
+ unless record
352
+ NewRelic::Agent.logger.debug "Unable to find any records in the event's 'Records' array"
353
+ return
354
+ end
355
+
356
+ arn = record.fetch('eventSourceARN', nil) || # SQS/Kinesis Stream/DynamoDB/CodeCommit
357
+ record.dig('s3', 'bucket', 'arn') || # S3
358
+ record.fetch('EventSubscriptionArn', nil) # SNS
359
+
360
+ unless arn
361
+ NewRelic::Agent.logger.debug "Unable to determine an event source arn from the event's 'Records' array"
362
+ end
363
+
364
+ arn
152
365
  end
153
366
 
154
367
  def add_agent_attribute(attribute, value)
155
368
  NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
156
369
  end
157
370
 
371
+ def process_response(response)
372
+ return response unless category == :web && response.respond_to?(:fetch)
373
+
374
+ http_status = response.fetch(:statusCode, response.fetch('statusCode', nil))
375
+ return unless http_status
376
+
377
+ add_agent_attribute('http.statusCode', http_status)
378
+
379
+ response
380
+ end
381
+
158
382
  def cold?
159
383
  return @cold if defined?(@cold)
160
384
 
@@ -163,7 +387,12 @@ module NewRelic
163
387
  end
164
388
 
165
389
  def reset!
390
+ @event = nil
391
+ @category = nil
166
392
  @context = nil
393
+ @headers = nil
394
+ @http_method = nil
395
+ @http_uri = nil
167
396
  @payloads.replace({})
168
397
  end
169
398
  end
@@ -0,0 +1,155 @@
1
+ {
2
+ "alb": {
3
+ "attributes": {},
4
+ "name": "alb",
5
+ "required_keys": [
6
+ "httpMethod",
7
+ "requestContext.elb"
8
+ ]
9
+ },
10
+
11
+ "apiGateway": {
12
+ "attributes": {
13
+ "aws.lambda.eventSource.accountId": "requestContext.accountId",
14
+ "aws.lambda.eventSource.apiId": "requestContext.apiId",
15
+ "aws.lambda.eventSource.resourceId": "requestContext.resourceId",
16
+ "aws.lambda.eventSource.resourcePath": "requestContext.resourcePath",
17
+ "aws.lambda.eventSource.stage": "requestContext.stage"
18
+ },
19
+ "name": "apiGateway",
20
+ "required_keys": [
21
+ "headers",
22
+ "httpMethod",
23
+ "path",
24
+ "requestContext",
25
+ "requestContext.stage"
26
+ ]
27
+ },
28
+
29
+ "apiGatewayV2": {
30
+ "attributes": {
31
+ "aws.lambda.eventSource.accountId": "requestContext.accountId",
32
+ "aws.lambda.eventSource.apiId": "requestContext.apiId",
33
+ "aws.lambda.eventSource.stage": "requestContext.stage"
34
+ },
35
+ "name": "apiGatewayV2",
36
+ "required_keys": [
37
+ "version",
38
+ "headers",
39
+ "requestContext.http",
40
+ "requestContext.http.path",
41
+ "requestContext.http.method",
42
+ "requestContext.stage"
43
+ ]
44
+ },
45
+
46
+ "cloudFront": {
47
+ "attributes": {},
48
+ "name": "cloudFront",
49
+ "required_keys": [
50
+ "Records[0].cf"
51
+ ]
52
+ },
53
+
54
+ "cloudWatchScheduled": {
55
+ "attributes": {
56
+ "aws.lambda.eventSource.account": "account",
57
+ "aws.lambda.eventSource.id": "id",
58
+ "aws.lambda.eventSource.region": "region",
59
+ "aws.lambda.eventSource.resource": "resources[0]",
60
+ "aws.lambda.eventSource.time": "time"
61
+ },
62
+ "name": "cloudWatch_scheduled",
63
+ "required_keys": [
64
+ "detail-type",
65
+ "source"
66
+ ]
67
+ },
68
+
69
+ "dynamoStreams": {
70
+ "attributes": {
71
+ "aws.lambda.eventSource.length": "Records.length"
72
+ },
73
+ "name": "dynamo_streams",
74
+ "required_keys": [
75
+ "Records[0].dynamodb"
76
+ ]
77
+ },
78
+
79
+ "firehose": {
80
+ "attributes": {
81
+ "aws.lambda.eventSource.length": "records.length",
82
+ "aws.lambda.eventSource.region": "region"
83
+ },
84
+ "name": "firehose",
85
+ "required_keys": [
86
+ "deliveryStreamArn",
87
+ "records[0].kinesisRecordMetadata"
88
+ ]
89
+ },
90
+
91
+ "kinesis": {
92
+ "attributes": {
93
+ "aws.lambda.eventSource.length": "Records.length",
94
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
95
+ },
96
+ "name": "kinesis",
97
+ "required_keys": [
98
+ "Records[0].kinesis"
99
+ ]
100
+ },
101
+
102
+ "s3": {
103
+ "attributes": {
104
+ "aws.lambda.eventSource.bucketName": "Records[0].s3.bucket.name",
105
+ "aws.lambda.eventSource.eventName": "Records[0].eventName",
106
+ "aws.lambda.eventSource.eventTime": "Records[0].eventTime",
107
+ "aws.lambda.eventSource.length": "Records.length",
108
+ "aws.lambda.eventSource.objectKey": "Records[0].s3.object.key",
109
+ "aws.lambda.eventSource.objectSequencer": "Records[0].s3.object.sequencer",
110
+ "aws.lambda.eventSource.objectSize": "Records[0].s3.object.size",
111
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
112
+ },
113
+ "name": "s3",
114
+ "required_keys": [
115
+ "Records[0].s3"
116
+ ]
117
+ },
118
+
119
+ "ses": {
120
+ "attributes": {
121
+ "aws.lambda.eventSource.date": "Records[0].ses.mail.commonHeaders.date",
122
+ "aws.lambda.eventSource.length": "Records.length",
123
+ "aws.lambda.eventSource.messageId": "Records[0].ses.mail.commonHeaders.messageId",
124
+ "aws.lambda.eventSource.returnPath": "Records[0].ses.mail.commonHeaders.returnPath"
125
+ },
126
+ "name": "ses",
127
+ "required_keys": [
128
+ "Records[0].ses"
129
+ ]
130
+ },
131
+
132
+ "sns": {
133
+ "attributes": {
134
+ "aws.lambda.eventSource.length": "Records.length",
135
+ "aws.lambda.eventSource.messageId": "Records[0].Sns.MessageId",
136
+ "aws.lambda.eventSource.timestamp": "Records[0].Sns.Timestamp",
137
+ "aws.lambda.eventSource.topicArn": "Records[0].Sns.TopicArn",
138
+ "aws.lambda.eventSource.type": "Records[0].Sns.Type"
139
+ },
140
+ "name": "sns",
141
+ "required_keys": [
142
+ "Records[0].Sns"
143
+ ]
144
+ },
145
+
146
+ "sqs": {
147
+ "attributes": {
148
+ "aws.lambda.eventSource.length": "Records.length"
149
+ },
150
+ "name": "sqs",
151
+ "required_keys": [
152
+ "Records[0].receiptHandle"
153
+ ]
154
+ }
155
+ }