elastic-apm 2.8.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_codecov.yml +5 -0
  3. data/.ci/.jenkins_exclude.yml +63 -0
  4. data/.ci/.jenkins_framework.yml +9 -0
  5. data/.ci/.jenkins_master_framework.yml +3 -0
  6. data/.ci/.jenkins_ruby.yml +11 -0
  7. data/.ci/Jenkinsfile +268 -0
  8. data/.ci/bin/check_paths_for_matches.py +80 -0
  9. data/.ci/downstreamTests.groovy +188 -0
  10. data/.ci/jobs/apm-agent-ruby-downstream.yml +37 -0
  11. data/.ci/jobs/apm-agent-ruby-linting-mbp.yml +38 -0
  12. data/.ci/jobs/apm-agent-ruby-mbp.yml +41 -0
  13. data/.ci/jobs/apm-agent-ruby.yml +4 -0
  14. data/.ci/jobs/defaults.yml +24 -0
  15. data/.ci/linting.groovy +32 -0
  16. data/.ci/prepare-git-context.sh +23 -0
  17. data/.pre-commit-config.yaml +22 -0
  18. data/.rspec +0 -1
  19. data/.rubocop.yml +3 -3
  20. data/CHANGELOG.md +59 -2
  21. data/docs/api.asciidoc +24 -7
  22. data/docs/configuration.asciidoc +43 -4
  23. data/docs/index.asciidoc +2 -0
  24. data/docs/log-correlation.asciidoc +96 -0
  25. data/docs/metrics.asciidoc +77 -6
  26. data/lib/elastic_apm.rb +37 -5
  27. data/lib/elastic_apm/agent.rb +29 -4
  28. data/lib/elastic_apm/central_config.rb +141 -0
  29. data/lib/elastic_apm/central_config/cache_control.rb +34 -0
  30. data/lib/elastic_apm/config.rb +165 -340
  31. data/lib/elastic_apm/config/bytes.rb +25 -0
  32. data/lib/elastic_apm/config/duration.rb +6 -8
  33. data/lib/elastic_apm/config/options.rb +134 -0
  34. data/lib/elastic_apm/config/regexp_list.rb +13 -0
  35. data/lib/elastic_apm/context_builder.rb +2 -0
  36. data/lib/elastic_apm/error/exception.rb +3 -1
  37. data/lib/elastic_apm/error_builder.rb +6 -3
  38. data/lib/elastic_apm/instrumenter.rb +6 -0
  39. data/lib/elastic_apm/metadata.rb +2 -1
  40. data/lib/elastic_apm/metrics.rb +2 -1
  41. data/lib/elastic_apm/metrics/vm.rb +60 -0
  42. data/lib/elastic_apm/normalizers/action_controller.rb +5 -2
  43. data/lib/elastic_apm/normalizers/action_mailer.rb +5 -2
  44. data/lib/elastic_apm/normalizers/action_view.rb +14 -9
  45. data/lib/elastic_apm/normalizers/active_record.rb +5 -2
  46. data/lib/elastic_apm/rails.rb +59 -0
  47. data/lib/elastic_apm/railtie.rb +11 -48
  48. data/lib/elastic_apm/span.rb +29 -7
  49. data/lib/elastic_apm/spies/faraday.rb +9 -2
  50. data/lib/elastic_apm/spies/http.rb +9 -2
  51. data/lib/elastic_apm/spies/mongo.rb +18 -3
  52. data/lib/elastic_apm/spies/net_http.rb +8 -2
  53. data/lib/elastic_apm/stacktrace/frame.rb +3 -1
  54. data/lib/elastic_apm/stacktrace_builder.rb +2 -2
  55. data/lib/elastic_apm/subscriber.rb +11 -3
  56. data/lib/elastic_apm/transport/connection.rb +17 -3
  57. data/lib/elastic_apm/transport/connection/proxy_pipe.rb +8 -1
  58. data/lib/elastic_apm/transport/filters/secrets_filter.rb +3 -1
  59. data/lib/elastic_apm/transport/serializers/error_serializer.rb +12 -2
  60. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +6 -1
  61. data/lib/elastic_apm/transport/serializers/span_serializer.rb +11 -3
  62. data/lib/elastic_apm/version.rb +1 -1
  63. metadata +26 -4
  64. data/Jenkinsfile +0 -280
  65. data/lib/elastic_apm/config/size.rb +0 -28
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elastic_apm/error'
4
+
3
5
  require 'elastic_apm/context_builder'
4
6
  require 'elastic_apm/error_builder'
5
7
  require 'elastic_apm/stacktrace_builder'
6
- require 'elastic_apm/error'
8
+
9
+ require 'elastic_apm/central_config'
7
10
  require 'elastic_apm/transport/base'
