newrelic_rpm 9.2.0 → 9.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +26 -0
  3. data/CHANGELOG.md +197 -2
  4. data/README.md +8 -4
  5. data/lib/new_relic/agent/attribute_pre_filtering.rb +109 -0
  6. data/lib/new_relic/agent/configuration/default_source.rb +218 -63
  7. data/lib/new_relic/agent/configuration/environment_source.rb +1 -1
  8. data/lib/new_relic/agent/configuration/manager.rb +14 -0
  9. data/lib/new_relic/agent/configuration/yaml_source.rb +13 -0
  10. data/lib/new_relic/agent/distributed_tracing.rb +1 -1
  11. data/lib/new_relic/agent/error_collector.rb +1 -1
  12. data/lib/new_relic/agent/instrumentation/action_controller_other_subscriber.rb +1 -1
  13. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -1
  14. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +2 -1
  15. data/lib/new_relic/agent/instrumentation/active_support_logger/instrumentation.rb +4 -0
  16. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +9 -0
  17. data/lib/new_relic/agent/instrumentation/concurrent_ruby/chain.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/concurrent_ruby/instrumentation.rb +3 -4
  19. data/lib/new_relic/agent/instrumentation/concurrent_ruby/prepend.rb +1 -1
  20. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +1 -2
  21. data/lib/new_relic/agent/instrumentation/curb/instrumentation.rb +4 -0
  22. data/lib/new_relic/agent/instrumentation/delayed_job/instrumentation.rb +3 -0
  23. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +4 -1
  24. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +3 -0
  25. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +10 -3
  26. data/lib/new_relic/agent/instrumentation/fiber/instrumentation.rb +1 -2
  27. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +10 -3
  28. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +4 -0
  29. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +4 -0
  30. data/lib/new_relic/agent/instrumentation/grpc/server/instrumentation.rb +4 -0
  31. data/lib/new_relic/agent/instrumentation/grpc_client.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/grpc_server.rb +1 -1
  33. data/lib/new_relic/agent/instrumentation/httpclient/instrumentation.rb +4 -0
  34. data/lib/new_relic/agent/instrumentation/httprb/instrumentation.rb +4 -0
  35. data/lib/new_relic/agent/instrumentation/logger/instrumentation.rb +3 -0
  36. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +12 -3
  37. data/lib/new_relic/agent/instrumentation/memcache.rb +2 -2
  38. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +4 -0
  39. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +4 -0
  40. data/lib/new_relic/agent/instrumentation/padrino/instrumentation.rb +4 -0
  41. data/lib/new_relic/agent/instrumentation/queue_time.rb +1 -1
  42. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +6 -0
  43. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +4 -0
  44. data/lib/new_relic/agent/instrumentation/rails_notifications/action_cable.rb +1 -1
  45. data/lib/new_relic/agent/instrumentation/rake/instrumentation.rb +4 -0
  46. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +4 -0
  47. data/lib/new_relic/agent/instrumentation/resque/instrumentation.rb +4 -0
  48. data/lib/new_relic/agent/instrumentation/roda/chain.rb +43 -0
  49. data/lib/new_relic/agent/instrumentation/roda/instrumentation.rb +56 -0
  50. data/lib/new_relic/agent/instrumentation/roda/prepend.rb +24 -0
  51. data/lib/new_relic/agent/instrumentation/roda/roda_transaction_namer.rb +30 -0
  52. data/lib/new_relic/agent/instrumentation/roda.rb +34 -0
  53. data/lib/new_relic/agent/instrumentation/sequel.rb +1 -1
  54. data/lib/new_relic/agent/instrumentation/sidekiq/client.rb +4 -0
  55. data/lib/new_relic/agent/instrumentation/sidekiq/server.rb +26 -3
  56. data/lib/new_relic/agent/instrumentation/sidekiq.rb +2 -2
  57. data/lib/new_relic/agent/instrumentation/sinatra/instrumentation.rb +4 -0
  58. data/lib/new_relic/agent/instrumentation/stripe.rb +28 -0
  59. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +77 -0
  60. data/lib/new_relic/agent/instrumentation/thread/chain.rb +1 -1
  61. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +0 -1
  62. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +1 -1
  63. data/lib/new_relic/agent/instrumentation/tilt/instrumentation.rb +4 -0
  64. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +5 -1
  65. data/lib/new_relic/agent/log_event_aggregator.rb +49 -2
  66. data/lib/new_relic/agent/log_event_attributes.rb +115 -0
  67. data/lib/new_relic/agent/logging.rb +4 -4
  68. data/lib/new_relic/agent/method_tracer_helpers.rb +26 -5
  69. data/lib/new_relic/agent/new_relic_service.rb +33 -17
  70. data/lib/new_relic/agent/pipe_service.rb +1 -1
  71. data/lib/new_relic/agent/tracer.rb +7 -6
  72. data/lib/new_relic/agent/transaction/abstract_segment.rb +52 -0
  73. data/lib/new_relic/agent/transaction/request_attributes.rb +45 -7
  74. data/lib/new_relic/agent/transaction/tracing.rb +6 -0
  75. data/lib/new_relic/agent/transaction.rb +9 -4
  76. data/lib/new_relic/agent/utilization/vendor.rb +5 -7
  77. data/lib/new_relic/agent.rb +50 -1
  78. data/lib/new_relic/cli/command.rb +1 -0
  79. data/lib/new_relic/control/class_methods.rb +1 -7
  80. data/lib/new_relic/control/frameworks/roda.rb +20 -0
  81. data/lib/new_relic/control/instrumentation.rb +0 -14
  82. data/lib/new_relic/dependency_detection.rb +16 -1
  83. data/lib/new_relic/language_support.rb +5 -0
  84. data/lib/new_relic/latest_changes.rb +1 -1
  85. data/lib/new_relic/noticed_error.rb +5 -2
  86. data/lib/new_relic/rack/agent_hooks.rb +1 -1
  87. data/lib/new_relic/rack/agent_middleware.rb +0 -16
  88. data/lib/new_relic/rack/browser_monitoring.rb +1 -1
  89. data/lib/new_relic/supportability_helper.rb +2 -0
  90. data/lib/new_relic/traced_thread.rb +2 -3
  91. data/lib/new_relic/version.rb +1 -1
  92. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  93. data/lib/tasks/bump_version.rake +21 -0
  94. data/lib/tasks/config.rake +3 -2
  95. data/lib/tasks/helpers/config.html.erb +93 -0
  96. data/lib/tasks/helpers/format.rb +11 -7
  97. data/lib/tasks/helpers/newrelicyml.rb +144 -0
  98. data/lib/tasks/helpers/version_bump.rb +62 -0
  99. data/lib/tasks/newrelicyml.rake +13 -0
  100. data/newrelic.yml +364 -267
  101. data/newrelic_rpm.gemspec +11 -7
  102. metadata +36 -25
  103. data/.gitignore +0 -43
  104. data/.project +0 -23
  105. data/.rubocop.yml +0 -1845
  106. data/.rubocop_todo.yml +0 -61
  107. data/.simplecov +0 -16
  108. data/.snyk +0 -11
  109. data/.yardopts +0 -27
  110. data/Brewfile +0 -13
  111. data/DOCKER.md +0 -167
  112. data/Dockerfile +0 -10
  113. data/Guardfile +0 -27
  114. data/config/database.yml +0 -5
  115. data/config.dot +0 -278
  116. data/docker-compose.yml +0 -107
  117. data/lefthook.yml +0 -9
  118. data/lib/tasks/helpers/removers.rb +0 -33
  119. data/lib/tasks/multiverse.rake +0 -6
  120. data/lib/tasks/multiverse.rb +0 -84
