newrelic_rpm 9.7.0 → 9.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +376 -2
  3. data/README.md +17 -18
  4. data/Rakefile +1 -1
  5. data/lib/boot/strap.rb +101 -0
  6. data/lib/new_relic/agent/agent.rb +4 -1
  7. data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
  8. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
  9. data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
  10. data/lib/new_relic/agent/agent_logger.rb +3 -1
  11. data/lib/new_relic/agent/aws.rb +68 -0
  12. data/lib/new_relic/agent/configuration/default_source.rb +519 -23
  13. data/lib/new_relic/agent/configuration/environment_source.rb +14 -2
  14. data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
  15. data/lib/new_relic/agent/configuration/manager.rb +51 -8
  16. data/lib/new_relic/agent/configuration/security_policy_source.rb +11 -0
  17. data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
  18. data/lib/new_relic/agent/connect/request_builder.rb +1 -1
  19. data/lib/new_relic/agent/custom_event_aggregator.rb +27 -1
  20. data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
  21. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  22. data/lib/new_relic/agent/database.rb +39 -0
  23. data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
  24. data/lib/new_relic/agent/error_collector.rb +39 -10
  25. data/lib/new_relic/agent/harvester.rb +1 -1
  26. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  27. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -8
  28. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +3 -0
  29. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +1 -12
  30. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/instrumentation.rb +7 -3
  31. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
  32. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
  33. data/lib/new_relic/agent/instrumentation/async_http.rb +4 -3
  34. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
  35. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
  36. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
  37. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
  38. data/lib/new_relic/agent/instrumentation/aws_sqs/chain.rb +37 -0
  39. data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
  40. data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
  41. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +23 -0
  42. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  43. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
  44. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -2
  45. data/lib/new_relic/agent/instrumentation/curb.rb +3 -4
  46. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
  47. data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
  48. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +64 -0
  49. data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
  50. data/lib/new_relic/agent/instrumentation/dynamodb.rb +23 -0
  51. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +58 -8
  52. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +0 -2
  53. data/lib/new_relic/agent/instrumentation/ethon.rb +0 -4
  54. data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
  55. data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
  56. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  57. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
  58. data/lib/new_relic/agent/instrumentation/grpc_server.rb +1 -1
  59. data/lib/new_relic/agent/instrumentation/httpclient.rb +0 -1
  60. data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
  61. data/lib/new_relic/agent/instrumentation/httpx.rb +0 -4
  62. data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
  63. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  64. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  65. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  66. data/lib/new_relic/agent/instrumentation/logstasher.rb +25 -0
  67. data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
  68. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +6 -0
  69. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  70. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  71. data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
  72. data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
  73. data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
  74. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  75. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  76. data/lib/new_relic/agent/instrumentation/rake.rb +0 -1
  77. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
  78. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  79. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
  80. data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
  81. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  82. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  83. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  84. data/lib/new_relic/agent/instrumentation/redis.rb +11 -5
  85. data/lib/new_relic/agent/instrumentation/resque.rb +0 -4
  86. data/lib/new_relic/agent/instrumentation/roda.rb +4 -4
  87. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  88. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  89. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
  90. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
  91. data/lib/new_relic/agent/instrumentation/ruby_openai/chain.rb +36 -0
  92. data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +196 -0
  93. data/lib/new_relic/agent/instrumentation/ruby_openai/prepend.rb +20 -0
  94. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +35 -0
  95. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
  96. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
  97. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +22 -1
  98. data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
  99. data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
  100. data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
  101. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +13 -6
  102. data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
  103. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  104. data/lib/new_relic/agent/llm/chat_completion_message.rb +25 -0
  105. data/lib/new_relic/agent/llm/chat_completion_summary.rb +66 -0
  106. data/lib/new_relic/agent/llm/embedding.rb +60 -0
  107. data/lib/new_relic/agent/llm/llm_event.rb +95 -0
  108. data/lib/new_relic/agent/llm/response_headers.rb +80 -0
  109. data/lib/new_relic/agent/llm.rb +49 -0
  110. data/lib/new_relic/agent/local_log_decorator.rb +8 -1
  111. data/lib/new_relic/agent/log_event_aggregator.rb +120 -44
  112. data/lib/new_relic/agent/messaging.rb +11 -5
  113. data/lib/new_relic/agent/new_relic_service.rb +12 -2
  114. data/lib/new_relic/agent/serverless_handler.rb +400 -0
  115. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  116. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  117. data/lib/new_relic/agent/span_event_primitive.rb +8 -10
  118. data/lib/new_relic/agent/system_info.rb +14 -0
  119. data/lib/new_relic/agent/threading/agent_thread.rb +1 -2
  120. data/lib/new_relic/agent/tracer.rb +5 -5
  121. data/lib/new_relic/agent/transaction/abstract_segment.rb +1 -1
  122. data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
  123. data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
  124. data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
  125. data/lib/new_relic/agent/transaction/tracing.rb +2 -2
  126. data/lib/new_relic/agent/transaction.rb +2 -6
  127. data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
  128. data/lib/new_relic/agent.rb +198 -10
  129. data/lib/new_relic/constants.rb +2 -0
  130. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  131. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  132. data/lib/new_relic/control/frameworks/rails4.rb +1 -3
  133. data/lib/new_relic/control/instance_methods.rb +8 -0
  134. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  135. data/lib/new_relic/control/security_interface.rb +57 -0
  136. data/lib/new_relic/control.rb +1 -1
  137. data/lib/new_relic/dependency_detection.rb +10 -5
  138. data/lib/new_relic/environment_report.rb +2 -2
  139. data/lib/new_relic/helper.rb +15 -0
  140. data/lib/new_relic/language_support.rb +3 -1
  141. data/lib/new_relic/local_environment.rb +14 -10
  142. data/lib/new_relic/rack/browser_monitoring.rb +28 -12
  143. data/lib/new_relic/supportability_helper.rb +2 -0
  144. data/lib/new_relic/thread_local_storage.rb +31 -0
  145. data/lib/new_relic/version.rb +2 -2
  146. data/lib/sequel/extensions/new_relic_instrumentation.rb +3 -2
  147. data/lib/tasks/config.rake +8 -3
  148. data/lib/tasks/gha.rake +31 -0
  149. data/lib/tasks/helpers/config.html.erb +3 -2
  150. data/lib/tasks/helpers/format.rb +1 -1
  151. data/lib/tasks/helpers/newrelicyml.rb +76 -13
  152. data/lib/tasks/instrumentation_generator/instrumentation.thor +31 -22
  153. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -1
  154. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -1
  155. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +11 -8
  156. data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +1 -1
  157. data/newrelic.yml +387 -143
  158. data/newrelic_rpm.gemspec +2 -0
  159. data/test/agent_helper.rb +17 -2
  160. metadata +80 -3
