newrelic_rpm 9.2.2 → 9.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +26 -0
  3. data/CHANGELOG.md +168 -0
  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 +176 -32
  7. data/lib/new_relic/agent/configuration/environment_source.rb +1 -1
  8. data/lib/new_relic/agent/configuration/manager.rb +3 -2
  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/instrumentation/action_controller_other_subscriber.rb +1 -1
  12. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -1
  13. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +2 -1
  14. data/lib/new_relic/agent/instrumentation/active_support_logger/instrumentation.rb +4 -0
  15. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +9 -0
  16. data/lib/new_relic/agent/instrumentation/concurrent_ruby/chain.rb +1 -1
  17. data/lib/new_relic/agent/instrumentation/concurrent_ruby/instrumentation.rb +3 -4
  18. data/lib/new_relic/agent/instrumentation/concurrent_ruby/prepend.rb +1 -1
  19. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +1 -2
  20. data/lib/new_relic/agent/instrumentation/curb/instrumentation.rb +4 -0
  21. data/lib/new_relic/agent/instrumentation/delayed_job/instrumentation.rb +3 -0
  22. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +4 -1
  23. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +3 -0
  24. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +10 -3
  25. data/lib/new_relic/agent/instrumentation/fiber/instrumentation.rb +1 -2
  26. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +10 -3
  27. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +4 -0
  28. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +4 -0
  29. data/lib/new_relic/agent/instrumentation/grpc/server/instrumentation.rb +4 -0
  30. data/lib/new_relic/agent/instrumentation/grpc_client.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/grpc_server.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/httpclient/instrumentation.rb +4 -0
  33. data/lib/new_relic/agent/instrumentation/httprb/instrumentation.rb +4 -0
  34. data/lib/new_relic/agent/instrumentation/logger/instrumentation.rb +3 -0
  35. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +12 -3
  36. data/lib/new_relic/agent/instrumentation/memcache.rb +2 -2
  37. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +4 -0
  38. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +4 -0
  39. data/lib/new_relic/agent/instrumentation/padrino/instrumentation.rb +4 -0
  40. data/lib/new_relic/agent/instrumentation/queue_time.rb +1 -1
  41. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +6 -0
  42. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +4 -0
  43. data/lib/new_relic/agent/instrumentation/rails_notifications/action_cable.rb +1 -1
  44. data/lib/new_relic/agent/instrumentation/rake/instrumentation.rb +4 -0
  45. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +4 -0
  46. data/lib/new_relic/agent/instrumentation/resque/instrumentation.rb +4 -0
  47. data/lib/new_relic/agent/instrumentation/roda/chain.rb +43 -0
  48. data/lib/new_relic/agent/instrumentation/roda/instrumentation.rb +56 -0
  49. data/lib/new_relic/agent/instrumentation/roda/prepend.rb +24 -0
  50. data/lib/new_relic/agent/instrumentation/roda/roda_transaction_namer.rb +30 -0
  51. data/lib/new_relic/agent/instrumentation/roda.rb +34 -0
  52. data/lib/new_relic/agent/instrumentation/sequel.rb +1 -1
  53. data/lib/new_relic/agent/instrumentation/sidekiq/client.rb +4 -0
  54. data/lib/new_relic/agent/instrumentation/sidekiq/server.rb +26 -3
  55. data/lib/new_relic/agent/instrumentation/sidekiq.rb +2 -2
  56. data/lib/new_relic/agent/instrumentation/sinatra/instrumentation.rb +4 -0
  57. data/lib/new_relic/agent/instrumentation/stripe.rb +28 -0
  58. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +77 -0
  59. data/lib/new_relic/agent/instrumentation/thread/chain.rb +1 -1
  60. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +0 -1
  61. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +1 -1
  62. data/lib/new_relic/agent/instrumentation/tilt/instrumentation.rb +4 -0
  63. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +5 -1
  64. data/lib/new_relic/agent/log_event_aggregator.rb +49 -2
  65. data/lib/new_relic/agent/log_event_attributes.rb +115 -0
  66. data/lib/new_relic/agent/logging.rb +4 -4
  67. data/lib/new_relic/agent/method_tracer_helpers.rb +26 -5
  68. data/lib/new_relic/agent/new_relic_service.rb +33 -17
  69. data/lib/new_relic/agent/pipe_service.rb +1 -1
  70. data/lib/new_relic/agent/tracer.rb +6 -5
  71. data/lib/new_relic/agent/transaction/abstract_segment.rb +52 -0
  72. data/lib/new_relic/agent/transaction/request_attributes.rb +45 -7
  73. data/lib/new_relic/agent/transaction.rb +5 -4
  74. data/lib/new_relic/agent/utilization/vendor.rb +5 -7
  75. data/lib/new_relic/agent.rb +50 -1
  76. data/lib/new_relic/cli/command.rb +1 -0
  77. data/lib/new_relic/control/class_methods.rb +1 -7
  78. data/lib/new_relic/control/frameworks/roda.rb +20 -0
  79. data/lib/new_relic/dependency_detection.rb +6 -0
  80. data/lib/new_relic/language_support.rb +5 -0
  81. data/lib/new_relic/latest_changes.rb +1 -1
  82. data/lib/new_relic/noticed_error.rb +5 -2
  83. data/lib/new_relic/rack/agent_hooks.rb +1 -1
  84. data/lib/new_relic/rack/agent_middleware.rb +0 -16
  85. data/lib/new_relic/rack/browser_monitoring.rb +1 -1
  86. data/lib/new_relic/supportability_helper.rb +2 -0
  87. data/lib/new_relic/traced_thread.rb +2 -3
  88. data/lib/new_relic/version.rb +2 -2
  89. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  90. data/lib/tasks/bump_version.rake +21 -0
  91. data/lib/tasks/config.rake +3 -2
  92. data/lib/tasks/helpers/config.html.erb +93 -0
  93. data/lib/tasks/helpers/format.rb +11 -7
  94. data/lib/tasks/helpers/newrelicyml.rb +144 -0
  95. data/lib/tasks/helpers/version_bump.rb +62 -0
  96. data/lib/tasks/newrelicyml.rake +13 -0
  97. data/newrelic.yml +362 -265
  98. data/newrelic_rpm.gemspec +11 -7
  99. metadata +36 -25
  100. data/.gitignore +0 -43
  101. data/.project +0 -23
  102. data/.rubocop.yml +0 -1845
  103. data/.rubocop_todo.yml +0 -61
  104. data/.simplecov +0 -16
  105. data/.snyk +0 -11
  106. data/.yardopts +0 -27
  107. data/Brewfile +0 -13
  108. data/DOCKER.md +0 -167
  109. data/Dockerfile +0 -10
  110. data/Guardfile +0 -27
  111. data/config/database.yml +0 -5
  112. data/config.dot +0 -278
  113. data/docker-compose.yml +0 -107
  114. data/lefthook.yml +0 -9
  115. data/lib/tasks/helpers/removers.rb +0 -33
  116. data/lib/tasks/multiverse.rake +0 -6
  117. data/lib/tasks/multiverse.rb +0 -84