8
- require 'elastic_apm/spies'
9
11
  require 'elastic_apm/metrics'
10
12
 
13
+ require 'elastic_apm/spies'
14
+
11
15
  module ElasticAPM
12
16
  # rubocop:disable Metrics/ClassLength
13
17
  # @api private
@@ -57,6 +61,7 @@ module ElasticAPM
57
61
  !!@instance
58
62
  end
59
63
 
64
+ # rubocop:disable Metrics/MethodLength
60
65
  def initialize(config)
61
66
  @config = config
62
67
 
@@ -64,6 +69,7 @@ module ElasticAPM
64
69
  @context_builder = ContextBuilder.new(config)
65
70
  @error_builder = ErrorBuilder.new(self)
66
71
 
72
+ @central_config = CentralConfig.new(config)
67
73
  @transport = Transport::Base.new(config)
68
74
  @instrumenter = Instrumenter.new(
69
75
  config,
@@ -71,9 +77,18 @@ module ElasticAPM
71
77
  ) { |event| enqueue event }
72
78
  @metrics = Metrics.new(config) { |event| enqueue event }
73
79
  end
80
+ # rubocop:enable Metrics/MethodLength
74
81
 
75
- attr_reader :config, :transport, :instrumenter,
76
- :stacktrace_builder, :context_builder, :error_builder, :metrics
82
+ attr_reader(
83
+ :central_config,
84
+ :config,
85
+ :context_builder,
86
+ :error_builder,
87
+ :instrumenter,
88
+ :metrics,
89
+ :stacktrace_builder,
90
+ :transport
91
+ )
77
92
 
78
93
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
79
94
  def start
@@ -81,6 +96,7 @@ module ElasticAPM
81
96
  info '[%s] Starting agent, reporting to %s', VERSION, config.server_url
82
97
  end
83
98
 
99
+ central_config.start
84
100
  transport.start
85
101
  instrumenter.start
86
102
  metrics.start
@@ -97,6 +113,7 @@ module ElasticAPM
97
113
  def stop
98
114
  debug 'Stopping agent'
99
115
 
116
+ central_config.stop
100
117
  metrics.stop
101
118
  instrumenter.stop
102
119
  transport.stop
@@ -142,9 +159,12 @@ module ElasticAPM
142
159
  instrumenter.end_transaction(result)
143
160
  end
144
161
 
162
+ # rubocop:disable Metrics/ParameterLists
145
163
  def start_span(
146
164
  name = nil,
147
165
  type = nil,
166
+ subtype: nil,
167
+ action: nil,
148
168
  backtrace: nil,
149
169
  context: nil,
150
170
  trace_context: nil
@@ -152,11 +172,14 @@ module ElasticAPM
152
172
  instrumenter.start_span(
153
173
  name,
154
174
  type,
175
+ subtype: subtype,
176
+ action: action,
155
177
  backtrace: backtrace,
156
178
  context: context,
157
179
  trace_context: trace_context
158
180
  )
159
181
  end
182
+ # rubocop:enable Metrics/ParameterLists
160
183
 
161
184
  def end_span
162
185
  instrumenter.end_span
@@ -189,6 +212,7 @@ module ElasticAPM
189
212
  handled: handled
190
213
  )
191
214
  enqueue error
215
+ error.id
192
216
  end
193
217
 
194
218
  def report_message(message, context: nil, backtrace: nil, **attrs)
@@ -199,6 +223,7 @@ module ElasticAPM
199
223
  **attrs
200
224
  )
201
225
  enqueue error
226
+ error.id
202
227
  end
203
228
 