@@ -92,14 +92,18 @@ module NewRelic
92
92
  elsif type == Symbol
93
93
  self[config_key] = value.to_sym
94
94
  elsif type == Array
95
- self[config_key] = value.split(/\s*,\s*/)
95
+ self[config_key] = if DEFAULTS[config_key].key?(:transform)
96
+ DEFAULTS[config_key][:transform].call(value)
97
+ else
98
+ value.split(/\s*,\s*/)
99
+ end
96
100
  elsif type == NewRelic::Agent::Configuration::Boolean
97
101
  if /false|off|no/i.match?(value)
98
102
  self[config_key] = false
99
103
  elsif !value.nil?
100
104
  self[config_key] = true
101
105
  end
102
- else
106
+ elsif !serverless?
103
107
  ::NewRelic::Agent.logger.info("#{environment_key} does not have a corresponding configuration setting (#{config_key} does not exist).")
104
108
  ::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
109
  self[config_key] = value
@@ -114,6 +118,14 @@ module NewRelic
114
118
  def collect_new_relic_environment_variable_keys
115
119
  ENV.keys.select { |key| key.match(SUPPORTED_PREFIXES) }
116
120
  end
121
+
122
+ # we can't rely on the :'serverless_mode.enabled' config parameter being
123
+ # set yet to signify serverless mode given that we're in the midst of
124
+ # building the config but we can always rely on the env var being set
125
+ # by the Lambda layer
126
+ def serverless?
127
+ NewRelic::Agent::ServerlessHandler.env_var_set?
128
+ end
117
129
  end
