newrelic_rpm 9.2.2 → 9.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +21 -0
  3. data/CHANGELOG.md +55 -0
  4. data/README.md +4 -4
  5. data/lib/new_relic/agent/configuration/default_source.rb +77 -29
  6. data/lib/new_relic/agent/configuration/manager.rb +3 -2
  7. data/lib/new_relic/agent/configuration/yaml_source.rb +13 -0
  8. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -1
  9. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +2 -1
  10. data/lib/new_relic/agent/instrumentation/concurrent_ruby/chain.rb +1 -1
  11. data/lib/new_relic/agent/instrumentation/concurrent_ruby/instrumentation.rb +1 -2
  12. data/lib/new_relic/agent/instrumentation/concurrent_ruby/prepend.rb +1 -1
  13. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +10 -3
  14. data/lib/new_relic/agent/instrumentation/fiber/instrumentation.rb +1 -2
  15. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +10 -3
  16. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +3 -3
  17. data/lib/new_relic/agent/instrumentation/rails_notifications/action_cable.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/thread/chain.rb +1 -1
  19. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +0 -1
  20. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +1 -1
  21. data/lib/new_relic/agent/log_event_aggregator.rb +49 -2
  22. data/lib/new_relic/agent/log_event_attributes.rb +115 -0
  23. data/lib/new_relic/agent/logging.rb +4 -4
  24. data/lib/new_relic/agent/method_tracer_helpers.rb +26 -5
  25. data/lib/new_relic/agent/tracer.rb +2 -2
  26. data/lib/new_relic/agent.rb +37 -0
  27. data/lib/new_relic/dependency_detection.rb +6 -0
  28. data/lib/new_relic/latest_changes.rb +1 -1
  29. data/lib/new_relic/supportability_helper.rb +1 -0
  30. data/lib/new_relic/traced_thread.rb +2 -3
  31. data/lib/new_relic/version.rb +2 -2
  32. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  33. data/lib/tasks/bump_version.rake +21 -0
  34. data/lib/tasks/helpers/newrelicyml.rb +144 -0
  35. data/lib/tasks/helpers/version_bump.rb +62 -0
  36. data/lib/tasks/multiverse.rb +0 -8
  37. data/lib/tasks/newrelicyml.rake +13 -0
  38. data/newrelic.yml +307 -266
  39. data/newrelic_rpm.gemspec +5 -4
  40. metadata +12 -22
  41. data/.gitignore +0 -43
  42. data/.project +0 -23
  43. data/.rubocop.yml +0 -1845
  44. data/.rubocop_todo.yml +0 -61
  45. data/.simplecov +0 -16
  46. data/.snyk +0 -11
  47. data/.yardopts +0 -27
  48. data/Brewfile +0 -13
  49. data/DOCKER.md +0 -167
  50. data/Dockerfile +0 -10
  51. data/Guardfile +0 -27
  52. data/config/database.yml +0 -5
  53. data/config.dot +0 -278
  54. data/docker-compose.yml +0 -107
  55. data/lefthook.yml +0 -9
  56. data/test/agent_helper.rb +0 -1027
@@ -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
@@ -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 @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
@@ -419,10 +419,10 @@ module NewRelic
419
419
  NewRelic::Agent.config[:'instrumentation.thread.tracing']
420
420
  end
421
421
 
422
- def thread_block_with_current_transaction(*args, segment_name:, parent: nil, &block)
422
+ def thread_block_with_current_transaction(segment_name:, parent: nil, &block)
423
423
  parent ||= current_segment
424
424
  current_txn = ::Thread.current[:newrelic_tracer_state]&.current_transaction if ::Thread.current[:newrelic_tracer_state]&.is_execution_traced?
425
- proc do
425
+ proc do |*args|
426
426
  begin
427
427
  if current_txn && !current_txn.finished?
428
428
  NewRelic::Agent::Tracer.state.current_transaction = current_txn
@@ -649,6 +649,43 @@ module NewRelic
649
649
  end
650
650
  end
651
651
 
652
+ # Add custom attributes to log events for the current agent instance.
653
+ #
654
+ # @param [Hash] params A Hash of attributes to attach to log
655
+ # events. The agent accepts up to 240 custom
656
+ # log event attributes.
657
+ #
658
+ # Keys will be coerced into Strings and must
659
+ # be less than 256 characters. Keys longer
660
+ # than 255 characters will be truncated.
661
+ #
662
+ # Values may be Strings, Symbols, numeric
663
+ # values or Booleans and must be less than
664
+ # 4095 characters. If the value is a String
665
+ # or a Symbol, values longer than 4094
666
+ # characters will be truncated. If the value
667
+ # exceeds 4094 characters and is of a
668
+ # different class, the attribute pair will
669
+ # be dropped.
670
+ #
671
+ # This API can be called multiple times.
672
+ # If the same key is passed more than once,
673
+ # the value associated with the last call
674
+ # will be preserved.
675
+ #
676
+ # Attribute pairs with empty or nil contents
677
+ # will be dropped.
678
+ # @api public
679
+ def add_custom_log_attributes(params)
680
+ record_api_supportability_metric(:add_custom_log_attributes)
681
+
682
+ if params.is_a?(Hash)
683
+ NewRelic::Agent.agent.log_event_aggregator.add_custom_attributes(params)
684
+ else
685
+ NewRelic::Agent.logger.warn("Bad argument passed to #add_custom_log_attributes. Expected Hash but got #{params.class}.")
686
+ end
687
+ end
688
+
652
689
  # 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