@@ -0,0 +1,34 @@
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 'roda/instrumentation'
6
+ require_relative 'roda/roda_transaction_namer'
7
+
8
+ DependencyDetection.defer do
9
+ named :roda
10
+
11
+ depends_on do
12
+ defined?(Roda) &&
13
+ Gem::Version.new(Roda::RodaVersion) >= Gem::Version.new('3.19.0') &&
14
+ Roda::RodaPlugins::Base::ClassMethods.private_method_defined?(:build_rack_app) &&
15
+ Roda::RodaPlugins::Base::InstanceMethods.method_defined?(:_roda_handle_main_route)
16
+ end
17
+
18
+ executes do
19
+ require_relative '../../rack/agent_hooks'
20
+ require_relative '../../rack/browser_monitoring'
21
+
22
+ NewRelic::Agent.logger.info('Installing Roda instrumentation')
23
+
24
+ if use_prepend?
25
+ require_relative 'roda/prepend'
26
+ prepend_instrument Roda.singleton_class, NewRelic::Agent::Instrumentation::Roda::Build::Prepend
27
+ prepend_instrument Roda, NewRelic::Agent::Instrumentation::Roda::Prepend
28
+ else
29
+ require_relative 'roda/chain'
30
+ chain_instrument NewRelic::Agent::Instrumentation::Roda::Build::Chain
31
+ chain_instrument NewRelic::Agent::Instrumentation::Roda::Chain
32
+ end
33
+ end
34
+ end
@@ -30,7 +30,7 @@ DependencyDetection.defer do
30
30
  else
31
31
  NewRelic::Agent.logger.info('Detected Sequel version %s.' % [Sequel::VERSION])
