hoss-agent 1.0.6

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 (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 +313 -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 +102 -0
  51. data/lib/hoss/spies/http.rb +81 -0
  52. data/lib/hoss/spies/net_http.rb +97 -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 +326 -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,313 @@
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
+
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
+ warn '[HOSS] No API Key provided' unless self.api_key
113
+
114
+ yield self if block_given?
115
+
116
+ self.logger ||= build_logger
117
+
118
+ @__view_paths ||= []
119
+ @__root_path ||= Dir.pwd
120
+ end
121
+
122
+ attr_accessor :__view_paths, :__root_path
123
+ attr_accessor :logger
124
+
125
+ attr_reader :options
126
+
127
+ def assign(update)
128
+ return unless update
129
+ update.each { |key, value| send(:"#{key}=", value) }
130
+ end
131
+
132
+ def available_instrumentations
133
+ %w[
134
+ http
135
+ net_http
136
+ faraday
137
+ ]
138
+ end
139
+
140
+ def enabled_instrumentations
141
+ available_instrumentations - disable_instrumentations
142
+ end
143
+
144
+ def method_missing(name, *args)
145
+ return super unless DEPRECATED_OPTIONS.include?(name)
146
+ warn "The option `#{name}' has been removed."
147
+ end
148
+
149
+ def replace_options(new_options)
150
+ return if new_options.nil? || new_options.empty?
151
+ options_copy = @options.dup
152
+ new_options.each do |key, value|
153
+ options_copy.fetch(key.to_sym).set(value)
154
+ end
155
+ @options = options_copy
156
+ end
157
+
158
+ def app=(app)
159
+ case app_type?(app)
160
+ when :sinatra
161
+ set_sinatra(app)
162
+ when :rails
163
+ set_rails(app)
164
+ else
165
+ self.service_name = 'ruby'
166
+ end
167
+ end
168
+
169
+ def use_ssl?
170
+ api_host.start_with?('https')
171
+ end
172
+
173
+ def span_frames_min_duration?
174
+ span_frames_min_duration != 0
175
+ end
176
+
177
+ def span_frames_min_duration=(value)
178
+ super
179
+ @span_frames_min_duration_us = nil
180
+ end
181
+
182
+ def span_frames_min_duration_us
183
+ @span_frames_min_duration_us ||= span_frames_min_duration * 1_000_000
184
+ end
185
+
186
+ def ssl_context
187
+ return unless use_ssl?
188
+
189
+ @ssl_context ||=
190
+ OpenSSL::SSL::SSLContext.new.tap do |context|
191
+ if server_ca_cert
192
+ context.ca_file = server_ca_cert
193
+ else
194
+ context.cert_store =
195
+ OpenSSL::X509::Store.new.tap(&:set_default_paths)
196
+ end
197
+
198
+ context.verify_mode =
199
+ if verify_server_cert
200
+ OpenSSL::SSL::VERIFY_PEER
201
+ else
202
+ OpenSSL::SSL::VERIFY_NONE
203
+ end
204
+ end
205
+ end
206
+
207
+ def inspect
208
+ super.split.first + '>'
209
+ end
210
+
211
+ # Deprecations
212
+
213
+ def default_tags=(value)
214
+ warn '[DEPRECATED] The option default_tags has been renamed to ' \
215
+ 'default_labels.'
216
+ self.default_labels = value
217
+ end
218
+
219
+ def custom_key_filters=(value)
220
+ unless value == self.class.schema[:custom_key_filters][:default]
221
+ warn '[DEPRECATED] The option custom_key_filters is being removed. ' \
222
+ 'See sanitize_field_names for an alternative.'
223
+ end
224
+
225
+ set(:custom_key_filters, value)
226
+ end
227
+
228
+ def disabled_instrumentations
229
+ disable_instrumentations
230
+ end
231
+
232
+ def active
233
+ enabled
234
+ end
235
+ alias active? active
236
+
237
+ def disabled_instrumentations=(value)
238
+ warn '[DEPRECATED] The option disabled_instrumentations has been ' \
239
+ 'renamed to disable_instrumentations to align with other agents.'
240
+ self.disable_instrumentations = value
241
+ end
242
+
243
+ def active=(value)
244
+ warn '[DEPRECATED] The option active has been renamed to enabled ' \
245
+ 'to align with other agents and with the remote config.'
246
+ self.enabled = value
247
+ end
248
+
249
+ private
250
+
251
+ def load_config_file
252
+ filename = File.join(@__root_path || Dir.pwd, config_file)
253
+ return unless File.exist?(filename)
254
+ read = File.read(filename)
255
+ evaled = ERB.new(read).result
256
+ YAML.safe_load(evaled)
257
+ end
258
+
259
+ def load_env
260
+ @options.values.each_with_object({}) do |option, opts|
261
+ next unless (value = ENV[option.env_key])
262
+ opts[option.key] = value
263
+ end
264
+ end
265
+
266
+ def build_logger
267
+ Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
268
+ logger.level = log_level
269
+ end
270
+ end
271
+
272
+ def app_type?(app)
273
+ if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
274
+ return :rails
275
+ end
276
+
277
+ if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
278
+ return :sinatra
279
+ end
280
+
281
+ nil
282
+ end
283
+
284
+ def set_sinatra(app)
285
+ self.service_name = format_name(service_name || app.to_s)
286
+ self.framework_name = framework_name || 'Sinatra'
287
+ self.framework_version = framework_version || ::Sinatra::VERSION
288
+ self.__root_path = Dir.pwd
289
+ end
290
+
291
+ def set_rails(app)
292
+ self.service_name ||= format_name(service_name || rails_app_name(app))
293
+ self.framework_name ||= 'Ruby on Rails'
294
+ self.framework_version ||= ::Rails::VERSION::STRING
295
+ self.logger ||= ::Rails.logger
296
+
297
+ self.__root_path = ::Rails.root.to_s
298
+ self.__view_paths = app.config.paths['app/views'].existent
299
+ end
300
+
301
+ def rails_app_name(app)
302
+ if ::Rails::VERSION::MAJOR >= 6
303
+ app.class.module_parent_name
304
+ else
305
+ app.class.parent_name
306
+ end
307
+ end
308
+
309
+ def format_name(str)
310
+ str&.gsub('::', '_')
311
+ end
312
+ end
313
+ end