newrelic_rpm 9.7.1 → 9.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/README.md +1 -1
  4. data/lib/new_relic/agent/agent.rb +4 -1
  5. data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
  6. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
  7. data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
  8. data/lib/new_relic/agent/agent_logger.rb +2 -1
  9. data/lib/new_relic/agent/configuration/default_source.rb +67 -3
  10. data/lib/new_relic/agent/configuration/environment_source.rb +9 -1
  11. data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
  12. data/lib/new_relic/agent/configuration/manager.rb +28 -8
  13. data/lib/new_relic/agent/configuration/security_policy_source.rb +11 -0
  14. data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
  15. data/lib/new_relic/agent/connect/request_builder.rb +1 -1
  16. data/lib/new_relic/agent/custom_event_aggregator.rb +4 -4
  17. data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
  18. data/lib/new_relic/agent/error_collector.rb +2 -0
  19. data/lib/new_relic/agent/harvester.rb +1 -1
  20. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/instrumentation.rb +7 -3
  21. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -0
  22. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +6 -1
  23. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +6 -0
  24. data/lib/new_relic/agent/instrumentation/ruby_openai/chain.rb +36 -0
  25. data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +196 -0
  26. data/lib/new_relic/agent/instrumentation/ruby_openai/prepend.rb +20 -0
  27. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +35 -0
  28. data/lib/new_relic/agent/llm/chat_completion_message.rb +25 -0
  29. data/lib/new_relic/agent/llm/chat_completion_summary.rb +66 -0
  30. data/lib/new_relic/agent/llm/embedding.rb +60 -0
  31. data/lib/new_relic/agent/llm/llm_event.rb +95 -0
  32. data/lib/new_relic/agent/llm/response_headers.rb +80 -0
  33. data/lib/new_relic/agent/llm.rb +49 -0
  34. data/lib/new_relic/agent/log_event_aggregator.rb +1 -16
  35. data/lib/new_relic/agent/new_relic_service.rb +12 -2
  36. data/lib/new_relic/agent/serverless_handler.rb +171 -0
  37. data/lib/new_relic/agent/threading/agent_thread.rb +1 -2
  38. data/lib/new_relic/agent/tracer.rb +5 -5
  39. data/lib/new_relic/agent/transaction/abstract_segment.rb +1 -1
  40. data/lib/new_relic/agent/transaction/tracing.rb +2 -2
  41. data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
  42. data/lib/new_relic/agent.rb +102 -8
  43. data/lib/new_relic/constants.rb +2 -0
  44. data/lib/new_relic/control/instance_methods.rb +7 -0
  45. data/lib/new_relic/local_environment.rb +13 -6
  46. data/lib/new_relic/rack/browser_monitoring.rb +8 -4
  47. data/lib/new_relic/supportability_helper.rb +2 -0
  48. data/lib/new_relic/thread_local_storage.rb +31 -0
  49. data/lib/new_relic/version.rb +2 -2
  50. data/lib/tasks/config.rake +2 -1
  51. data/newrelic.yml +27 -1
  52. metadata +14 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bdcea3ab1fea41b672c672c931a6005966bef1500709deb9d85681912cf7d33
4
- data.tar.gz: 7d2bcd8db6e959d909e26dd4c3d8e8a99c7cec6572204a1c0283ce65521a5d6a
3
+ metadata.gz: ad2c3da566c3fda3369af14dd1d25b82a582c3db88c910474634b1b8169648fc
4
+ data.tar.gz: 1ebe2637f6cef4b3b3e70afb157b91516cc6bf37511f1acb1f3ec5e1caf917e5
5
5
  SHA512:
6
- metadata.gz: '053846613ea0a66e691e15cd2159c3ada613f3f66dcfc9232f723569a54d3659f84b8b16ed178e7eb46691a36dc182addaf187370b15862e3f05d3e9335f50b3'
7
- data.tar.gz: 588be3aa5b6e29b2cb1c79e89d4b3bd4af5fc5e41e819f1916c52107aca1ba41f8ca9d573ccf7d8def38d9fb166a2cd73ae2bf9733de2bc03472cd517cdf1801
6
+ metadata.gz: d86c1de9e30e3952b7d5dbec62845693795e6c3b0273b5f34a19a0a550e208314774d3da08246c26c8e903657c36b71b91ae170911869f3e801879e8c8ee4e05
7
+ data.tar.gz: bb5732481d984d24366be27b1186e35b753b7dff7613774b8f4f50749e516db6556edd93b1017e656535cf83ef306e31199e44b03d677ea8c249f472f7f30222
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
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
+
19
+ ## v9.8.0
20
+
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.
22
+
23
+ - **Feature: Add instrumentation for ruby-openai**
24
+
25
+ Instrumentation has been added for the [ruby-openai](https://github.com/alexrudall/ruby-openai) gem, supporting versions 3.4.0 and higher [(PR#2442)](https://github.com/newrelic/newrelic-ruby-agent/pull/2442). While ruby-openai instrumentation is enabled by default, the configuration option `ai_monitoring.enabled` is disabled by default and controls all AI monitoring. `ai_monitoring.enabled` must be set to `true` in order to receive ruby-openai instrumentation. High-Security Mode must be disabled in order to receive AI monitoring.
26
+
27
+ Calls to embedding and chat completion endpoints are automatically traced. These events can be enhanced with the introduction of two new APIs. Custom attributes can also be added to LLM events using the API `NewRelic::Agent.add_custom_attributes`, but they must be prefixed with `llm.`. For example, `NewRelic::Agent.add_custom_attributes({'llm.user_id': user_id})`.
28
+
29
+ - **Feature: Add AI monitoring APIs**
30
+
31
+ This version introduces two new APIs that allow users to record additional information on LLM events:
32
+ * `NewRelic::Agent.record_llm_feedback_event` - Records user feedback events.
33
+ * `NewRelic::Agent.set_llm_token_count_callback` - Sets a callback proc for calculating `token_count` attributes for embedding and chat completion message events.
34
+
35
+ Visit [RubyDoc](https://rubydoc.info/github/newrelic/newrelic-ruby-agent/) for more information on each of these APIs.
36
+
37
+ - **Feature: Store tracer state on thread-level**
38
+
39
+ A new configuration option, `thread_local_tracer_state`, stores New Relic's tracer state on the thread-level, as opposed to the default fiber-level storage. This configuration is turned off by default. Our thanks go to community member [@markiz](https://github.com/markiz) who contributed the idea, code, configuration option, and tests for this new feature! [PR#2475](https://github.com/newrelic/newrelic-ruby-agent/pull/2475).
40
+
41
+ - **Bugfix: Harden the browser agent insertion logic**
42
+
43
+ With [Issue#2462](https://github.com/newrelic/newrelic-ruby-agent/issues/2462), community member [@miry](https://github.com/miry) explained that it was possible for an HTTP response headers hash to have symbols for values. Not only would these symbols prevent the inclusion of the New Relic browser agent tag in the response body, but more importantly they would cause an exception that would bubble up to the monitored web application itself. With [PR#2465](https://github.com/newrelic/newrelic-ruby-agent/pull/2465) symbol based values are now supported and all other potential future exceptions are now handled. Additionally, the refactor to support symbols has been shown through benchmarking to give the processing of string and mixed type hashes a slight speed boost too.
44
+
45
+ - **Bugfix: Prevent Exception in Active Support Broadcast logger instrumentation**
46
+
47
+ Previously, in certain situations the agent could cause an exception to be raised when attempting to interact with a broadcast log event. This has been fixed. Thanks to [@nathan-appere](https://github.com/nathan-appere) for reporting this issue and providing a fix! [PR#2510](https://github.com/newrelic/newrelic-ruby-agent/pull/2510)
48
+
3
49
 
4
50
  ## v9.7.1
5
51
 
@@ -19,7 +65,7 @@ Version 9.7.0 introduces ViewComponent instrumentation, changes the endpoint use
19
65
 
20
66
  - **Feature: ViewComponent instrumentation**
21
67
 
22
- [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)
23
69
 
24
70
  - **Feature: Use root path to access Elasticsearch cluster name**
25
71
 
data/README.md CHANGED
@@ -119,7 +119,7 @@ If you have any questions, or to execute our corporate CLA (required if your con
119
119
 
120
120
  As noted in our [security policy](https://github.com/newrelic/newrelic-ruby-agent/security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals.
121
121
 
122
- If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [HackerOne](https://hackerone.com/newrelic).
122
+ If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [our bug bounty program](https://docs.newrelic.com/docs/security/security-privacy/information-security/report-security-vulnerabilities/).
123
123
 
124
124
  If you would like to contribute to this project, please review [these guidelines](https://github.com/newrelic/newrelic-ruby-agent/blob/main/CONTRIBUTING.md).
125
125
 
@@ -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
- # Don't connect if we're already connected, or if we tried to connect
31
- # and were rejected with prejudice because of a license issue, unless
32
- # we're forced to by force_reconnect.
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
- error.message, \
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
- default_settings = ::NewRelic::Agent::Configuration::DEFAULTS[key]
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
@@ -360,6 +375,26 @@ module NewRelic
360
375
  - a.third.event
361
376
  DESCRIPTION
362
377
  },
378
+ :'ai_monitoring.enabled' => {
379
+ :default => false,
380
+ :public => true,
381
+ :type => Boolean,
382
+ :allowed_from_server => false,
383
+ :description => 'If `false`, all LLM instrumentation (OpenAI only for now) will be disabled and no metrics, events, or spans will be sent. AI Monitoring is automatically disabled if `high_security` mode is enabled.'
384
+ },
385
+ :'ai_monitoring.record_content.enabled' => {
386
+ :default => true,
387
+ :public => true,
388
+ :type => Boolean,
389
+ :allowed_from_server => false,
390
+ :description => <<~DESCRIPTION
391
+ If `false`, LLM instrumentation (OpenAI only for now) will not capture input and output content on specific LLM events.
392
+
393
+ The excluded attributes include:
394
+ * `content` from LlmChatCompletionMessage events
395
+ * `input` from LlmEmbedding events
396
+ DESCRIPTION
397
+ },
363
398
  # this is only set via server side config
364
399
  :apdex_t => {
365
400
  :default => 0.5,
@@ -554,6 +589,13 @@ module NewRelic
554
589
  :allowed_from_server => false,
555
590
  :description => 'When set to `true`, forces a synchronous connection to the New Relic [collector](/docs/using-new-relic/welcome-new-relic/get-started/glossary/#collector) during application startup. For very short-lived processes, this helps ensure the New Relic agent has time to report.'
556
591
  },
592
+ :thread_local_tracer_state => {
593
+ :default => false,
594
+ :public => true,
595
+ :type => Boolean,
596
+ :allowed_from_server => false,
597
+ :description => 'If `true`, tracer state storage is thread-local, otherwise, fiber-local'
598
+ },
557
599
  :timeout => {
558
600
  :default => 2 * 60, # 2 minutes
559
601
  :public => true,
@@ -720,7 +762,7 @@ module NewRelic
720
762
  :public => true,
721
763
  :type => Integer,
722
764
  :allowed_from_server => false,
723
- :description => 'Defines the maximum number of frames in an error backtrace. Backtraces over this amount are truncated at the beginning and end.'
765
+ :description => 'Defines the maximum number of frames in an error backtrace. Backtraces over this amount are truncated in the middle, preserving the beginning and the end of the stack trace.'
724
766
  },
725
767
  :'error_collector.max_event_samples_stored' => {
726
768
  :default => 100,
@@ -773,6 +815,7 @@ module NewRelic
773
815
  :public => true,
774
816
  :type => String,
775
817
  :allowed_from_server => false,
818
+ :allowlist => %w[debug info warn error fatal unknown DEBUG INFO WARN ERROR FATAL UNKNOWN],
776
819
  :description => <<~DESCRIPTION
777
820
  Sets the minimum level a log event must have to be forwarded to New Relic.
778
821
 
@@ -1570,6 +1613,15 @@ module NewRelic
1570
1613
  :allowed_from_server => false,
1571
1614
  :description => 'Controls auto-instrumentation of `Net::HTTP` at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
1572
1615
  },
1616
+ :'instrumentation.ruby_openai' => {
1617
+ :default => 'auto',
1618
+ :documentation_default => 'auto',
1619
+ :public => true,
1620
+ :type => String,
1621
+ :dynamic_name => true,
1622
+ :allowed_from_server => false,
1623
+ :description => 'Controls auto-instrumentation of the ruby-openai gem at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
1624
+ },
1573
1625
  :'instrumentation.puma_rack' => {
1574
1626
  :default => value_of(:'instrumentation.rack'),
1575
1627
  :documentation_default => 'auto',
@@ -1808,6 +1860,17 @@ module NewRelic
1808
1860
  :transform => DefaultSource.method(:convert_to_regexp_list),
1809
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).'
1810
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
+ },
1811
1874
  # Sidekiq
1812
1875
  :'sidekiq.args.include' => {
1813
1876
  default: NewRelic::EMPTY_ARRAY,
@@ -2216,6 +2279,7 @@ module NewRelic
2216
2279
  :public => true,
2217
2280
  :type => Symbol,
2218
2281
  :allowed_from_server => false,
2282
+ :allowlist => %i[none low medium high],
2219
2283
  :external => :infinite_tracing,
2220
2284
  :description => <<~DESC
2221
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
- else
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
@@ -19,6 +19,7 @@ module NewRelic
19
19
  :'elasticsearch.obfuscate_queries' => true,
20
20
  :'transaction_tracer.record_redis_arguments' => false,
21
21
 
22
+ :'ai_monitoring.enabled' => false,
22
23
  :'custom_insights_events.enabled' => false,
23
24
  :'strip_exception_messages.enabled' => true
24
25
  })
@@ -34,6 +34,7 @@ module NewRelic
34
34
  def initialize
35
35
  reset_to_defaults
36
36
  @callbacks = Hash.new { |hash, key| hash[key] = [] }
37
+ @lock = Mutex.new
37
38
  end
38
39
 
39
40
  def add_config_for_testing(source, level = 0)
@@ -137,7 +138,11 @@ module NewRelic
137
138
  end
138
139
 
139
140
  def evaluate_and_apply_transformations(key, value)
140
- apply_transformations(key, evaluate_procs(value))
141
+ evaluated = evaluate_procs(value)
142
+ default = enforce_allowlist(key, evaluated)
143
+ return default if default
144
+
145
+ apply_transformations(key, evaluated)
141
146
  end
142
147
 
143
148
  def apply_transformations(key, value)
@@ -145,7 +150,7 @@ module NewRelic
145
150
  begin
146
151
  transform.call(value)
147
152
  rescue => e
148
- ::NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
153
+ NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
149
154
  raise e
150
155
  end
151
156
  else
@@ -153,8 +158,21 @@ module NewRelic
153
158
  end
154
159
  end
155
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
+
156
170
  def transform_from_default(key)
157
- ::NewRelic::Agent::Configuration::DefaultSource.transform_for(key)
171
+ default_source.transform_for(key)
172
+ end
173
+
174
+ def default_source
175
+ NewRelic::Agent::Configuration::DefaultSource
158
176
  end
159
177
 
160
178
  def register_callback(key, &proc)
@@ -213,7 +231,7 @@ module NewRelic
213
231
  begin
214
232
  thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call)
215
233
  rescue => e
216
- ::NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
234
+ NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
217
235
  thawed_layer[k] = nil
218
236
  end
219
237
  thawed_layer.delete(:config)
@@ -364,9 +382,11 @@ module NewRelic
364
382
  def reset_cache
365
383
  return new_cache unless defined?(@cache) && @cache
366
384
 
367
- preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
368
- new_cache
369
- preserved.each { |k, v| @cache[k] = v }
385
+ @lock.synchronize do
386
+ preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
387
+ new_cache
388
+ preserved.each { |k, v| @cache[k] = v }
389
+ end
370
390
 
371
391
  @cache
372
392
  end
@@ -380,7 +400,7 @@ module NewRelic
380
400
  # is expensive enough that we don't want to do it unless we're
381
401
  # actually going to be logging the message based on our current log
382
402
  # level, so use a `do` block.
383
- ::NewRelic::Agent.logger.debug do
403
+ NewRelic::Agent.logger.debug do
384
404
  hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
385
405
  "Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}"
386
406
  end
@@ -7,6 +7,8 @@ require 'new_relic/agent/configuration/dotted_hash'
7
7
  module NewRelic
8
8
  module Agent
9
9
  module Configuration
10
+ # The Language Security Policy Source gives customers the ability to
11
+ # configure high security mode settings.
10
12
  class SecurityPolicySource < DottedHash
11
13
  class << self
12
14
  def enabled?(option)
@@ -147,6 +149,15 @@ module NewRelic
147
149
  permitted_fn: nil
148
150
  }
149
151
  ],
152
+ 'ai_monitoring' => [
153
+ {
154
+ option: :'ai_monitoring.enabled',
155
+ supported: true,
156
+ enabled_fn: method(:enabled?),
157
+ disabled_value: false,
158
+ permitted_fn: nil
159
+ }
160
+ ],
150
161
  'allow_raw_exception_messages' => [
151
162
  {
152
163
  option: :'strip_exception_messages.enabled',
@@ -53,6 +53,8 @@ module NewRelic
53
53
  protected
54
54
 
55
55
  def validate_config_file_path(path)
56
+ return if NewRelic::Agent.config[:'serverless_mode.enabled']
57
+
56
58
  expanded_path = File.expand_path(path)
57
59
 
58
60
  if path.empty? || !File.exist?(expanded_path)
@@ -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 => 'ruby',
27
+ :language => LANGUAGE,
28
28
  :labels => Agent.config.parsed_labels,
29
29
  :agent_version => NewRelic::VERSION::STRING,
30
30
  :environment => @environment_report,
@@ -52,11 +52,11 @@ module NewRelic
52
52
  {TYPE => type,
53
53
  TIMESTAMP => Process.clock_gettime(Process::CLOCK_REALTIME).to_i,
54
54
  PRIORITY => priority},
55
- create_custom_event_attributes(attributes)
55
+ create_custom_event_attributes(type, attributes)
56
56
  ]
57
57
  end
58
58
 
59
- def create_custom_event_attributes(attributes)
59
+ def create_custom_event_attributes(type, attributes)
60
60
  result = AttributeProcessing.flatten_and_coerce(attributes)
61
61
 
62
62
  if result.size > MAX_ATTRIBUTE_COUNT
@@ -70,9 +70,9 @@ module NewRelic
70
70
  key = key[0, MAX_NAME_SIZE]
71
71
  end
72
72
 
73
- # value is limited to 4095
73
+ # value is limited to 4095 except for LLM content-related events
74
74
  if val.is_a?(String) && val.length > MAX_ATTRIBUTE_SIZE
75
- val = val[0, MAX_ATTRIBUTE_SIZE]
75
+ val = val[0, MAX_ATTRIBUTE_SIZE] unless NewRelic::Agent::LLM.exempt_event_attribute?(type, key)
76
76
  end
77
77
 
78
78
  new_result[key] = val
@@ -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,
@@ -216,6 +216,8 @@ module NewRelic
216
216
  def notice_segment_error(segment, exception, options = {})
217
217
  return if skip_notice_error?(exception)
218
218
 
219
+ options.merge!(segment.llm_event.error_attributes(exception)) if segment.llm_event
220
+
219
221
  segment.set_noticed_error(create_noticed_error(exception, options))
220
222
  exception
221
223
  rescue => e
@@ -38,7 +38,7 @@ module NewRelic
38
38
  end
39
39
 
40
40
  def harvest_thread_enabled?
41
- !NewRelic::Agent.config[:disable_harvest_thread]
41
+ !NewRelic::Agent.config[:disable_harvest_thread] && !NewRelic::Agent.config[:'serverless_mode.enabled']
42
42
  end
43
43
 
44
44
  def restart_harvest_thread
@@ -5,9 +5,13 @@
5
5
  module NewRelic::Agent::Instrumentation
6
6
  module ActiveSupportBroadcastLogger
7
7
  def record_one_broadcast_with_new_relic(*args)
8
- broadcasts[1..-1].each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, true) }
9
- yield
10
- broadcasts.each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, false) }
8
+ if broadcasts && broadcasts[1..-1]
9
+ broadcasts[1..-1].each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, true) }
10
+ yield
11
+ broadcasts.each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, false) }
12
+ else
13
+ NewRelic::Agent.logger.error('Error recording broadcasted logger')
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -11,6 +11,7 @@ DependencyDetection.defer do
11
11
 