690
  #
654
691
  # @param [String] user_id The user id to add to the current transaction attributes
@@ -27,6 +27,8 @@ module DependencyDetection
27
27
  @items.each do |item|
28
28
  if item.dependencies_satisfied?
29
29
  item.execute
30
+ else
31
+ item.configure_as_unsatisfied unless item.disabled_configured?
30
32
  end
31
33
  end
32
34
  end
@@ -62,6 +64,10 @@ module DependencyDetection
62
64
  !executed and check_dependencies
63
65
  end
64
66
 
67
+ def configure_as_unsatisfied
68
+ NewRelic::Agent.config.instance_variable_get(:@cache)[config_key] = :unsatisfied
69
+ end
70
+
65
71
  def source_location_for(klass, method_name)
66
72
  Object.instance_method(:method).bind(klass.allocate).call(method_name).source_location.to_s
67
73
  end
@@ -8,7 +8,7 @@ module NewRelic
8
8
  File.join(File.dirname(__FILE__), '..', '..', 'CHANGELOG.md')
9
9
  end
10
10
 
11
- FOOTER = <<'EOS'
11
+ FOOTER = <<EOS
12
12
  See https://github.com/newrelic/newrelic-ruby-agent/blob/main/CHANGELOG.md for a full list of
13
13
  changes.
14
14
  EOS
@@ -13,6 +13,7 @@ module NewRelic
13
13
  :insert_distributed_trace_headers,
14
14
  :accept_distributed_trace_headers,
15
15
  :add_custom_attributes,
16
+ :add_custom_log_attributes,
16
17
  :add_custom_span_attributes,
17
18
  :add_instrumentation,
18
19
  :add_method_tracer,
@@ -22,15 +22,14 @@ module NewRelic
22
22
  # @api public
23
23
  def initialize(*args, &block)
24
24
  NewRelic::Agent.record_api_supportability_metric(:traced_thread)
25
- traced_block = create_traced_block(*args, &block)
25
+ traced_block = create_traced_block(&block)
26
26
  super(*args, &traced_block)
27
27
  end
28
28
 
29
- def create_traced_block(*args, &block)
29
+ def create_traced_block(&block)
30
30
  return block if NewRelic::Agent.config[:'instrumentation.thread.tracing'] # if this is on, don't double trace
31
31
 
32
32
  NewRelic::Agent::Tracer.thread_block_with_current_transaction(
33
- *args,
34
33
  segment_name: 'Ruby/TracedThread',
35
34
  &block
36
35
  )
@@ -6,8 +6,8 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 2
10
- TINY = 2
9
+ MINOR = 3
10
+ TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
13
13
  end
@@ -27,7 +27,7 @@ module Sequel
27
27
  # If you don't want your models or database connections to be instrumented,
28
28
  # you can disable them by setting `disable_database_instrumentation` in
29
29
  # your `newrelic.yml` to `true`. It will also honor the
30
- # `disable_activerecord_instrumentation` setting.
30
+ # `disable_active_record_instrumentation` setting.
31
31
  #
32
32
  module NewRelicInstrumentation
33
33
  module Naming
