atatus 1.0.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 (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +57 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-THIRD-PARTY +205 -0
  7. data/README.md +13 -0
  8. data/Rakefile +19 -0
  9. data/atatus.gemspec +36 -0
  10. data/atatus.yml +2 -0
  11. data/bench/.gitignore +2 -0
  12. data/bench/app.rb +53 -0
  13. data/bench/benchmark.rb +36 -0
  14. data/bench/report.rb +55 -0
  15. data/bench/rubyprof.rb +39 -0
  16. data/bench/stackprof.rb +23 -0
  17. data/bin/build_docs +5 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/bin/with_framework +7 -0
  21. data/lib/atatus.rb +325 -0
  22. data/lib/atatus/agent.rb +260 -0
  23. data/lib/atatus/central_config.rb +141 -0
  24. data/lib/atatus/central_config/cache_control.rb +34 -0
  25. data/lib/atatus/collector/base.rb +329 -0
  26. data/lib/atatus/collector/builder.rb +317 -0
  27. data/lib/atatus/collector/transport.rb +72 -0
  28. data/lib/atatus/config.rb +248 -0
  29. data/lib/atatus/config/bytes.rb +25 -0
  30. data/lib/atatus/config/duration.rb +23 -0
  31. data/lib/atatus/config/options.rb +134 -0
  32. data/lib/atatus/config/regexp_list.rb +13 -0
  33. data/lib/atatus/context.rb +33 -0
  34. data/lib/atatus/context/request.rb +11 -0
  35. data/lib/atatus/context/request/socket.rb +19 -0
  36. data/lib/atatus/context/request/url.rb +42 -0
  37. data/lib/atatus/context/response.rb +22 -0
  38. data/lib/atatus/context/user.rb +42 -0
  39. data/lib/atatus/context_builder.rb +97 -0
  40. data/lib/atatus/deprecations.rb +22 -0
  41. data/lib/atatus/error.rb +22 -0
  42. data/lib/atatus/error/exception.rb +46 -0
  43. data/lib/atatus/error/log.rb +24 -0
  44. data/lib/atatus/error_builder.rb +76 -0
  45. data/lib/atatus/instrumenter.rb +224 -0
  46. data/lib/atatus/internal_error.rb +6 -0
  47. data/lib/atatus/logging.rb +55 -0
  48. data/lib/atatus/metadata.rb +19 -0
  49. data/lib/atatus/metadata/process_info.rb +18 -0
  50. data/lib/atatus/metadata/service_info.rb +61 -0
  51. data/lib/atatus/metadata/system_info.rb +35 -0
  52. data/lib/atatus/metadata/system_info/container_info.rb +121 -0
  53. data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
  54. data/lib/atatus/metadata/system_info/os_info.rb +31 -0
  55. data/lib/atatus/metrics.rb +98 -0
  56. data/lib/atatus/metrics/cpu_mem.rb +240 -0
  57. data/lib/atatus/metrics/vm.rb +60 -0
  58. data/lib/atatus/metricset.rb +19 -0
  59. data/lib/atatus/middleware.rb +76 -0
  60. data/lib/atatus/naively_hashable.rb +21 -0
  61. data/lib/atatus/normalizers.rb +68 -0
  62. data/lib/atatus/normalizers/action_controller.rb +27 -0
  63. data/lib/atatus/normalizers/action_mailer.rb +26 -0
  64. data/lib/atatus/normalizers/action_view.rb +77 -0
  65. data/lib/atatus/normalizers/active_record.rb +45 -0
  66. data/lib/atatus/opentracing.rb +346 -0
  67. data/lib/atatus/rails.rb +61 -0
  68. data/lib/atatus/railtie.rb +30 -0
  69. data/lib/atatus/span.rb +125 -0
  70. data/lib/atatus/span/context.rb +40 -0
  71. data/lib/atatus/span_helpers.rb +44 -0
  72. data/lib/atatus/spies.rb +86 -0
  73. data/lib/atatus/spies/action_dispatch.rb +28 -0
  74. data/lib/atatus/spies/delayed_job.rb +68 -0
  75. data/lib/atatus/spies/elasticsearch.rb +36 -0
  76. data/lib/atatus/spies/faraday.rb +70 -0
  77. data/lib/atatus/spies/http.rb +44 -0
  78. data/lib/atatus/spies/json.rb +22 -0
  79. data/lib/atatus/spies/mongo.rb +87 -0
  80. data/lib/atatus/spies/net_http.rb +70 -0
  81. data/lib/atatus/spies/rake.rb +45 -0
  82. data/lib/atatus/spies/redis.rb +27 -0
  83. data/lib/atatus/spies/sequel.rb +47 -0
  84. data/lib/atatus/spies/sidekiq.rb +89 -0
  85. data/lib/atatus/spies/sinatra.rb +41 -0
  86. data/lib/atatus/spies/tilt.rb +27 -0
  87. data/lib/atatus/sql_summarizer.rb +35 -0
  88. data/lib/atatus/stacktrace.rb +16 -0
  89. data/lib/atatus/stacktrace/frame.rb +52 -0
  90. data/lib/atatus/stacktrace_builder.rb +104 -0
  91. data/lib/atatus/subscriber.rb +77 -0
  92. data/lib/atatus/trace_context.rb +85 -0
  93. data/lib/atatus/transaction.rb +100 -0
  94. data/lib/atatus/transport/base.rb +174 -0
  95. data/lib/atatus/transport/connection.rb +156 -0
  96. data/lib/atatus/transport/connection/http.rb +116 -0
  97. data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
  98. data/lib/atatus/transport/filters.rb +43 -0
  99. data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
  100. data/lib/atatus/transport/serializers.rb +93 -0
  101. data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
  102. data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
  103. data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
  104. data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
  105. data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
  106. data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
  107. data/lib/atatus/transport/worker.rb +73 -0
  108. data/lib/atatus/util.rb +42 -0
  109. data/lib/atatus/util/inflector.rb +93 -0
  110. data/lib/atatus/util/lru_cache.rb +48 -0
  111. data/lib/atatus/util/prefixed_logger.rb +18 -0
  112. data/lib/atatus/util/throttle.rb +35 -0
  113. data/lib/atatus/version.rb +5 -0
  114. data/vendor/.gitkeep +0 -0
  115. metadata +190 -0
@@ -0,0 +1,317 @@
1
+ require 'socket'
2
+ require 'atatus/metadata'
3
+
4
+ module Atatus
5
+ class Builder
6
+ AGENT_NAME = 'Ruby'
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @metadata = Metadata::SystemInfo.new(config)
11
+ end
12
+
13
+ attr_reader :config
14
+
15
+ def common()
16
+ common = {
17
+ appName: @config.app_name,
18
+ licenseKey: @config.license_key,
19
+ agent: {
20
+ name: AGENT_NAME,
21
+ version: VERSION
22
+ },
23
+ # tags: @config.default_tags,
24
+ uniqueHostname: Socket.gethostname,
25
+ hostname: @metadata.hostname,
26
+ hostId: @metadata.hwinfo.hostid
27
+ }
28
+ unless @metadata.container.nil?
29
+ common[:containerId] = @metadata.container[:id] if @metadata.container.key?(:id)
30
+ end
31
+ common
32
+ end
33
+
34
+ def txns(start_time, end_time, data)
35
+ payload = common()
36
+ payload[:startTime] = (start_time.to_f * 1000).to_i
37
+ payload[:endTime] = (end_time.to_f * 1000).to_i
38
+ payload[:transactions] = build_txns_obj(data)
39
+ payload
40
+ end
41
+
42
+ def traces(start_time, end_time, data)
43
+ payload = common()
44
+ payload[:startTime] = (start_time.to_f * 1000).to_i
45
+ payload[:endTime] = (end_time.to_f * 1000).to_i
46
+ payload[:traces] = build_traces_obj(data)
47
+ payload
48
+ end
49
+
50
+ def error_metrics(start_time, end_time, metrics_data, requests_data)
51
+ payload = common()
52
+ payload[:startTime] = (start_time.to_f * 1000).to_i
53
+ payload[:endTime] = (end_time.to_f * 1000).to_i
54
+ payload[:errorMetrics] = build_error_metrics_obj(metrics_data)
55
+ payload[:errorRequests] = build_error_requests_obj(requests_data)
56
+ payload
57
+ end
58
+
59
+ def hostinfo(start_time)
60
+ payload = common()
61
+ payload[:timestamp] = (start_time.to_f * 1000).to_i
62
+ payload[:environment] = build_hostinfo_obj()
63
+ payload
64
+ end
65
+
66
+ def errors(start_time, end_time, error_data)
67
+ payload = common()
68
+ payload[:errors] = build_errors_obj(error_data)
69
+ payload
70
+ end
71
+
72
+ def metrics(start_time, end_time, metric_data)
73
+ payload = common()
74
+ payload[:startTime] = (start_time.to_f * 1000).to_i
75
+ payload[:endTime] = (end_time.to_f * 1000).to_i
76
+ payload[:ruby] = metric_data
77
+ payload
78
+ end
79
+
80
+ private
81
+
82
+ def keyword_field(value)
83
+ Util.truncate(value)
84
+ end
85
+
86
+ def build_request(context)
87
+ return unless context
88
+ return unless context.request
89
+
90
+ request = {
91
+ accept: context.request.headers['Accept'],
92
+ 'accept-encoding': context.request.headers['Accept-Encoding'],
93
+ 'accept-language': context.request.headers['Accept-Language'],
94
+ host: context.request.url.hostname,
95
+ port: (context.request.url.port).to_i,
96
+ method: context.request.method,
97
+ userAgent: context.request.headers['User-Agent'],
98
+ path: context.request.url.pathname
99
+ }
100
+ unless context.response.nil?
101
+ unless context.response.status_code.nil?
102
+ request[:statusCode] = context.response.status_code
103
+ end
104
+ end
105
+ request
106
+ end
107
+
108
+ def build_error_requests_obj(requests_data)
109
+ error_requests = []
110
+ requests_data.each do |v|
111
+ error_request = {}
112
+ error_request[:name] = v['name']
113
+ error_request[:type] = @config.framework_name
114
+ error_request[:kind] = "Ruby"
115
+ error_request[:request] = build_request(v['context'])
116
+ error_requests << error_request
117
+ end
118
+ error_requests
119
+ end
120
+
121
+ def build_errors_obj(data)
122
+ errors = []
123
+ data.each do |v|
124
+ error = {}
125
+ error[:timestamp] = v.timestamp
126
+ unless v.transaction.nil?
127
+ error[:transaction] = v.transaction[:name] if v.transaction.key?(:name)
128
+ end
129
+ error[:request] = build_request(v.context)
130
+ error[:exceptions] = []
131
+ exception = {}
132
+ exception[:class] = v.exception.type
133
+ exception[:message] = v.exception.message
134
+ exception[:stacktrace] = []
135
+ unless v.exception.stacktrace.nil?
136
+ unless v.exception.stacktrace.frames.nil?
137
+ v.exception.stacktrace.frames.each do |f|
138
+ frame = {}
139
+ frame[:f] = f.filename
140
+ frame[:m] = f.function
141
+ frame[:ln] = f.lineno
142
+ if f.library_frame == false
143
+ frame[:inp] = true
144
+ unless f.context_line.nil?
145
+ frame[:code] = []
146
+
147
+ unless f.pre_context.nil?
148
+ psize = f.pre_context.size
149
+ lineno = 0
150
+ if f.lineno - psize >= 0
151
+ lineno = f.lineno - psize
152
+ end
153
+ f.pre_context.each do |c|
154
+ frame[:code].push([lineno.to_s, c])
155
+ lineno += 1
156
+ end
157
+ end
158
+
159
+ frame[:code].push([f.lineno.to_s, f.context_line])
160
+
161
+ unless f.post_context.nil?
162
+ psize = f.post_context.size
163
+ lineno = f.lineno + 1
164
+ f.post_context.each do |c|
165
+ frame[:code].push([lineno.to_s, c])
166
+ lineno += 1
167
+ end
168
+ end
169
+ end
170
+ end
171
+ exception[:stacktrace] << frame
172
+ end
173
+ end
174
+ end
175
+ error[:exceptions] << exception
176
+ errors << error
177
+ end
178
+ errors
179
+ end
180
+
181
+ def build_metric(name, value)
182
+ return unless name
183
+ return unless value
184
+
185
+ {
186
+ name: name,
187
+ type: value.type,
188
+ kind: value.kind,
189
+ durations: [value.count, Util.ms(value.min), Util.ms(value.max), Util.ms(value.total)]
190
+ }
191
+ end
192
+
193
+ def build_txns_obj(data)
194
+ txns = []
195
+ data.each do |t, v|
196
+ txn = build_metric(t, v)
197
+ txn[:traces] = []
198
+ v.spans.each do |l, u|
199
+ txn[:traces] << build_metric(l, u)
200
+ end
201
+
202
+ txns << txn
203
+ end
204
+ txns
205
+ end
206
+
207
+ def build_traces_obj(data)
208
+ traces = []
209
+ data.each do |txn|
210
+ trace = {}
211
+ trace[:name] = txn.name
212
+ trace[:type] = @config.framework_name
213
+ trace[:kind] = "Ruby"
214
+ trace[:start] = txn.timestamp
215
+ trace[:duration] = Util.ms(txn.duration)
216
+ trace[:request] = build_request(txn.context)
217
+ trace[:entries] = []
218
+ trace[:funcs] = []
219
+ i = 0
220
+
221
+ unless txn.spans.nil?
222
+ unless txn.spans.empty?
223
+ txn.spans.each do |span|
224
+ entry = {}
225
+ entry[:lv] = 1
226
+ if span.timestamp >= txn.timestamp
227
+ entry[:so] = Util.ms(span.timestamp - txn.timestamp)
228
+ else
229
+ entry[:so] = 0
230
+ end
231
+ entry[:du] = Util.ms(span.duration)
232
+ entry[:ly] = {}
233
+ entry[:ly][:name] = span.name
234
+ entry[:ly][:type] = Atatus::Collector::Layer.span_type(span.subtype)
235
+ entry[:ly][:kind] = Atatus::Collector::Layer.span_kind(span.type)
236
+ unless span.context.nil?
237
+ unless span.context.db.nil?
238
+ unless span.context.db.statement.nil?
239
+ entry[:dt] = {}
240
+ entry[:dt][:query] = span.context.db.statement
241
+ end
242
+ end
243
+ end
244
+ trace[:entries] << entry
245
+ func_index = trace[:funcs].index(span.name)
246
+ if func_index.nil?
247
+ trace[:funcs] << span.name
248
+ func_index = i
249
+ i = i + 1
250
+ end
251
+ entry[:i] = func_index
252
+ end
253
+ end
254
+ end
255
+
256
+ traces << trace
257
+ end
258
+ traces
259
+ end
260
+
261
+ def build_error_metrics_obj(metrics_data)
262
+ error_metrics = []
263
+ metrics_data.each do |t, v|
264
+ error_metric = {}
265
+ error_metric[:name] = t
266
+ error_metric[:type] = @config.framework_name
267
+ error_metric[:kind] = "Ruby"
268
+ error_metric[:statusCodes] = v
269
+ error_metrics << error_metric
270
+ end
271
+ error_metrics
272
+ end
273
+
274
+ def build_hostinfo_obj()
275
+ environment = {}
276
+ environment[:gems] = get_gem_list
277
+ environment[:host] = build_hostinfo_env_host()
278
+ environment[:settings] = build_hostinfo_env_settings()
279
+ environment
280
+ end
281
+
282
+ def build_hostinfo_env_host()
283
+ host_info = {
284
+ cpu: [@metadata.hwinfo.cpuinfo],
285
+ ram: @metadata.hwinfo.meminfo,
286
+ hostname: @metadata.hostname,
287
+ uniqueHostname: Socket.gethostname,
288
+ bootId: @metadata.hwinfo.hostid,
289
+ hostId: @metadata.hwinfo.hostid,
290
+ os: @metadata.osinfo.os,
291
+ kernel: @metadata.osinfo.kernel
292
+ }
293
+ unless @metadata.container.nil?
294
+ host_info[:containerId] = @metadata.container[:id] if @metadata.container.key?(:id)
295
+ end
296
+ host_info
297
+ end
298
+
299
+ def build_hostinfo_env_settings()
300
+ {
301
+ agentVersion: VERSION,
302
+ appName: @config.app_name,
303
+ framework: @config.framework_name,
304
+ frameworkVersion: @config.framework_version,
305
+ logLevel: @config.log_level,
306
+ ruby: RUBY_VERSION
307
+ }
308
+ end
309
+
310
+ def get_gem_list()
311
+ Bundler.rubygems.all_specs.map {|spec| [spec.name, spec.version.to_s]}.sort.to_h
312
+ rescue => e
313
+ # logger.warn("Couldn't fetch Gem information: #{e.message}")
314
+ {}
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+ require 'thread'
3
+ require 'net/http'
4
+ require 'atatus/collector/builder'
5
+ require 'json'
6
+
7
+ module Atatus
8
+ class BaseTransport
9
+ # include Logging
10
+
11
+ TXN_ENDPOINT = '/track/apm/txn'.freeze
12
+ TRACE_ENDPOINT = '/track/apm/trace'.freeze
13
+ HOSTINFO_ENDPOINT = "/track/apm/hostinfo".freeze
14
+ ERROR_ENDPOINT = "/track/apm/error".freeze
15
+ ERROR_METRIC_ENDPOINT = "/track/apm/error_metric".freeze
16
+ METRIC_ENDPOINT = "/track/apm/metric".freeze
17
+
18
+ def initialize(config, hostname, port)
19
+ @config = config
20
+ @hostname = hostname
21
+ @port = port
22
+
23
+ @builder = Atatus::Builder.new(config)
24
+
25
+ @headers = {}
26
+ @headers['Content-Type'] = "application/json"
27
+ end
28
+
29
+ def txn(start_time, end_time, data)
30
+ payload = @builder.txns(start_time, end_time, data)
31
+ post(TXN_ENDPOINT, payload)
32
+ end
33
+
34
+ def trace(start_time, end_time, data)
35
+ payload = @builder.traces(start_time, end_time, data)
36
+ post(TRACE_ENDPOINT, payload)
37
+ end
38
+
39
+ def error_metrics(start_time, end_time, metrics_data, requests_data)
40
+ payload = @builder.error_metrics(start_time, end_time, metrics_data, requests_data)
41
+ post(ERROR_METRIC_ENDPOINT, payload)
42
+ end
43
+
44
+ def hostinfo(start_time)
45
+ payload = @builder.hostinfo(start_time)
46
+ post(HOSTINFO_ENDPOINT, payload)
47
+ end
48
+
49
+ def errors(start_time, end_time, error_data)
50
+ payload = @builder.errors(start_time, end_time, error_data)
51
+ post(ERROR_ENDPOINT, payload)
52
+ end
53
+
54
+ def metrics(start_time, end_time, metric_data)
55
+ payload = @builder.metrics(start_time, end_time, metric_data)
56
+ post(METRIC_ENDPOINT, payload)
57
+ end
58
+
59
+ private
60
+
61
+ def post(endpoint, data)
62
+ request = Net::HTTP::Post.new(endpoint, @headers)
63
+ request.body = ::JSON.dump(data)
64
+ http = Net::HTTP.new(@hostname, @port)
65
+ http.use_ssl = true
66
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
67
+ response = http.start { |http| http.request(request) }
68
+ rescue StandardError => e
69
+ puts "Sending to Atatus backend failed: ", e.inspect, response # To fix
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'yaml'
5
+ require 'erb'
6
+
7
+ require 'atatus/util/prefixed_logger'
8
+
9
+ require 'atatus/config/options'
10
+ require 'atatus/config/duration'
11
+ require 'atatus/config/bytes'
12
+ require 'atatus/config/regexp_list'
13
+
14
+ module Atatus
15
+ # rubocop:disable Metrics/ClassLength
16
+ # @api private
17
+ class Config
18
+ extend Options
19
+
20
+ DEPRECATED_OPTIONS = %i[].freeze
21
+
22
+ # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
23
+ option :app_name, type: :string
24
+ option :license_key, type: :string
25
+ option :trace_threshold, type: :int, default: 2000
26
+ option :config_file, type: :string, default: 'config/atatus.yml'
27
+ option :server_url, type: :string, default: 'http://localhost:8200'
28
+ option :secret_token, type: :string
29
+
30
+ option :active, type: :bool, default: true
31
+ option :api_buffer_size, type: :int, default: 256
32
+ option :api_request_size, type: :bytes, default: '750kb', converter: Bytes.new
33
+ option :api_request_time, type: :float, default: '10s', converter: Duration.new
34
+ option :capture_body, type: :string, default: 'off'
35
+ option :capture_headers, type: :bool, default: true
36
+ option :capture_env, type: :bool, default: true
37
+ option :central_config, type: :bool, default: true
38
+ option :current_user_email_method, type: :string, default: 'email'
39
+ option :current_user_id_method, type: :string, default: 'id'
40
+ option :current_user_username_method, type: :string, default: 'username'
41
+ option :custom_key_filters, type: :list, default: [], converter: RegexpList.new
42
+ option :default_tags, type: :dict, default: {}
43
+ option :default_labels, type: :dict, default: {}
44
+ option :disable_send, type: :bool, default: false
45
+ option :disable_start_message, type: :bool, default: false
46
+ option :disabled_instrumentations, type: :list, default: %w[json]
47
+ option :disabled_spies, type: :list, default: []
48
+ option :environment, type: :string, default: ENV['RAILS_ENV'] || ENV['RACK_ENV']
49
+ option :framework_name, type: :string
50
+ option :framework_version, type: :string
51
+ option :filter_exception_types, type: :list, default: []
52
+ option :global_labels, type: :dict
53
+ option :hostname, type: :string
54
+ option :http_compression, type: :bool, default: true
55
+ option :ignore_url_patterns, type: :list, default: [], converter: RegexpList.new
56
+ option :instrument, type: :bool, default: true
57
+ option :instrumented_rake_tasks, type: :list, default: []
58
+ option :log_level, type: :int, default: Logger::INFO
59
+ option :log_path, type: :string, default: '-'
60
+ option :metrics_interval, type: :int, default: '30s', converter: Duration.new
61
+ option :pool_size, type: :int, default: 1
62
+ option :proxy_address, type: :string
63
+ option :proxy_headers, type: :dict
64
+ option :proxy_password, type: :string
65
+ option :proxy_port, type: :int
66
+ option :proxy_username, type: :string
67
+ option :server_ca_cert, type: :string
68
+ option :service_name, type: :string
69
+ option :service_version, type: :string
70
+ option :source_lines_error_app_frames, type: :int, default: 5
71
+ option :source_lines_error_library_frames, type: :int, default: 0
72
+ option :source_lines_span_app_frames, type: :int, default: 5
73
+ option :source_lines_span_library_frames, type: :int, default: 0
74
+ option :span_frames_min_duration, type: :float, default: '5ms', converter: Duration.new(default_unit: 'ms')
75
+ option :stack_trace_limit, type: :int, default: 999_999
76
+ option :transaction_max_spans, type: :int, default: 500
77
+ option :transaction_sample_rate, type: :float, default: 1.0
78
+ option :verify_server_cert, type: :bool, default: true
79
+ # rubocop:enable Metrics/LineLength, Layout/ExtraSpacing
80
+
81
+ # rubocop:disable Metrics/MethodLength
82
+ def initialize(options = {})
83
+ @options = load_schema
84
+
85
+ custom_logger = options.delete(:logger)
86
+
87
+ assign(options)
88
+
89
+ # Pick out config_file specifically as we need it now to load it,
90
+ # but still need the other env vars to have precedence
91
+ env = load_env
92
+ if (env_config_file = env.delete(:config_file))
93
+ self.config_file = env_config_file
94
+ end
95
+
96
+ assign(load_config_file)
97
+ assign(env)
98
+
99
+ yield self if block_given?
100
+
101
+ @logger = custom_logger || build_logger
102
+
103
+ @__view_paths = []
104
+ @__root_path = Dir.pwd
105
+ end
106
+ # rubocop:enable Metrics/MethodLength
107
+
108
+ attr_accessor :__view_paths, :__root_path
109
+ attr_accessor :logger
110
+
111
+ attr_reader :options
112
+
113
+ def assign(update)
114
+ return unless update
115
+ update.each { |key, value| send(:"#{key}=", value) }
116
+ end
117
+
118
+ # rubocop:disable Metrics/MethodLength
119
+ def available_instrumentations
120
+ %w[
121
+ delayed_job
122
+ elasticsearch
123
+ faraday
124
+ http
125
+ json
126
+ mongo
127
+ net_http
128
+ redis
129
+ sequel
130
+ sidekiq
131
+ sinatra
132
+ tilt
133
+ rake
134
+ ]
135
+ end
136
+ # rubocop:enable Metrics/MethodLength
137
+
138
+ def enabled_instrumentations
139
+ available_instrumentations - disabled_instrumentations
140
+ end
141
+
142
+ def method_missing(name, *args)
143
+ return super unless DEPRECATED_OPTIONS.include?(name)
144
+ warn "The option `#{name}' has been removed."
145
+ end
146
+
147
+ def app=(app)
148
+ case app_type?(app)
149
+ when :sinatra
150
+ set_sinatra(app)
151
+ when :rails
152
+ set_rails(app)
153
+ else
154
+ self.service_name = 'ruby'
155
+ end
156
+ end
157
+
158
+ def use_ssl?
159
+ server_url.start_with?('https')
160
+ end
161
+
162
+ def collect_metrics?
163
+ metrics_interval > 0
164
+ end
165
+
166
+ def span_frames_min_duration?
167
+ span_frames_min_duration != 0
168
+ end
169
+
170
+ def span_frames_min_duration=(value)
171
+ super
172
+ @span_frames_min_duration_us = nil
173
+ end
174
+
175
+ def span_frames_min_duration_us
176
+ @span_frames_min_duration_us ||= span_frames_min_duration * 1_000_000
177
+ end
178
+
179
+ def inspect
180
+ super.split.first + '>'
181
+ end
182
+
183
+ private
184
+
185
+ def load_config_file
186
+ return unless File.exist?(config_file)
187
+
188
+ read = File.read(config_file)
189
+ evaled = ERB.new(read).result
190
+ YAML.safe_load(evaled)
191
+ end
192
+
193
+ def load_env
194
+ @options.values.each_with_object({}) do |option, opts|
195
+ next unless (value = ENV[option.env_key])
196
+ opts[option.key] = value
197
+ end
198
+ end
199
+
200
+ def build_logger
201
+ Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
202
+ logger.level = log_level
203
+ end
204
+ end
205
+
206
+ def app_type?(app)
207
+ if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
208
+ return :rails
209
+ end
210
+
211
+ if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
212
+ return :sinatra
213
+ end
214
+
215
+ nil
216
+ end
217
+
218
+ def set_sinatra(app)
219
+ self.service_name = format_name(service_name || app.to_s)
220
+ self.framework_name = framework_name || 'Sinatra'
221
+ self.framework_version = framework_version || Sinatra::VERSION
222
+ self.__root_path = Dir.pwd
223
+ end
224
+
225
+ def set_rails(app) # rubocop:disable Metrics/AbcSize
226
+ self.service_name ||= format_name(service_name || rails_app_name(app))
227
+ self.framework_name ||= 'Rails'
228
+ self.framework_version ||= ::Rails::VERSION::STRING
229
+ self.logger ||= ::Rails.logger
230
+
231
+ self.__root_path = ::Rails.root.to_s
232
+ self.__view_paths = app.config.paths['app/views'].existent
233
+ end
234
+
235
+ def rails_app_name(app)
236
+ if ::Rails::VERSION::MAJOR >= 6
237
+ app.class.module_parent_name
238
+ else
239
+ app.class.parent_name
240
+ end
241
+ end
242
+
243
+ def format_name(str)
244
+ str && str.gsub('::', '_')
245
+ end
246
+ end
247
+ # rubocop:enable Metrics/ClassLength
248
+ end