204
229
  # filters
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/central_config/cache_control'
4
+
5
+ module ElasticAPM
6
+ # @api private
7
+ class CentralConfig
8
+ include Logging
9
+
10
+ # @api private
11
+ class ResponseError < InternalError
12
+ def initialize(response)
13
+ @response = response
14
+ end
15
+
16
+ attr_reader :response
17
+ end
18
+ class ClientError < ResponseError; end
19
+ class ServerError < ResponseError; end
20
+
21
+ DEFAULT_MAX_AGE = 300
22
+
23
+ def initialize(config)
24
+ @config = config
25
+ @modified_options = {}
26
+ @service_info = {
27
+ 'service.name': config.service_name,
28
+ 'service.environment': config.environment
29
+ }.to_json
30
+ end
31
+
32
+ attr_reader :config
33
+ attr_reader :scheduled_task, :promise # for specs
34
+
35
+ def start
36
+ return unless config.central_config?
37
+
38
+ fetch_and_apply_config
39
+ end
40
+
41
+ def fetch_and_apply_config
42
+ @promise =
43
+ Concurrent::Promise
44
+ .execute(&method(:fetch_config))
45
+ .on_success(&method(:handle_success))
46
+ .rescue(&method(:handle_error))
47
+ end
48
+
49
+ def stop
50
+ @scheduled_task&.cancel
51
+ end
52
+
53
+ # rubocop:disable Metrics/MethodLength
54
+ def fetch_config
55
+ resp = perform_request
56
+
57
+ case resp.status
58
+ when 200..299
59
+ resp
60
+ when 300..399
61
+ resp
62
+ when 400..499
63
+ raise ClientError, resp
64
+ when 500..599
65
+ raise ServerError, resp
66
+ end
67
+ end
68
+ # rubocop:enable Metrics/MethodLength
69
+
70
+ def assign(update)
71
+ # For each updated option, store the original value,
72
+ # unless already stored
73
+ update.each_key do |key|
74
+ @modified_options[key] ||= config.get(key.to_sym)&.value
75
+ end
76
+
77
+ # If the new update doesn't set a previously modified option,
78
+ # revert it to the original
79
+ @modified_options.each_key do |key|
80
+ next if update.key?(key)
81
+ update[key] = @modified_options.delete(key)
82
+ end
83
+
84
+ config.assign(update)
85
+ end
86
+
87
+ private
88
+
89
+ # rubocop:disable Metrics/MethodLength
90
+ def handle_success(resp)
91
+ if resp.status == 304
92
+ info 'Received 304 Not Modified'
93
+ else
94
+ update = JSON.parse(resp.body.to_s)
95
+ assign(update)
96
+
97
+ info 'Updated config from Kibana'
98
+ end
99
+
100
+ schedule_next_fetch(resp)
101
+
102
+ true
103
+ rescue Exception => e
104
+ error 'Failed to apply remote config, %s', e.inspect
105
+ debug { e.backtrace.join('\n') }
106
+ end
107
+ # rubocop:enable Metrics/MethodLength
108
+
109
+ def handle_error(error)
110
+ error(
111
+ 'Failed fetching config: %s, trying again in %d seconds',
112
+ error.response.body, DEFAULT_MAX_AGE
113
+ )
114
+
115
+ assign({})
116
+
117
+ schedule_next_fetch(error.response)
118
+ end
119
+
120
+ def perform_request
121
+ Http.post(
122
+ config.server_url + '/agent/v1/config/',
123
+ body: @service_info,
124
+ headers: { etag: 1, content_type: 'application/json' }
125
+ )
126
+ end
127
+
128
+ def schedule_next_fetch(resp)
129
+ seconds =
130
+ if (cache_header = resp.headers['Cache-Control'])
131
+ CacheControl.new(cache_header).max_age
132
+ else
133
+ DEFAULT_MAX_AGE
134
+ end
135
+
136
+ @scheduled_task =
137
+ Concurrent::ScheduledTask
138
+ .execute(seconds, &method(:fetch_and_apply_config))
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class CentralConfig
5
+ # @api private
6
+ class CacheControl
7
+ def initialize(value)
8
+ @header = value
9
+ parse!(value)
10
+ end
11
+
12
+ attr_reader(
13
+ :must_revalidate,
14
+ :no_cache,
15
+ :no_store,
16
+ :no_transform,
17
+ :public,
18
+ :private,
19
+ :proxy_revalidate,
20
+ :max_age,
21
+ :s_maxage
22
+ )
23
+
24
+ private
25
+
26
+ def parse!(value)
27
+ value.split(',').each do |token|
28
+ k, v = token.split('=').map(&:strip)
29
+ instance_variable_set(:"@#{k.gsub('-', '_')}", v ? v.to_i : true)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,246 +5,119 @@ require 'yaml'
5
5
  require 'erb'
6
6
 
7
7
  require 'elastic_apm/util/prefixed_logger'
8
+
9
+ require 'elastic_apm/config/options'
8
10
  require 'elastic_apm/config/duration'
9
- require 'elastic_apm/config/size'
11
+ require 'elastic_apm/config/bytes'
12
+ require 'elastic_apm/config/regexp_list'
10
13
 
11
14
  module ElasticAPM
12
- class ConfigError < StandardError; end
13
-
14
15
  # rubocop:disable Metrics/ClassLength
15
16
  # @api private
16
17
  class Config