118
130
  end
119
131
  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,14 @@ 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
+ boolean = enforce_boolean(key, value)
146
+ evaluated = boolean if [true, false].include?(boolean)
147
+
148
+ apply_transformations(key, evaluated)
141
149
  end
142
150
 
143
151
  def apply_transformations(key, value)
@@ -145,7 +153,7 @@ module NewRelic
145
153
  begin
146
154
  transform.call(value)
147
155
  rescue => e
148
- ::NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
156
+ NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
149
157
  raise e
150
158
  end
151
159
  else
@@ -153,8 +161,33 @@ module NewRelic
153
161
  end
154
162
  end
155
163
 
164
+ def enforce_allowlist(key, value)
165
+ return unless allowlist = default_source.allowlist_for(key)
166
+ return if allowlist.include?(value)
167
+
168
+ default = default_source.default_for(key)
169
+ NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
170
+ default
171
+ end
172
+
173
+ def enforce_boolean(key, value)
174
+ type = default_source.value_from_defaults(key, :type)
175
+ return unless type == Boolean
176
+
177
+ bool_value = default_source.boolean_for(key, value)
178
+ return bool_value unless bool_value.nil?
179
+
180
+ default = default_source.default_for(key)
181
+ NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
182
+ default
183
+ end
184
+
156
185
  def transform_from_default(key)
157
- ::NewRelic::Agent::Configuration::DefaultSource.transform_for(key)
186
+ default_source.transform_for(key)
187
+ end
188
+
189
+ def default_source
190
+ NewRelic::Agent::Configuration::DefaultSource
158
191
  end
159
192
 
160
193
  def register_callback(key, &proc)
@@ -213,7 +246,7 @@ module NewRelic
213
246
  begin
214
247
  thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call)
215
248
  rescue => e
216
- ::NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
249
+ NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
217
250
  thawed_layer[k] = nil
218
251
  end
219
252
  thawed_layer.delete(:config)
@@ -364,9 +397,19 @@ module NewRelic
364
397
  def reset_cache
365
398
  return new_cache unless defined?(@cache) && @cache
366
399
 
367
- preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
368
- new_cache
369
- preserved.each { |k, v| @cache[k] = v }
400
+ # Modifying the @cache hash under JRuby - even with a `synchronize do`
401
+ # block and a `Hash#dup` operation - has been known to cause issues
402
+ # with JRuby for concurrent access of the hash while it is being
403
+ # modified. The hash really only needs to be modified for the benefit
404
+ # of the security agent, so if JRuby is in play and the security agent
405
+ # is not, don't attempt to modify the hash at all and return early.
406
+ return new_cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled']
407
+
408
+ @lock.synchronize do
409
+ preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
410
+ new_cache
411
+ preserved.each { |k, v| @cache[k] = v }
412
+ end
370
413
 
371
414
  @cache
372
415
  end
@@ -380,7 +423,7 @@ module NewRelic
380
423
  # is expensive enough that we don't want to do it unless we're
381
424
  # actually going to be logging the message based on our current log
382
425
  # level, so use a `do` block.
383
- ::NewRelic::Agent.logger.debug do
426
+ NewRelic::Agent.logger.debug do
384
427
  hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
385
428
  "Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}"
386
429
  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,
@@ -14,6 +14,9 @@ module NewRelic
14
14
  TIMESTAMP = 'timestamp'.freeze
15
15
  PRIORITY = 'priority'.freeze
16
16
  EVENT_TYPE_REGEX = /^[a-zA-Z0-9:_ ]+$/.freeze
17
+ MAX_ATTRIBUTE_COUNT = 64
18
+ MAX_ATTRIBUTE_SIZE = 4095
19
+ MAX_NAME_SIZE = 255
17
20
 
