newrelic_rpm 9.5.0 → 9.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +154 -7
  3. data/CONTRIBUTING.md +0 -7
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/bin/newrelic +2 -9
  7. data/bin/newrelic_rpm +15 -0
  8. data/init.rb +2 -2
  9. data/lib/new_relic/agent/agent.rb +1 -1
  10. data/lib/new_relic/agent/agent_helpers/shutdown.rb +1 -1
  11. data/lib/new_relic/agent/agent_helpers/special_startup.rb +1 -1
  12. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +2 -2
  13. data/lib/new_relic/agent/agent_helpers/startup.rb +2 -2
  14. data/lib/new_relic/agent/attribute_filter.rb +3 -3
  15. data/lib/new_relic/agent/configuration/default_source.rb +131 -36
  16. data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
  17. data/lib/new_relic/agent/configuration/manager.rb +13 -9
  18. data/lib/new_relic/agent/configuration/security_policy_source.rb +11 -0
  19. data/lib/new_relic/agent/custom_event_aggregator.rb +27 -1
  20. data/lib/new_relic/agent/datastores/mongo/metric_translator.rb +1 -1
  21. data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +3 -3
  22. data/lib/new_relic/agent/error_collector.rb +2 -0
  23. data/lib/new_relic/agent/event_loop.rb +1 -1
  24. data/lib/new_relic/agent/http_clients/abstract.rb +4 -0
  25. data/lib/new_relic/agent/http_clients/async_http_wrappers.rb +80 -0
  26. data/lib/new_relic/agent/http_clients/curb_wrappers.rb +1 -3
  27. data/lib/new_relic/agent/http_clients/ethon_wrappers.rb +109 -0
  28. data/lib/new_relic/agent/http_clients/excon_wrappers.rb +0 -3
  29. data/lib/new_relic/agent/http_clients/http_rb_wrappers.rb +1 -3
  30. data/lib/new_relic/agent/http_clients/httpclient_wrappers.rb +0 -3
  31. data/lib/new_relic/agent/http_clients/httpx_wrappers.rb +91 -0
  32. data/lib/new_relic/agent/http_clients/net_http_wrappers.rb +1 -4
  33. data/lib/new_relic/agent/http_clients/typhoeus_wrappers.rb +0 -3
  34. data/lib/new_relic/agent/instrumentation/active_merchant.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +1 -2
  36. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/chain.rb +69 -0
  37. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/instrumentation.rb +17 -0
  38. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/prepend.rb +37 -0
  39. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +23 -0
  40. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +3 -1
  41. data/lib/new_relic/agent/instrumentation/async_http/chain.rb +23 -0
  42. data/lib/new_relic/agent/instrumentation/async_http/instrumentation.rb +37 -0
  43. data/lib/new_relic/agent/instrumentation/async_http/prepend.rb +15 -0
  44. data/lib/new_relic/agent/instrumentation/async_http.rb +28 -0
  45. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -0
  46. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -1
  47. data/lib/new_relic/agent/instrumentation/ethon/chain.rb +39 -0
  48. data/lib/new_relic/agent/instrumentation/ethon/instrumentation.rb +105 -0
  49. data/lib/new_relic/agent/instrumentation/ethon/prepend.rb +35 -0
  50. data/lib/new_relic/agent/instrumentation/ethon.rb +39 -0
  51. data/lib/new_relic/agent/instrumentation/fiber/instrumentation.rb +1 -4
  52. data/lib/new_relic/agent/instrumentation/grpc_server.rb +1 -1
  53. data/lib/new_relic/agent/instrumentation/httpx/chain.rb +20 -0
  54. data/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb +51 -0
  55. data/lib/new_relic/agent/instrumentation/httpx/prepend.rb +15 -0
  56. data/lib/new_relic/agent/instrumentation/httpx.rb +27 -0
  57. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -3
  58. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +7 -1
  59. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +1 -0
  60. data/lib/new_relic/agent/instrumentation/roda/ignorer.rb +45 -0
  61. data/lib/new_relic/agent/instrumentation/roda/instrumentation.rb +12 -0
  62. data/lib/new_relic/agent/instrumentation/roda/roda_transaction_namer.rb +1 -2
  63. data/lib/new_relic/agent/instrumentation/roda.rb +2 -0
  64. data/lib/new_relic/agent/instrumentation/ruby_openai/chain.rb +36 -0
  65. data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +197 -0
  66. data/lib/new_relic/agent/instrumentation/ruby_openai/prepend.rb +20 -0
  67. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +35 -0
  68. data/lib/new_relic/agent/instrumentation/sidekiq.rb +3 -1
  69. data/lib/new_relic/agent/instrumentation/sinatra/ignorer.rb +1 -1
  70. data/lib/new_relic/agent/instrumentation/sinatra/transaction_namer.rb +1 -3
  71. data/lib/new_relic/agent/instrumentation/sinatra.rb +1 -1
  72. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +1 -4
  73. data/lib/new_relic/agent/instrumentation/view_component/chain.rb +21 -0
  74. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +39 -0
  75. data/lib/new_relic/agent/instrumentation/view_component/prepend.rb +13 -0
  76. data/lib/new_relic/agent/instrumentation/view_component.rb +26 -0
  77. data/lib/new_relic/agent/javascript_instrumentor.rb +0 -1
  78. data/lib/new_relic/agent/llm/chat_completion_message.rb +25 -0
  79. data/lib/new_relic/agent/llm/chat_completion_summary.rb +66 -0
  80. data/lib/new_relic/agent/llm/embedding.rb +60 -0
  81. data/lib/new_relic/agent/llm/llm_event.rb +95 -0
  82. data/lib/new_relic/agent/llm/response_headers.rb +80 -0
  83. data/lib/new_relic/agent/llm.rb +49 -0
  84. data/lib/new_relic/agent/messaging.rb +2 -2
  85. data/lib/new_relic/agent/monitors/synthetics_monitor.rb +12 -1
  86. data/lib/new_relic/agent/new_relic_service/encoders.rb +2 -2
  87. data/lib/new_relic/agent/new_relic_service.rb +8 -6
  88. data/lib/new_relic/agent/obfuscator.rb +0 -2
  89. data/lib/new_relic/agent/pipe_channel_manager.rb +2 -2
  90. data/lib/new_relic/agent/rules_engine/segment_terms_rule.rb +1 -2
  91. data/lib/new_relic/agent/rules_engine.rb +1 -1
  92. data/lib/new_relic/agent/span_event_primitive.rb +16 -4
  93. data/lib/new_relic/agent/sql_sampler.rb +0 -1
  94. data/lib/new_relic/agent/system_info.rb +26 -0
  95. data/lib/new_relic/agent/threading/agent_thread.rb +1 -2
  96. data/lib/new_relic/agent/tracer.rb +9 -10
  97. data/lib/new_relic/agent/transaction/abstract_segment.rb +4 -1
  98. data/lib/new_relic/agent/transaction/external_request_segment.rb +5 -2
  99. data/lib/new_relic/agent/transaction/message_broker_segment.rb +1 -2
  100. data/lib/new_relic/agent/transaction/request_attributes.rb +1 -3
  101. data/lib/new_relic/agent/transaction/tracing.rb +11 -1
  102. data/lib/new_relic/agent/transaction.rb +25 -2
  103. data/lib/new_relic/agent/transaction_error_primitive.rb +16 -0
  104. data/lib/new_relic/agent/transaction_event_primitive.rb +19 -0
  105. data/lib/new_relic/agent/utilization/gcp.rb +1 -3
  106. data/lib/new_relic/agent/vm/{mri_vm.rb → c_ruby_vm.rb} +7 -15
  107. data/lib/new_relic/agent/vm.rb +2 -2
  108. data/lib/new_relic/agent/worker_loop.rb +1 -1
  109. data/lib/new_relic/agent.rb +102 -7
  110. data/lib/new_relic/base64.rb +25 -0
  111. data/lib/new_relic/cli/command.rb +6 -4
  112. data/lib/new_relic/constants.rb +5 -0
  113. data/lib/new_relic/control/frameworks/rails.rb +17 -5
  114. data/lib/new_relic/control/instrumentation.rb +1 -1
  115. data/lib/new_relic/language_support.rb +4 -0
  116. data/lib/new_relic/local_environment.rb +22 -13
  117. data/lib/new_relic/rack/browser_monitoring.rb +8 -4
  118. data/lib/new_relic/supportability_helper.rb +3 -1
  119. data/lib/new_relic/thread_local_storage.rb +31 -0
  120. data/lib/new_relic/version.rb +1 -1
  121. data/lib/tasks/config.rake +1 -1
  122. data/lib/tasks/helpers/config.html.erb +6 -6
  123. data/lib/tasks/helpers/newrelicyml.rb +1 -1
  124. data/lib/tasks/instrumentation_generator/instrumentation.thor +3 -3
  125. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -1
  126. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -1
  127. data/lib/tasks/tests.rake +71 -0
  128. data/newrelic.yml +76 -36
  129. data/newrelic_rpm.gemspec +5 -4
  130. data/test/agent_helper.rb +14 -2
  131. metadata +43 -7
  132. data/bin/newrelic_cmd +0 -7
