hoss-agent 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/Bug_report.md +40 -0
  3. data/.github/ISSUE_TEMPLATE/Feature_request.md +17 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +60 -0
  5. data/.gitignore +27 -0
  6. data/.rspec +2 -0
  7. data/Dockerfile +43 -0
  8. data/Gemfile +105 -0
  9. data/LICENSE +201 -0
  10. data/hoss-agent.gemspec +42 -0
  11. data/lib/hoss-agent.rb +210 -0
  12. data/lib/hoss.rb +21 -0
  13. data/lib/hoss/agent.rb +235 -0
  14. data/lib/hoss/central_config.rb +184 -0
  15. data/lib/hoss/central_config/cache_control.rb +51 -0
  16. data/lib/hoss/child_durations.rb +64 -0
  17. data/lib/hoss/config.rb +315 -0
  18. data/lib/hoss/config/bytes.rb +42 -0
  19. data/lib/hoss/config/duration.rb +40 -0
  20. data/lib/hoss/config/options.rb +154 -0
  21. data/lib/hoss/config/regexp_list.rb +30 -0
  22. data/lib/hoss/config/wildcard_pattern_list.rb +54 -0
  23. data/lib/hoss/context.rb +64 -0
  24. data/lib/hoss/context/request.rb +28 -0
  25. data/lib/hoss/context/request/socket.rb +36 -0
  26. data/lib/hoss/context/request/url.rb +59 -0
  27. data/lib/hoss/context/response.rb +47 -0
  28. data/lib/hoss/context/user.rb +59 -0
  29. data/lib/hoss/context_builder.rb +112 -0
  30. data/lib/hoss/deprecations.rb +39 -0
  31. data/lib/hoss/error.rb +49 -0
  32. data/lib/hoss/error/exception.rb +70 -0
  33. data/lib/hoss/error/log.rb +41 -0
  34. data/lib/hoss/error_builder.rb +90 -0
  35. data/lib/hoss/event.rb +131 -0
  36. data/lib/hoss/instrumenter.rb +107 -0
  37. data/lib/hoss/internal_error.rb +23 -0
  38. data/lib/hoss/logging.rb +70 -0
  39. data/lib/hoss/metadata.rb +36 -0
  40. data/lib/hoss/metadata/process_info.rb +35 -0
  41. data/lib/hoss/metadata/service_info.rb +76 -0
  42. data/lib/hoss/metadata/system_info.rb +47 -0
  43. data/lib/hoss/metadata/system_info/container_info.rb +136 -0
  44. data/lib/hoss/naively_hashable.rb +38 -0
  45. data/lib/hoss/rails.rb +68 -0
  46. data/lib/hoss/railtie.rb +42 -0
  47. data/lib/hoss/report.rb +9 -0
  48. data/lib/hoss/sinatra.rb +53 -0
  49. data/lib/hoss/spies.rb +104 -0
  50. data/lib/hoss/spies/faraday.rb +106 -0
  51. data/lib/hoss/spies/http.rb +86 -0
  52. data/lib/hoss/spies/net_http.rb +101 -0
  53. data/lib/hoss/stacktrace.rb +33 -0
  54. data/lib/hoss/stacktrace/frame.rb +66 -0
  55. data/lib/hoss/stacktrace_builder.rb +124 -0
  56. data/lib/hoss/transport/base.rb +191 -0
  57. data/lib/hoss/transport/connection.rb +55 -0
  58. data/lib/hoss/transport/connection/http.rb +139 -0
  59. data/lib/hoss/transport/connection/proxy_pipe.rb +94 -0
  60. data/lib/hoss/transport/filters.rb +60 -0
  61. data/lib/hoss/transport/filters/hash_sanitizer.rb +77 -0
  62. data/lib/hoss/transport/filters/secrets_filter.rb +48 -0
  63. data/lib/hoss/transport/headers.rb +74 -0
  64. data/lib/hoss/transport/serializers.rb +113 -0
  65. data/lib/hoss/transport/serializers/context_serializer.rb +112 -0
  66. data/lib/hoss/transport/serializers/error_serializer.rb +92 -0
  67. data/lib/hoss/transport/serializers/event_serializer.rb +73 -0
  68. data/lib/hoss/transport/serializers/metadata_serializer.rb +92 -0
  69. data/lib/hoss/transport/serializers/report_serializer.rb +33 -0
  70. data/lib/hoss/transport/user_agent.rb +48 -0
  71. data/lib/hoss/transport/worker.rb +330 -0
  72. data/lib/hoss/util.rb +54 -0
  73. data/lib/hoss/util/inflector.rb +110 -0
  74. data/lib/hoss/util/lru_cache.rb +65 -0
  75. data/lib/hoss/util/throttle.rb +52 -0
  76. data/lib/hoss/version.rb +22 -0
  77. metadata +147 -0