@@ -0,0 +1,115 @@
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 Agent
7
+ class LogEventAttributes
8
+ MAX_ATTRIBUTE_COUNT = 240 # limit is 255, assume we send 15
9
+ ATTRIBUTE_KEY_CHARACTER_LIMIT = 255
10
+ ATTRIBUTE_VALUE_CHARACTER_LIMIT = 4094
11
+
12
+ def add_custom_attributes(attributes)
13
+ return if defined?(@custom_attribute_limit_reached) && @custom_attribute_limit_reached
14
+
15
+ attributes.each do |key, value|
16
+ next if absent?(key) || absent?(value)
17
+
18
+ add_custom_attribute(key, value)
19
+ end
20
+ end
21
+
22
+ def custom_attributes
23
+ @custom_attributes ||= {}
24
+ end
25
+
26
+ private
27
+
28
+ class TruncationError < StandardError
29
+ attr_reader :attribute, :limit
30
+
31
+ def initialize(attribute, limit, msg = "Can't truncate")
32
+ @attribute = attribute
33
+ @limit = limit
34
+ super(msg)
35
+ end
36
+ end
37
+
38
+ class InvalidTypeError < StandardError
39
+ attr_reader :attribute
40
+
41
+ def initialize(attribute, msg = 'Invalid attribute type')
42
+ @attribute = attribute
43
+ super(msg)
44
+ end
45
+ end
46
+
47
+ def absent?(value)
48
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
49
+ end
50
+
51
+ def add_custom_attribute(key, value)
52
+ if custom_attributes.size >= MAX_ATTRIBUTE_COUNT
53
+ NewRelic::Agent.logger.warn(
54
+ 'Too many custom log attributes defined. ' \
55
+ "Only taking the first #{MAX_ATTRIBUTE_COUNT}."
56
+ )
57
+ @custom_attribute_limit_reached = true
58
+ return
59
+ end
60
+
61
+ @custom_attributes.merge!(truncate_attributes(key_to_string(key), value))
62
+ end
63
+
64
+ def key_to_string(key)
65
+ key.is_a?(String) ? key : key.to_s
66
+ end
67
+
68
+ def truncate_attribute(attribute, limit)
69
+ case attribute
70
+ when Integer
71
+ if attribute.digits.length > limit
72
+ raise TruncationError.new(attribute, limit)
73
+ end
74
+ when Float
75
+ if attribute.to_s.length > limit
76
+ raise TruncationError.new(attribute, limit)
77
+ end
78
+ when String, Symbol
79
+ if attribute.length > limit
80
+ attribute = attribute.slice(0..(limit - 1))
81
+ end
82
+ when TrueClass, FalseClass
83
+ attribute
84
+ else
85
+ raise InvalidTypeError.new(attribute)
86
+ end
87
+
88
+ attribute
89
+ end
90
+
91
+ def truncate_attributes(key, value)
92
+ key = truncate_attribute(key, ATTRIBUTE_KEY_CHARACTER_LIMIT)
93
+ value = truncate_attribute(value, ATTRIBUTE_VALUE_CHARACTER_LIMIT)
94
+
95
+ {key => value}
96
+ rescue TruncationError => e
97
+ NewRelic::Agent.logger.warn(
98
+ "Dropping custom log attribute #{key} => #{value} \n" \
99
+ "Length exceeds character limit of #{e.limit}. " \
100
+ "Can't truncate: #{e.attribute}"
101
+ )
102
+
103
+ {}
104
+ rescue InvalidTypeError => e
105
+ NewRelic::Agent.logger.warn(
106
+ "Dropping custom log attribute #{key} => #{value} \n" \
107
+ "Invalid type of #{e.attribute.class} given. " \
108
+ "Can't send #{e.attribute}."
109
+ )
110
+
111
+ {}
112
+ end
113
+ end
114
+ end
115
+ end
@@ -62,6 +62,10 @@ module NewRelic
62
62
  message << CLOSING_BRACE << NEWLINE