18
21
  named :CustomEventAggregator
19
22
  capacity_key :'custom_insights_events.max_samples_stored'
@@ -49,10 +52,33 @@ module NewRelic
49
52
  {TYPE => type,
50
53
  TIMESTAMP => Process.clock_gettime(Process::CLOCK_REALTIME).to_i,
51
54
  PRIORITY => priority},
52
- AttributeProcessing.flatten_and_coerce(attributes)
55
+ create_custom_event_attributes(type, attributes)
53
56
  ]
54
57
  end
55
58
 
59
+ def create_custom_event_attributes(type, attributes)
60
+ result = AttributeProcessing.flatten_and_coerce(attributes)
61
+
62
+ if result.size > MAX_ATTRIBUTE_COUNT
63
+ NewRelic::Agent.logger.warn("Custom event attributes are limited to #{MAX_ATTRIBUTE_COUNT}. Discarding #{result.size - MAX_ATTRIBUTE_COUNT} attributes")
64
+ result = result.first(MAX_ATTRIBUTE_COUNT)
65
+ end
66
+
67
+ result.each_with_object({}) do |(key, val), new_result|
68
+ # name is limited to 255
69
+ if key.is_a?(String) && key.length > MAX_NAME_SIZE
70
+ key = key[0, MAX_NAME_SIZE]
71
+ end
72
+
73
+ # value is limited to 4095 except for LLM content-related events
74
+ if val.is_a?(String) && val.length > MAX_ATTRIBUTE_SIZE
75
+ val = val[0, MAX_ATTRIBUTE_SIZE] unless NewRelic::Agent::LLM.exempt_event_attribute?(type, key)
76
+ end
77
+
78
+ new_result[key] = val
79
+ end
80
+ end
81
+
56
82
  def after_initialize
57
83
  @type_strings = Hash.new { |hash, key| hash[key] = key.to_s.freeze }
58
84
  end
@@ -7,17 +7,17 @@ module NewRelic
7
7
  module Database
8
8
  module ObfuscationHelpers