@@ -0,0 +1,184 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'hoss/central_config/cache_control'
21
+
22
+ module Hoss
23
+ # @api private
24
+ class CentralConfig
25
+ include Logging
26
+
27
+ # @api private
28
+ class ResponseError < InternalError
29
+ def initialize(response)
30
+ @response = response
31
+ end
32
+
33
+ attr_reader :response
34
+ end
35
+ class ClientError < ResponseError; end
36
+ class ServerError < ResponseError; end
37
+
38
+ def initialize(config)
39
+ @config = config
40
+ @modified_options = {}
41
+ @authorization = "Bearer #{@config.api_key}"
42
+ @http = Transport::Connection::Http.new(config)
43
+ @etag = 1
44
+ end
45
+
46
+ attr_reader :config
47
+ attr_reader :scheduled_task, :promise # for specs
48
+
49
+ def start
50
+ return unless config.central_config?
51
+
52
+ debug 'Starting CentralConfig'
53
+
54
+ fetch_and_apply_config
55
+ end
56
+
57
+ def stop
58
+ debug 'Stopping CentralConfig'
59
+
60
+ @scheduled_task&.cancel
61
+ end
62
+
63
+ def fetch_and_apply_config
64
+ @promise =
65
+ Concurrent::Promise
66
+ .execute(&method(:fetch_config))
67
+ .on_success(&method(:handle_success))
68
+ .rescue(&method(:handle_error))
69
+ end
70
+
71
+ def fetch_config
72
+ resp = perform_request
73
+ case resp.status
74
+ when 200..299
75
+ resp
76
+ when 300..399
77
+ resp
78
+ when 400..499
79
+ resp
80
+ # raise ClientError, resp
81
+ when 500..599
82
+ resp
83
+ # raise ServerError, resp
84
+ end
85
+ end
86
+
87
+ def assign(update)
88
+ # For each updated option, store the original value,
89
+ # unless already stored
90
+ update.each_key do |key|
91
+ @modified_options[key] ||= config.get(key.to_sym)&.value
92
+ end
93
+
94
+ # If the new update doesn't set a previously modified option,
95
+ # revert it to the original
96
+ @modified_options.each_key do |key|
97
+ next if update.key?(key)
98
+ update[key] = @modified_options.delete(key)
99
+ end
100
+ @config.replace_options(update)
101
+ end
102
+
103
+ def handle_forking!
104
+ stop
105
+ start
106
+ end
107
+
108
+ private
109
+
110
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
111
+ def handle_success(resp)
112
+ if (etag = resp.headers['Etag'])
113
+ @etag = etag
114
+ end
115
+
116
+ if resp.status == 304
117
+ info 'Received 304 Not Modified'
118
+ else
119
+ if resp.body && !resp.body.empty?
120
+ update = JSON.parse(resp.body.to_s)
121
+ assign(update['data']) unless update.nil? or update['data'].nil?
122
+ end
123
+
124
+ if update && update.any?
125
+ info 'Updated config'
126
+ debug 'Modified: %s', update.inspect
127
+ debug 'Modified original options: %s', @modified_options.inspect
128
+ end
129
+ end
130
+
131
+ schedule_next_fetch(resp)
132
+
133
+ true
134
+ rescue Exception => e
135
+ error 'Failed to apply remote config, %s', e.inspect
136
+ debug e.backtrace.join('\n')
137
+ end
138
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
139
+
140
+ def handle_error(error)
141
+ puts error.backtrace
142
+ # For tests, WebMock failures don't have real responses
143
+ response = error.response if error.respond_to?(:response)
144
+
145
+ debug(
146
+ 'Failed fetching config: %s, trying again in %d seconds',
147
+ response&.body, @config.remote_config_fetch_interval
148
+ )
149
+
150
+ assign({})
151
+
152
+ schedule_next_fetch(response)
153
+ end
154
+
155
+ def perform_request
156
+ body = '{"query":"query AgentConfig {\n agentConfig {\n accountApiConfiguration {\n uuid\n hostBlacklist\n sanitizedHeaders\n sanitizedQueryParams\n sanitizedBodyFields {\n type\n value\n }\n bodyCapture\n }\n apis {\n uuid\n name\n rootDomain\n hosts\n configuration(mergeWithAccountConfiguration: true) {\n uuid\n sanitizedHeaders\n sanitizedQueryParams\n bodyCapture\n sanitizedBodyFields {\n type\n value\n }\n }\n }\n accountRestrictions {\n ingressDisabled\n }\n }\n}","operationName":"AgentConfig"}'
157
+ @http.post(api_host, body: body, headers: headers)
158
+ end
159
+
160
+ def api_host
161
+ @api_host ||=
162
+ config.api_host +
163
+ '/api/graphql'
164
+ end
165
+
166
+ def headers
167
+ { 'Etag': @etag, 'Authorization': @authorization, 'HOSS-SKIP-INSTRUMENTATION': true }
168
+ end
169
+
170
+ def schedule_next_fetch(resp = nil)
171
+ headers = resp&.headers
172
+ seconds =
173
+ if headers && headers['Cache-Control']
174
+ CacheControl.new(headers['Cache-Control']).max_age
175
+ else
176
+ @config.remote_config_fetch_interval
177
+ end
178
+
179
+ @scheduled_task =
180
+ Concurrent::ScheduledTask
181
+ .execute(seconds, &method(:fetch_and_apply_config))
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,51 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module Hoss
21
+ class CentralConfig
22
+ # @api private
23
+ class CacheControl
24
+ def initialize(value)
25
+ @header = value
26
+ parse!(value)
27
+ end
28
+
29
+ attr_reader(
30
+ :must_revalidate,
31
+ :no_cache,
32
+ :no_store,
33
+ :no_transform,
34
+ :public,
35
+ :private,
36
+ :proxy_revalidate,
37
+ :max_age,
38
+ :s_maxage
39
+ )
40
+
41
+ private
42
+
43
+ def parse!(value)
44
+ value.split(',').each do |token|
45
+ k, v = token.split('=').map(&:strip)
46
+ instance_variable_set(:"@#{k.tr('-', '_')}", v ? v.to_i : true)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module Hoss
21
+ # @api private
22
+ module ChildDurations
23
+ # @api private
24
+ module Methods
25
+ def child_durations
26
+ @child_durations ||= Durations.new
27
+ end
28
+
29
+ def child_started
30
+ child_durations.start
31
+ end
32
+
33
+ def child_stopped
34
+ child_durations.stop
35
+ end
36
+ end
37
+
38
+ # @api private
39
+ class Durations
40
+ def initialize
41
+ @nesting_level = 0
42
+ @start = nil
43
+ @duration = 0
44
+ @mutex = Mutex.new
45
+ end
46
+
47
+ attr_reader :duration
48
+
49
+ def start
50
+ @mutex.synchronize do
51
+ @nesting_level += 1
52
+ @start = Util.micros if @nesting_level == 1
53
+ end
54
+ end
55
+
56
+ def stop
57
+ @mutex.synchronize do
58
+ @nesting_level -= 1
59
+ @duration = (Util.micros - @start) if @nesting_level == 0
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,315 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'hoss/config/options'
21
+ require 'hoss/config/duration'
22
+ require 'hoss/config/bytes'
23
+ require 'hoss/config/regexp_list'
24
+ require 'hoss/config/wildcard_pattern_list'
25
+
26
+ module Hoss
27
+ # @api private
28
+ class Config
29
+ extend Options
30
+
31
+ DEPRECATED_OPTIONS = %i[].freeze
32
+
33
+ # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
34
+ option :config_file, type: :string, default: 'config/hoss.yml'
35
+ option :api_host, type: :url, default: ENV['HOSS_API_HOST'] || 'https://app.hoss.com'
36
+ option :api_key, type: :string, default: ENV['HOSS_API_KEY']
37
+ option :ingress_host, type: :string, default: ENV['HOSS_INGRESS_HOST'] || 'https://ingress.hoss.com/v1'
38
+ option :max_queue_size, type: :int, default: 3000
39
+ option :batch_size, type: :int, default: ENV['HOSS_BATCH_SIZE'] || 512000
40
+ option :max_event_retries, type: :int, default: 100
41
+ option :remote_config_fetch_interval, type: :int, default: 300
42
+ option :agentConfig, type: :dict
43
+ option :debug, type: :boolean, default: ENV['HOSS_DEBUG']
44
+ option :disable_reporting, type: :boolean, default: ENV['HOSS_DISABLE_REPORTING'] == 'true'
45
+
46
+ option :central_config, type: :bool, default: true
47
+ option :capture_body, type: :string, default: 'off'
48
+ option :capture_headers, type: :bool, default: true
49
+ option :capture_env, type: :bool, default: true
50
+ option :current_user_email_method, type: :string, default: 'email'
51
+ option :current_user_id_method, type: :string, default: 'id'
52
+ option :current_user_username_method, type: :string, default: 'username'
53
+ option :custom_key_filters, type: :list, default: [], converter: RegexpList.new
54
+ option :default_labels, type: :dict, default: {}
55
+ option :disable_start_message, type: :bool, default: false
56
+ option :disable_instrumentations, type: :list, default: %w[]
57
+ option :enabled, type: :bool, default: true
58
+ option :environment, type: :string, default: ENV['RAILS_ENV'] || ENV['RACK_ENV']
59
+ option :framework_name, type: :string
60
+ option :framework_version, type: :string
61
+ option :filter_exception_types, type: :list, default: []
62
+ option :global_labels, type: :dict
63
+ option :hostname, type: :string
64
+ option :http_compression, type: :bool, default: true
65
+ option :instrument, type: :bool, default: true
66
+ option :log_level, type: :int, default: Logger::WARN
67
+ option :log_path, type: :string
68
+ option :pool_size, type: :int, default: 1
69
+ option :proxy_address, type: :string
70
+ option :proxy_headers, type: :dict
71
+ option :proxy_password, type: :string
72
+ option :proxy_port, type: :int
73
+ option :proxy_username, type: :string
74
+ option :recording, type: :bool, default: true
75
+ option :sanitize_field_names, type: :list, default: [], converter: WildcardPatternList.new
76
+ option :server_ca_cert, type: :string
77
+ option :service_name, type: :string
78
+ option :service_node_name, type: :string
79
+ option :service_version, type: :string
80
+ option :span_frames_min_duration, type: :float, default: '5ms', converter: Duration.new(default_unit: 'ms')
81
+ option :stack_trace_limit, type: :int, default: 999_999
82
+ option :verify_server_cert, type: :bool, default: true
83
+
84
+ # rubocop:enable Metrics/LineLength, Layout/ExtraSpacing
85
+ def initialize(options = {})
86
+ @options = load_schema
87
+
88
+ if options.is_a? String
89
+ assign(api_key: options)
90
+ else
91
+ assign(options)
92
+ end
93
+
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
99
+ end
100
+
101
+ assign(load_config_file)
102
+ assign(env)
103
+
104
+ if self.debug == true || self.debug == 'true'
105
+ assign(
106
+ log_level: Logger::DEBUG,
107
+ debug: true,
108
+ log_path: '-'
109
+ )
110
+ end
111
+
112
+ unless self.disable_reporting.nil?
113
+ self.disable_reporting = self.disable_reporting == 'true' || self.disable_reporting == true
114
+ end
115
+
116
+ yield self if block_given?
117
+
118
+ self.logger ||= build_logger
119
+
120
+ @__view_paths ||= []
121
+ @__root_path ||= Dir.pwd
122
+ end
123
+
124
+ attr_accessor :__view_paths, :__root_path
125
+ attr_accessor :logger
126
+
127
+ attr_reader :options
128
+
129
+ def assign(update)
130
+ return unless update
131
+ update.each { |key, value| send(:"#{key}=", value) }
132
+ end
133
+
134
+ def available_instrumentations
135
+ %w[
136
+ http
137
+ net_http
138
+ faraday
139
+ ]
140
+ end
141
+
142
+ def enabled_instrumentations
143
+ available_instrumentations - disable_instrumentations
144
+ end
145
+
146
+ def method_missing(name, *args)
147
+ return super unless DEPRECATED_OPTIONS.include?(name)
148
+ warn "The option `#{name}' has been removed."
149
+ end
150
+
151
+ def replace_options(new_options)
152
+ return if new_options.nil? || new_options.empty?
153
+ options_copy = @options.dup
154
+ new_options.each do |key, value|
155
+ options_copy.fetch(key.to_sym).set(value)
156
+ end
157
+ @options = options_copy
158
+ end
159
+
160
+ def app=(app)
161
+ case app_type?(app)
162
+ when :sinatra
163
+ set_sinatra(app)
164
+ when :rails
165
+ set_rails(app)
166
+ else
167
+ self.service_name = 'ruby'
168
+ end
169
+ end
170
+
171
+ def use_ssl?
172
+ api_host.start_with?('https')
173
+ end
174
+
175
+ def span_frames_min_duration?
176
+ span_frames_min_duration != 0
177
+ end
178
+
179
+ def span_frames_min_duration=(value)
180
+ super
181
+ @span_frames_min_duration_us = nil
182
+ end
183
+
184
+ def span_frames_min_duration_us
185
+ @span_frames_min_duration_us ||= span_frames_min_duration * 1_000_000
186
+ end
187
+
188
+ def ssl_context
189
+ return unless use_ssl?
190
+
191
+ @ssl_context ||=
192
+ OpenSSL::SSL::SSLContext.new.tap do |context|
193
+ if server_ca_cert
194
+ context.ca_file = server_ca_cert
195
+ else
196
+ context.cert_store =
197
+ OpenSSL::X509::Store.new.tap(&:set_default_paths)
198
+ end
199
+
200
+ context.verify_mode =
201
+ if verify_server_cert
202
+ OpenSSL::SSL::VERIFY_PEER
203
+ else
204
+ OpenSSL::SSL::VERIFY_NONE
205
+ end
206
+ end
207
+ end
208
+
209
+ def inspect
210
+ super.split.first + '>'
211
+ end
212
+
213
+ # Deprecations
214
+
215
+ def default_tags=(value)
216
+ warn '[DEPRECATED] The option default_tags has been renamed to ' \
217
+ 'default_labels.'
218
+ self.default_labels = value
219
+ end
220
+
221
+ def custom_key_filters=(value)
222
+ unless value == self.class.schema[:custom_key_filters][:default]
223
+ warn '[DEPRECATED] The option custom_key_filters is being removed. ' \
224
+ 'See sanitize_field_names for an alternative.'
225
+ end
226
+
227
+ set(:custom_key_filters, value)
228
+ end
229
+
230
+ def disabled_instrumentations
231
+ disable_instrumentations
232
+ end
233
+
234
+ def active
235
+ enabled
236
+ end
237
+ alias active? active
238
+
239
+ def disabled_instrumentations=(value)
240
+ warn '[DEPRECATED] The option disabled_instrumentations has been ' \
241
+ 'renamed to disable_instrumentations to align with other agents.'
242
+ self.disable_instrumentations = value
243
+ end
244
+
245
+ def active=(value)
246
+ warn '[DEPRECATED] The option active has been renamed to enabled ' \
247
+ 'to align with other agents and with the remote config.'
248
+ self.enabled = value
249
+ end
250
+
251
+ private
252
+
253
+ def load_config_file
254
+ filename = File.join(@__root_path || Dir.pwd, config_file)
255
+ return unless File.exist?(filename)
256
+ read = File.read(filename)
257
+ evaled = ERB.new(read).result
258
+ YAML.safe_load(evaled)
259
+ end
260
+
261
+ def load_env
262
+ @options.values.each_with_object({}) do |option, opts|
263
+ next unless (value = ENV[option.env_key])
264
+ opts[option.key] = value
265
+ end
266
+ end
267
+
268
+ def build_logger
269
+ Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
270
+ logger.level = log_level
271
+ end
272
+ end
273
+
274
+ def app_type?(app)
275
+ if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
276
+ return :rails
277
+ end
278
+
279
+ if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
280
+ return :sinatra
281
+ end
282
+
283
+ nil
284
+ end
285
+
286
+ def set_sinatra(app)
287
+ self.service_name = format_name(service_name || app.to_s)
288
+ self.framework_name = framework_name || 'Sinatra'
289
+ self.framework_version = framework_version || ::Sinatra::VERSION
290
+ self.__root_path = Dir.pwd
291
+ end
292
+
293
+ def set_rails(app)
294
+ self.service_name ||= format_name(service_name || rails_app_name(app))
295
+ self.framework_name ||= 'Ruby on Rails'
296
+ self.framework_version ||= ::Rails::VERSION::STRING
297
+ self.logger ||= ::Rails.logger
298
+
299
+ self.__root_path = ::Rails.root.to_s
300
+ self.__view_paths = app.config.paths['app/views'].existent
301
+ end
302
+
303
+ def rails_app_name(app)
304
+ if ::Rails::VERSION::MAJOR >= 6
305
+ app.class.module_parent_name
306
+ else
307
+ app.class.parent_name
308
+ end
309
+ end
310
+
311
+ def format_name(str)
312
+ str&.gsub('::', '_')
313
+ end
314
+ end
315
+ end