63
63
  end
64
64
 
65
+ def clear_tags!
66
+ # No-op; just avoiding issues with act-fluent-logger-rails
67
+ end
68
+
65
69
  private
66
70
 
67
71
  def add_app_name(message)
@@ -138,10 +142,6 @@ module NewRelic
138
142
  end
139
143
  message.to_json
140
144
  end
141
-
142
- def clear_tags!
143
- # No-op; just avoiding issues with act-fluent-logger-rails
144
- end
145
145
  end
146
146
 
147
147
  # This logger decorates logs with trace and entity metadata, and emits log
@@ -46,13 +46,15 @@ module NewRelic
46
46
  cache_key = "#{object.object_id}#{method_name}".freeze
47
47
  return @code_information[cache_key] if @code_information.key?(cache_key)
48
48
 
49
- namespace, location, is_class_method = namespace_and_location(object, method_name.to_sym)
49
+ info = namespace_and_location(object, method_name.to_sym)
50
+ return ::NewRelic::EMPTY_HASH if info.empty?
50
51
 
52
+ namespace, location, is_class_method = info
51
53
  @code_information[cache_key] = {filepath: location.first,
52
54
  lineno: location.last,
53
55
  function: "#{'self.' if is_class_method}#{method_name}",
54
56
  namespace: namespace}.freeze
55
- rescue => e
57
+ rescue StandardError => e
56
58
  ::NewRelic::Agent.logger.warn("Unable to determine source code info for '#{object}', " \
57
59
  "method '#{method_name}' - #{e.class}: #{e.message}")
58
60
  ::NewRelic::Agent.increment_metric(SOURCE_CODE_INFORMATION_FAILURE_METRIC, 1)
@@ -66,10 +68,10 @@ module NewRelic
66
68
  end
67
69
 
68
70
  # The string representation of a singleton class looks like
69
- # '#<Class:MyModule::MyClass>'. Return the 'MyModule::MyClass' part of
70
- # that string
71
+ # '#<Class:MyModule::MyClass>', or '#<Class:MyModule::MyClass(id: integer, attribute: string)>'
72
+ # Return the 'MyModule::MyClass' part of that string
71
73
  def klass_name(object)
72
- name = Regexp.last_match(1) if object.to_s =~ /^#<Class:(.*)>$/
74
+ name = Regexp.last_match(1) if object.to_s =~ /^#<Class:([\w:]+).*>$/
73
75
  return name if name
74
76
 
75
77
  raise "Unable to glean a class name from string '#{object}'"
@@ -101,6 +103,9 @@ module NewRelic
101
103
  klass = object.singleton_class? ? klassify_singleton(object) : object
