newrelic_rpm 9.8.0 → 9.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -2
- data/lib/new_relic/agent/agent.rb +4 -1
- data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
- data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
- data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
- data/lib/new_relic/agent/agent_logger.rb +2 -1
- data/lib/new_relic/agent/configuration/default_source.rb +30 -2
- data/lib/new_relic/agent/configuration/environment_source.rb +9 -1
- data/lib/new_relic/agent/configuration/manager.rb +22 -5
- data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
- data/lib/new_relic/agent/connect/request_builder.rb +1 -1
- data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
- data/lib/new_relic/agent/harvester.rb +1 -1
- data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +6 -1
- data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +1 -2
- data/lib/new_relic/agent/log_event_aggregator.rb +1 -16
- data/lib/new_relic/agent/new_relic_service.rb +12 -2
- data/lib/new_relic/agent/serverless_handler.rb +171 -0
- data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
- data/lib/new_relic/agent.rb +11 -8
- data/lib/new_relic/constants.rb +2 -0
- data/lib/new_relic/control/instance_methods.rb +7 -0
- data/lib/new_relic/local_environment.rb +13 -6
- data/lib/new_relic/version.rb +1 -1
- data/lib/tasks/config.rake +2 -1
- data/newrelic.yml +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad2c3da566c3fda3369af14dd1d25b82a582c3db88c910474634b1b8169648fc
|
4
|
+
data.tar.gz: 1ebe2637f6cef4b3b3e70afb157b91516cc6bf37511f1acb1f3ec5e1caf917e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d86c1de9e30e3952b7d5dbec62845693795e6c3b0273b5f34a19a0a550e208314774d3da08246c26c8e903657c36b71b91ae170911869f3e801879e8c8ee4e05
|
7
|
+
data.tar.gz: bb5732481d984d24366be27b1186e35b753b7dff7613774b8f4f50749e516db6556edd93b1017e656535cf83ef306e31199e44b03d677ea8c249f472f7f30222
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# New Relic Ruby Agent Release Notes
|
2
2
|
|
3
|
+
## v9.9.0
|
4
|
+
|
5
|
+
Version 9.9.0 introduces support for AWS Lambda serverless function observability, adds support for Elasticsearch 8.13.0, and adds the 'request.temperature' attribute to chat completion summaries in ruby-openai instrumentation.
|
6
|
+
|
7
|
+
- **Feature: Serverless Mode for AWS Lambda**
|
8
|
+
|
9
|
+
The Ruby agent is now capable of operating in a quick and light serverless mode suitable for observing AWS Lambda function invocations. For serverless use, the agent is delivered by a New Relic Lambda [layer](https://github.com/newrelic/newrelic-lambda-layers) that can be associated with a Lambda function. All reported data will appear in New Relic's dedicated serverless UI views. Only AWS based Lambda functions are supported for now, though support for other cloud hosted serverless offerings may be added in future depending on Ruby customer demand. The serverless functionality is only intended for use with the official New Relic Ruby layers for Lambda. Any existing workflows that involve the manual use of the Ruby agent in an AWS Lambda context without a New Relic layer should not be impacted.
|
10
|
+
|
11
|
+
- **Feature: Add support for Elasticsearch 8.13.0**
|
12
|
+
|
13
|
+
Elasticsearch 8.13.0 increased the number of arguments used in the method the agent instruments, `Elastic::Transport::Client#perform_request`. Now, the agent supports a variable number of arguments for the instrumented method to prevent future `ArgumentError`s.
|
14
|
+
|
15
|
+
- **Bugfix: Add 'request.temperature' to ruby-openai chat completion summaries**
|
16
|
+
|
17
|
+
Previously, the agent was not reporting the `request.temperature` attribute on `LlmChatCompletionSummary` events through ruby-openai instrumentation. We are now reporting this attribute.
|
18
|
+
|
3
19
|
## v9.8.0
|
4
20
|
|
5
21
|
Version 9.8.0 introduces instrumentation for ruby-openai, adds the option to store tracer state on the thread-level, hardens the browser agent insertion logic to better proactively anticipate errors, and prevents excpetions from being raised in the Active Support Broadcast logger instrumentation.
|
@@ -15,7 +31,7 @@ Version 9.8.0 introduces instrumentation for ruby-openai, adds the option to sto
|
|
15
31
|
This version introduces two new APIs that allow users to record additional information on LLM events:
|
16
32
|
* `NewRelic::Agent.record_llm_feedback_event` - Records user feedback events.
|
17
33
|
* `NewRelic::Agent.set_llm_token_count_callback` - Sets a callback proc for calculating `token_count` attributes for embedding and chat completion message events.
|
18
|
-
|
34
|
+
|
19
35
|
Visit [RubyDoc](https://rubydoc.info/github/newrelic/newrelic-ruby-agent/) for more information on each of these APIs.
|
20
36
|
|
21
37
|
- **Feature: Store tracer state on thread-level**
|
@@ -49,7 +65,7 @@ Version 9.7.0 introduces ViewComponent instrumentation, changes the endpoint use
|
|
49
65
|
|
50
66
|
- **Feature: ViewComponent instrumentation**
|
51
67
|
|
52
|
-
[ViewComponent](https://viewcomponent.org/) is a now an instrumented library. [PR#2367](https://github.com/newrelic/newrelic-ruby-agent/pull/2367)
|
68
|
+
[ViewComponent](https://viewcomponent.org/) is a now an instrumented library. [PR#2367](https://github.com/newrelic/newrelic-ruby-agent/pull/2367)
|
53
69
|
|
54
70
|
- **Feature: Use root path to access Elasticsearch cluster name**
|
55
71
|
|
@@ -34,6 +34,7 @@ require 'new_relic/agent/utilization_data'
|
|
34
34
|
require 'new_relic/environment_report'
|
35
35
|
require 'new_relic/agent/attribute_filter'
|
36
36
|
require 'new_relic/agent/adaptive_sampler'
|
37
|
+
require 'new_relic/agent/serverless_handler'
|
37
38
|
require 'new_relic/agent/connect/request_builder'
|
38
39
|
require 'new_relic/agent/connect/response_handler'
|
39
40
|
|
@@ -96,6 +97,7 @@ module NewRelic
|
|
96
97
|
@monotonic_gc_profiler = VM::MonotonicGCProfiler.new
|
97
98
|
@adaptive_sampler = AdaptiveSampler.new(Agent.config[:sampling_target],
|
98
99
|
Agent.config[:sampling_target_period_in_seconds])
|
100
|
+
@serverless_handler = ServerlessHandler.new
|
99
101
|
end
|
100
102
|
|
101
103
|
def init_event_handlers
|
@@ -172,6 +174,7 @@ module NewRelic
|
|
172
174
|
attr_reader :transaction_event_recorder
|
173
175
|
attr_reader :attribute_filter
|
174
176
|
attr_reader :adaptive_sampler
|
177
|
+
attr_reader :serverless_handler
|
175
178
|
|
176
179
|
def transaction_event_aggregator
|
177
180
|
@transaction_event_recorder.transaction_event_aggregator
|
@@ -307,7 +310,7 @@ module NewRelic
|
|
307
310
|
@stats_engine = StatsEngine.new
|
308
311
|
end
|
309
312
|
|
310
|
-
def flush_pipe_data
|
313
|
+
def flush_pipe_data # used only by resque
|
311
314
|
if connected? && @service.is_a?(PipeService)
|
312
315
|
transmit_data_types
|
313
316
|
end
|
@@ -27,9 +27,13 @@ module NewRelic
|
|
27
27
|
@connect_state == :disconnected
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def serverless?
|
31
|
+
Agent.config[:'serverless_mode.enabled']
|
32
|
+
end
|
33
|
+
|
34
|
+
# Don't connect if we're already connected, if we're in serverless mode,
|
35
|
+
# or if we tried to connect and were rejected with prejudice because of
|
36
|
+
# a license issue, unless we're forced to by force_reconnect.
|
33
37
|
def should_connect?(force = false)
|
34
38
|
force || (!connected? && !disconnected?)
|
35
39
|
end
|
@@ -62,10 +66,8 @@ module NewRelic
|
|
62
66
|
# no longer try to connect to the server, saving the
|
63
67
|
# application and the server load
|
64
68
|
def handle_license_error(error)
|
65
|
-
::NewRelic::Agent.logger.error(
|
66
|
-
|
67
|
-
'Visit NewRelic.com to obtain a valid license key, or to upgrade your account.'
|
68
|
-
)
|
69
|
+
::NewRelic::Agent.logger.error(error.message,
|
70
|
+
'Visit newrelic.com to obtain a valid license key, or to upgrade your account.')
|
69
71
|
disconnect
|
70
72
|
end
|
71
73
|
|
@@ -94,7 +96,7 @@ module NewRelic
|
|
94
96
|
# connects, then configures the agent using the response from
|
95
97
|
# the connect service
|
96
98
|
def connect_to_server
|
97
|
-
request_builder = ::NewRelic::Agent::Connect::RequestBuilder.new(
|
99
|
+
request_builder = ::NewRelic::Agent::Connect::RequestBuilder.new(
|
98
100
|
@service,
|
99
101
|
Agent.config,
|
100
102
|
event_harvest_config,
|
@@ -128,7 +128,7 @@ module NewRelic
|
|
128
128
|
catch_errors do
|
129
129
|
NewRelic::Agent.disable_all_tracing do
|
130
130
|
connect(connection_options)
|
131
|
-
if connected?
|
131
|
+
if NewRelic::Agent.instance.connected?
|
132
132
|
create_and_run_event_loop
|
133
133
|
# never reaches here unless there is a problem or
|
134
134
|
# the agent is exiting
|
@@ -124,6 +124,8 @@ module NewRelic
|
|
124
124
|
# Warn the user if they have configured their agent not to
|
125
125
|
# send data, that way we can see this clearly in the log file
|
126
126
|
def monitoring?
|
127
|
+
return false if Agent.config[:'serverless_mode.enabled']
|
128
|
+
|
127
129
|
if Agent.config[:monitor_mode]
|
128
130
|
true
|
129
131
|
else
|
@@ -146,7 +148,6 @@ module NewRelic
|
|
146
148
|
end
|
147
149
|
end
|
148
150
|
|
149
|
-
# A correct license key exists and is of the proper length
|
150
151
|
def has_correct_license_key?
|
151
152
|
has_license_key? && correct_license_length
|
152
153
|
end
|
@@ -132,7 +132,8 @@ module NewRelic
|
|
132
132
|
end
|
133
133
|
|
134
134
|
def wants_stdout?
|
135
|
-
::NewRelic::Agent.config[:log_file_path].casecmp(NewRelic::STANDARD_OUT) == 0
|
135
|
+
::NewRelic::Agent.config[:log_file_path].casecmp(NewRelic::STANDARD_OUT) == 0 ||
|
136
|
+
::NewRelic::Agent.config[:'serverless_mode.enabled']
|
136
137
|
end
|
137
138
|
|
138
139
|
def find_or_create_file_path(path_setting, root)
|
@@ -52,9 +52,24 @@ module NewRelic
|
|
52
52
|
result
|
53
53
|
end
|
54
54
|
|
55
|
+
def self.default_settings(key)
|
56
|
+
::NewRelic::Agent::Configuration::DEFAULTS[key]
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.value_from_defaults(key, subkey)
|
60
|
+
default_settings(key)&.send(:[], subkey)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.allowlist_for(key)
|
64
|
+
value_from_defaults(key, :allowlist)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.default_for(key)
|
68
|
+
value_from_defaults(key, :default)
|
69
|
+
end
|
70
|
+
|
55
71
|
def self.transform_for(key)
|
56
|
-
|
57
|
-
default_settings[:transform] if default_settings
|
72
|
+
value_from_defaults(key, :transform)
|
58
73
|
end
|
59
74
|
|
60
75
|
def self.config_search_paths # rubocop:disable Metrics/AbcSize
|
@@ -800,6 +815,7 @@ module NewRelic
|
|
800
815
|
:public => true,
|
801
816
|
:type => String,
|
802
817
|
:allowed_from_server => false,
|
818
|
+
:allowlist => %w[debug info warn error fatal unknown DEBUG INFO WARN ERROR FATAL UNKNOWN],
|
803
819
|
:description => <<~DESCRIPTION
|
804
820
|
Sets the minimum level a log event must have to be forwarded to New Relic.
|
805
821
|
|
@@ -1844,6 +1860,17 @@ module NewRelic
|
|
1844
1860
|
:transform => DefaultSource.method(:convert_to_regexp_list),
|
1845
1861
|
:description => 'Define transactions you want the agent to ignore, by specifying a list of patterns matching the URI you want to ignore. For more detail, see [the docs on ignoring specific transactions](/docs/agents/ruby-agent/api-guides/ignoring-specific-transactions/#config-ignoring).'
|
1846
1862
|
},
|
1863
|
+
# Serverless
|
1864
|
+
:'serverless_mode.enabled' => {
|
1865
|
+
:default => false,
|
1866
|
+
:public => true,
|
1867
|
+
:type => Boolean,
|
1868
|
+
:allowed_from_server => false,
|
1869
|
+
:transform => proc { |bool| NewRelic::Agent::ServerlessHandler.env_var_set? || bool },
|
1870
|
+
:description => 'If `true`, the agent will operate in a streamlined mode suitable for use with short-lived ' \
|
1871
|
+
'serverless functions. NOTE: Only AWS Lambda functions are supported currently and this ' \
|
1872
|
+
"option is not intended for use without [New Relic's Ruby Lambda layer](https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/monitoring-aws-lambda-serverless-monitoring/) offering."
|
1873
|
+
},
|
1847
1874
|
# Sidekiq
|
1848
1875
|
:'sidekiq.args.include' => {
|
1849
1876
|
default: NewRelic::EMPTY_ARRAY,
|
@@ -2252,6 +2279,7 @@ module NewRelic
|
|
2252
2279
|
:public => true,
|
2253
2280
|
:type => Symbol,
|
2254
2281
|
:allowed_from_server => false,
|
2282
|
+
:allowlist => %i[none low medium high],
|
2255
2283
|
:external => :infinite_tracing,
|
2256
2284
|
:description => <<~DESC
|
2257
2285
|
Configure the compression level for data sent to the trace observer.
|
@@ -99,7 +99,7 @@ module NewRelic
|
|
99
99
|
elsif !value.nil?
|
100
100
|
self[config_key] = true
|
101
101
|
end
|
102
|
-
|
102
|
+
elsif !serverless?
|
103
103
|
::NewRelic::Agent.logger.info("#{environment_key} does not have a corresponding configuration setting (#{config_key} does not exist).")
|
104
104
|
::NewRelic::Agent.logger.info('Run `rake newrelic:config:docs` or visit https://docs.newrelic.com/docs/apm/agents/ruby-agent/configuration/ruby-agent-configuration to see a list of available configuration settings.')
|
105
105
|
self[config_key] = value
|
@@ -114,6 +114,14 @@ module NewRelic
|
|
114
114
|
def collect_new_relic_environment_variable_keys
|
115
115
|
ENV.keys.select { |key| key.match(SUPPORTED_PREFIXES) }
|
116
116
|
end
|
117
|
+
|
118
|
+
# we can't rely on the :'serverless_mode.enabled' config parameter being
|
119
|
+
# set yet to signify serverless mode given that we're in the midst of
|
120
|
+
# building the config but we can always rely on the env var being set
|
121
|
+
# by the Lambda layer
|
122
|
+
def serverless?
|
123
|
+
NewRelic::Agent::ServerlessHandler.env_var_set?
|
124
|
+
end
|
117
125
|
end
|
118
126
|
end
|
119
127
|
end
|
@@ -138,7 +138,11 @@ module NewRelic
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def evaluate_and_apply_transformations(key, value)
|
141
|
-
|
141
|
+
evaluated = evaluate_procs(value)
|
142
|
+
default = enforce_allowlist(key, evaluated)
|
143
|
+
return default if default
|
144
|
+
|
145
|
+
apply_transformations(key, evaluated)
|
142
146
|
end
|
143
147
|
|
144
148
|
def apply_transformations(key, value)
|
@@ -146,7 +150,7 @@ module NewRelic
|
|
146
150
|
begin
|
147
151
|
transform.call(value)
|
148
152
|
rescue => e
|
149
|
-
|
153
|
+
NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
|
150
154
|
raise e
|
151
155
|
end
|
152
156
|
else
|
@@ -154,8 +158,21 @@ module NewRelic
|
|
154
158
|
end
|
155
159
|
end
|
156
160
|
|
161
|
+
def enforce_allowlist(key, value)
|
162
|
+
return unless allowlist = default_source.allowlist_for(key)
|
163
|
+
return if allowlist.include?(value)
|
164
|
+
|
165
|
+
default = default_source.default_for(key)
|
166
|
+
NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
|
167
|
+
default
|
168
|
+
end
|
169
|
+
|
157
170
|
def transform_from_default(key)
|
158
|
-
|
171
|
+
default_source.transform_for(key)
|
172
|
+
end
|
173
|
+
|
174
|
+
def default_source
|
175
|
+
NewRelic::Agent::Configuration::DefaultSource
|
159
176
|
end
|
160
177
|
|
161
178
|
def register_callback(key, &proc)
|
@@ -214,7 +231,7 @@ module NewRelic
|
|
214
231
|
begin
|
215
232
|
thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call)
|
216
233
|
rescue => e
|
217
|
-
|
234
|
+
NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
|
218
235
|
thawed_layer[k] = nil
|
219
236
|
end
|
220
237
|
thawed_layer.delete(:config)
|
@@ -383,7 +400,7 @@ module NewRelic
|
|
383
400
|
# is expensive enough that we don't want to do it unless we're
|
384
401
|
# actually going to be logging the message based on our current log
|
385
402
|
# level, so use a `do` block.
|
386
|
-
|
403
|
+
NewRelic::Agent.logger.debug do
|
387
404
|
hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
|
388
405
|
"Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}"
|
389
406
|
end
|
@@ -24,7 +24,7 @@ module NewRelic
|
|
24
24
|
:host => local_host,
|
25
25
|
:display_host => Agent.config[:'process_host.display_name'],
|
26
26
|
:app_name => Agent.config[:app_name],
|
27
|
-
:language =>
|
27
|
+
:language => LANGUAGE,
|
28
28
|
:labels => Agent.config.parsed_labels,
|
29
29
|
:agent_version => NewRelic::VERSION::STRING,
|
30
30
|
:environment => @environment_report,
|
@@ -35,7 +35,7 @@ module NewRelic
|
|
35
35
|
|
36
36
|
class << self
|
37
37
|
def for_transaction(transaction)
|
38
|
-
return nil unless connected?
|
38
|
+
return nil unless Agent.instance.connected?
|
39
39
|
|
40
40
|
payload = new
|
41
41
|
payload.version = VERSION
|
@@ -101,10 +101,6 @@ module NewRelic
|
|
101
101
|
transaction.current_segment.guid
|
102
102
|
end
|
103
103
|
end
|
104
|
-
|
105
|
-
def connected?
|
106
|
-
Agent.instance.connected?
|
107
|
-
end
|
108
104
|
end
|
109
105
|
|
110
106
|
attr_accessor :version,
|
@@ -10,7 +10,11 @@ module NewRelic::Agent::Instrumentation
|
|
10
10
|
OPERATION = 'perform_request'
|
11
11
|
INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
|
12
12
|
|
13
|
-
|
13
|
+
# We need the positional arguments `params` and `body`
|
14
|
+
# to capture the nosql statement
|
15
|
+
# *args protects the instrumented method if new arguments are added to
|
16
|
+
# perform_request
|
17
|
+
def perform_request_with_tracing(_method, _path, params = {}, body = nil, _headers = nil, *_args)
|
14
18
|
return yield unless NewRelic::Agent::Tracer.tracing_enabled?
|
15
19
|
|
16
20
|
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
@@ -22,6 +26,7 @@ module NewRelic::Agent::Instrumentation
|
|
22
26
|
port_path_or_id: nr_hosts[:port],
|
23
27
|
database_name: nr_cluster_name
|
24
28
|
)
|
29
|
+
|
25
30
|
begin
|
26
31
|
NewRelic::Agent::Tracer.capture_segment_error(segment) { yield }
|
27
32
|
ensure
|
@@ -68,7 +68,7 @@ module NewRelic::Agent::Instrumentation
|
|
68
68
|
vendor: VENDOR,
|
69
69
|
request_max_tokens: (parameters[:max_tokens] || parameters['max_tokens'])&.to_i,
|
70
70
|
request_model: parameters[:model] || parameters['model'],
|
71
|
-
|
71
|
+
request_temperature: (parameters[:temperature] || parameters['temperature'])&.to_f,
|
72
72
|
metadata: llm_custom_attributes
|
73
73
|
)
|
74
74
|
end
|
@@ -128,7 +128,6 @@ module NewRelic::Agent::Instrumentation
|
|
128
128
|
def update_chat_completion_messages(messages, response, summary)
|
129
129
|
messages += create_chat_completion_response_messages(response, messages.size, summary.id)
|
130
130
|
response_id = response['id'] || NewRelic::Agent::GuidGenerator.generate_guid
|
131
|
-
|
132
131
|
messages.each do |message|
|
133
132
|
message.id = "#{response_id}-#{message.sequence}"
|
134
133
|
message.request_id = summary.request_id
|
@@ -247,21 +247,6 @@ module NewRelic
|
|
247
247
|
message.byteslice(0...MAX_BYTES)
|
248
248
|
end
|
249
249
|
|
250
|
-
def minimum_log_level
|
251
|
-
if Logger::Severity.constants.include?(configured_log_level_constant)
|
252
|
-
configured_log_level_constant
|
253
|
-
else
|
254
|
-
NewRelic::Agent.logger.log_once(
|
255
|
-
:error,
|
256
|
-
'Invalid application_logging.forwarding.log_level ' \
|
257
|
-
"'#{NewRelic::Agent.config[LOG_LEVEL_KEY]}' specified! " \
|
258
|
-
"Must be one of #{Logger::Severity.constants.join('|')}. " \
|
259
|
-
"Using default level of 'debug'"
|
260
|
-
)
|
261
|
-
:DEBUG
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
250
|
def configured_log_level_constant
|
266
251
|
format_log_level_constant(NewRelic::Agent.config[LOG_LEVEL_KEY])
|
267
252
|
end
|
@@ -275,7 +260,7 @@ module NewRelic
|
|
275
260
|
# always record custom log levels
|
276
261
|
return false unless Logger::Severity.constants.include?(severity_constant)
|
277
262
|
|
278
|
-
Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(
|
263
|
+
Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(configured_log_level_constant)
|
279
264
|
end
|
280
265
|
end
|
281
266
|
end
|
@@ -143,6 +143,9 @@ module NewRelic
|
|
143
143
|
end
|
144
144
|
|
145
145
|
def metric_data(stats_hash)
|
146
|
+
# let the serverless handler handle serialization
|
147
|
+
return NewRelic::Agent.agent.serverless_handler.metric_data(stats_hash) if NewRelic::Agent.agent.serverless?
|
148
|
+
|
146
149
|
timeslice_start = stats_hash.started_at
|
147
150
|
timeslice_end = stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)
|
148
151
|
metric_data_array = build_metric_data_array(stats_hash)
|
@@ -154,6 +157,9 @@ module NewRelic
|
|
154
157
|
end
|
155
158
|
|
156
159
|
def error_data(unsent_errors)
|
160
|
+
# let the serverless handler handle serialization
|
161
|
+
return NewRelic::Agent.agent.serverless_handler.error_data(unsent_errors) if NewRelic::Agent.agent.serverless?
|
162
|
+
|
157
163
|
invoke_remote(:error_data, [@agent_id, unsent_errors],
|
158
164
|
:item_count => unsent_errors.size)
|
159
165
|
end
|
@@ -554,6 +560,8 @@ module NewRelic
|
|
554
560
|
# enough to be worth compressing, and handles any errors the
|
555
561
|
# server may return
|
556
562
|
def invoke_remote(method, payload = [], options = {})
|
563
|
+
return NewRelic::Agent.agent.serverless_handler.store_payload(method, payload) if NewRelic::Agent.agent.serverless?
|
564
|
+
|
557
565
|
start_ts = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
558
566
|
request_send_ts, response_check_ts = nil
|
559
567
|
data, encoding, size, serialize_finish_ts = marshal_payload(method, payload, options)
|
@@ -561,8 +569,10 @@ module NewRelic
|
|
561
569
|
response, request_send_ts, response_check_ts = invoke_remote_send_request(method, payload, data, encoding)
|
562
570
|
@marshaller.load(decompress_response(response))
|
563
571
|
ensure
|
564
|
-
|
565
|
-
|
572
|
+
unless NewRelic::Agent.agent.serverless?
|
573
|
+
record_timing_supportability_metrics(method, start_ts, serialize_finish_ts, request_send_ts, response_check_ts)
|
574
|
+
record_size_supportability_metrics(method, size, options[:item_count]) if size
|
575
|
+
end
|
566
576
|
end
|
567
577
|
|
568
578
|
def handle_serialization_error(method, e)
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# This file is distributed under New Relic's license terms.
|
2
|
+
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
require 'new_relic/base64'
|
7
|
+
|
8
|
+
module NewRelic
|
9
|
+
module Agent
|
10
|
+
class ServerlessHandler
|
11
|
+
ATTRIBUTE_ARN = 'aws.lambda.arn'
|
12
|
+
ATTRIBUTE_COLD_START = 'aws.lambda.coldStart'
|
13
|
+
ATTRIBUTE_REQUEST_ID = 'aws.requestId'
|
14
|
+
AGENT_ATTRIBUTE_DESTINATIONS = NewRelic::Agent::AttributeFilter::DST_TRANSACTION_TRACER |
|
15
|
+
NewRelic::Agent::AttributeFilter::DST_TRANSACTION_EVENTS
|
16
|
+
EXECUTION_ENVIRONMENT = "AWS_Lambda_ruby#{RUBY_VERSION.rpartition('.').first}".freeze
|
17
|
+
LAMBDA_MARKER = 'NR_LAMBDA_MONITORING'
|
18
|
+
LAMBDA_ENVIRONMENT_VARIABLE = 'AWS_LAMBDA_FUNCTION_NAME'
|
19
|
+
METHOD_BLOCKLIST = %i[agent_command_results connect get_agent_commands preconnect profile_data
|
20
|
+
shutdown].freeze
|
21
|
+
NAMED_PIPE = '/tmp/newrelic-telemetry'
|
22
|
+
SUPPORTABILITY_METRIC = 'Supportability/AWSLambda/HandlerInvocation'
|
23
|
+
FUNCTION_NAME = 'lambda_function'
|
24
|
+
PAYLOAD_VERSION = ENV.fetch('NEW_RELIC_SERVERLESS_PAYLOAD_VERSION', 2)
|
25
|
+
|
26
|
+
def self.env_var_set?
|
27
|
+
ENV.key?(LAMBDA_ENVIRONMENT_VARIABLE)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@context = nil
|
32
|
+
@payloads = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def invoke_lambda_function_with_new_relic(event:, context:, method_name:, namespace: nil)
|
36
|
+
NewRelic::Agent.increment_metric(SUPPORTABILITY_METRIC)
|
37
|
+
|
38
|
+
@context = context
|
39
|
+
|
40
|
+
NewRelic::Agent::Tracer.in_transaction(category: :other, name: function_name) do
|
41
|
+
add_agent_attributes
|
42
|
+
|
43
|
+
NewRelic::LanguageSupport.constantize(namespace).send(method_name, event: event, context: context)
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
harvest!
|
47
|
+
write_output
|
48
|
+
reset!
|
49
|
+
end
|
50
|
+
|
51
|
+
def store_payload(method, payload)
|
52
|
+
return if METHOD_BLOCKLIST.include?(method)
|
53
|
+
|
54
|
+
@payloads[method] = payload
|
55
|
+
end
|
56
|
+
|
57
|
+
def metric_data(stats_hash)
|
58
|
+
payload = [nil,
|
59
|
+
stats_hash.started_at,
|
60
|
+
(stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)),
|
61
|
+
[]]
|
62
|
+
stats_hash.each do |metric_spec, stats|
|
63
|
+
next if stats.is_reset?
|
64
|
+
|
65
|
+
hash = {name: metric_spec.name}
|
66
|
+
hash[:scope] = metric_spec.scope unless metric_spec.scope.empty?
|
67
|
+
|
68
|
+
payload.last.push([hash, [
|
69
|
+
stats.call_count,
|
70
|
+
stats.total_call_time,
|
71
|
+
stats.total_exclusive_time,
|
72
|
+
stats.min_call_time,
|
73
|
+
stats.max_call_time,
|
74
|
+
stats.sum_of_squares
|
75
|
+
]])
|
76
|
+
end
|
77
|
+
|
78
|
+
return if payload.last.empty?
|
79
|
+
|
80
|
+
store_payload(:metric_data, payload)
|
81
|
+
end
|
82
|
+
|
83
|
+
def error_data(errors)
|
84
|
+
store_payload(:error_data, [nil, errors.map(&:to_collector_array)])
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def harvest!
|
90
|
+
NewRelic::Agent.instance.harvest_and_send_analytic_event_data
|
91
|
+
NewRelic::Agent.instance.harvest_and_send_custom_event_data
|
92
|
+
NewRelic::Agent.instance.harvest_and_send_data_types
|
93
|
+
end
|
94
|
+
|
95
|
+
def metadata
|
96
|
+
m = {arn: @context.invoked_function_arn,
|
97
|
+
protocol_version: NewRelic::Agent::NewRelicService::PROTOCOL_VERSION,
|
98
|
+
function_version: @context.function_version,
|
99
|
+
execution_environment: EXECUTION_ENVIRONMENT,
|
100
|
+
agent_version: NewRelic::VERSION::STRING}
|
101
|
+
if PAYLOAD_VERSION >= 2
|
102
|
+
m[:metadata_version] = PAYLOAD_VERSION
|
103
|
+
m[:agent_language] = NewRelic::LANGUAGE
|
104
|
+
end
|
105
|
+
m
|
106
|
+
end
|
107
|
+
|
108
|
+
def function_name
|
109
|
+
ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_output
|
113
|
+
string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
|
114
|
+
|
115
|
+
return puts string unless use_named_pipe?
|
116
|
+
|
117
|
+
File.write(NAMED_PIPE, string)
|
118
|
+
|
119
|
+
NewRelic::Agent.logger.debug "Wrote serverless payload to #{NAMED_PIPE}\n" \
|
120
|
+
"BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
|
121
|
+
end
|
122
|
+
|
123
|
+
def payload_v1
|
124
|
+
payload_hash = {'metadata' => metadata, 'data' => @payloads}
|
125
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
|
126
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
127
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
128
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, base64_encoded]
|
129
|
+
::JSON.dump(array)
|
130
|
+
end
|
131
|
+
|
132
|
+
def payload_v2
|
133
|
+
json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
|
134
|
+
gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
|
135
|
+
base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
|
136
|
+
array = [PAYLOAD_VERSION, LAMBDA_MARKER, metadata, base64_encoded]
|
137
|
+
::JSON.dump(array)
|
138
|
+
end
|
139
|
+
|
140
|
+
def use_named_pipe?
|
141
|
+
return @use_named_pipe if defined?(@use_named_pipe)
|
142
|
+
|
143
|
+
@use_named_pipe = File.exist?(NAMED_PIPE) && File.writable?(NAMED_PIPE)
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_agent_attributes
|
147
|
+
return unless NewRelic::Agent::Tracer.current_transaction
|
148
|
+
|
149
|
+
add_agent_attribute(ATTRIBUTE_COLD_START, true) if cold?
|
150
|
+
add_agent_attribute(ATTRIBUTE_ARN, @context.invoked_function_arn)
|
151
|
+
add_agent_attribute(ATTRIBUTE_REQUEST_ID, @context.aws_request_id)
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_agent_attribute(attribute, value)
|
155
|
+
NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
|
156
|
+
end
|
157
|
+
|
158
|
+
def cold?
|
159
|
+
return @cold if defined?(@cold)
|
160
|
+
|
161
|
+
@cold = false
|
162
|
+
true
|
163
|
+
end
|
164
|
+
|
165
|
+
def reset!
|
166
|
+
@context = nil
|
167
|
+
@payloads.replace({})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -16,26 +16,27 @@ module NewRelic
|
|
16
16
|
module TransactionErrorPrimitive
|
17
17
|
extend self
|
18
18
|
|
19
|
-
SAMPLE_TYPE = 'TransactionError'
|
20
|
-
TYPE_KEY = 'type'
|
21
|
-
ERROR_CLASS_KEY = 'error.class'
|
22
|
-
ERROR_MESSAGE_KEY = 'error.message'
|
23
|
-
ERROR_EXPECTED_KEY = 'error.expected'
|
24
|
-
TIMESTAMP_KEY = 'timestamp'
|
25
|
-
PORT_KEY = 'port'
|
26
|
-
NAME_KEY = 'transactionName'
|
27
|
-
DURATION_KEY = 'duration'
|
28
|
-
SAMPLED_KEY = 'sampled'
|
29
|
-
|
30
|
-
|
31
|
-
SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'
|
32
|
-
SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'
|
33
|
-
SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'
|
19
|
+
SAMPLE_TYPE = 'TransactionError'
|
20
|
+
TYPE_KEY = 'type'
|
21
|
+
ERROR_CLASS_KEY = 'error.class'
|
22
|
+
ERROR_MESSAGE_KEY = 'error.message'
|
23
|
+
ERROR_EXPECTED_KEY = 'error.expected'
|
24
|
+
TIMESTAMP_KEY = 'timestamp'
|
25
|
+
PORT_KEY = 'port'
|
26
|
+
NAME_KEY = 'transactionName'
|
27
|
+
DURATION_KEY = 'duration'
|
28
|
+
SAMPLED_KEY = 'sampled'
|
29
|
+
CAT_GUID_KEY = 'nr.transactionGuid'
|
30
|
+
CAT_REFERRING_TRANSACTION_GUID_KEY = 'nr.referringTransactionGuid'
|
31
|
+
SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'
|
32
|
+
SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'
|
33
|
+
SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'
|
34
34
|
SYNTHETICS_TYPE_KEY = 'nr.syntheticsType'
|
35
35
|
SYNTHETICS_INITIATOR_KEY = 'nr.syntheticsInitiator'
|
36
36
|
SYNTHETICS_KEY_PREFIX = 'nr.synthetics'
|
37
|
-
PRIORITY_KEY = 'priority'
|
38
|
-
SPAN_ID_KEY = 'spanId'
|
37
|
+
PRIORITY_KEY = 'priority'
|
38
|
+
SPAN_ID_KEY = 'spanId'
|
39
|
+
GUID_KEY = 'guid'
|
39
40
|
|
40
41
|
SYNTHETICS_PAYLOAD_EXPECTED = [:synthetics_resource_id, :synthetics_job_id, :synthetics_monitor_id, :synthetics_type, :synthetics_initiator]
|
41
42
|
|
@@ -57,7 +58,10 @@ module NewRelic
|
|
57
58
|
}
|
58
59
|
|
59
60
|
attrs[SPAN_ID_KEY] = span_id if span_id
|
61
|
+
# don't use safe navigation - leave off keys with missing values
|
62
|
+
# instead of using nil
|
60
63
|
attrs[PORT_KEY] = noticed_error.request_port if noticed_error.request_port
|
64
|
+
attrs[GUID_KEY] = noticed_error.transaction_id if noticed_error.transaction_id
|
61
65
|
|
62
66
|
if payload
|
63
67
|
attrs[NAME_KEY] = payload[:name]
|
@@ -93,8 +97,8 @@ module NewRelic
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def append_cat(payload, sample)
|
96
|
-
sample[
|
97
|
-
sample[
|
100
|
+
sample[CAT_GUID_KEY] = payload[:guid] if payload[:guid]
|
101
|
+
sample[CAT_REFERRING_TRANSACTION_GUID_KEY] = payload[:referring_transaction_guid] if payload[:referring_transaction_guid]
|
98
102
|
end
|
99
103
|
end
|
100
104
|
end
|
data/lib/new_relic/agent.rb
CHANGED
@@ -709,16 +709,19 @@ module NewRelic
|
|
709
709
|
def add_custom_attributes(params) # THREAD_LOCAL_ACCESS
|
710
710
|
record_api_supportability_metric(:add_custom_attributes)
|
711
711
|
|
712
|
-
|
713
|
-
Transaction.tl_current&.add_custom_attributes(params)
|
714
|
-
|
715
|
-
segment = ::NewRelic::Agent::Tracer.current_segment
|
716
|
-
if segment
|
717
|
-
add_new_segment_attributes(params, segment)
|
718
|
-
end
|
719
|
-
else
|
712
|
+
unless params.is_a?(Hash)
|
720
713
|
::NewRelic::Agent.logger.warn("Bad argument passed to #add_custom_attributes. Expected Hash but got #{params.class}")
|
714
|
+
return
|
721
715
|
end
|
716
|
+
|
717
|
+
if NewRelic::Agent.agent&.serverless?
|
718
|
+
::NewRelic::Agent.logger.warn('Custom attributes are not supported in serverless mode')
|
719
|
+
return
|
720
|
+
end
|
721
|
+
|
722
|
+
Transaction.tl_current&.add_custom_attributes(params)
|
723
|
+
segment = ::NewRelic::Agent::Tracer.current_segment
|
724
|
+
add_new_segment_attributes(params, segment) if segment
|
722
725
|
end
|
723
726
|
|
724
727
|
def add_new_segment_attributes(params, segment)
|
data/lib/new_relic/constants.rb
CHANGED
@@ -76,6 +76,7 @@ module NewRelic
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def determine_env(options)
|
79
|
+
options[:env] = :serverless if local_env.discovered_dispatcher == :serverless
|
79
80
|
env = options[:env] || self.env
|
80
81
|
env = env.to_s
|
81
82
|
|
@@ -95,6 +96,12 @@ module NewRelic
|
|
95
96
|
def configure_agent(env, options)
|
96
97
|
manual = Agent::Configuration::ManualSource.new(options)
|
97
98
|
Agent.config.replace_or_add_config(manual)
|
99
|
+
|
100
|
+
# if manual config sees serverless mode enabled, then the proc
|
101
|
+
# must have returned 'true'. don't bother with YAML and high security
|
102
|
+
# in a serverless context
|
103
|
+
return if Agent.config[:'serverless_mode.enabled']
|
104
|
+
|
98
105
|
yaml_source = Agent::Configuration::YamlSource.new(config_file_path, env)
|
99
106
|
log_yaml_source_failures(yaml_source) if yaml_source.failed?
|
100
107
|
Agent.config.replace_or_add_config(yaml_source)
|
@@ -60,21 +60,22 @@ module NewRelic
|
|
60
60
|
|
61
61
|
def discover_dispatcher
|
62
62
|
dispatchers = %w[
|
63
|
+
serverless
|
64
|
+
puma
|
65
|
+
sidekiq
|
66
|
+
falcon
|
67
|
+
delayed_job
|
68
|
+
unicorn
|
63
69
|
passenger
|
70
|
+
resque
|
64
71
|
torquebox
|
65
72
|
trinidad
|
66
73
|
glassfish
|
67
|
-
resque
|
68
|
-
sidekiq
|
69
|
-
delayed_job
|
70
|
-
puma
|
71
74
|
thin
|
72
75
|
mongrel
|
73
76
|
litespeed
|
74
|
-
unicorn
|
75
77
|
webrick
|
76
78
|
fastcgi
|
77
|
-
falcon
|
78
79
|
]
|
79
80
|
while dispatchers.any? && @discovered_dispatcher.nil?
|
80
81
|
send('check_for_' + (dispatchers.shift))
|
@@ -198,6 +199,12 @@ module NewRelic
|
|
198
199
|
@discovered_dispatcher = :passenger
|
199
200
|
end
|
200
201
|
|
202
|
+
def check_for_serverless
|
203
|
+
return unless NewRelic::Agent.config[:'serverless_mode.enabled']
|
204
|
+
|
205
|
+
@discovered_dispatcher = :serverless
|
206
|
+
end
|
207
|
+
|
201
208
|
public
|
202
209
|
|
203
210
|
# outputs a human-readable description
|
data/lib/new_relic/version.rb
CHANGED
data/lib/tasks/config.rake
CHANGED
@@ -22,7 +22,8 @@ namespace :newrelic do
|
|
22
22
|
'error_collector' => "The agent collects and reports all uncaught exceptions by default. These configuration options allow you to customize the error collection.\n\nFor information on ignored and expected errors, [see this page on Error Analytics in APM](/docs/agents/manage-apm-agents/agent-data/manage-errors-apm-collect-ignore-or-mark-expected/). To set expected errors via the `NewRelic::Agent.notice_error` Ruby method, [consult the Ruby agent API](/docs/agents/ruby-agent/api-guides/sending-handled-errors-new-relic/).",
|
23
23
|
'browser_monitoring' => "The browser monitoring [page load timing](/docs/browser/new-relic-browser/page-load-timing/page-load-timing-process) feature (sometimes referred to as real user monitoring or RUM) gives you insight into the performance real users are experiencing with your website. This is accomplished by measuring the time it takes for your users' browsers to download and render your web pages by injecting a small amount of JavaScript code into the header and footer of each page.",
|
24
24
|
'application_logging' => "The Ruby agent supports [APM logs in context](/docs/apm/new-relic-apm/getting-started/get-started-logs-context). For some tips on configuring logs for the Ruby agent, see [Configure Ruby logs in context](/docs/logs/logs-context/configure-logs-context-ruby).\n\nAvailable logging-related config options include:",
|
25
|
-
'analytics_events' => '[New Relic dashboards](/docs/query-your-data/explore-query-data/dashboards/introduction-new-relic-one-dashboards) is a resource to gather and visualize data about your software and what it says about your business. With it you can quickly and easily create real-time dashboards to get immediate answers about end-user experiences, clickstreams, mobile activities, and server transactions.'
|
25
|
+
'analytics_events' => '[New Relic dashboards](/docs/query-your-data/explore-query-data/dashboards/introduction-new-relic-one-dashboards) is a resource to gather and visualize data about your software and what it says about your business. With it you can quickly and easily create real-time dashboards to get immediate answers about end-user experiences, clickstreams, mobile activities, and server transactions.',
|
26
|
+
'ai_monitoring' => "This section includes Ruby agent configurations for setting up AI monitoring.\n\n<Callout variant='important'>You need to enable distributed tracing to capture trace and feedback data. It is turned on by default in Ruby agents 8.0.0 and higher.</Callout>"
|
26
27
|
}
|
27
28
|
|
28
29
|
NAME_OVERRIDES = {
|
data/newrelic.yml
CHANGED
@@ -629,6 +629,12 @@ common: &default_settings
|
|
629
629
|
# before shutting down.
|
630
630
|
# send_data_on_exit: true
|
631
631
|
|
632
|
+
# If true, the agent will operate in a streamlined mode suitable for use with
|
633
|
+
# short-lived serverless functions. NOTE: Only AWS Lambda functions are supported
|
634
|
+
# currently and this option is not intended for use without New Relic's Ruby
|
635
|
+
# Lambda layer offering.
|
636
|
+
# serverless_mode.enabled: false
|
637
|
+
|
632
638
|
# An array of strings that will collectively serve as a denylist for filtering
|
633
639
|
# which Sidekiq job arguments get reported to New Relic. To capture any Sidekiq
|
634
640
|
# arguments, 'job.sidekiq.args.*' must be added to the separate
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: newrelic_rpm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.
|
4
|
+
version: 9.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tanna McClure
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-
|
14
|
+
date: 2024-04-17 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -562,6 +562,7 @@ files:
|
|
562
562
|
- lib/new_relic/agent/samplers/memory_sampler.rb
|
563
563
|
- lib/new_relic/agent/samplers/object_sampler.rb
|
564
564
|
- lib/new_relic/agent/samplers/vm_sampler.rb
|
565
|
+
- lib/new_relic/agent/serverless_handler.rb
|
565
566
|
- lib/new_relic/agent/span_event_aggregator.rb
|
566
567
|
- lib/new_relic/agent/span_event_primitive.rb
|
567
568
|
- lib/new_relic/agent/sql_sampler.rb
|