newrelic_rpm 9.7.0 → 9.16.1
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 +376 -2
- data/README.md +17 -18
- data/Rakefile +1 -1
- data/lib/boot/strap.rb +101 -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 +3 -1
- data/lib/new_relic/agent/aws.rb +68 -0
- data/lib/new_relic/agent/configuration/default_source.rb +519 -23
- data/lib/new_relic/agent/configuration/environment_source.rb +14 -2
- data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
- data/lib/new_relic/agent/configuration/manager.rb +51 -8
- data/lib/new_relic/agent/configuration/security_policy_source.rb +11 -0
- 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/custom_event_aggregator.rb +27 -1
- data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
- data/lib/new_relic/agent/database/obfuscator.rb +1 -0
- data/lib/new_relic/agent/database.rb +39 -0
- data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
- data/lib/new_relic/agent/error_collector.rb +39 -10
- data/lib/new_relic/agent/harvester.rb +1 -1
- 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 +3 -0
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +1 -12
- data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/instrumentation.rb +7 -3
- 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 +4 -3
- 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/chain.rb +37 -0
- data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
- data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
- data/lib/new_relic/agent/instrumentation/aws_sqs.rb +23 -0
- data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
- data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
- data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -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/chain.rb +27 -0
- data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +64 -0
- data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
- data/lib/new_relic/agent/instrumentation/dynamodb.rb +23 -0
- data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +58 -8
- 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.rb +1 -1
- data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
- data/lib/new_relic/agent/instrumentation/grpc_server.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/chain.rb +21 -0
- data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
- data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
- data/lib/new_relic/agent/instrumentation/logstasher.rb +25 -0
- data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
- data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +6 -0
- 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/rack/instrumentation.rb +3 -0
- data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
- 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/cluster_middleware.rb +26 -0
- data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
- data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
- data/lib/new_relic/agent/instrumentation/redis.rb +11 -5
- data/lib/new_relic/agent/instrumentation/resque.rb +0 -4
- 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/ruby_openai/chain.rb +36 -0
- data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +196 -0
- data/lib/new_relic/agent/instrumentation/ruby_openai/prepend.rb +20 -0
- data/lib/new_relic/agent/instrumentation/ruby_openai.rb +35 -0
- 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/stripe_subscriber.rb +22 -1
- 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 +13 -6
- 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/llm/chat_completion_message.rb +25 -0
- data/lib/new_relic/agent/llm/chat_completion_summary.rb +66 -0
- data/lib/new_relic/agent/llm/embedding.rb +60 -0
- data/lib/new_relic/agent/llm/llm_event.rb +95 -0
- data/lib/new_relic/agent/llm/response_headers.rb +80 -0
- data/lib/new_relic/agent/llm.rb +49 -0
- data/lib/new_relic/agent/local_log_decorator.rb +8 -1
- data/lib/new_relic/agent/log_event_aggregator.rb +120 -44
- data/lib/new_relic/agent/messaging.rb +11 -5
- data/lib/new_relic/agent/new_relic_service.rb +12 -2
- data/lib/new_relic/agent/serverless_handler.rb +400 -0
- 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 +8 -10
- data/lib/new_relic/agent/system_info.rb +14 -0
- data/lib/new_relic/agent/threading/agent_thread.rb +1 -2
- data/lib/new_relic/agent/tracer.rb +5 -5
- data/lib/new_relic/agent/transaction/abstract_segment.rb +1 -1
- data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
- 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/transaction/tracing.rb +2 -2
- 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 +198 -10
- data/lib/new_relic/constants.rb +2 -0
- 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/control/instance_methods.rb +8 -0
- data/lib/new_relic/control/private_instance_methods.rb +4 -0
- data/lib/new_relic/control/security_interface.rb +57 -0
- data/lib/new_relic/control.rb +1 -1
- data/lib/new_relic/dependency_detection.rb +10 -5
- 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 +14 -10
- data/lib/new_relic/rack/browser_monitoring.rb +28 -12
- data/lib/new_relic/supportability_helper.rb +2 -0
- data/lib/new_relic/thread_local_storage.rb +31 -0
- data/lib/new_relic/version.rb +2 -2
- data/lib/sequel/extensions/new_relic_instrumentation.rb +3 -2
- data/lib/tasks/config.rake +8 -3
- data/lib/tasks/gha.rake +31 -0
- data/lib/tasks/helpers/config.html.erb +3 -2
- data/lib/tasks/helpers/format.rb +1 -1
- data/lib/tasks/helpers/newrelicyml.rb +76 -13
- data/lib/tasks/instrumentation_generator/instrumentation.thor +31 -22
- data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -1
- data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -1
- data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +11 -8
- data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +1 -1
- data/newrelic.yml +387 -143
- data/newrelic_rpm.gemspec +2 -0
- data/test/agent_helper.rb +17 -2
- metadata +80 -3
@@ -0,0 +1,400 @@
|
|
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
|
+
require 'uri'
|
8
|
+
|
9
|
+
require_relative 'serverless_handler_event_sources'
|
10
|
+
|
11
|
+
module NewRelic
|
12
|
+
module Agent
|
13
|
+
class ServerlessHandler
|
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
|
+
DIGIT = /\d/
|
26
|
+
EVENT_SOURCES = NewRelic::Agent::ServerlessHandlerEventSources.to_hash
|
27
|
+
|
28
|
+
def self.env_var_set?
|
29
|
+
ENV.key?(LAMBDA_ENVIRONMENT_VARIABLE)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@event = nil
|
34
|
+
@context = nil
|
35
|
+
@payloads = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def invoke_lambda_function_with_new_relic(event:, context:, method_name:, namespace: nil)
|
39
|
+
NewRelic::Agent.increment_metric(SUPPORTABILITY_METRIC)
|
40
|
+
|
41
|
+
@event, @context = event, context
|
42
|
+
|
43
|
+
NewRelic::Agent::Tracer.in_transaction(category: category, name: function_name) do
|
44
|
+
prep_transaction
|
45
|
+
|
46
|
+
process_response(NewRelic::LanguageSupport.constantize(namespace)
|
47
|
+
.send(method_name, event: event, context: context))
|
48
|
+
end
|
49
|
+
ensure
|
50
|
+
harvest!
|
51
|
+
write_output
|
52
|
+
reset!
|
53
|
+
end
|
54
|
+
|
55
|
+
def store_payload(method, payload)
|
56
|
+
return if METHOD_BLOCKLIST.include?(method)
|
57
|
+
|
58
|
+
@payloads[method] = payload
|
59
|
+
end
|
60
|
+
|
61
|
+
def metric_data(stats_hash)
|
62
|
+
payload = [nil,
|
63
|
+
stats_hash.started_at,
|
64
|
+
(stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)),
|
65
|
+
[]]
|
66
|
+
stats_hash.each do |metric_spec, stats|
|
67
|
+
next if stats.is_reset?
|
68
|
+
|
69
|
+
hash = {name: metric_spec.name}
|
70
|
+
hash[:scope] = metric_spec.scope unless metric_spec.scope.empty?
|
71
|
+
|
72
|
+
payload.last.push([hash, [
|
73
|
+
stats.call_count,
|
74
|
+
stats.total_call_time,
|
75
|
+
stats.total_exclusive_time,
|
76
|
+
stats.min_call_time,
|
77
|
+
stats.max_call_time,
|
78
|
+
stats.sum_of_squares
|
79
|
+
]])
|
80
|
+
end
|
81
|
+
|
82
|
+
return if payload.last.empty?
|
83
|
+
|
84
|
+
store_payload(:metric_data, payload)
|
85
|
+
end
|
86
|
+
|
87
|
+
def error_data(errors)
|
88
|
+
store_payload(:error_data, [nil, errors.map(&:to_collector_array)])
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def prep_transaction
|
94
|
+
process_api_gateway_info
|
95
|
+
process_headers
|
96
|
+
add_agent_attributes
|
97
|
+
end
|
98
|
+
|
99
|
+
def harvest!
|
100
|
+
NewRelic::Agent.instance.harvest_and_send_analytic_event_data
|
101
|
+
NewRelic::Agent.instance.harvest_and_send_custom_event_data
|
102
|
+
NewRelic::Agent.instance.harvest_and_send_data_types
|
103
|
+
end
|
104
|
+
|
105
|
+
def metadata
|
106
|
+
m = {arn: @context.invoked_function_arn,
|
107
|
+
protocol_version: NewRelic::Agent::NewRelicService::PROTOCOL_VERSION,
|
108
|
+
function_version: @context.function_version,
|
109
|
+
execution_environment: EXECUTION_ENVIRONMENT,
|
110
|
+
agent_version: NewRelic::VERSION::STRING}
|
111
|
+
if PAYLOAD_VERSION >= 2
|
112
|
+
m[:metadata_version] = PAYLOAD_VERSION
|
113
|
+
m[:agent_language] = NewRelic::LANGUAGE
|
114
|
+
end
|
115
|
+
m
|
116
|
+
end
|
117
|
+
|
118
|
+
def function_name
|
119
|
+
ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
|
120
|
+
end
|
121
|
+
|
122
|
+
def category
|
123
|
+
@category ||=
|
124
|
+
@event&.dig('requestContext', 'http', 'method') || @event&.fetch('httpMethod', nil) ? :web : :other
|
125
|
+
end
|
126
|
+
|
127
|
+
def write_output
|
128
|
+
string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
|
129
|
+
|
130
|
+
return puts string unless use_named_pipe?
|
131
|
+
|
132
|
+
File.write(NAMED_PIPE, string)
|
133
|
+
|
134
|
+
NewRelic::Agent.logger.debug "Wrote serverless payload to #{NAMED_PIPE}\n" \
|
135
|
+
"BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
|
136
|
+
end
|
137
|
+
|
138
|
+
def payload_v1 # New Relic serverless payload v1
|
139
|
+
payload_hash = {'metadata' => metadata, 'data' => @payloads}
|
140
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
|
141
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
142
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
143
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, base64_encoded]
|
144
|
+
::JSON.dump(array)
|
145
|
+
end
|
146
|
+
|
147
|
+
def payload_v2 # New Relic serverless payload v2
|
148
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
|
149
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
150
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
151
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, metadata, base64_encoded]
|
152
|
+
::JSON.dump(array)
|
153
|
+
end
|
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
|
+
|
237
|
+
def use_named_pipe?
|
238
|
+
return @use_named_pipe if defined?(@use_named_pipe)
|
239
|
+
|
240
|
+
@use_named_pipe = File.exist?(NAMED_PIPE) && File.writable?(NAMED_PIPE)
|
241
|
+
end
|
242
|
+
|
243
|
+
def add_agent_attributes
|
244
|
+
return unless NewRelic::Agent::Tracer.current_transaction
|
245
|
+
|
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
|
365
|
+
end
|
366
|
+
|
367
|
+
def add_agent_attribute(attribute, value)
|
368
|
+
NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
|
369
|
+
end
|
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
|
+
|
382
|
+
def cold?
|
383
|
+
return @cold if defined?(@cold)
|
384
|
+
|
385
|
+
@cold = false
|
386
|
+
true
|
387
|
+
end
|
388
|
+
|
389
|
+
def reset!
|
390
|
+
@event = nil
|
391
|
+
@category = nil
|
392
|
+
@context = nil
|
393
|
+
@headers = nil
|
394
|
+
@http_method = nil
|
395
|
+
@http_uri = nil
|
396
|
+
@payloads.replace({})
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
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
|
@@ -52,6 +52,8 @@ module NewRelic
|
|
52
52
|
DATASTORE_CATEGORY = 'datastore'
|
53
53
|
CLIENT = 'client'
|
54
54
|
|
55
|
+
DB_STATEMENT_MAX_BYTES = 4096
|
56
|
+
|
55
57
|
# Builds a Hash of error attributes as well as the Span ID when
|
56
58
|
# an error is present. Otherwise, returns nil when no error present.
|
57
59
|
def error_attributes(segment)
|
@@ -79,17 +81,13 @@ module NewRelic
|
|
79
81
|
intrinsics[SPAN_KIND_KEY] = CLIENT
|
80
82
|
intrinsics[SERVER_ADDRESS_KEY] = segment.uri.host
|
81
83
|
intrinsics[SERVER_PORT_KEY] = segment.uri.port
|
82
|
-
agent_attributes =
|
84
|
+
agent_attributes = {}
|
83
85
|
|
84
86
|
if allowed?(HTTP_URL_KEY)
|
85
87
|
agent_attributes[HTTP_URL_KEY] = truncate(segment.uri)
|
86
88
|
end
|
87
89
|
|
88
|
-
|
89
|
-
agent_attributes.merge!(agent_attributes(segment))
|
90
|
-
end
|
91
|
-
|
92
|
-
[intrinsics, custom_attributes(segment), agent_attributes]
|
90
|
+
[intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
|
93
91
|
end
|
94
92
|
|
95
93
|
def for_datastore_segment(segment) # rubocop:disable Metrics/AbcSize
|
@@ -99,7 +97,7 @@ module NewRelic
|
|
99
97
|
intrinsics[SPAN_KIND_KEY] = CLIENT
|
100
98
|
intrinsics[CATEGORY_KEY] = DATASTORE_CATEGORY
|
101
99
|
|
102
|
-
agent_attributes =
|
100
|
+
agent_attributes = {}
|
103
101
|
|
104
102
|
if segment.database_name && allowed?(DB_INSTANCE_KEY)
|
105
103
|
agent_attributes[DB_INSTANCE_KEY] = truncate(segment.database_name)
|
@@ -118,12 +116,12 @@ module NewRelic
|
|
118
116
|
agent_attributes[DB_SYSTEM_KEY] = segment.product if allowed?(DB_SYSTEM_KEY)
|
119
117
|
|
120
118
|
if segment.sql_statement && allowed?(DB_STATEMENT_KEY)
|
121
|
-
agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql,
|
119
|
+
agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql, DB_STATEMENT_MAX_BYTES)
|
122
120
|
elsif segment.nosql_statement && allowed?(DB_STATEMENT_KEY)
|
123
|
-
agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement,
|
121
|
+
agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, DB_STATEMENT_MAX_BYTES)
|
124
122
|
end
|
125
123
|
|
126
|
-
[intrinsics, custom_attributes(segment), agent_attributes]
|
124
|
+
[intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
|
127
125
|
end
|
128
126
|
|
129
127
|
private
|
@@ -19,6 +19,20 @@ module NewRelic
|
|
19
19
|
RbConfig::CONFIG['target_os']
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.os_distribution
|
23
|
+
case
|
24
|
+
when darwin? then :darwin
|
25
|
+
when linux? then :linux
|
26
|
+
when bsd? then :bsd
|
27
|
+
when windows? then :windows
|
28
|
+
else ruby_os_identifier
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.windows?
|
33
|
+
!!(ruby_os_identifier[/mingw|mswin/i])
|
34
|
+
end
|
35
|
+
|
22
36
|
def self.darwin?
|
23
37
|
!!(ruby_os_identifier =~ /darwin/i)
|
24
38
|
end
|
@@ -9,8 +9,7 @@ module NewRelic
|
|
9
9
|
def self.create(label, &blk)
|
10
10
|
::NewRelic::Agent.logger.debug("Creating AgentThread: #{label}")
|
11
11
|
wrapped_blk = proc do
|
12
|
-
if
|
13
|
-
txn = ::Thread.current[:newrelic_tracer_state].current_transaction
|
12
|
+
if (txn = ::NewRelic::ThreadLocalStorage[:newrelic_tracer_state]&.current_transaction)
|
14
13
|
::NewRelic::Agent.logger.warn("AgentThread created with current transaction #{txn.best_name}")
|
15
14
|
end
|
16
15
|
begin
|