102
104
  name = klass.name || '(Anonymous)'
103
105
  is_class_method = false
106
+
107
+ return controller_info(klass, name, is_class_method) if controller_without_method?(klass, method_name)
108
+
104
109
  method = if (klass.instance_methods + klass.private_instance_methods).include?(method_name)
105
110
  klass.instance_method(method_name)
106
111
  else
@@ -109,6 +114,22 @@ module NewRelic
109
114
  end
110
115
  [name, method.source_location, is_class_method]
111
116
  end
117
+
118
+ # Rails controllers can be a special case because by default, controllers in Rails
119
+ # automatically render views with names that correspond to valid routes. This means
120
+ # that a controller method may not have a corresponding method in the controller class.
121
+ def controller_without_method?(klass, method_name)
122
+ defined?(Rails) &&
123
+ defined?(ApplicationController) &&
124
+ klass < ApplicationController &&
125
+ !klass.method_defined?(method_name)
126
+ end
127
+
128
+ def controller_info(klass, name, is_class_method)
129
+ path = Rails.root.join("app/controllers/#{klass.name.underscore}.rb")
130
+
131
+ File.exist?(path) ? [name, [path.to_s, 1], is_class_method] : []
132
+ end
112
133
  end
113
134
  end
114
135
  end
@@ -3,7 +3,6 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  require 'zlib'
6
- require 'timeout'
7
6
  require 'new_relic/agent/audit_logger'
8
7
  require 'new_relic/agent/new_relic_service/encoders'
9
8
  require 'new_relic/agent/new_relic_service/marshaller'
@@ -19,7 +18,13 @@ module NewRelic
19
18
 
20
19
  # These include Errno connection errors, and all indicate that the
21
20
  # underlying TCP connection may be in a bad state.
22
- CONNECTION_ERRORS = [Timeout::Error, EOFError, SystemCallError, SocketError].freeze
21
+ CONNECTION_ERRORS = [Net::OpenTimeout, Net::ReadTimeout, EOFError, SystemCallError, SocketError]
22
+ # TODO: MAJOR VERSION - Net::WriteTimeout wasn't defined until Ruby 2.6.
23
+ # Once support for Ruby 2.5 is dropped, we should simply include
24
+ # Net::WriteTimeout in the connection errors array directly instead
25
+ # of with a conditional
26
+ CONNECTION_ERRORS << Net::WriteTimeout if defined?(Net::WriteTimeout)
27
+ CONNECTION_ERRORS.freeze
23
28
 
24
29
  # The maximum number of times to attempt an HTTP request
25
30
  MAX_ATTEMPTS = 2
@@ -319,13 +324,15 @@ module NewRelic
319
324
 
320
325
  def start_connection(conn)
321
326
  NewRelic::Agent.logger.debug("Opening TCP connection to #{conn.address}:#{conn.port}")
322
- Timeout.timeout(@request_timeout) { conn.start }
323
- conn
327
+ conn.start
324
328
  end
325
329
 
326
330
  def setup_connection_timeouts(conn)
327
- # We use Timeout explicitly instead of this
328
- conn.read_timeout = nil
331
+ conn.open_timeout = @request_timeout
332
+ conn.read_timeout = @request_timeout
333
+ # TODO: MAJOR VERSION - #write_timeout= requires Ruby 2.6+, so remove
334
+ # the conditional check once support for Ruby 2.5 is dropped
335
+ conn.write_timeout = @request_timeout if conn.respond_to?(:write_timeout=)
329
336
 
330
337
  if conn.respond_to?(:keep_alive_timeout) && NewRelic::Agent.config[:aggressive_keepalive]
331
338
  conn.keep_alive_timeout = NewRelic::Agent.config[:keep_alive_timeout]
@@ -362,8 +369,8 @@ module NewRelic
362
369
  conn = create_http_connection
363
370
  start_connection(conn)
364
371
  conn
365
- rescue Timeout::Error
366
- ::NewRelic::Agent.logger.info('Timeout while attempting to connect. You may need to install system-level CA Certificates, as the ruby agent no longer includes these.')
372
+ rescue Net::OpenTimeout
373
+ ::NewRelic::Agent.logger.info('Timed out while attempting to connect. For SSL issues, you may need to install system-level CA Certificates to be used by Net::HTTP.')
367
374
  raise
368
375
  end
369
376
 
@@ -436,13 +443,9 @@ module NewRelic
436
443
  end
437
444
 
438
445
  def attempt_request(request, opts)
439
- response = nil
440
446
  conn = http_connection
441
447
  ::NewRelic::Agent.logger.debug("Sending request to #{opts[:collector]}#{opts[:uri]} with #{request.method}")
442
- Timeout.timeout(@request_timeout) do
443
- response = conn.request(request)
444
- end
445
- response
448
+ conn.request(request)
446
449
  end
