hoss-agent 1.0.9

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 +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