32
32
  NewRelic::Agent.logger.info('Please see additional documentation: ' +
33
- 'https://newrelic.com/docs/ruby/sequel-instrumentation')
33
+ 'https://docs.newrelic.com/docs/apm/agents/ruby-agent/frameworks/sequel-instrumentation/')
34
34
  end
35
35
 
36
36
  Sequel.synchronize { Sequel::DATABASES.dup }.each do |db|
@@ -6,7 +6,11 @@ module NewRelic::Agent::Instrumentation::Sidekiq
6
6
  class Client
7
7
  include Sidekiq::ClientMiddleware if defined?(Sidekiq::ClientMiddleware)
8
8
 
9
+ INSTRUMENTATION_NAME = 'SidekiqClient'
10
+
9
11
  def call(_worker_class, job, *_)
12
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
13
+
10
14
  job[NewRelic::NEWRELIC_KEY] ||= distributed_tracing_headers if ::NewRelic::Agent.config[:'distributed_tracing.enabled']
11
15
  yield
12
16
  end
@@ -7,9 +7,16 @@ module NewRelic::Agent::Instrumentation::Sidekiq
7
7
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
8
8
  include Sidekiq::ServerMiddleware if defined?(Sidekiq::ServerMiddleware)
9
9
 
10
+ ATTRIBUTE_BASE_NAMESPACE = 'sidekiq.args'
11
+ ATTRIBUTE_FILTER_TYPES = %i[include exclude].freeze
12
+ ATTRIBUTE_JOB_NAMESPACE = :"job.#{ATTRIBUTE_BASE_NAMESPACE}"
13
+ INSTRUMENTATION_NAME = 'SidekiqServer'
14
+
10
15
  # Client middleware has additional parameters, and our tests use the
11
16
  # middleware client-side to work inline.
12
17
  def call(worker, msg, queue, *_)
18
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
19
+
13
20
  trace_args = if worker.respond_to?(:newrelic_trace_args)
14
21
  worker.newrelic_trace_args(msg, queue)
15
22
  else
@@ -18,10 +25,16 @@ module NewRelic::Agent::Instrumentation::Sidekiq
18
25
  trace_headers = msg.delete(NewRelic::NEWRELIC_KEY)
19
26
 
20
27
  perform_action_with_newrelic_trace(trace_args) do
21
- NewRelic::Agent::Transaction.merge_untrusted_agent_attributes(msg['args'], :'job.sidekiq.args',
22
- NewRelic::Agent::AttributeFilter::DST_NONE)
28
+ NewRelic::Agent::Transaction.merge_untrusted_agent_attributes(
29
+ NewRelic::Agent::AttributePreFiltering.pre_filter(msg['args'], self.class.nr_attribute_options),
30
+ ATTRIBUTE_JOB_NAMESPACE,
31
+ NewRelic::Agent::AttributeFilter::DST_NONE
32
+ )
33
+
34
+ if ::NewRelic::Agent.config[:'distributed_tracing.enabled'] && trace_headers&.any?
35
+ ::NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(trace_headers, 'Other')
36
+ end
23
37
 
24
- ::NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(trace_headers, 'Other') if ::NewRelic::Agent.config[:'distributed_tracing.enabled'] && trace_headers&.any?
25
38
  yield
26
39
  end
27
40
  end
@@ -33,5 +46,15 @@ module NewRelic::Agent::Instrumentation::Sidekiq
33
46
  :category => 'OtherTransaction/SidekiqJob'
34
47
  }
35
48
  end
49
+
50
+ def self.nr_attribute_options
51
+ @nr_attribute_options ||= begin
52
+ ATTRIBUTE_FILTER_TYPES.each_with_object({}) do |type, opts|
53
+ pattern =
54
+ NewRelic::Agent::AttributePreFiltering.formulate_regexp_union(:"#{ATTRIBUTE_BASE_NAMESPACE}.#{type}")
55
+ opts[type] = pattern if pattern
56
+ end.merge(attribute_namespace: ATTRIBUTE_JOB_NAMESPACE)
57
+ end
58
+ end
36
59
  end
37
60
  end
@@ -43,8 +43,8 @@ DependencyDetection.defer do
43
43
  executes do
44
44
  next unless Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new('5.0.0')
45
45
 