12
12
  depends_on do
13
13
  defined?(Concurrent) &&
14
+ defined?(Concurrent::VERSION) &&
14
15
  Gem::Version.new(Concurrent::VERSION) >= Gem::Version.new('1.1.5')
15
16
  end
16
17
 
@@ -10,7 +10,11 @@ module NewRelic::Agent::Instrumentation
10
10
  OPERATION = 'perform_request'
11
11
  INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
12
12
 
13
- def perform_request_with_tracing(method, path, params = {}, body = nil, headers = nil)
13
+ # We need the positional arguments `params` and `body`
14
+ # to capture the nosql statement
15
+ # *args protects the instrumented method if new arguments are added to
16
+ # perform_request
17
+ def perform_request_with_tracing(_method, _path, params = {}, body = nil, _headers = nil, *_args)
14
18
  return yield unless NewRelic::Agent::Tracer.tracing_enabled?
15
19
 
16
20
  NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
@@ -22,6 +26,7 @@ module NewRelic::Agent::Instrumentation
22
26
  port_path_or_id: nr_hosts[:port],
23
27
  database_name: nr_cluster_name
24
28
  )
29
+
25
30
  begin
26
31
  NewRelic::Agent::Tracer.capture_segment_error(segment) { yield }
27
32
  ensure