@@ -0,0 +1,21 @@
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 './helpers/version_bump'
6
+
7
+ namespace :newrelic do
8
+ namespace :version do
9
+ desc 'Returns the current version'
10
+ task :current do
11
+ puts "#{NewRelic::VERSION::STRING}"
12
+ end
13
+
14
+ desc 'Update version file and changelog to next version'
15
+ task :bump, [:format] => [] do |t, args|
16
+ new_version = VersionBump.update_version
17
+ VersionBump.update_changelog(new_version)
18
+ puts "New version: #{new_version}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,144 @@
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 '../../new_relic/agent/configuration/default_source'
6
+
7
+ module NewRelicYML
8
+ CRITICAL = [:'agent_enabled', :'app_name', :'license_key', :'log_level']
9
+ DEFAULTS = NewRelic::Agent::Configuration::DEFAULTS
10
+ # Skip because not configurable via yml
11
+ SKIP = [:'defer_rails_initialization']
12
+ # Don't evaluate Procs, instead use set values
13
+ PROCS = {:'config_path' => 'newrelic.yml',
14
+ :'process_host.display_name' => 'default hostname',
15
+ :'transaction_tracer.transaction_threshold' => 1.0}
16
+
17
+ HEADER = <<~HEADER
18
+ #
19
+ # This file configures the New Relic Agent. New Relic monitors Ruby, Java,
20
+ # .NET, PHP, Python, Node, and Go applications with deep visibility and low
21
+ # overhead. For more information, visit www.newrelic.com.
22
+
23
+ # Generated <%= Time.now.strftime('%B %d, %Y') %><%= ", for version \#{@agent_version}" if @agent_version %>
24
+ #<%= "\\n# \#{generated_for_user}\\n#" if generated_for_user %>
25
+ # For full documentation of agent configuration options, please refer to
26
+ # https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration
27
+
28
+ common: &default_settings
29
+ # Required license key associated with your New Relic account.
30
+ license_key: <%= license_key %>
31
+
32
+ # Your application name. Renaming here affects where data displays in New
33
+ # Relic. For more details, see https://docs.newrelic.com/docs/apm/new-relic-apm/maintenance/renaming-applications
34
+ app_name: <%= app_name %>
35
+
36
+ # To disable the agent regardless of other settings, uncomment the following:
37
+ # agent_enabled: false
38
+
39
+ # Logging level for log/newrelic_agent.log; options are error, warn, info, or
40
+ # debug.
41
+ log_level: info
42
+
43
+ # All of the following configuration options are optional. Review them, and
44
+ # uncomment or edit them if they appear relevant to your application needs.
45
+
46
+ HEADER
47
+
48
+ FOOTER = <<~FOOTER
49
+ # Environment-specific settings are in this section.
50
+ # RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment.
51
+ # If your application has other named environments, configure them here.
52
+ development:
53
+ <<: *default_settings
54
+ app_name: <%= app_name %> (Development)
55
+
56
+ test:
57
+ <<: *default_settings
58
+ # It doesn't make sense to report to New Relic from automated test runs.
59
+ monitor_mode: false
60
+
61
+ staging:
62
+ <<: *default_settings
63
+ app_name: <%= app_name %> (Staging)
64
+
65
+ production:
66
+ <<: *default_settings
67
+ FOOTER
68
+
69
+ def self.get_configs(defaults)
70
+ defaults.sort.each_with_object({}) do |(key, value), final_configs|
71
+ next if CRITICAL.include?(key) || SKIP.include?(key)
72
+
73
+ next unless public_config?(value) && !deprecated?(value)
74
+
75
+ sanitized_description = sanitize_description(value[:description])
76
+ description = format_description(sanitized_description)
77
+ default = default_value(key, value)
78
+ final_configs[key] = {description: description, default: default}
79
+ end
80
+ end
81
+
82
+ def self.public_config?(value)
83
+ value[:public] == true
84
+ end
85
+
86
+ def self.deprecated?(value)
87
+ value[:deprecated] == true
88
+ end
89
+
90
+ def self.sanitize_description(description)
91
+ # remove callouts
92
+ description = description.split("\n").reject { |line| line.match?('</?Callout') }.join("\n")
93
+ # remove InlinePopover, keep the text inside type
94
+ description.gsub!(/<InlinePopover type="(.*)" \/>/, '\1')
95
+ # remove hyperlinks
96
+ description.gsub!(/\[([^\]]+)\]\([^\)]+\)/, '\1')
97
+ # remove single pairs of backticks
98
+ description.gsub!(/`([^`]+)`/, '\1')
99
+ # removed href links
100
+ description.gsub!(/<a href="(.*)">(.*)<\/a>/, '\2')
101
+
102
+ description
103
+ end
104
+
105
+ def self.format_description(description)
106
+ # remove leading and trailing whitespace
107
+ description.strip!
108
+ # wrap text after 80 characters
109
+ description.gsub!(/(.{1,80})(\s+|\Z)/, "\\1\n")
110
+ # add hashtags to lines
111
+ description = description.split("\n").map { |line| " # #{line}" }.join("\n")
112
+
113
+ description
114
+ end
115
+
116
+ def self.default_value(key, config_hash)
117
+ if PROCS.include?(key)
118
+ PROCS[key]
119
+ else
120
+ default = config_hash[:documentation_default].nil? ? config_hash[:default] : config_hash[:documentation_default]
121
+ default = 'nil' if default.nil?
122
+ default = '""' if default == ''
123
+
124
+ default
125
+ end
126
+ end
127
+
128
+ def self.build_string(defaults)
129
+ configs = get_configs(defaults)
130
+ yml_string = ''
131
+
132
+ configs.each do |key, value|
133
+ yml_string += "#{value[:description]}\n # #{key}: #{value[:default]}\n\n"
134
+ end
135
+
136
+ yml_string
137
+ end
138
+
139
+ # :nocov:
140
+ def self.write_file(defaults = DEFAULTS)
141
+ File.write('newrelic.yml', HEADER + build_string(defaults) + FOOTER)
142
+ end
143
+ # :nocov:
144
+ end