447
450
 
448
451
  def handle_error_response(response, endpoint)
@@ -450,7 +453,9 @@ module NewRelic
450
453
  when Net::HTTPRequestTimeOut,
451
454
  Net::HTTPTooManyRequests,
452
455
  Net::HTTPInternalServerError,
453
- Net::HTTPServiceUnavailable
456
+ Net::HTTPServiceUnavailable,
457
+ Net::OpenTimeout,
458
+ Net::ReadTimeout
454
459
  handle_server_connection_exception(response, endpoint)
455
460
  when Net::HTTPBadRequest,
456
461
  Net::HTTPForbidden,
@@ -471,9 +476,20 @@ module NewRelic
471
476
  when Net::HTTPGone
472
477
  handle_gone_response(response, endpoint)
473
478
  else
474
- record_endpoint_attempts_supportability_metrics(endpoint)
475
- record_error_response_supportability_metrics(response.code)
476
- raise UnrecoverableServerException, "#{response.code}: #{response.message}"
479
+ # TODO: MAJOR VERSION - Net::WriteTimeout wasn't defined until
480
+ # Ruby 2.6, so it can't be included in the case statement
481
+ # as a constant and instead needs to be found here. Once
482
+ # support for Ruby 2.5 is dropped, we should have
483
+ # Net::WriteTimeout sit in the 'when' clause above alongside
484
+ # Net::OpenTimeout and Net::ReadTimeout and this entire if/else
485
+ # conditional can be removed.
486
+ if response.respond_to?(:name) && response.name == 'Net::WriteTimeout'
487
+ handle_server_connection_exception(response, endpoint)
488
+ else
489
+ record_endpoint_attempts_supportability_metrics(endpoint)
490
+ record_error_response_supportability_metrics(response.code)
491
+ raise UnrecoverableServerException, "#{response.code}: #{response.message}"
492
+ end
477
493
  end
478
494
  response
479
495
  end
@@ -15,7 +15,7 @@ module NewRelic
15
15
  if @pipe && @pipe.parent_pid != $$
16
16
  @pipe.after_fork_in_child
17
17
  else
18
- NewRelic::Agent.logger.error('No communication channel to parent process, please see https://newrelic.com/docs/ruby/resque-instrumentation for more information.')
18
+ NewRelic::Agent.logger.error('No communication channel to parent process, please see https://docs.newrelic.com/docs/apm/agents/ruby-agent/background-jobs/resque-instrumentation/ for more information.')
19
19
  end
20
20
  end
21
21
 
@@ -66,9 +66,10 @@ module NewRelic
66
66
  #
67
67
  # @api public
68
68
  def transaction_sampled?
69
- if txn = current_transaction
70
- txn.sampled?
71
- end
69
+ txn = current_transaction
70
+ return false unless txn
71
+
72
+ txn.sampled?
72
73
  end
73
74
  alias_method :sampled?, :transaction_sampled?
74
75
 
@@ -419,12 +420,12 @@ module NewRelic
419
420
  NewRelic::Agent.config[:'instrumentation.thread.tracing']
420
421
  end
421
422
 
422
- def thread_block_with_current_transaction(*args, segment_name:, parent: nil, &block)
423
+ def thread_block_with_current_transaction(segment_name:, parent: nil, &block)
423
424
  parent ||= current_segment
424
425
  current_txn = ::Thread.current[:newrelic_tracer_state]&.current_transaction if ::Thread.current[:newrelic_tracer_state]&.is_execution_traced?
425
- proc do
426
+ proc do |*args|
426
427
  begin
427
- if current_txn
428
+ if current_txn && !current_txn.finished?
428
429
  NewRelic::Agent::Tracer.state.current_transaction = current_txn
429
430
  current_txn.async = true
430
431
  segment_name += "/Thread#{::Thread.current.object_id}/Fiber#{::Fiber.current.object_id}" if NewRelic::Agent.config[:'thread_ids_enabled']
@@ -24,6 +24,9 @@ module NewRelic
24
24
  attr_writer :record_metrics, :record_scoped_metric, :record_on_finish
25
25
  attr_reader :noticed_error
26
26
 
27
+ CALLBACK = :@callback
28
+ SEGMENT = 'segment'
29
+
27
30
  def initialize(name = nil, start_time = nil)
28
31
  @name = name
29
32
  @starting_segment_key = NewRelic::Agent::Tracer.current_segment_key
@@ -49,6 +52,7 @@ module NewRelic
49
52
  @code_function = nil
50
53
  @code_lineno = nil
51
54
  @code_namespace = nil
55
+ invoke_callback
52
56
  end
53
57
 
54
58
  def start
@@ -327,6 +331,54 @@ module NewRelic
327
331
  Tracer.state
328
332
  end
329
333
  end
