newrelic_rpm 9.12.0 → 9.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +217 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +16 -20
- data/lib/boot/strap.rb +4 -3
- data/lib/new_relic/agent/agent.rb +4 -0
- data/lib/new_relic/agent/agent_helpers/connect.rb +3 -0
- data/lib/new_relic/agent/agent_helpers/harvest.rb +3 -0
- data/lib/new_relic/agent/agent_helpers/shutdown.rb +3 -0
- data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -0
- data/lib/new_relic/agent/agent_helpers/startup.rb +7 -0
- data/lib/new_relic/agent/aws.rb +6 -0
- data/lib/new_relic/agent/configuration/default_source.rb +363 -31
- data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
- data/lib/new_relic/agent/configuration/manager.rb +23 -0
- data/lib/new_relic/agent/configuration/yaml_source.rb +6 -1
- data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
- data/lib/new_relic/agent/database.rb +41 -1
- data/lib/new_relic/agent/distributed_tracing.rb +2 -2
- data/lib/new_relic/agent/health_check.rb +136 -0
- data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
- data/lib/new_relic/agent/instrumentation/active_record.rb +1 -8
- data/lib/new_relic/agent/instrumentation/active_record_helper.rb +5 -1
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +9 -16
- data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
- data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
- data/lib/new_relic/agent/instrumentation/async_http.rb +1 -2
- data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/chain.rb +21 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/instrumentation.rb +66 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/prepend.rb +15 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_firehose.rb +22 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/chain.rb +21 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/instrumentation.rb +91 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/prepend.rb +15 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis.rb +22 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
- data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
- data/lib/new_relic/agent/instrumentation/aws_sqs.rb +0 -2
- data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
- data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +0 -2
- data/lib/new_relic/agent/instrumentation/curb.rb +3 -4
- data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
- data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +1 -1
- data/lib/new_relic/agent/instrumentation/dynamodb.rb +0 -2
- data/lib/new_relic/agent/instrumentation/elasticsearch.rb +0 -2
- data/lib/new_relic/agent/instrumentation/ethon.rb +0 -4
- data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
- data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
- data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +0 -3
- data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
- data/lib/new_relic/agent/instrumentation/httpclient.rb +0 -1
- data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
- data/lib/new_relic/agent/instrumentation/httpx.rb +0 -4
- data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
- data/lib/new_relic/agent/instrumentation/logstasher.rb +0 -2
- data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
- data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
- data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
- data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
- data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
- data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
- data/lib/new_relic/agent/instrumentation/rake.rb +0 -1
- data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
- data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
- data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
- data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
- data/lib/new_relic/agent/instrumentation/redis.rb +7 -6
- data/lib/new_relic/agent/instrumentation/resque.rb +7 -5
- data/lib/new_relic/agent/instrumentation/roda.rb +4 -4
- data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
- data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delayed_class.rb +1 -1
- data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
- data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
- data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
- data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
- data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
- data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +11 -5
- data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
- data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
- data/lib/new_relic/agent/local_log_decorator.rb +12 -2
- data/lib/new_relic/agent/log_event_aggregator.rb +28 -2
- data/lib/new_relic/agent/messaging.rb +11 -5
- data/lib/new_relic/agent/new_relic_service.rb +8 -2
- data/lib/new_relic/agent/serverless_handler.rb +241 -12
- data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
- data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
- data/lib/new_relic/agent/span_event_primitive.rb +4 -2
- data/lib/new_relic/agent/system_info.rb +14 -0
- data/lib/new_relic/agent/threading/backtrace_node.rb +10 -1
- data/lib/new_relic/agent/transaction/message_broker_segment.rb +3 -0
- data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
- data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
- data/lib/new_relic/agent.rb +95 -2
- data/lib/new_relic/control/frameworks/grape.rb +14 -0
- data/lib/new_relic/control/frameworks/padrino.rb +14 -0
- data/lib/new_relic/control/frameworks/rails4.rb +1 -3
- data/lib/new_relic/dependency_detection.rb +11 -13
- data/lib/new_relic/environment_report.rb +2 -2
- data/lib/new_relic/helper.rb +15 -0
- data/lib/new_relic/language_support.rb +3 -1
- data/lib/new_relic/local_environment.rb +1 -4
- data/lib/new_relic/version.rb +1 -1
- data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
- data/lib/tasks/helpers/newrelicyml.rb +73 -11
- data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -1
- data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +11 -8
- data/newrelic.yml +224 -79
- data/test/agent_helper.rb +8 -1
- metadata +32 -6
@@ -25,6 +25,7 @@ module NewRelic
|
|
25
25
|
METRICS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Metrics/Ruby/%s'.freeze
|
26
26
|
FORWARDING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Forwarding/Ruby/%s'.freeze
|
27
27
|
DECORATING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/LocalDecorating/Ruby/%s'.freeze
|
28
|
+
LABELS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Labels/Ruby/%s'.freeze
|
28
29
|
MAX_BYTES = 32768 # 32 * 1024 bytes (32 kibibytes)
|
29
30
|
|
30
31
|
named :LogEventAggregator
|
@@ -38,6 +39,7 @@ module NewRelic
|
|
38
39
|
METRICS_ENABLED_KEY = :'application_logging.metrics.enabled'
|
39
40
|
FORWARDING_ENABLED_KEY = :'application_logging.forwarding.enabled'
|
40
41
|
DECORATING_ENABLED_KEY = :'application_logging.local_decorating.enabled'
|
42
|
+
LABELS_ENABLED_KEY = :'application_logging.forwarding.labels.enabled'
|
41
43
|
LOG_LEVEL_KEY = :'application_logging.forwarding.log_level'
|
42
44
|
CUSTOM_ATTRIBUTES_KEY = :'application_logging.forwarding.custom_attributes'
|
43
45
|
|
@@ -51,6 +53,7 @@ module NewRelic
|
|
51
53
|
@high_security = NewRelic::Agent.config[:high_security]
|
52
54
|
@instrumentation_logger_enabled = NewRelic::Agent::Instrumentation::Logger.enabled?
|
53
55
|
@attributes = NewRelic::Agent::LogEventAttributes.new
|
56
|
+
|
54
57
|
register_for_done_configuring(events)
|
55
58
|
end
|
56
59
|
|
@@ -186,6 +189,10 @@ module NewRelic
|
|
186
189
|
attributes.add_custom_attributes(custom_attributes)
|
187
190
|
end
|
188
191
|
|
192
|
+
def labels
|
193
|
+
@labels ||= create_labels
|
194
|
+
end
|
195
|
+
|
189
196
|
# Because our transmission format (MELT) is different than historical
|
190
197
|
# agent payloads, extract the munging here to keep the service focused
|
191
198
|
# on the general harvest + transmit instead of the format.
|
@@ -201,8 +208,9 @@ module NewRelic
|
|
201
208
|
# To save on unnecessary data transmission, trim the entity.type
|
202
209
|
# sent by classic logs-in-context
|
203
210
|
common_attributes.delete(ENTITY_TYPE_KEY)
|
204
|
-
|
205
|
-
common_attributes.merge!(
|
211
|
+
aggregator = NewRelic::Agent.agent.log_event_aggregator
|
212
|
+
common_attributes.merge!(aggregator.attributes.custom_attributes)
|
213
|
+
common_attributes.merge!(aggregator.labels)
|
206
214
|
|
207
215
|
_, items = data
|
208
216
|
payload = [{
|
@@ -247,6 +255,7 @@ module NewRelic
|
|
247
255
|
record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
|
248
256
|
record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
|
249
257
|
record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
|
258
|
+
record_configuration_metric(LABELS_SUPPORTABILITY_FORMAT, LABELS_ENABLED_KEY)
|
250
259
|
|
251
260
|
add_custom_attributes(NewRelic::Agent.config[CUSTOM_ATTRIBUTES_KEY])
|
252
261
|
end
|
@@ -327,6 +336,23 @@ module NewRelic
|
|
327
336
|
|
328
337
|
Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(configured_log_level_constant)
|
329
338
|
end
|
339
|
+
|
340
|
+
def create_labels
|
341
|
+
return NewRelic::EMPTY_HASH unless NewRelic::Agent.config[LABELS_ENABLED_KEY]
|
342
|
+
|
343
|
+
downcased_exclusions = NewRelic::Agent.config[:'application_logging.forwarding.labels.exclude'].map(&:downcase)
|
344
|
+
log_labels = {}
|
345
|
+
|
346
|
+
NewRelic::Agent.config.parsed_labels.each do |parsed_label|
|
347
|
+
next if downcased_exclusions.include?(parsed_label['label_type'].downcase)
|
348
|
+
|
349
|
+
# labels are referred to as tags in the UI, so prefix the
|
350
|
+
# label-related attributes with 'tags.*'
|
351
|
+
log_labels["tags.#{parsed_label['label_type']}"] = parsed_label['label_value']
|
352
|
+
end
|
353
|
+
|
354
|
+
log_labels
|
355
|
+
end
|
330
356
|
end
|
331
357
|
end
|
332
358
|
end
|
@@ -117,7 +117,8 @@ module NewRelic
|
|
117
117
|
queue_name: nil,
|
118
118
|
exchange_type: nil,
|
119
119
|
reply_to: nil,
|
120
|
-
correlation_id: nil
|
120
|
+
correlation_id: nil,
|
121
|
+
action: nil)
|
121
122
|
|
122
123
|
state = Tracer.state
|
123
124
|
return yield if state.current_transaction
|
@@ -125,12 +126,12 @@ module NewRelic
|
|
125
126
|
txn = nil
|
126
127
|
|
127
128
|
begin
|
128
|
-
txn_name = transaction_name(library, destination_type, destination_name)
|
129
|
+
txn_name = transaction_name(library, destination_type, destination_name, action)
|
129
130
|
|
130
131
|
txn = Tracer.start_transaction(name: txn_name, category: :message)
|
131
|
-
|
132
132
|
if headers
|
133
|
-
|
133
|
+
NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) # to handle the new w3c headers
|
134
|
+
txn.distributed_tracer.consume_message_headers(headers, state, library) # to do the expected old things
|
134
135
|
CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v|
|
135
136
|
txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil?
|
136
137
|
end
|
@@ -327,12 +328,17 @@ module NewRelic
|
|
327
328
|
NewRelic::Agent.config[:'message_tracer.segment_parameters.enabled']
|
328
329
|
end
|
329
330
|
|
330
|
-
def transaction_name(library, destination_type, destination_name)
|
331
|
+
def transaction_name(library, destination_type, destination_name, action = nil)
|
331
332
|
transaction_name = Transaction::MESSAGE_PREFIX + library
|
332
333
|
transaction_name << NewRelic::SLASH
|
333
334
|
transaction_name << Transaction::MessageBrokerSegment::TYPES[destination_type]
|
334
335
|
transaction_name << NewRelic::SLASH
|
335
336
|
|
337
|
+
if action == :consume
|
338
|
+
transaction_name << 'Consume'
|
339
|
+
transaction_name << NewRelic::SLASH
|
340
|
+
end
|
341
|
+
|
336
342
|
case destination_type
|
337
343
|
when :queue
|
338
344
|
transaction_name << Transaction::MessageBrokerSegment::NAMED
|
@@ -455,6 +455,8 @@ module NewRelic
|
|
455
455
|
end
|
456
456
|
|
457
457
|
def handle_error_response(response, endpoint)
|
458
|
+
NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::HTTP_ERROR, [response.code, endpoint])
|
459
|
+
|
458
460
|
case response
|
459
461
|
when Net::HTTPRequestTimeOut,
|
460
462
|
Net::HTTPTooManyRequests,
|
@@ -637,9 +639,13 @@ module NewRelic
|
|
637
639
|
def send_request(opts)
|
638
640
|
request = prep_request(opts)
|
639
641
|
response = relay_request(request, opts)
|
640
|
-
return response if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
641
642
|
|
642
|
-
|
643
|
+
if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
644
|
+
NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::HEALTHY)
|
645
|
+
response
|
646
|
+
else
|
647
|
+
handle_error_response(response, opts[:endpoint])
|
648
|
+
end
|
643
649
|
end
|
644
650
|
|
645
651
|
def log_response(response)
|
@@ -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:
|
41
|
-
|
43
|
+
NewRelic::Agent::Tracer.in_transaction(category: category, name: function_name) do
|
44
|
+
prep_transaction
|
42
45
|
|
43
|
-
NewRelic::LanguageSupport.constantize(namespace)
|
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(
|
150
|
-
add_agent_attribute(
|
151
|
-
add_agent_attribute(
|
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
|
+
}
|
@@ -0,0 +1,49 @@
|
|
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
|
+
|
7
|
+
module NewRelic
|
8
|
+
module Agent
|
9
|
+
# ServerlessHandlerEventSources - New Relic's language agent devs maintain
|
10
|
+
# a cross-agent JSON map of all AWS resources with the potential to invoke
|
11
|
+
# an AWS Lambda function by issuing it an event. This map is used to glean
|
12
|
+
# source specific attributes while instrumenting the function's invocation.
|
13
|
+
#
|
14
|
+
# Given that the event arrives as a Ruby hash argument to the AWS Lambda
|
15
|
+
# function, the JSON map's values need to be converted into arrays that can
|
16
|
+
# be passed to `Hash#dig`. So a value such as `'records[0].name'` needs to
|
17
|
+
# be converted to `['records', 0, 'name']`. This class's `.to_hash` method
|
18
|
+
# yields the converted data.
|
19
|
+
#
|
20
|
+
# Furthermore, `.length` calls are converted to Ruby `#size` notation to
|
21
|
+
# denote that a method call must be performed on the dug value.
|
22
|
+
class ServerlessHandlerEventSources
|
23
|
+
JSON_SOURCE = File.join(File.dirname(__FILE__), 'serverless_handler_event_sources.json').freeze
|
24
|
+
JSON_RAW = JSON.parse(File.read(JSON_SOURCE)).freeze
|
25
|
+
|
26
|
+
def self.to_hash
|
27
|
+
JSON_RAW.each_with_object({}) do |(type, info), hash|
|
28
|
+
hash[type] = {'attributes' => {},
|
29
|
+
'name' => info['name'],
|
30
|
+
'required_keys' => []}
|
31
|
+
info['attributes'].each { |attr, value| hash[type]['attributes'][attr] = transform(value) }
|
32
|
+
info['required_keys'].each { |key| hash[type]['required_keys'].push(transform(key)) }
|
33
|
+
end.freeze
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.transform(value)
|
37
|
+
value.gsub(/\[(\d+)\]/, '.\1').split('.').map do |e|
|
38
|
+
if e.match?(/^\d+$/)
|
39
|
+
e.to_i
|
40
|
+
elsif e == 'length'
|
41
|
+
'#size'
|
42
|
+
else
|
43
|
+
e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|