46
- deprecation_msg = 'Instrumentation for Sidekiq versions below 5.0.0 is deprecated.' \
47
- 'They will stop being monitored in version 9.0.0. ' \
46
+ deprecation_msg = 'Instrumentation for Sidekiq versions below 5.0.0 is deprecated ' \
47
+ 'and will be dropped entirely in a future major New Relic Ruby agent release.' \
48
48
  'Please upgrade your Sidekiq version to continue receiving full support. '
49
49
 
50
50
  NewRelic::Agent.logger.log_once(
@@ -13,6 +13,8 @@ module NewRelic::Agent::Instrumentation
13
13
  module Tracer
14
14
  include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
15
15
 
16
+ INSTRUMENTATION_NAME = 'Sinatra'
17
+
16
18
  def self.included(clazz)
17
19
  clazz.extend(self)
18
20
  end
@@ -90,6 +92,8 @@ module NewRelic::Agent::Instrumentation
90
92
  end
91
93
 
92
94
  def dispatch_with_tracing
95
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
96
+
93
97
  request_params = get_request_params
94
98
  filtered_params = ::NewRelic::Agent::ParameterFiltering::apply_filters(request.env, request_params || {})
95
99
 
@@ -0,0 +1,28 @@
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 'new_relic/agent/instrumentation/stripe_subscriber'
6
+
7
+ DependencyDetection.defer do
8
+ named :stripe
9
+
10
+ depends_on do
11
+ NewRelic::Agent.config[:'instrumentation.stripe'] == 'enabled'
12
+ end
13
+
14
+ depends_on do
15
+ defined?(Stripe) &&
16
+ Gem::Version.new(Stripe::VERSION) >= Gem::Version.new('5.38.0')
17
+ end
18
+
19
+ executes do
20
+ NewRelic::Agent.logger.info('Installing Stripe instrumentation')
21
+ end
22
+
23
+ executes do
24
+ newrelic_subscriber = NewRelic::Agent::Instrumentation::StripeSubscriber.new
25
+ Stripe::Instrumentation.subscribe(:request_begin) { |event| newrelic_subscriber.start_segment(event) }
26
+ Stripe::Instrumentation.subscribe(:request_end) { |event| newrelic_subscriber.finish_segment(event) }
27
+ end
28
+ end
@@ -0,0 +1,77 @@
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
+ module Instrumentation
8
+ class StripeSubscriber
9
+ DEFAULT_DESTINATIONS = AttributeFilter::DST_SPAN_EVENTS
10
+ EVENT_ATTRIBUTES = %i[http_status method num_retries path request_id].freeze
11
+ ATTRIBUTE_NAMESPACE = 'stripe.user_data'
12
+ ATTRIBUTE_FILTER_TYPES = %i[include exclude].freeze
13
+
14
+ def start_segment(event)
15
+ return unless is_execution_traced?
16
+
17
+ segment = NewRelic::Agent::Tracer.start_segment(name: metric_name(event))
18
+ event.user_data[:newrelic_segment] = segment
19
+ rescue => e
20
+ NewRelic::Agent.logger.error("Error starting New Relic Stripe segment: #{e}")
21
+ end
22
+
23
+ def finish_segment(event)
24
+ return unless is_execution_traced?
25
+
26
+ segment = remove_and_return_nr_segment(event)
27
+ add_stripe_attributes(segment, event)
28
+ add_custom_attributes(segment, event)
29
+ rescue => e
30
+ NewRelic::Agent.logger.error("Error finishing New Relic Stripe segment: #{e}")
31
+ ensure
32
+ segment&.finish
33
+ end
34
+
35
+ private
36
+
37
+ def is_execution_traced?
38
+ NewRelic::Agent::Tracer.state.is_execution_traced?
39
+ end
40
+
41
+ def metric_name(event)
42
+ "Stripe#{event.path}/#{event.method}"
43
+ end
44
+
45
+ def add_stripe_attributes(segment, event)
46
+ EVENT_ATTRIBUTES.each do |attribute|
47
+ segment.add_agent_attribute("stripe_#{attribute}", event.send(attribute), DEFAULT_DESTINATIONS)
48
+ end
49
+ end
50
+
51
+ def add_custom_attributes(segment, event)
52
+ return if NewRelic::Agent.config[:'stripe.user_data.include'].empty?
53
+
54
+ filtered_attributes = NewRelic::Agent::AttributePreFiltering.pre_filter_hash(event.user_data, nr_attribute_options)
55
+ filtered_attributes.each do |key, value|
56
+ segment.add_agent_attribute("stripe_user_data_#{key}", value, DEFAULT_DESTINATIONS)
57
+ end
58
+ end
59
+
60
+ def nr_attribute_options
61
+ ATTRIBUTE_FILTER_TYPES.each_with_object({}) do |type, opts|
62
+ pattern =
63
+ NewRelic::Agent::AttributePreFiltering.formulate_regexp_union(:"#{ATTRIBUTE_NAMESPACE}.#{type}")
64
+ opts[type] = pattern if pattern
65
+ end
66
+ end
67
+
68
+ def remove_and_return_nr_segment(event)
69
+ segment = event.user_data[:newrelic_segment]
70
+ event.user_data.delete(:newrelic_segment)
71
+
72
+ segment
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -14,7 +14,7 @@ module NewRelic::Agent::Instrumentation
14
14
  alias_method(:initialize_without_new_relic, :initialize)
15
15
 
16
16
  def initialize(*args, &block)
17
- traced_block = add_thread_tracing(*args, &block)
17
+ traced_block = add_thread_tracing(&block)
18
18
  initialize_with_newrelic_tracing { initialize_without_new_relic(*args, &traced_block) }
19
19
  end
20
20
  end
@@ -17,7 +17,6 @@ module NewRelic
17
17
  return block if !NewRelic::Agent::Tracer.thread_tracing_enabled?
18
18
 
19
19
  NewRelic::Agent::Tracer.thread_block_with_current_transaction(
20
- *args,
21
20
  segment_name: 'Ruby/Thread',
22
21
  &block
23
22
  )
@@ -12,7 +12,7 @@ module NewRelic
12
12
  include NewRelic::Agent::Instrumentation::MonitoredThread
13
13
 
14
14
  def initialize(*args, &block)
15
- traced_block = add_thread_tracing(*args, &block)
15
+ traced_block = add_thread_tracing(&block)
16
16
  initialize_with_newrelic_tracing { super(*args, &traced_block) }
17
17
  end
18
18
  end
@@ -6,6 +6,8 @@ module NewRelic
6
6
  module Agent
7
7
  module Instrumentation
8
8
  module Tilt
9
+ INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
10
+
9
11
  def metric_name(klass, file)
10
12
  "View/#{klass}/#{file}/Rendering"
11
13
  end
@@ -21,6 +23,8 @@ module NewRelic
21
23
  end
22
24
 
23
25
  def render_with_tracing(*args, &block)
26
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
27
+
24
28
  begin
25
29
  finishable = Tracer.start_segment(
26
30
  name: metric_name(self.class, create_filename_for_metric(self.file))
@@ -8,8 +8,8 @@ module NewRelic
8
8
  module Typhoeus
9
9
  HYDRA_SEGMENT_NAME = 'External/Multiple/Typhoeus::Hydra/run'
10
10
  NOTICEABLE_ERROR_CLASS = 'Typhoeus::Errors::TyphoeusError'
11
-
12
11
  EARLIEST_VERSION = Gem::Version.new('0.5.3')
12
+ INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
13
13
 
14
14
  def self.is_supported_version?
15
15
  Gem::Version.new(::Typhoeus::VERSION) >= EARLIEST_VERSION
@@ -31,6 +31,8 @@ module NewRelic
31
31
  end
32
32
 
33
33
  def with_tracing
34
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
35
+
34
36
  segment = NewRelic::Agent::Tracer.start_segment(name: HYDRA_SEGMENT_NAME)
35
37
  instance_variable_set(:@__newrelic_hydra_segment, segment)
36
38
  begin
@@ -41,6 +43,8 @@ module NewRelic
41
43
  end
42
44
 
43
45
  def self.trace(request)
46
+ NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
47
+
44
48
  state = NewRelic::Agent::Tracer.state
45
49
  return unless state.is_execution_traced?
46
50
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  require 'new_relic/agent/event_aggregator'
6
6
  require 'new_relic/agent/log_priority'
7
+ require 'new_relic/agent/log_event_attributes'
7
8
 
8
9
  module NewRelic
9
10
  module Agent
@@ -36,6 +37,10 @@ module NewRelic
36
37
  METRICS_ENABLED_KEY = :'application_logging.metrics.enabled'
37
38
  FORWARDING_ENABLED_KEY = :'application_logging.forwarding.enabled'
38
39
  DECORATING_ENABLED_KEY = :'application_logging.local_decorating.enabled'
40
+ LOG_LEVEL_KEY = :'application_logging.forwarding.log_level'
41
+ CUSTOM_ATTRIBUTES_KEY = :'application_logging.forwarding.custom_attributes'
42
+
43
+ attr_reader :attributes
39
44
 
40
45
  def initialize(events)
41
46
  super(events)
@@ -44,6 +49,7 @@ module NewRelic
44
49
  @seen_by_severity = Hash.new(0)
45
50
  @high_security = NewRelic::Agent.config[:high_security]
46
51
  @instrumentation_logger_enabled = NewRelic::Agent::Instrumentation::Logger.enabled?
52
+ @attributes = NewRelic::Agent::LogEventAttributes.new
47
53
  register_for_done_configuring(events)
48
54
  end
49
55
 
@@ -63,8 +69,9 @@ module NewRelic
63
69
  end
64
70
  end
65
71
 
72
+ return if severity_too_low?(severity)
66
73
  return if formatted_message.nil? || formatted_message.empty?
67
- return unless NewRelic::Agent.config[:'application_logging.forwarding.enabled']
74
+ return unless NewRelic::Agent.config[FORWARDING_ENABLED_KEY]
68
75
  return if @high_security
69
76
 
70
77
  txn = NewRelic::Agent::Transaction.tl_current
@@ -114,6 +121,10 @@ module NewRelic
114
121
  ]
115
122
  end
116
123
 
124
+ def add_custom_attributes(custom_attributes)
125
+ attributes.add_custom_attributes(custom_attributes)
126
+ end
127
+
117
128
  # Because our transmission format (MELT) is different than historical
118
129
  # agent payloads, extract the munging here to keep the service focused
119
130
  # on the general harvest + transmit instead of the format.
@@ -130,6 +141,8 @@ module NewRelic
130
141
  # sent by classic logs-in-context
131
142
  common_attributes.delete(ENTITY_TYPE_KEY)
132
143
 
144
+ common_attributes.merge!(NewRelic::Agent.agent.log_event_aggregator.attributes.custom_attributes)
145
+
133
146
  _, items = data
134
147
  payload = [{
135
148
  common: {attributes: common_attributes},
@@ -149,6 +162,7 @@ module NewRelic
149
162
  @seen = 0
150
163
  @seen_by_severity.clear
151
164
  end
165
+
152
166
  super
153
167
  end
154
168
 
@@ -168,6 +182,8 @@ module NewRelic
168
182
  record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
169
183
  record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
170
184
  record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
185
+
186
+ add_custom_attributes(NewRelic::Agent.config[CUSTOM_ATTRIBUTES_KEY])
171
187
  end
172
188
  end
173
189
 
@@ -191,7 +207,7 @@ module NewRelic
191
207
  # these until harvest before recording them
192
208
  def record_customer_metrics
193
209
  return unless enabled?
194
- return unless NewRelic::Agent.config[:'application_logging.metrics.enabled']
210
+ return unless NewRelic::Agent.config[METRICS_ENABLED_KEY]
195
211
 
196
212
  @counter_lock.synchronize do
197
213
  return unless @seen > 0
@@ -230,6 +246,37 @@ module NewRelic
230
246
 
231
247
  message.byteslice(0...MAX_BYTES)
232
248
  end
249
+
250
+ def minimum_log_level
251
+ if Logger::Severity.constants.include?(configured_log_level_constant)
252
+ configured_log_level_constant
253
+ else
254
+ NewRelic::Agent.logger.log_once(
255
+ :error,
256
+ 'Invalid application_logging.forwarding.log_level ' \
257
+ "'#{NewRelic::Agent.config[LOG_LEVEL_KEY]}' specified! " \
258
+ "Must be one of #{Logger::Severity.constants.join('|')}. " \
259
+ "Using default level of 'debug'"
260
+ )
261
+ :DEBUG
262
+ end
263
+ end
264
+
265
+ def configured_log_level_constant
266
+ format_log_level_constant(NewRelic::Agent.config[LOG_LEVEL_KEY])
267
+ end
268
+
269
+ def format_log_level_constant(log_level)
270
+ log_level.upcase.to_sym
271
+ end
272
+
273
+ def severity_too_low?(severity)
274
+ severity_constant = format_log_level_constant(severity)
275
+ # always record custom log levels
276
+ return false unless Logger::Severity.constants.include?(severity_constant)
277
+
278
+ Logger::Severity.const_get(severity_constant) < Logger::Severity.const_get(minimum_log_level)
279
+ end
233
280
  end
234
281
  end
235
282
  end
@@ -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