334
+
335
+ # for segment callback usage info, see self.set_segment_callback
336
+ def invoke_callback
337
+ return unless self.class.instance_variable_defined?(CALLBACK)
338
+
339
+ NewRelic::Agent.logger.debug("Invoking callback for #{self.class.name}...")
340
+ self.class.instance_variable_get(CALLBACK).call
341
+ end
342
+
343
+ # Setting and invoking a segment callback
344
+ # =======================================
345
+ # Each individual segment class such as `ExternalRequestSegment` allows
346
+ # for exactly one instance of a `Proc` (meaning a proc or lambda) to be
347
+ # set as a callback. A callback can be set on a segment class by calling
348
+ # `.set_segment_callback` with a proc or lambda as the only argument.
349
+ # If set, the callback will be invoked with `#call` at segment class
350
+ # initialization time.
351
+ #
352
+ # Example usage:
353
+ # callback = -> { puts 'Hello, World! }
354
+ # ExternalRequestSegment.set_segment_callback(callback)
355
+ # ExternalRequestSegment.new(library, uri, procedure)
356
+ #
357
+ # A callback set on a segment class will only be called when that
358
+ # specific segment class is initialized. Other segment classes will not
359
+ # be impacted.
360
+ #
361
+ # Great caution should be taken in the defining of the callback block
362
+ # to not have the block perform anything too time consuming or resource
363
+ # intensive in order to keep the New Relic Ruby agent operating
364
+ # normally.
365
+ #
366
+ # Given that callbacks are user defined, they must be set entirely at
367
+ # the user's own risk. It is recommended that each callback use
368
+ # conditional logic that only performs work for certain qualified
369
+ # segments. It is recommended that each callback be thoroughly tested
370
+ # in non-production environments before being introduced to production
371
+ # environments.
372
+ def self.set_segment_callback(callback_proc)
373
+ unless callback_proc.is_a?(Proc)
374
+ NewRelic::Agent.logger.error("#{self}.#{__method__}: expected an argument of type Proc, " \
375
+ "got #{callback_proc.class}")
376
+ return
377
+ end
378
+
379
+ NewRelic::Agent.record_api_supportability_metric(:set_segment_callback)
380
+ instance_variable_set(CALLBACK, callback_proc)
381
+ end
330
382
  end
331
383
  end
332
384
  end
@@ -8,11 +8,17 @@ module NewRelic
8
8
  module Agent
9
9
  class Transaction
10
10
  class RequestAttributes
11
- attr_reader :request_path, :referer, :accept, :content_length, :content_type,
12
- :host, :port, :user_agent, :request_method
11
+ # the HTTP standard has "referrer" mispelled as "referer"
12
+ attr_reader :accept, :content_length, :content_type, :host, :other_headers, :port, :referer, :request_method,
13
+ :request_path, :user_agent
13
14
 
14
15
  HTTP_ACCEPT_HEADER_KEY = 'HTTP_ACCEPT'.freeze
15
16
 
17
+ BASE_HEADERS = %w[CONTENT_LENGTH CONTENT_TYPE HTTP_ACCEPT HTTP_REFERER HTTP_USER_AGENT PATH_INFO REMOTE_HOST
18
+ REQUEST_METHOD REQUEST_URI SERVER_PORT].freeze
19
+
20
+ ATTRIBUTE_PREFIX = 'request.headers.'
21
+
16
22
  def initialize(request)
17
23
  @request_path = path_from_request(request)
18
24
  @referer = referer_from_request(request)
@@ -23,6 +29,7 @@ module NewRelic
23
29
  @port = port_from_request(request)
24
30
  @user_agent = attribute_from_request(request, :user_agent)
25
31
  @request_method = attribute_from_request(request, :request_method)
32
+ @other_headers = other_headers_from_request(request)
26
33
  end
27
34
 
28
35
  def assign_agent_attributes(txn)
@@ -31,14 +38,17 @@ module NewRelic
31
38
  AttributeFilter::DST_ERROR_COLLECTOR
32
39
 
33
40
  if referer
34
- txn.add_agent_attribute(:'request.headers.referer', referer, AttributeFilter::DST_ERROR_COLLECTOR)
41
+ destinations = allow_other_headers? ? default_destinations : AttributeFilter::DST_ERROR_COLLECTOR
42
+ txn.add_agent_attribute(:'request.headers.referer', referer, destinations)
35
43
  end
36
44
 
37
45
  if request_path
38
- txn.add_agent_attribute(:'request.uri',
39
- request_path,
40
- AttributeFilter::DST_TRANSACTION_TRACER |
41
- AttributeFilter::DST_ERROR_COLLECTOR)
46
+ destinations = if allow_other_headers?
47
+ default_destinations
48
+ else
49
+ AttributeFilter::DST_TRANSACTION_TRACER | AttributeFilter::DST_ERROR_COLLECTOR
50
+ end
51
+ txn.add_agent_attribute(:'request.uri', request_path, destinations)
42
52
  end