@@ -32,7 +32,13 @@ module NewRelic
32
32
  end
33
33
 
34
34
  wrapped_response = NewRelic::Agent::HTTPClients::NetHTTPResponse.new(response)
35
+
36
+ if NewRelic::Agent::LLM.openai_parent?(segment)
37
+ NewRelic::Agent::LLM.populate_openai_response_headers(wrapped_response, segment.parent)
38
+ end
39
+
35
40
  segment.process_response_headers(wrapped_response)
41
+
36
42
  response
37
43
  ensure
38
44
  segment&.finish
@@ -0,0 +1,36 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ module NewRelic::Agent::Instrumentation
6
+ module OpenAI::Chain
7
+ def self.instrument!
8
+ ::OpenAI::Client.class_eval do
9
+ include NewRelic::Agent::Instrumentation::OpenAI
10
+
11
+ alias_method(:json_post_without_new_relic, :json_post)
12
+
13
+ # In versions 4.0.0+ json_post is an instance method
14
+ # defined in the OpenAI::HTTP module, included by the
15
+ # OpenAI::Client class
16
+ def json_post(**kwargs)
17
+ json_post_with_new_relic(**kwargs) do
18
+ json_post_without_new_relic(**kwargs)
19
+ end
20
+ end
21
+
22
+ # In versions below 4.0.0 json_post is a class method
23
+ # on OpenAI::Client
24
+ class << self
25
+ alias_method(:json_post_without_new_relic, :json_post)
26
+
27
+ def json_post(**kwargs)
28
+ json_post_with_new_relic(**kwargs) do
29
+ json_post_without_new_relic(**kwargs)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end