@@ -31,9 +31,14 @@ module NewRelic
31
31
  SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'.freeze
32
32
  SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'.freeze
33
33
  SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'.freeze
34
+ SYNTHETICS_TYPE_KEY = 'nr.syntheticsType'
35
+ SYNTHETICS_INITIATOR_KEY = 'nr.syntheticsInitiator'
36
+ SYNTHETICS_KEY_PREFIX = 'nr.synthetics'
34
37
  PRIORITY_KEY = 'priority'.freeze
35
38
  SPAN_ID_KEY = 'spanId'.freeze
36
39
 
40
+ SYNTHETICS_PAYLOAD_EXPECTED = [:synthetics_resource_id, :synthetics_job_id, :synthetics_monitor_id, :synthetics_type, :synthetics_initiator]
41
+
37
42
  def create(noticed_error, payload, span_id)
38
43
  [
39
44
  intrinsic_attributes_for(noticed_error, payload, span_id),
@@ -71,9 +76,20 @@ module NewRelic
71
76
  end
72
77
 
73
78
  def append_synthetics(payload, sample)
79
+ return unless payload[:synthetics_job_id]
80
+
74
81
  sample[SYNTHETICS_RESOURCE_ID_KEY] = payload[:synthetics_resource_id] if payload[:synthetics_resource_id]
75
82
  sample[SYNTHETICS_JOB_ID_KEY] = payload[:synthetics_job_id] if payload[:synthetics_job_id]
76
83
  sample[SYNTHETICS_MONITOR_ID_KEY] = payload[:synthetics_monitor_id] if payload[:synthetics_monitor_id]
84
+ sample[SYNTHETICS_TYPE_KEY] = payload[:synthetics_type] if payload[:synthetics_type]
85
+ sample[SYNTHETICS_INITIATOR_KEY] = payload[:synthetics_initiator] if payload[:synthetics_initiator]
86
+
87
+ payload.each do |k, v|
88
+ next unless k.to_s.start_with?('synthetics_') && !SYNTHETICS_PAYLOAD_EXPECTED.include?(k)
89
+
90
+ new_key = SYNTHETICS_KEY_PREFIX + NewRelic::LanguageSupport.camelize(k.to_s.gsub('synthetics_', ''))
91
+ sample[new_key] = v
92
+ end
77
93
  end
78
94
 
79
95
  def append_cat(payload, sample)
@@ -38,6 +38,11 @@ module NewRelic
38
38
  SYNTHETICS_RESOURCE_ID_KEY = 'nr.syntheticsResourceId'
39
39
  SYNTHETICS_JOB_ID_KEY = 'nr.syntheticsJobId'
40
40
  SYNTHETICS_MONITOR_ID_KEY = 'nr.syntheticsMonitorId'
41
+ SYNTHETICS_TYPE_KEY = 'nr.syntheticsType'
42
+ SYNTHETICS_INITIATOR_KEY = 'nr.syntheticsInitiator'
43
+ SYNTHETICS_KEY_PREFIX = 'nr.synthetics'
44
+
45
+ SYNTHETICS_PAYLOAD_EXPECTED = [:synthetics_resource_id, :synthetics_job_id, :synthetics_monitor_id, :synthetics_type, :synthetics_initiator]
41
46
 
42
47
  def create(payload)
43
48
  intrinsics = {
@@ -71,9 +76,23 @@ module NewRelic
71
76
  optionally_append(SYNTHETICS_RESOURCE_ID_KEY, :synthetics_resource_id, sample, payload)
72
77
  optionally_append(SYNTHETICS_JOB_ID_KEY, :synthetics_job_id, sample, payload)
73
78
  optionally_append(SYNTHETICS_MONITOR_ID_KEY, :synthetics_monitor_id, sample, payload)
79
+ optionally_append(SYNTHETICS_TYPE_KEY, :synthetics_type, sample, payload)
80
+ optionally_append(SYNTHETICS_INITIATOR_KEY, :synthetics_initiator, sample, payload)
81
+ append_synthetics_info_attributes(sample, payload)
74
82
  append_cat_alternate_path_hashes(sample, payload)
75
83
  end
76
84
 
85
+ def append_synthetics_info_attributes(sample, payload)
86
+ return unless payload.include?(:synthetics_job_id)
87
+
88
+ payload.each do |k, v|
89
+ next unless k.to_s.start_with?('synthetics_') && !SYNTHETICS_PAYLOAD_EXPECTED.include?(k)
90
+
91
+ new_key = SYNTHETICS_KEY_PREFIX + NewRelic::LanguageSupport.camelize(k.to_s.gsub('synthetics_', ''))
92
+ sample[new_key] = v.to_s
93
+ end
94
+ end
95
+
77
96
  def append_cat_alternate_path_hashes(sample, payload)
78
97
  if payload.include?(:cat_alternate_path_hashes)
79
98
  sample[CAT_ALTERNATE_PATH_HASHES_KEY] = payload[:cat_alternate_path_hashes].sort.join(COMMA)
@@ -24,10 +24,8 @@ module NewRelic
24
24
  body
25
25
  end
26
26
 
27
- SLASH = '/'.freeze
28
-
29
27
  def trim_leading(value)
30
- value.split(SLASH).last
28
+ value.split(NewRelic::SLASH).last
31
29
  end
32
30
  end
33
31
  end
@@ -8,7 +8,7 @@ require 'new_relic/agent/vm/snapshot'
8
8
  module NewRelic
9
9
  module Agent
10
10
  module VM
11
- class MriVM
11
+ class CRubyVM
12
12
  def snapshot
13
13
  snap = Snapshot.new
14
14
  gather_stats(snap)
@@ -24,7 +24,7 @@ module NewRelic
24
24
 
25
25
  def gather_gc_stats(snap)
26
26
  gather_gc_runs(snap) if supports?(:gc_runs)
27
- gather_derived_stats(snap) if GC.respond_to?(:stat)
27
+ gather_derived_stats(snap)
28
28
  end
29
29
 
30
30
  def gather_gc_runs(snap)
@@ -33,19 +33,11 @@ module NewRelic
33
33
 
34
34
  def gather_derived_stats(snap)
35
35
  stat = GC.stat
36
- snap.total_allocated_object = derive_from_gc_stats(%i[total_allocated_objects total_allocated_object], stat)
37
- snap.major_gc_count = derive_from_gc_stats(:major_gc_count, stat)
38
- snap.minor_gc_count = derive_from_gc_stats(:minor_gc_count, stat)
39
- snap.heap_live = derive_from_gc_stats(%i[heap_live_slots heap_live_slot heap_live_num], stat)
40
- snap.heap_free = derive_from_gc_stats(%i[heap_free_slots heap_free_slot heap_free_num], stat)
41
- end
42
-
43
- def derive_from_gc_stats(keys, stat)
44
- Array(keys).each do |key|
45
- value = stat[key]
46
- return value if value
47
- end
48
- nil
36
+ snap.total_allocated_object = stat.fetch(:total_allocated_objects, nil)
37
+ snap.major_gc_count = stat.fetch(:major_gc_count, nil)
38
+ snap.minor_gc_count = stat.fetch(:minor_gc_count, nil)
39
+ snap.heap_live = stat.fetch(:heap_live_slots, nil)
40
+ snap.heap_free = stat.fetch(:heap_free_slots, nil)
49
41
  end
50
42
 
51
43
  def gather_gc_time(snap)
@@ -3,7 +3,7 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  require 'new_relic/language_support'
6
- require 'new_relic/agent/vm/mri_vm'
6
+ require 'new_relic/agent/vm/c_ruby_vm'
7
7
  require 'new_relic/agent/vm/jruby_vm'
8
8
 
9
9
  module NewRelic
@@ -21,7 +21,7 @@ module NewRelic
21
21
  if NewRelic::LanguageSupport.jruby?
22
22
  JRubyVM.new
23
23
  else
24
- MriVM.new
24
+ CRubyVM.new
25
25
  end
26
26
  end
27
27
  end
@@ -88,7 +88,7 @@ module NewRelic
88
88
  raise
89
89
  rescue => e
90
90
  # Don't blow out the stack for anything that hasn't already propagated
91
- ::NewRelic::Agent.logger.error('Error running task in Agent Worker Loop:', e)
91
+ ::NewRelic::Agent.logger.error('Error running task in agent worker loop:', e)
92
92
  end
93
93
  end
94
94
  end
@@ -31,6 +31,7 @@ module NewRelic
31
31
  require 'new_relic/noticed_error'
32
32
  require 'new_relic/agent/noticeable_error'
33
33
  require 'new_relic/supportability_helper'
34
+ require 'new_relic/thread_local_storage'
34
35
 
35
36
  require 'new_relic/agent/encoding_normalizer'
36
37
  require 'new_relic/agent/stats'
@@ -62,6 +63,7 @@ module NewRelic
62
63
  require 'new_relic/agent/attribute_processing'
63
64
  require 'new_relic/agent/linking_metadata'
64
65
  require 'new_relic/agent/local_log_decorator'
66
+ require 'new_relic/agent/llm'
65
67
 
66
68
  require 'new_relic/agent/instrumentation/controller_instrumentation'
67
69
 
@@ -105,11 +107,14 @@ module NewRelic
105
107
 
106
108
  # placeholder name used when we cannot determine a transaction's name
107
109
  UNKNOWN_METRIC = '(unknown)'.freeze
110
+ LLM_FEEDBACK_MESSAGE = 'LlmFeedbackMessage'
108
111
 
109
112
  attr_reader :error_group_callback
113
+ attr_reader :llm_token_count_callback
110
114
 
111
115
  @agent = nil
112
116
  @error_group_callback = nil
117
+ @llm_token_count_callback = nil
113
118
  @logger = nil
114
119
  @tracer_lock = Mutex.new
115
120
  @tracer_queue = []
@@ -252,7 +257,7 @@ module NewRelic
252
257
 
253
258
  # @!group Recording custom errors
254
259
 
255
- # Set a filter to be applied to errors that the Ruby Agent will
260
+ # Set a filter to be applied to errors that the Ruby agent will
256
261
  # track. The block should evaluate to the exception to track
257
262
  # (which could be different from the original exception) or nil to
258
263
  # ignore this exception.
@@ -387,17 +392,103 @@ module NewRelic
387
392
  nil
388
393
  end
389
394
 
395
+ # Records user feedback events for LLM applications. This API must pass
396
+ # the current trace id as a parameter, which can be obtained using:
397
+ #
398
+ # NewRelic::Agent::Tracer.current_trace_id
399
+ #
400
+ # @param [String] ID of the trace where the chat completion(s) related
401
+ # to the feedback occurred.
402
+ #
403
+ # @param [String or Integer] Rating provided by an end user
404
+ # (ex: “Good", "Bad”, 1, 2, 5, 8, 10).
405
+ #
406
+ # @param [optional, String] Category of the feedback as provided by the
407
+ # end user (ex: “informative”, “inaccurate”).
408
+ #
409
+ # @param start_time [optional, String] Freeform text feedback from an
410
+ # end user.
411
+ #
412
+ # @param [optional, Hash] Set of key-value pairs to store any other
413
+ # desired data to submit with the feedback event.
414
+ #
415
+ # @api public
416
+ #
417
+ def record_llm_feedback_event(trace_id:,
418
+ rating:,
419
+ category: nil,
420
+ message: nil,
421
+ metadata: NewRelic::EMPTY_HASH)
422
+
423
+ record_api_supportability_metric(:record_llm_feedback_event)
424
+ unless NewRelic::Agent.config[:'distributed_tracing.enabled']
425
+ return NewRelic::Agent.logger.error('Distributed tracing must be enabled to record LLM feedback')
426
+ end
427
+
428
+ feedback_message_event = {
429
+ 'trace_id': trace_id,
430
+ 'rating': rating,
431
+ 'category': category,
432
+ 'message': message,
433
+ 'id': NewRelic::Agent::GuidGenerator.generate_guid,
434
+ 'ingest_source': NewRelic::Agent::Llm::LlmEvent::INGEST_SOURCE
435
+ }
436
+ feedback_message_event.merge!(metadata) unless metadata.empty?
437
+
438
+ NewRelic::Agent.record_custom_event(LLM_FEEDBACK_MESSAGE, feedback_message_event)
439
+ rescue ArgumentError
440
+ raise
441
+ rescue => exception
442
+ NewRelic::Agent.logger.error('record_llm_feedback_event', exception)
443
+ end
444
+
445
+ # @!endgroup
446
+
447
+ # @!group LLM callbacks
448
+
449
+ # Set a callback proc for calculating `token_count` attributes for
450
+ # LlmEmbedding and LlmChatCompletionMessage events
451
+ #
452
+ # @param callback_proc [Proc] the callback proc
453
+ #
454
+ # This method should be called only once to set a callback for
455
+ # use with all LLM token calculations. If it is called multiple times, each
456
+ # new callback will replace the old one.
457
+ #
458
+ # The proc will be called with a single hash as its input argument and
459
+ # must return an Integer representing the number of tokens used for that
460
+ # particular prompt, completion message, or embedding. Values less than or
461
+ # equal to 0 will not be attached to an event.
462
+ #
463
+ # The hash has the following keys:
464
+ #
465
+ # :model => [String] The name of the LLM model
466
+ # :content => [String] The message content or prompt
467
+ #
468
+ # @api public
469
+ #
470
+ def set_llm_token_count_callback(callback_proc)
471
+ unless callback_proc.is_a?(Proc)
472
+ NewRelic::Agent.logger.error("#{self}.#{__method__}: expected an argument of type Proc, " \
473
+ "got #{callback_proc.class}")
474
+ return
475
+ end
476
+
477
+ record_api_supportability_metric(:set_llm_token_count_callback)
478
+ @llm_token_count_callback = callback_proc
479
+ end
480
+
390
481
  # @!endgroup
391
482
 
392
483
  # @!group Manual agent configuration and startup/shutdown
393
484
 
394
- # Call this to manually start the Agent in situations where the Agent does
485
+ # Call this to manually start the agent in situations where the agent does
395
486
  # not auto-start.
396
487
  #
397
- # When the app environment loads, so does the Agent. However, the
398
- # Agent will only connect to the service if a web front-end is found. If
488
+ # When the app environment loads, so does the agent. However, the
489
+ # agent will only connect to the service if a web front-end is found. If
399
490
  # you want to selectively monitor ruby processes that don't use
400
- # web plugins, then call this method in your code and the Agent
491
+ # web plugins, then call this method in your code and the agent
401
492
  # will fire up and start reporting to the service.
402
493
  #
403
494
  # Options are passed in as overrides for values in the
@@ -633,7 +724,9 @@ module NewRelic
633
724
  def add_new_segment_attributes(params, segment)
634
725
  # Make sure not to override existing segment-level custom attributes
635
726
  segment_custom_keys = segment.attributes.custom_attributes.keys.map(&:to_sym)
636
- segment.add_custom_attributes(params.reject { |k, _v| segment_custom_keys.include?(k.to_sym) })
727
+ segment.add_custom_attributes(params.reject do |k, _v|
728
+ segment_custom_keys.include?(k.to_sym) if k.respond_to?(:to_sym) # param keys can be integers
729
+ end)
637
730
  end
638
731
 
639
732
  # Add custom attributes to the span event for the current span. Attributes will be visible on spans in the
@@ -661,7 +754,9 @@ module NewRelic
661
754
  end
662
755
  end
663
756
 
664
- # Add custom attributes to log events for the current agent instance.
757
+ # Add global custom attributes to log events for the current agent instance. As these attributes are global to the
758
+ # agent instance, they will be attached to all log events generated by the agent, and this methods usage isn't
759
+ # suitable for setting dynamic values.
665
760
  #
666
761
  # @param [Hash] params A Hash of attributes to attach to log
667
762
  # events. The agent accepts up to 240 custom
@@ -0,0 +1,25 @@
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
6
+ module Base64
7
+ extend self
8
+
9
+ def encode64(bin)
10
+ [bin].pack('m')
11
+ end
12
+
13
+ def decode64(str)
14
+ str.unpack1('m')
15
+ end
16
+
17
+ def strict_encode64(bin)
18
+ [bin].pack('m0')
19
+ end
20
+
21
+ def strict_decode64(str)
22
+ str.unpack1('m0')
23
+ end
24
+ end
25
+ end
@@ -60,11 +60,13 @@ module NewRelic
60
60
  extra = []
61
61
  options = ARGV.options do |opts|
62
62
  script_name = File.basename($0)
63
- # TODO: MAJOR VERSION - remove newrelic_cmd, deprecated since version 2.13
64
- if /newrelic_cmd$/.match?(script_name)
65
- $stdout.puts "warning: the 'newrelic_cmd' script has been renamed 'newrelic'"
66
- script_name = 'newrelic'
63
+
64
+ # TODO: MAJOR VERSION - remove newrelic, deprecated since version x.xx
65
+ if /newrelic$/.match?(script_name)
66
+ $stdout.puts "warning: the 'newrelic' script has been renamed 'newrelic_rpm'"
67
+ script_name = 'newrelic_rpm'
67
68
  end
69
+
68
70
  opts.banner = "Usage: #{script_name} [ #{@command_names.join(' | ')} ] [options]"
69
71
  opts.separator("use '#{script_name} <command> -h' to see detailed command options")
70
72
  opts
@@ -3,6 +3,8 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  module NewRelic
6
+ ASTERISK = '*'
7
+
6
8
  PRIORITY_PRECISION = 6
7
9
 
8
10
  EMPTY_ARRAY = [].freeze
@@ -35,4 +37,7 @@ module NewRelic
35
37
 
36
38
  CONNECT_RETRY_PERIODS = [15, 15, 30, 60, 120, 300]
37
39
  MAX_RETRY_PERIOD = 300
40
+
41
+ SLASH = '/'
42
+ ROOT = SLASH
38
43
  end
@@ -10,6 +10,9 @@ module NewRelic
10
10
  # Rails specific configuration, instrumentation, environment values,
11
11
  # etc.
12
12
  class Rails < NewRelic::Control::Frameworks::Ruby
13
+ BROWSER_MONITORING_INSTALLED_SINGLETON = NewRelic::Agent.config
14
+ BROWSER_MONITORING_INSTALLED_VARIABLE = :@browser_monitoring_installed
15
+
13
16
  def env
14
17
  @env ||= (ENV['NEW_RELIC_ENV'] || RAILS_ENV.dup)
15
18
  end
@@ -71,7 +74,7 @@ module NewRelic
71
74
  # Might not be running if it does not think mongrel, thin,
72
75
  # passenger, etc. is running, if it thinks it's a rake task, or
73
76
  # if the agent_enabled is false.
74
- ::NewRelic::Agent.logger.info('New Relic Agent not running. Skipping browser monitoring and agent hooks.')
77
+ ::NewRelic::Agent.logger.info('New Relic agent not running. Skipping browser monitoring and agent hooks.')
75
78
  else
76
79
  install_browser_monitoring(rails_config)
77
80
  install_agent_hooks(rails_config)
@@ -89,17 +92,17 @@ module NewRelic
89
92
  return unless NewRelic::Rack::AgentHooks.needed?
90
93
 
91
94
  config.middleware.use(NewRelic::Rack::AgentHooks)
92
- ::NewRelic::Agent.logger.debug('Installed New Relic Agent Hooks middleware')
95
+ ::NewRelic::Agent.logger.debug('Installed New Relic agent hooks middleware')
93
96
  rescue => e
94
- ::NewRelic::Agent.logger.warn('Error installing New Relic Agent Hooks middleware', e)
97
+ ::NewRelic::Agent.logger.warn('Error installing New Relic agent hooks middleware', e)
95
98
  end
96
99
  end
97
100
 
98
101
  def install_browser_monitoring(config)
99
102
  @install_lock.synchronize do
100
- return if defined?(@browser_monitoring_installed) && @browser_monitoring_installed
103
+ return if browser_agent_already_installed?
101
104
 
102
- @browser_monitoring_installed = true
105
+ mark_browser_agent_as_installed
103
106
  return if config.nil? || !config.respond_to?(:middleware) || !Agent.config[:'browser_monitoring.auto_instrument']
104
107
 
105
108
  begin
@@ -112,6 +115,15 @@ module NewRelic
112
115
  end
113
116
  end
114
117
 
118
+ def browser_agent_already_installed?
119
+ BROWSER_MONITORING_INSTALLED_SINGLETON.instance_variable_defined?(BROWSER_MONITORING_INSTALLED_VARIABLE) &&
120
+ BROWSER_MONITORING_INSTALLED_SINGLETON.instance_variable_get(BROWSER_MONITORING_INSTALLED_VARIABLE)
121
+ end
122
+
123
+ def mark_browser_agent_as_installed
124
+ BROWSER_MONITORING_INSTALLED_SINGLETON.instance_variable_set(BROWSER_MONITORING_INSTALLED_VARIABLE, true)
125
+ end
126
+
115
127
  def rails_version
116
128
  @rails_version ||= Gem::Version.new(::Rails::VERSION::STRING)
117
129
  end
@@ -70,7 +70,7 @@ module NewRelic
70
70
  def rails_32_deprecation
71
71
  return unless defined?(Rails::VERSION) && Gem::Version.new(Rails::VERSION::STRING) <= Gem::Version.new('3.2')
72
72
 
73
- deprecation_msg = 'The Ruby Agent is dropping support for Rails 3.2 ' \
73
+ deprecation_msg = 'The Ruby agent is dropping support for Rails 3.2 ' \
74
74
  'in a future major release. Please upgrade your Rails version to continue receiving support. ' \
75
75
 
76
76
  Agent.logger.log_once(
@@ -83,6 +83,10 @@ module NewRelic
83
83
  camelized[0].downcase.concat(camelized[1..-1])
84
84
  end
85
85
 
86
+ def snakeize(string)
87
+ string.gsub(/(.)([A-Z])/, '\1_\2').downcase
88
+ end
89
+
86
90
  def bundled_gem?(gem_name)
87
91
  defined?(Bundler) && Bundler.rubygems.all_specs.map(&:name).include?(gem_name)
88
92
  rescue => e
@@ -74,6 +74,7 @@ module NewRelic
74
74
  unicorn
75
75
  webrick
76
76
  fastcgi
77
+ falcon
77
78
  ]
78
79
  while dispatchers.any? && @discovered_dispatcher.nil?
79
80
  send('check_for_' + (dispatchers.shift))
@@ -126,16 +127,24 @@ module NewRelic
126
127
  end
127
128
 
128
129
  def check_for_unicorn
129
- if (defined?(::Unicorn) && defined?(::Unicorn::HttpServer)) && NewRelic::LanguageSupport.object_space_usable?
130
- v = find_class_in_object_space(::Unicorn::HttpServer)
131
- @discovered_dispatcher = :unicorn if v
132
- end
130
+ return unless (defined?(::Unicorn) && defined?(::Unicorn::HttpServer)) &&
131
+ NewRelic::LanguageSupport.object_space_usable?
132
+
133
+ v = find_class_in_object_space(::Unicorn::HttpServer)
134
+ @discovered_dispatcher = :unicorn if v
133
135
  end
134
136
 
135
137
  def check_for_puma
136
- if defined?(::Puma) && File.basename($0) == 'puma'
137
- @discovered_dispatcher = :puma
138
- end
138
+ return unless defined?(::Puma) && File.basename($0) == 'puma'
139
+
140
+ @discovered_dispatcher = :puma
141
+ end
142
+
143
+ def check_for_falcon
144
+ return unless defined?(::Falcon::Server) &&
145
+ NewRelic::LanguageSupport.object_space_usable?
146
+
147
+ @discovered_dispatcher = :falcon if find_class_in_object_space(::Falcon::Server)
139
148
  end
140
149
 
141
150
  def check_for_delayed_job
@@ -178,15 +187,15 @@ module NewRelic
178
187
  end
179
188
 
180
189
  def check_for_litespeed
181
- if caller.pop.include?('fcgi-bin/RailsRunner.rb')
182
- @discovered_dispatcher = :litespeed
183
- end
190
+ return unless caller.pop.include?('fcgi-bin/RailsRunner.rb')
191
+
192
+ @discovered_dispatcher = :litespeed
184
193
  end
185
194
 
186
195
  def check_for_passenger
187
- if defined?(::PhusionPassenger)
188
- @discovered_dispatcher = :passenger
189
- end
196
+ return unless defined?(::PhusionPassenger)
197
+
198
+ @discovered_dispatcher = :passenger
190
199
  end
191
200
 
192
201
  public
@@ -23,8 +23,8 @@ module NewRelic
23
23
  CONTENT_TYPE = 'Content-Type'.freeze
24
24
  CONTENT_DISPOSITION = 'Content-Disposition'.freeze
25
25
  CONTENT_LENGTH = 'Content-Length'.freeze
26
- ATTACHMENT = 'attachment'.freeze
27
- TEXT_HTML = 'text/html'.freeze
26
+ ATTACHMENT = /attachment/.freeze
27
+ TEXT_HTML = %r{text/html}.freeze
28
28
 
29
29
  BODY_START = '<body'.freeze
30
30
  HEAD_START = '<head'.freeze
@@ -65,6 +65,10 @@ module NewRelic
65
65
  html?(headers) &&
66
66
  !attachment?(headers) &&
67
67
  !streaming?(env, headers)
68
+ rescue StandardError => e
69
+ NewRelic::Agent.logger.error('RUM instrumentation applicability check failed on exception:' \
70
+ "#{e.class} - #{e.message}")
71
+ false
68
72
  end
69
73
 
70
74
  private
@@ -100,11 +104,11 @@ module NewRelic
100
104
 
101
105
  def html?(headers)
102
106
  # needs else branch coverage
103
- headers[CONTENT_TYPE] && headers[CONTENT_TYPE].include?(TEXT_HTML) # rubocop:disable Style/SafeNavigation
107
+ headers[CONTENT_TYPE]&.match?(TEXT_HTML)
104
108
  end
105
109
 
106
110
  def attachment?(headers)
107
- headers[CONTENT_DISPOSITION]&.include?(ATTACHMENT)
111
+ headers[CONTENT_DISPOSITION]&.match?(ATTACHMENT)
108
112
  end
109
113
 
110
114
  def streaming?(env, headers)
@@ -43,9 +43,11 @@ module NewRelic
43
43
  :process_response_metadata,
44
44
  :record_custom_event,
45
45
  :record_metric,
46
+ :record_llm_feedback_event,
46
47
  :recording_web_transaction?,
47
48
  :require_test_helper,
48
49
  :set_error_group_callback,
50
+ :set_llm_token_count_callback,
49
51
  :set_segment_callback,
50
52
  :set_sql_obfuscator,
51
53
  :set_transaction_name,
@@ -80,7 +82,7 @@ module NewRelic
80
82
  "Expected #{klass} for `#{name}` but got #{arg.class}"
81
83
 
82
84
  NewRelic::Agent.logger.warn(message)
83
- nil
85
+ false
84
86
  end
85
87
  end
86
88
  end
@@ -0,0 +1,31 @@
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
6
+ module ThreadLocalStorage
7
+ def self.get(thread, key)
8
+ if Agent.config[:thread_local_tracer_state]
9
+ thread.thread_variable_get(key)
10
+ else
11
+ thread[key]
12
+ end
13
+ end
14
+
15
+ def self.set(thread, key, value)
16
+ if Agent.config[:thread_local_tracer_state]
17
+ thread.thread_variable_set(key, value)
18
+ else
19
+ thread[key] = value
20
+ end
21
+ end
22
+
23
+ def self.[](key)
24
+ get(::Thread.current, key)
25
+ end
26
+
27
+ def self.[]=(key, value)
28
+ set(::Thread.current, key, value)
29
+ end
30
+ end
31
+ end
@@ -6,7 +6,7 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 5
9
+ MINOR = 8
10
10
  TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
@@ -19,7 +19,7 @@ namespace :newrelic do
19
19
  DISABLING => 'Use these settings to toggle instrumentation types during agent startup.',
20
20
  ATTRIBUTES => '[Attributes](/docs/features/agent-attributes) are key-value pairs containing information that determines the properties of an event or transaction. These key-value pairs can be viewed within transaction traces in APM, traced errors in APM, transaction events in dashboards, and page views in dashboards. You can customize exactly which attributes will be sent to each of these destinations',
21
21
  'transaction_tracer' => 'The [transaction traces](/docs/apm/traces/transaction-traces/transaction-traces) feature collects detailed information from a selection of transactions, including a summary of the calling sequence, a breakdown of time spent, and a list of SQL queries and their query plans (on mysql and postgresql). Available features depend on your New Relic subscription level.',
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/).",
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
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.'