43
53
 
44
54
  if accept
@@ -64,6 +74,14 @@ module NewRelic
64
74
  if request_method
65
75
  txn.add_agent_attribute(:'request.method', request_method, default_destinations)
66
76
  end
77
+
78
+ if port && allow_other_headers?
79
+ txn.add_agent_attribute(:'request.headers.port', port, default_destinations)
80
+ end
81
+
82
+ other_headers.each do |header, value|
83
+ txn.add_agent_attribute(header, value, default_destinations)
84
+ end
67
85
  end
68
86
 
69
87
  private
@@ -116,6 +134,26 @@ module NewRelic
116
134
  env[key]
117
135
  end
118
136
  end
137
+
138
+ def allow_other_headers?
139
+ NewRelic::Agent.config[:allow_all_headers] && !NewRelic::Agent.config[:high_security]
140
+ end
141
+
142
+ def other_headers_from_request(request)
143
+ # confirm that `request` is an instance of `Rack::Request` by checking
144
+ # for #each_header
145
+ return NewRelic::EMPTY_HASH unless allow_other_headers? && request.respond_to?(:each_header)
146
+
147
+ request.each_header.with_object({}) do |(header, value), hash|
148
+ next if BASE_HEADERS.include?(header)
149
+
150
+ hash[formatted_header(header)] = value
151
+ end
152
+ end
153
+
154
+ def formatted_header(raw_name)
155
+ "#{ATTRIBUTE_PREFIX}#{NewRelic::LanguageSupport.camelize_with_first_letter_downcased(raw_name)}".to_sym
156
+ end
119
157
  end
120
158
  end
121
159
  end
@@ -29,6 +29,12 @@ module NewRelic
29
29
  else
30
30
  segment.record_on_finish = true
31
31
  ::NewRelic::Agent.logger.debug("Segment limit of #{segment_limit} reached, ceasing collection.")
32
+
33
+ if finished?
34
+ ::NewRelic::Agent.logger.debug("Transaction #{best_name} has finished but segments still being created, resetting state.")
35
+ NewRelic::Agent::Tracer.state.reset
36
+ NewRelic::Agent.record_metric('Supportability/Transaction/SegmentLimitReachedAfterFinished/ResetState', 1)
37
+ end
32
38
  end
33
39
  segment.transaction_assigned
34
40
  end
@@ -31,11 +31,12 @@ module NewRelic
31
31
  RAKE_PREFIX = "#{OTHER_TRANSACTION_PREFIX}Rake/"
32
32
  MESSAGE_PREFIX = "#{OTHER_TRANSACTION_PREFIX}Message/"
33
33
  RACK_PREFIX = "#{CONTROLLER_PREFIX}Rack/"
34
+ RODA_PREFIX = "#{CONTROLLER_PREFIX}Roda/"
34
35
  SINATRA_PREFIX = "#{CONTROLLER_PREFIX}Sinatra/"
35
36
  GRAPE_PREFIX = "#{CONTROLLER_PREFIX}Grape/"
36
37
  ACTION_CABLE_PREFIX = "#{CONTROLLER_PREFIX}ActionCable/"
37
38
 
38
- WEB_TRANSACTION_CATEGORIES = [:web, :controller, :uri, :rack, :sinatra, :grape, :middleware, :action_cable].freeze
39
+ WEB_TRANSACTION_CATEGORIES = %i[action_cable controller grape middleware rack roda sinatra web uri].freeze
39
40
 
40
41
  MIDDLEWARE_SUMMARY_METRICS = ['Middleware/all'].freeze
41
42
  WEB_SUMMARY_METRIC = 'HttpDispatcher'
@@ -289,7 +290,7 @@ module NewRelic
289
290
  end
290
291
 
291
292
  def sampled?
292
- return unless Agent.config[:'distributed_tracing.enabled']
293
+ return false unless Agent.config[:'distributed_tracing.enabled']
293
294
 
294
295
  if @sampled.nil?
295
296
  @sampled = NewRelic::Agent.instance.adaptive_sampler.sampled?
@@ -439,6 +440,10 @@ module NewRelic
439
440
  segments.first
440
441
  end
441
442
 
443
+ def finished?
444
+ initial_segment&.finished?
445
+ end
446
+
442
447
  def create_initial_segment(options = {})
443
448
  segment = create_segment(@default_name, options)
444
449
  segment.record_scoped_metric = false
@@ -541,8 +546,8 @@ module NewRelic
541
546
  end
542
547
 
543
548
  def user_defined_rules_ignore?