17
- DEFAULTS = {
18
- config_file: 'config/elastic_apm.yml',
19
-
20
- server_url: 'http://localhost:8200',
21
-
22
- active: true,
23
- api_buffer_size: 256,
24
- api_request_size: '750kb',
25
- api_request_time: '10s',
26
- capture_body: 'off',
27
- capture_headers: true,
28
- capture_env: true,
29
- current_user_email_method: :email,
30
- current_user_id_method: :id,
31
- current_user_username_method: :username,
32
- custom_key_filters: [],
33
- default_tags: {},
34
- disable_send: false,
35
- disable_start_message: false,
36
- disabled_spies: %w[json],
37
- environment: ENV['RAILS_ENV'] || ENV['RACK_ENV'],
38
- filter_exception_types: [],
39
- http_compression: true,
40
- ignore_url_patterns: [],
41
- instrument: true,
42
- instrumented_rake_tasks: [],
43
- log_level: Logger::INFO,
44
- log_path: nil,
45
- metrics_interval: '30s',
46
- pool_size: 1,
47
- source_lines_error_app_frames: 5,
48
- source_lines_error_library_frames: 0,
49
- source_lines_span_app_frames: 5,
50
- source_lines_span_library_frames: 0,
51
- span_frames_min_duration: '5ms',
52
- stack_trace_limit: 999_999,
53
- transaction_max_spans: 500,
54
- transaction_sample_rate: 1.0,
55
- verify_server_cert: true,
56
-
57
- view_paths: [],
58
- root_path: Dir.pwd
59
- }.freeze
60
-
61
- ENV_TO_KEY = {
62
- 'ELASTIC_APM_SERVER_URL' => 'server_url',
63
- 'ELASTIC_APM_SECRET_TOKEN' => 'secret_token',
64
-
65
- 'ELASTIC_APM_ACTIVE' => [:bool, 'active'],
66
- 'ELASTIC_APM_API_BUFFER_SIZE' => [:int, 'api_buffer_size'],
67
- 'ELASTIC_APM_API_REQUEST_SIZE' => [:int, 'api_request_size'],
68
- 'ELASTIC_APM_API_REQUEST_TIME' => 'api_request_time',
69
- 'ELASTIC_APM_CAPTURE_BODY' => 'capture_body',
70
- 'ELASTIC_APM_CAPTURE_HEADERS' => [:bool, 'capture_headers'],
71
- 'ELASTIC_APM_CAPTURE_ENV' => [:bool, 'capture_env'],
72
- 'ELASTIC_APM_CONFIG_FILE' => 'config_file',
73
- 'ELASTIC_APM_CUSTOM_KEY_FILTERS' => [:list, 'custom_key_filters'],
74
- 'ELASTIC_APM_DEFAULT_TAGS' => [:dict, 'default_tags'],
75
- 'ELASTIC_APM_DISABLED_SPIES' => [:list, 'disabled_spies'],
76
- 'ELASTIC_APM_DISABLE_SEND' => [:bool, 'disable_send'],
77
- 'ELASTIC_APM_DISABLE_START_MESSAGE' => [:bool, 'disable_start_message'],
78
- 'ELASTIC_APM_ENVIRONMENT' => 'environment',
79
- 'ELASTIC_APM_FRAMEWORK_NAME' => 'framework_name',
80
- 'ELASTIC_APM_FRAMEWORK_VERSION' => 'framework_version',
81
- 'ELASTIC_APM_HOSTNAME' => 'hostname',
82
- 'ELASTIC_APM_IGNORE_URL_PATTERNS' => [:list, 'ignore_url_patterns'],
83
- 'ELASTIC_APM_INSTRUMENT' => [:bool, 'instrument'],
84
- 'ELASTIC_APM_INSTRUMENTED_RAKE_TASKS' =>
85
- [:list, 'instrumented_rake_tasks'],
86
- 'ELASTIC_APM_LOG_LEVEL' => [:int, 'log_level'],
87
- 'ELASTIC_APM_LOG_PATH' => 'log_path',
88
- 'ELASTIC_APM_METRICS_INTERVAL' => 'metrics_interval',
89
- 'ELASTIC_APM_PROXY_ADDRESS' => 'proxy_address',
90
- 'ELASTIC_APM_PROXY_HEADERS' => [:dict, 'proxy_headers'],
91
- 'ELASTIC_APM_PROXY_PASSWORD' => 'proxy_password',
92
- 'ELASTIC_APM_PROXY_PORT' => [:int, 'proxy_port'],
93
- 'ELASTIC_APM_PROXY_USERNAME' => 'proxy_username',
94
- 'ELASTIC_APM_POOL_SIZE' => [:int, 'pool_size'],
95
- 'ELASTIC_APM_SERVER_CA_CERT' => 'server_ca_cert',
96
- 'ELASTIC_APM_SERVICE_NAME' => 'service_name',
97
- 'ELASTIC_APM_SERVICE_VERSION' => 'service_version',
98
- 'ELASTIC_APM_SOURCE_LINES_ERROR_APP_FRAMES' =>
99
- [:int, 'source_lines_error_app_frames'],
100
- 'ELASTIC_APM_SOURCE_LINES_ERROR_LIBRARY_FRAMES' =>
101
- [:int, 'source_lines_error_library_frames'],
102
- 'ELASTIC_APM_SOURCE_LINES_SPAN_APP_FRAMES' =>
103
- [:int, 'source_lines_span_app_frames'],
104
- 'ELASTIC_APM_SOURCE_LINES_SPAN_LIBRARY_FRAMES' =>
105
- [:int, 'source_lines_span_library_frames'],
106
- 'ELASTIC_APM_SPAN_FRAMES_MIN_DURATION' => 'span_frames_min_duration',
107
- 'ELASTIC_APM_STACK_TRACE_LIMIT' => [:int, 'stack_trace_limit'],
108
- 'ELASTIC_APM_TRANSACTION_MAX_SPANS' => [:int, 'transaction_max_spans'],
109
- 'ELASTIC_APM_TRANSACTION_SAMPLE_RATE' =>
110
- [:float, 'transaction_sample_rate'],
111
- 'ELASTIC_APM_VERIFY_SERVER_CERT' => [:bool, 'verify_server_cert']
112
- }.freeze
113
-
114
- DURATION_KEYS = %i[
115
- api_request_time
116
- span_frames_min_duration
117
- metrics_interval
18
+ extend Options
19
+
20
+ DEPRECATED_OPTIONS = %i[
21
+ compression_level=
22
+ compression_minimum_size=
23
+ debug_http=
24
+ debug_transactions=
25
+ flush_interval=
26
+ http_open_timeout=
27
+ http_read_timeout=
28
+ enabled_environments=
29
+ disable_environment_warning=
118
30
  ].freeze
119
- DURATION_DEFAULT_UNITS = { # default is 's'
120
- span_frames_min_duration: 'ms'
121
- }.freeze
122
31
 
123
- SIZE_KEYS = %i[api_request_size].freeze
124
- SIZE_DEFAULT_UNITS = { api_request_size: 'kb' }.freeze
32
+ # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
33
+ option :config_file, type: :string, default: 'config/elastic_apm.yml'
34
+ option :server_url, type: :string, default: 'http://localhost:8200'
35
+ option :secret_token, type: :string
36
+
37
+ option :active, type: :bool, default: true
38
+ option :api_buffer_size, type: :int, default: 256
39
+ option :api_request_size, type: :bytes, default: '750kb', converter: Bytes.new
40
+ option :api_request_time, type: :float, default: '10s', converter: Duration.new
41
+ option :capture_body, type: :string, default: 'off'
42
+ option :capture_headers, type: :bool, default: true
43
+ option :capture_env, type: :bool, default: true
44
+ option :central_config, type: :bool, default: true
45
+ option :current_user_email_method, type: :string, default: 'email'
46
+ option :current_user_id_method, type: :string, default: 'id'
47
+ option :current_user_username_method, type: :string, default: 'username'
48
+ option :custom_key_filters, type: :list, default: [], converter: RegexpList.new
49
+ option :default_tags, type: :dict, default: {}
50
+ option :disable_send, type: :bool, default: false
51
+ option :disable_start_message, type: :bool, default: false
52
+ option :disabled_spies, type: :list, default: %w[json]
53
+ option :environment, type: :string, default: ENV['RAILS_ENV'] || ENV['RACK_ENV']
54
+ option :framework_name, type: :string
55
+ option :framework_version, type: :string
56
+ option :filter_exception_types, type: :list, default: []
57
+ option :global_labels, type: :dict
58
+ option :hostname, type: :string
59
+ option :http_compression, type: :bool, default: true
60
+ option :ignore_url_patterns, type: :list, default: [], converter: RegexpList.new
61
+ option :instrument, type: :bool, default: true
62
+ option :instrumented_rake_tasks, type: :list, default: []
63
+ option :log_level, type: :int, default: Logger::INFO
64
+ option :log_path, type: :string
65
+ option :metrics_interval, type: :int, default: '30s', converter: Duration.new
66
+ option :pool_size, type: :int, default: 1
67
+ option :proxy_address, type: :string
68
+ option :proxy_headers, type: :dict
69
+ option :proxy_password, type: :string
70
+ option :proxy_port, type: :int
71
+ option :proxy_username, type: :string
72
+ option :server_ca_cert, type: :string
73
+ option :service_name, type: :string
74
+ option :service_version, type: :string
75
+ option :source_lines_error_app_frames, type: :int, default: 5
76
+ option :source_lines_error_library_frames, type: :int, default: 0
77
+ option :source_lines_span_app_frames, type: :int, default: 5
78
+ option :source_lines_span_library_frames, type: :int, default: 0
79
+ option :span_frames_min_duration, type: :float, default: '5ms', converter: Duration.new(default_unit: 'ms')
80
+ option :stack_trace_limit, type: :int, default: 999_999
81
+ option :transaction_max_spans, type: :int, default: 500
82
+ option :transaction_sample_rate, type: :float, default: 1.0
83
+ option :verify_server_cert, type: :bool, default: true
84
+ # rubocop:enable Metrics/LineLength, Layout/ExtraSpacing
125
85
 
86
+ # rubocop:disable Metrics/MethodLength
126
87
  def initialize(options = {})
127
- set_defaults
128
-
129
- set_from_args(options)
130
- set_from_config_file
131
- set_from_env
132
-
133
- normalize_durations
134
- normalize_sizes
88
+ @options = load_schema
135
89
 
136
- yield self if block_given?
137
-
138
- build_logger if logger.nil?
139
- end
90
+ custom_logger = options.delete(:logger)
140
91
 
141
- attr_accessor :config_file
142
-
143
- attr_accessor :server_url
144
- attr_accessor :secret_token
145
-
146
- attr_accessor :active
147
- attr_accessor :api_buffer_size
148
- attr_accessor :api_request_size
149
- attr_accessor :api_request_time
150
- attr_accessor :capture_env
151
- attr_accessor :capture_headers
152
- attr_accessor :current_user_email_method
153
- attr_accessor :current_user_id_method
154
- attr_accessor :current_user_method
155
- attr_accessor :current_user_username_method
156
- attr_accessor :default_tags
157
- attr_accessor :disable_send
158
- attr_accessor :disable_start_message
159
- attr_accessor :disabled_spies
160
- attr_accessor :environment
161
- attr_accessor :filter_exception_types
162
- attr_accessor :framework_name
163
- attr_accessor :framework_version
164
- attr_accessor :hostname
165
- attr_accessor :http_compression
166
- attr_accessor :instrument
167
- attr_accessor :instrumented_rake_tasks
168
- attr_accessor :log_level
169
- attr_accessor :log_path
170
- attr_accessor :logger
171
- attr_accessor :metrics_interval
172
- attr_accessor :pool_size
173
- attr_accessor :proxy_address
174
- attr_accessor :proxy_headers
175
- attr_accessor :proxy_password
176
- attr_accessor :proxy_port
177
- attr_accessor :proxy_username
178
- attr_accessor :server_ca_cert
179
- attr_accessor :service_name
180
- attr_accessor :service_version
181
- attr_accessor :source_lines_error_app_frames
182
- attr_accessor :source_lines_error_library_frames
183
- attr_accessor :source_lines_span_app_frames
184
- attr_accessor :source_lines_span_library_frames
185
- attr_accessor :stack_trace_limit
186
- attr_accessor :transaction_max_spans
187
- attr_accessor :transaction_sample_rate
188
- attr_accessor :verify_server_cert
189
-
190
- attr_reader :capture_body
191
- attr_reader :custom_key_filters
192
- attr_reader :ignore_url_patterns
193
- attr_reader :span_frames_min_duration
194
- attr_reader :span_frames_min_duration_us
195
-
196
- attr_writer :alert_logger
197
-
198
- attr_accessor :view_paths
199
- attr_accessor :root_path
200
-
201
- alias :active? :active
202
- alias :capture_body? :capture_body
203
- alias :capture_headers? :capture_headers
204
- alias :capture_env? :capture_env
205
- alias :disable_send? :disable_send
206
- alias :disable_start_message? :disable_start_message
207
- alias :http_compression? :http_compression
208
- alias :instrument? :instrument
209
- alias :verify_server_cert? :verify_server_cert
210
-
211
- def alert_logger
212
- @alert_logger ||= PrefixedLogger.new($stdout, prefix: Logging::PREFIX)
213
- end
92
+ assign(options)
214
93
 
215
- def app=(app)
216
- case app_type?(app)
217
- when :sinatra
218
- set_sinatra(app)
219
- when :rails
220
- set_rails(app)
221
- else
222
- self.service_name = 'ruby'
94
+ # Pick out config_file specifically as we need it now to load it,
95
+ # but still need the other env vars to have precedence
96
+ env = load_env
97
+ if (env_config_file = env.delete(:config_file))
98
+ self.config_file = env_config_file
223
99
  end
224
- end
225
100
 
226
- def app_type?(app)
227
- if defined?(Rails::Application) && app.is_a?(Rails::Application)
228
- return :rails
229
- end
101
+ assign(load_config_file)
102
+ assign(env)
230
103
 
231
- if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
232
- return :sinatra
233
- end
104
+ yield self if block_given?
234
105
 
235
- nil
236
- end
106
+ @logger = custom_logger || build_logger
237
107
 
238
- def use_ssl?
239
- server_url.start_with?('https')
108
+ @__view_paths = []
109
+ @__root_path = Dir.pwd
240
110
  end
111
+ # rubocop:enable Metrics/MethodLength
241
112
 
242
- def custom_key_filters=(filters)
243
- @custom_key_filters = Array(filters).map(&Regexp.method(:new))
244
- end
113
+ attr_accessor :__view_paths, :__root_path
114
+ attr_accessor :logger
115
+
116
+ attr_reader :options
245
117
 
246
- def ignore_url_patterns=(strings)
247
- @ignore_url_patterns = Array(strings).map(&Regexp.method(:new))
118
+ def assign(update)
119
+ return unless update
120
+ update.each { |key, value| send(:"#{key}=", value) }
248
121
  end
249
122
 
250
123
  # rubocop:disable Metrics/MethodLength
@@ -271,183 +144,135 @@ module ElasticAPM
271
144
  available_spies - disabled_spies
272
145
  end
273
146
 
274
- def span_frames_min_duration=(duration)
275
- @span_frames_min_duration = duration
276
- @span_frames_min_duration_us = duration * 1_000_000
277
- end
278
-
279
- def span_frames_min_duration?
280
- span_frames_min_duration != 0
281
- end
282
-
283
- DEPRECATED_OPTIONS = %i[
284
- compression_level=
285
- compression_minimum_size=
286
- debug_http=
287
- debug_transactions=
288
- flush_interval=
289
- http_open_timeout=
290
- http_read_timeout=
291
- enabled_environments=
292
- disable_environment_warning=
293
- ].freeze
294
-
295
- def respond_to_missing?(name)
296
- return true if DEPRECATED_OPTIONS.include? name
297
- return true if name.to_s.end_with?('=')
298
- false
299
- end
300
-
301
147
  def method_missing(name, *args)
302
- if DEPRECATED_OPTIONS.include?(name)
303
- alert_logger.warn "The option `#{name}' has been removed."
304
- return
305
- end
306
-
307
- if name.to_s.end_with?('=')
308
- raise ConfigError, "No such option `#{name.to_s.delete('=')}'"
309
- end
310
-
311
- super
148
+ return super unless DEPRECATED_OPTIONS.include?(name)
149
+ warn "The option `#{name}' has been removed."
312
150
  end
313
151
 
314
- def collect_metrics?
315
- metrics_interval > 0
152
+ def app=(app)
153
+ case app_type?(app)
154
+ when :sinatra
155
+ set_sinatra(app)
156
+ when :rails
157
+ set_rails(app)
158
+ else
159
+ self.service_name = 'ruby'
160
+ end
316
161
  end
317
162
 
318
163
  # rubocop:disable Metrics/MethodLength
319
164
  def capture_body=(value)
320
165
  if value =~ /(all|transactions|errors|off)/
321
- @capture_body = value
166
+ set(:capture_body, value)
322
167
  return
323
168
  end
324
169
 
325
170
  case value
326
171
  when true
327
- alert_logger.warn "Boolean value for option `capture_body' has " \
172
+ warn "Boolean value for option `capture_body' has " \
328
173
  "been deprecated. Setting to 'all'"
329
- @capture_body = 'all'
174
+ self.capture_body = 'all'
330
175
  when false
331
- alert_logger.warn "Boolean value for option `capture_body' has " \
176
+ warn "Boolean value for option `capture_body' has " \
332
177
  "been deprecated. Setting to 'off'"
333
- @capture_body = 'off'
178
+ self.capture_body = 'off'
334
179
  else
335
- default = DEFAULTS[:capture_body]
336
- alert_logger.warn "Unknown value `#{value}' for option "\
180
+ default = options[:capture_body].default
181
+ warn "Unknown value `#{value}' for option "\
337
182
  "`capture_body'. Defaulting to `#{default}'"
338
- @capture_body = default
183
+ self.capture_body = default
339
184
  end
340
185
  end
341
186
  # rubocop:enable Metrics/MethodLength
342
187
 
343
- private
188
+ def use_ssl?
189
+ server_url.start_with?('https')
190
+ end
344
191
 
345
- def assign(options)
346
- options.each do |key, value|
347
- send("#{key}=", value)
348
- end
192
+ def collect_metrics?
193
+ metrics_interval > 0
349
194
  end
350
195
 
351
- def set_defaults
352
- assign(DEFAULTS)
196
+ def span_frames_min_duration?
197
+ span_frames_min_duration != 0
353
198
  end
354
199
 
355
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
356
- # rubocop:disable Metrics/AbcSize
357
- def set_from_env
358
- ENV_TO_KEY.each do |env_key, key|
359
- next unless (value = ENV[env_key])
360
-
361
- type, key = key if key.is_a? Array
362
-
363
- value =
364
- case type
365
- when :int then value.to_i
366
- when :float then value.to_f
367
- when :bool then !%w[0 false].include?(value.strip.downcase)
368
- when :list then value.split(/[ ,]/)
369
- when :dict then Hash[value.split(/[&,]/).map { |kv| kv.split('=') }]
370
- else value
371
- end
372
-
373
- send("#{key}=", value)
374
- end
200
+ def span_frames_min_duration=(value)
201
+ super
202
+ @span_frames_min_duration_us = nil
375
203
  end
376
- # rubocop:enable Metrics/AbcSize
377
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
378
204
 
379
- def set_from_args(options)
380
- assign(options)
381
- rescue ConfigError => e
382
- alert_logger.warn format(
383
- 'Failed to configure from arguments: %s',
384
- e.message
385
- )
205
+ def span_frames_min_duration_us
206
+ @span_frames_min_duration_us ||= span_frames_min_duration * 1_000_000
207
+ end
208
+
209
+ def inspect
210
+ super.split.first + '>'
386
211
  end
387
212
 
388
- def set_from_config_file
213
+ private
214
+
215
+ def load_config_file
389
216
  return unless File.exist?(config_file)
390
- assign(YAML.safe_load(ERB.new(File.read(config_file)).result) || {})
391
- rescue ConfigError => e
392
- alert_logger.warn format(
393
- 'Failed to configure from config file: %s',
394
- e.message
395
- )
217
+
218
+ read = File.read(config_file)
219
+ evaled = ERB.new(read).result
220
+ YAML.safe_load(evaled)
221
+ end
222
+
223
+ def load_env
224
+ @options.values.each_with_object({}) do |option, opts|
225
+ next unless (value = ENV[option.env_key])
226
+ opts[option.key] = value
227
+ end
228
+ end
229
+
230
+ def build_logger
231
+ Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
232
+ logger.level = log_level
233
+ end
234
+ end
235
+
236
+ def app_type?(app)
237
+ if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
238
+ return :rails
239
+ end
240
+
241
+ if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
242
+ return :sinatra
243
+ end
244
+
245
+ nil
396
246
  end
397
247
 
398
248
  def set_sinatra(app)
399
249
  self.service_name = format_name(service_name || app.to_s)
400
250
  self.framework_name = framework_name || 'Sinatra'
401
251
  self.framework_version = framework_version || Sinatra::VERSION
402
- self.root_path = Dir.pwd
252
+ self.__root_path = Dir.pwd
403
253
  end
404
254
 
405
255
  def set_rails(app) # rubocop:disable Metrics/AbcSize
406
256
  self.service_name ||= format_name(service_name || rails_app_name(app))
407
257
  self.framework_name ||= 'Ruby on Rails'
408
- self.framework_version ||= Rails::VERSION::STRING
409
- self.logger ||= Rails.logger
258
+ self.framework_version ||= ::Rails::VERSION::STRING
259
+ self.logger ||= ::Rails.logger
410
260
 
411
- self.root_path = Rails.root.to_s
412
- self.view_paths = app.config.paths['app/views'].existent
261
+ self.__root_path = ::Rails.root.to_s
262
+ self.__view_paths = app.config.paths['app/views'].existent
413
263
  end
414
264
 
415
265
  def rails_app_name(app)
416
- if Rails::VERSION::MAJOR >= 6
266
+ if ::Rails::VERSION::MAJOR >= 6
417
267
  app.class.module_parent_name
418
268
  else
419
269
  app.class.parent_name
420
270
  end
421
271
  end
422
272
 
423
- def build_logger
424
- logger = Logger.new(log_path == '-' ? STDOUT : log_path)
425
- logger.level = log_level
426
-
427
- self.logger = logger
428
- end
429
-
430
273
  def format_name(str)
431
274
  str && str.gsub('::', '_')
432
275
  end
433
-
434
- def normalize_durations
435
- DURATION_KEYS.each do |key|
436
- value = send(key).to_s
437
- default_unit = DURATION_DEFAULT_UNITS.fetch(key, 's')
438
- duration = Duration.parse(value, default_unit: default_unit)
439
- send("#{key}=", duration.seconds)
440
- end
441
- end
442
-
443
- def normalize_sizes
444
- SIZE_KEYS.each do |key|
445
- value = send(key).to_s
446
- default_unit = SIZE_DEFAULT_UNITS.fetch(key, 'b')
447
- size = Size.parse(value, default_unit: default_unit)
448
- send("#{key}=", size.bytes)
449
- end
450
- end
451
276
  end
452
277
  # rubocop:enable Metrics/ClassLength
453
278
  end