9
9
  COMPONENTS_REGEX_MAP = {
10
- :single_quotes => /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
11
- :double_quotes => /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
12
- :dollar_quotes => /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
13
- :uuids => /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
14
- :numeric_literals => /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
15
- :boolean_literals => /\b(?:true|false|null)\b/i,
16
- :hexadecimal_literals => /0x[0-9a-fA-F]+/,
17
- :comments => /(?:#|--).*?(?=\r|\n|$)/i,
18
- :multi_line_comments => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/,
19
- :oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
20
- }
10
+ single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
11
+ double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
12
+ dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
13
+ uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
14
+ numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
15
+ boolean_literals: /\b(?:true|false|null)\b/i,
16
+ hexadecimal_literals: /0x[0-9a-fA-F]+/,
17
+ comments: /(?:#|--).*?(?=\r|\n|$)/i,
18
+ multi_line_comments: %r{/\*.*?\*/}m,
19
+ oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/
20
+ }.freeze
21
21
 
22
22
  DIALECT_COMPONENTS = {
23
23
  :fallback => COMPONENTS_REGEX_MAP.keys,
@@ -2,6 +2,7 @@
2
2
  # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
3
  # frozen_string_literal: true
4
4
 
5
+ require 'singleton'
5
6
  require 'new_relic/agent/database/obfuscation_helpers'
6
7
 
7
8
  module NewRelic
@@ -90,6 +90,42 @@ module NewRelic
90
90
  ConnectionManager.instance.get_connection(config, &connector)
91
91
  end
92
92
 
93
+ def explain_this(statement, use_execute = false)
94
+ if supports_with_connection?
95
+ explain_this_using_with_connection(statement)
96
+ else
97
+ explain_this_using_adapter_connection(statement, use_execute)
98
+ end
99
+ rescue => e
100
+ NewRelic::Agent.logger.error("Couldn't fetch the explain plan for statement: #{e}")
101
+ end
102
+
103
+ def explain_this_using_with_connection(statement)
104
+ ::ActiveRecord::Base.with_connection do |conn|
105
+ conn.exec_query("EXPLAIN #{statement.sql}", "Explain #{statement.name}", statement.binds)
106
+ end
107
+ end
108
+
109
+ def explain_this_using_adapter_connection(statement, use_execute)
110
+ connection = get_connection(statement.config) do
111
+ ::ActiveRecord::Base.send(:"#{statement.config[:adapter]}_connection", statement.config)
112
+ end
113
+
114
+ if use_execute
115
+ connection.execute("EXPLAIN #{statement.sql}")
116
+ else
117
+ connection.exec_query("EXPLAIN #{statement.sql}", "Explain #{statement.name}", statement.binds)
118
+ end
119
+ end
120
+
121
+ # ActiveRecord v7.2.0 introduced with_connection
122
+ def supports_with_connection?
123
+ return @supports_with_connection if defined?(@supports_with_connection)
124
+
125
+ @supports_with_connection = defined?(::ActiveRecord::VERSION::STRING) &&
126
+ Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new('7.2.0')
127
+ end
128
+
93
129
  def close_connections
94
130
  ConnectionManager.instance.close_connections
95
131
  end
@@ -241,6 +277,7 @@ module NewRelic
241
277
  MYSQL_PREFIX = 'mysql'.freeze
242
278
  MYSQL2_PREFIX = 'mysql2'.freeze
243
279
  SQLITE_PREFIX = 'sqlite'.freeze
280
+ TRILOGY_PREFIX = 'trilogy'.freeze
244
281
 
245
282
  def symbolized_adapter(adapter)
246
283
  if adapter.start_with?(POSTGRES_PREFIX) || adapter == POSTGIS_PREFIX
@@ -253,6 +290,8 @@ module NewRelic
253
290
  :mysql2
254
291
  elsif adapter.start_with?(SQLITE_PREFIX)
255
292
  :sqlite
293
+ elsif adapter == TRILOGY_PREFIX
294
+ :trilogy
256
295
  else
257
296
  adapter.to_sym
258
297
  end
@@ -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,
@@ -110,6 +110,29 @@ module NewRelic
110
110
  false
111
111
  end
112
112
 
113
+ # Neither ignored nor expected errors impact apdex.
114
+ #
115
+ # Ignored errors are checked via `#error_is_ignored?`
116
+ # Expected errors are checked in 2 separate ways:
117
+ # 1. The presence of an `expected: true` attribute key/value pair in the
118
+ # options hash, which will be set if that key/value pair was used in
119
+ # the `notice_error` public API.
120
+ # 2. By calling `#expected?` which in turn calls `ErrorFilter#expected?`
121
+ # which checks for 3 things:
122
+ # - A match for user-defined HTTP status codes to expect
123
+ # - A match for user-defined error classes to expect
124
+ # - A match for user-defined error messages to expect
125
+ def error_affects_apdex?(error, options)
126
+ return false if error_is_ignored?(error)
127
+ return false if options[:expected]
128
+
129
+ !expected?(error, ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code)
130
+ rescue => e
131
+ NewRelic::Agent.logger.error("Could not determine if error '#{error}' should impact Apdex - " \
132
+ "#{e.class}: #{e.message}. Defaulting to 'true' (it should impact Apdex).")
133
+ true
134
+ end
135
+
113
136
  # Calling instance_variable_set on a wrapped Java object in JRuby will
114
137
  # generate a warning unless that object's class has already been marked
115
138
  # as persistent, so we skip tagging of exception objects that are actually
@@ -214,7 +237,10 @@ module NewRelic
214
237
  end
215
238
 
216
239
  def notice_segment_error(segment, exception, options = {})
217
- return if skip_notice_error?(exception)
240
+ status_code = process_http_status_code(exception, options)
241
+ return if skip_notice_error?(exception, status_code)
242
+
243
+ options.merge!(segment.llm_event.error_attributes(exception)) if segment.llm_event
218
244
 
219
245
  segment.set_noticed_error(create_noticed_error(exception, options))
220
246
  exception
@@ -225,15 +251,13 @@ module NewRelic
225
251
 
226
252
  # See NewRelic::Agent.notice_error for options and commentary
227
253
  def notice_error(exception, options = {}, span_id = nil)
228
- state = ::NewRelic::Agent::Tracer.state
229
- transaction = state.current_transaction
230
- status_code = transaction&.http_response_code
231
-
254
+ status_code = process_http_status_code(exception, options)
232
255
  return if skip_notice_error?(exception, status_code)
233
256
 
234
257
  tag_exception(exception)
235
258
 
236
- if options[:expected] || @error_filter.expected?(exception, status_code)
259
+ state = ::NewRelic::Agent::Tracer.state
260
+ if options[:expected]
237
261
  increment_expected_error_count!(state, exception)
238
262
  else
239
263
  increment_error_count!(state, exception, options)
@@ -241,10 +265,8 @@ module NewRelic
241
265
 
242
266
  noticed_error = create_noticed_error(exception, options)
243
267
  error_trace_aggregator.add_to_error_queue(noticed_error)
244
- transaction = state.current_transaction
245
- payload = transaction&.payload
246
- span_id ||= transaction&.current_segment ? transaction.current_segment.guid : nil
247
- error_event_aggregator.record(noticed_error, payload, span_id)
268
+ span_id ||= state.current_transaction&.current_segment&.guid
269
+ error_event_aggregator.record(noticed_error, state.current_transaction&.payload, span_id)
248
270
  exception
249
271
  rescue => e
250
272
  ::NewRelic::Agent.logger.warn("Failure when capturing error '#{exception}':", e)
@@ -334,6 +356,13 @@ module NewRelic
334
356
  def error_group_callback
335
357
  NewRelic::Agent.error_group_callback
336
358
  end
359
+
360
+ def process_http_status_code(exception, options)
361
+ status_code = ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code
362
+ options[:expected] = true if !options[:expected] && @error_filter.expected?(exception, status_code)
363
+
364
+ status_code
365
+ end
337
366
  end
338
367
  end
339
368
  end
@@ -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
@@ -35,17 +35,4 @@ DependencyDetection.defer do
35
35
  end
36
36
  end
37
37
  end
38
-
39
- executes do
40
- next unless Gem::Version.new(ActiveMerchant::VERSION) < Gem::Version.new('1.65.0')
41
-
42
- deprecation_msg = 'The Ruby agent is dropping support for ActiveMerchant versions below 1.65.0 ' \
43
- 'in version 9.0.0. Please upgrade your ActiveMerchant version to continue receiving full support. ' \
44
-
45
- NewRelic::Agent.logger.log_once(
46
- :warn,
47
- :deprecated_active_merchant_version,
48
- deprecation_msg
49
- )
50
- end
51
38
  end
@@ -9,14 +9,7 @@ module NewRelic
9
9
  module Instrumentation
10
10
  module ActiveRecord
11
11
  EXPLAINER = lambda do |statement|
12
- connection = NewRelic::Agent::Database.get_connection(statement.config) do
13
- ::ActiveRecord::Base.send("#{statement.config[:adapter]}_connection",
14
- statement.config)
15
- end
16
- # the following line needs else branch coverage
17
- if connection && connection.respond_to?(:execute) # rubocop:disable Style/SafeNavigation
18
- return connection.execute("EXPLAIN #{statement.sql}")
19
- end
12
+ NewRelic::Agent::Database.explain_this(statement, true)
20
13
  end
21
14
 
22
15
  def self.insert_instrumentation
@@ -170,6 +170,9 @@ module NewRelic
170
170
 
171
171
  'sqlite3' => 'SQLite',
172
172
 
173
+ # https://rubygems.org/gems/trilogy
174
+ 'trilogy' => 'MySQL',
175
+
173
176
  # https://rubygems.org/gems/activerecord-jdbcpostgresql-adapter
174
177
  'jdbcmysql' => 'MySQL',
175
178
 
@@ -70,18 +70,7 @@ module NewRelic
70
70
  end
71
71
 
72
72
  def get_explain_plan(statement)
73
- connection = NewRelic::Agent::Database.get_connection(statement.config) do
74
- ::ActiveRecord::Base.send("#{statement.config[:adapter]}_connection",
75
- statement.config)
76
- end
77
- # the following line needs else branch coverage
78
- if connection && connection.respond_to?(:exec_query) # rubocop:disable Style/SafeNavigation
79
- return connection.exec_query("EXPLAIN #{statement.sql}",
80
- "Explain #{statement.name}",
81
- statement.binds)
82
- end
83
- rescue => e
84
- NewRelic::Agent.logger.debug("Couldn't fetch the explain plan for #{statement} due to #{e}")
73
+ NewRelic::Agent::Database.explain_this(statement)
85
74
  end
86
75
 
87
76
  def active_record_config(payload)
@@ -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
@@ -12,8 +12,6 @@ DependencyDetection.defer do
12
12
  depends_on { defined?(ActiveSupport::BroadcastLogger) }
13
13
 
14
14
  executes do
15
- NewRelic::Agent.logger.info('Installing ActiveSupport::BroadcastLogger instrumentation')
16
-
17
15
  if use_prepend?
18
16
  prepend_instrument ActiveSupport::BroadcastLogger, NewRelic::Agent::Instrumentation::ActiveSupportBroadcastLogger::Prepend
19
17
  else
@@ -14,8 +14,6 @@ DependencyDetection.defer do
14
14
  end
15
15
 
16
16
  executes do
17
- NewRelic::Agent.logger.info('Installing ActiveSupport::Logger instrumentation')
18
-
19
17
  if use_prepend?
20
18
  # the only method currently instrumented is a class method
21
19
  prepend_instrument ActiveSupport::Logger.singleton_class, NewRelic::Agent::Instrumentation::ActiveSupportLogger::Prepend
@@ -10,13 +10,14 @@ DependencyDetection.defer do
10
10
  named :async_http
11
11
 
12
12
  depends_on do
13
- defined?(Async::HTTP) && Gem::Version.new(Async::HTTP::VERSION) >= Gem::Version.new('0.59.0')
13
+ defined?(Async::HTTP) &&
14
+ Gem::Version.new(Async::HTTP::VERSION) >= Gem::Version.new('0.59.0') &&
15
+ !defined?(Traces::Backend::NewRelic) # defined in the traces-backend-newrelic gem
14
16
  end
15
17
 
16
18
  executes do
17
- NewRelic::Agent.logger.info('Installing async_http instrumentation')
18
-
19
19
  require 'async/http/internet'
20
+
20
21
  if use_prepend?
21
22
  prepend_instrument Async::HTTP::Internet, NewRelic::Agent::Instrumentation::AsyncHttp::Prepend
22
23
  else
@@ -0,0 +1,33 @@
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_relative 'instrumentation'
6
+
7
+ module NewRelic::Agent::Instrumentation
8
+ module AwsSdkLambda::Chain
9
+ def self.instrument!
10
+ ::Aws::Lambda::Client.class_eval do
11
+ include NewRelic::Agent::Instrumentation::AwsSdkLambda
12
+
13
+ alias_method(:invoke_without_new_relic, :invoke)
14
+
15
+ def invoke(*args)
16
+ invoke_with_new_relic(*args) { invoke_without_new_relic(*args) }
17
+ end
18
+
19
+ alias_method(:invoke_async_without_new_relic, :invoke_async)
20
+
21
+ def invoke_async(*args)
22
+ invoke_async_with_new_relic(*args) { invoke_async_without_new_relic(*args) }
23
+ end
24
+
25
+ alias_method(:invoke_with_response_stream_without_new_relic, :invoke_with_response_stream)
26
+
27
+ def invoke_with_response_stream(*args)
28
+ invoke_with_response_stream_with_new_relic(*args) { invoke_with_response_stream_without_new_relic(*args) }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end