544
- return unless request_path
545
- return if (rules = NewRelic::Agent.config[:"rules.ignore_url_regexes"]).empty?
549
+ return false unless request_path
550
+ return false if (rules = NewRelic::Agent.config[:"rules.ignore_url_regexes"]).empty?
546
551
 
547
552
  rules.any? do |rule|
548
553
  request_path.match(rule)
@@ -75,14 +75,12 @@ module NewRelic
75
75
  processed_headers = headers
76
76
  raise if processed_headers.value?(:error)
77
77
 
78
- Timeout.timeout(1) do
79
- response = nil
80
- Net::HTTP.start(endpoint.host, endpoint.port) do |http|
81
- req = Net::HTTP::Get.new(endpoint, processed_headers)
82
- response = http.request(req)
83
- end
84
- response
78
+ response = nil
79
+ Net::HTTP.start(endpoint.host, endpoint.port, open_timeout: 1, read_timeout: 1) do |http|
80
+ req = Net::HTTP::Get.new(endpoint, processed_headers)
81
+ response = http.request(req)
85
82
  end
83
+ response
86
84
  rescue
87
85
  NewRelic::Agent.logger.debug("#{vendor_name} environment not detected")
88
86
  end
@@ -58,6 +58,7 @@ module NewRelic
58
58
  require 'new_relic/agent/deprecator'
59
59
  require 'new_relic/agent/logging'
60
60
  require 'new_relic/agent/distributed_tracing'
61
+ require 'new_relic/agent/attribute_pre_filtering'
61
62
  require 'new_relic/agent/attribute_processing'
62
63
  require 'new_relic/agent/linking_metadata'
63
64
  require 'new_relic/agent/local_log_decorator'
@@ -213,6 +214,17 @@ module NewRelic
213
214
  record_metric(metric_name, value)
214
215
  end
215
216
 
217
+ def record_instrumentation_invocation(library)
218
+ record_metric_once("Supportability/#{library}/Invoked")
219
+ end
220
+
221
+ # see ActiveSupport::Inflector.demodulize
222
+ def base_name(klass_name)
223
+ return klass_name unless ridx = klass_name.rindex('::')
224
+
225
+ klass_name[(ridx + 2), klass_name.length]
226
+ end
227
+
216
228
  SUPPORTABILITY_INCREMENT_METRIC = 'Supportability/API/increment_metric'.freeze
217
229
 
218
230
  # Increment a simple counter metric.
@@ -298,7 +310,7 @@ module NewRelic
298
310
 
299
311
  # Set a callback proc for determining an error's error group name
300
312
  #
301
- # @param [Proc] the callback proc
313
+ # @param callback_proc [Proc] the callback proc
302
314
  #
303
315
  # Typically this method should be called only once to set a callback for
304
316
  # use with all noticed errors. If it is called multiple times, each new
@@ -649,6 +661,43 @@ module NewRelic
649
661
  end
650
662
  end
651
663
 
664
+ # Add custom attributes to log events for the current agent instance.
665
+ #
666
+ # @param [Hash] params A Hash of attributes to attach to log
667
+ # events. The agent accepts up to 240 custom
668
+ # log event attributes.
669
+ #
670
+ # Keys will be coerced into Strings and must
671
+ # be less than 256 characters. Keys longer
672
+ # than 255 characters will be truncated.
673
+ #
674
+ # Values may be Strings, Symbols, numeric
675
+ # values or Booleans and must be less than
676
+ # 4095 characters. If the value is a String
677
+ # or a Symbol, values longer than 4094
678
+ # characters will be truncated. If the value
679
+ # exceeds 4094 characters and is of a
680
+ # different class, the attribute pair will
681
+ # be dropped.
682
+ #
683
+ # This API can be called multiple times.
684
+ # If the same key is passed more than once,
685
+ # the value associated with the last call
686
+ # will be preserved.
687
+ #
688
+ # Attribute pairs with empty or nil contents
689
+ # will be dropped.
690
+ # @api public
691
+ def add_custom_log_attributes(params)
692
+ record_api_supportability_metric(:add_custom_log_attributes)
693
+
694
+ if params.is_a?(Hash)
695
+ NewRelic::Agent.agent.log_event_aggregator.add_custom_attributes(params)
696
+ else
697
+ NewRelic::Agent.logger.warn("Bad argument passed to #add_custom_log_attributes. Expected Hash but got #{params.class}.")
698
+ end
699
+ end
700
+
652
701
  # Set the user id for the current transaction. When present, this value will be included in the agent attributes for transaction and error events as 'enduser.id'.
653
702
  #
654
703
  # @param [String] user_id The user id to add to the current transaction attributes
@@ -60,6 +60,7 @@ 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
63
64
  if /newrelic_cmd$/.match?(script_name)
64
65
  $stdout.puts "warning: the 'newrelic_cmd' script has been renamed 'newrelic'"
65
66
  script_name = 'newrelic'