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,9 @@
1
+ module Hoss
2
+ # @api private
3
+ class Report
4
+ extend Forwardable
5
+ attr_accessor(
6
+ :events
7
+ )
8
+ end
9
+ end
@@ -0,0 +1,53 @@
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
+ # Module for starting the Hoss agent and hooking into Sinatra.
22
+ module Sinatra
23
+ extend self
24
+ # Start the Hoss agent and hook into Sinatra.
25
+ #
26
+ # @param app [Sinatra::Base] A Sinatra app.
27
+ # @param config [Config, Hash] An instance of Config or a Hash config.
28
+ # @return [true, nil] true if the agent was started, nil otherwise.
29
+ def start(app, config = {})
30
+ config = Config.new(config) unless config.is_a?(Config)
31
+ configure_app(app, config)
32
+
33
+ Hoss.start(config)
34
+ Hoss.running?
35
+ rescue StandardError => e
36
+ config.logger.error format('Failed to start: %s', e.message)
37
+ config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
38
+ end
39
+
40
+ private
41
+
42
+ def configure_app(app, config)
43
+ config.service_name ||= format_name(app.to_s)
44
+ config.framework_name ||= 'Sinatra'
45
+ config.framework_version ||= ::Sinatra::VERSION
46
+ config.__root_path ||= Dir.pwd
47
+ end
48
+
49
+ def format_name(str)
50
+ str&.gsub('::', '_')
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,104 @@
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/util/inflector'
21
+
22
+ module Hoss
23
+ # @api private
24
+ module Spies
25
+ # @api private
26
+ class Registration
27
+ extend Forwardable
28
+
29
+ def initialize(const_name, require_paths, spy)
30
+ @const_name = const_name
31
+ @require_paths = Array(require_paths)
32
+ @spy = spy
33
+ end
34
+
35
+ attr_reader :const_name, :require_paths
36
+
37
+ def_delegator :@spy, :install
38
+ end
39
+
40
+ def self.require_hooks
41
+ @require_hooks ||= {}
42
+ end
43
+
44
+ def self.installed
45
+ @installed ||= {}
46
+ end
47
+
48
+ def self.register(*args)
49
+ registration = Registration.new(*args)
50
+
51
+ if safe_defined?(registration.const_name)
52
+ registration.install
53
+ installed[registration.const_name] = registration
54
+ else
55
+ register_require_hook registration
56
+ end
57
+ end
58
+
59
+ def self.register_require_hook(registration)
60
+ registration.require_paths.each do |path|
61
+ require_hooks[path] = registration
62
+ end
63
+ end
64
+
65
+ def self.hook_into(name)
66
+ return unless (registration = require_hooks[name])
67
+ return unless safe_defined?(registration.const_name)
68
+
69
+ installed[registration.const_name] = registration
70
+ registration.install
71
+
72
+ registration.require_paths.each do |path|
73
+ require_hooks.delete path
74
+ end
75
+ end
76
+
77
+ def self.safe_defined?(const_name)
78
+ Util::Inflector.safe_constantize(const_name)
79
+ end
80
+ end
81
+ end
82
+
83
+ unless ENV['HOSS_SKIP_REQUIRE_PATCH'] == '1'
84
+ # @api private
85
+ module Kernel
86
+ private
87
+
88
+ alias require_without_apm require
89
+
90
+ def require(path)
91
+ res = require_without_apm(path)
92
+
93
+ begin
94
+ Hoss::Spies.hook_into(path)
95
+ rescue ::Exception => e
96
+ puts "Failed hooking into '#{path}'. Please report this at " \
97
+ 'github.com/elastic/apm-agent-ruby'
98
+ puts e.backtrace.join("\n")
99
+ end
100
+
101
+ res
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,106 @@
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 Spies
23
+ # @api private
24
+ class FaradaySpy
25
+ TYPE = 'ext'
26
+ SUBTYPE = 'faraday'
27
+
28
+ def self.without_net_http
29
+ return yield unless defined?(NetHTTPSpy)
30
+
31
+ Hoss::Spies::NetHTTPSpy.disable_in do
32
+ yield
33
+ end
34
+ end
35
+
36
+ # rubocop:disable Metrics/CyclomaticComplexity
37
+ def install
38
+ ::Faraday::Connection.class_eval do
39
+ alias run_request_without_apm run_request
40
+
41
+ def run_request(method, url, body, headers, &block)
42
+ begin
43
+ Hoss.with_event do |event|
44
+ Hoss::Spies::FaradaySpy.without_net_http do
45
+ begin
46
+ uri = URI(build_url(url))
47
+ result = run_request_without_apm(method, url, body, headers) do |req|
48
+ if block_given?
49
+ yield req
50
+ new_path = req.path
51
+ new_query = URI.encode_www_form(req.params)
52
+ if uri.path != new_path || uri.query != new_query
53
+ test_uri = uri
54
+ test_uri.query = nil
55
+ begin
56
+ test_uri.path = new_path
57
+ rescue Exception => e
58
+ end
59
+ # The original url can be set to path if Faraday.get used
60
+ if test_uri.to_s != uri.to_s
61
+ uri.path = new_path
62
+ end
63
+ uri.query = new_query
64
+ end
65
+ end
66
+ event.request.method = method.to_s.upcase
67
+ event.request.url = uri.to_s
68
+ event.request.received_at = DateTime.now.strftime('%Q').to_i
69
+ event.request.headers['host'] = uri.hostname
70
+ req.headers.each {|n,v| event.request.headers[n] = v}
71
+ event.request.url = uri.to_s
72
+ event.request.body = req.body
73
+ end
74
+
75
+ if result
76
+ event.response = Hoss::Event::Response.new
77
+ event.response.received_at = DateTime.now.strftime('%Q').to_i
78
+ event.response.status_code = result.status.to_i
79
+ result.headers.each {|n,v| event.response.headers[n] = v}
80
+ event.response.body = result.body
81
+ end
82
+
83
+ result
84
+ rescue Exception => e
85
+ if e.respond_to?(:wrapped_exception) && e.wrapped_exception.is_a?(Net::OpenTimeout)
86
+ event.error = Hoss::Event::Error.new(Hoss::Event::Error::ConnectionTimeout)
87
+ else
88
+ event.error = Hoss::Event::Error.new(Hoss::Event::Error::ConnectionError)
89
+ end
90
+ event.error.received_at = DateTime.now.strftime('%Q').to_i
91
+ raise
92
+ end
93
+ end
94
+ end
95
+ rescue
96
+ run_request_without_apm(method, url, body, headers, &block)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ # rubocop:enable Metrics/CyclomaticComplexity
102
+ end
103
+
104
+ register 'Faraday', 'faraday', FaradaySpy.new
105
+ end
106
+ end
@@ -0,0 +1,86 @@
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 Spies
23
+ # @api private
24
+ class HTTPSpy
25
+ TYPE = 'ext'
26
+ SUBTYPE = 'http_rb'
27
+
28
+ def self.copy_request_body(body)
29
+ case body.source
30
+ when String
31
+ body.source
32
+ when nil
33
+ nil
34
+ else
35
+ ""
36
+ end
37
+ end
38
+
39
+ def install
40
+ ::HTTP::Client.class_eval do
41
+ alias perform_without_apm perform
42
+
43
+ def perform(req, options)
44
+ if req.headers['HOSS-SKIP-INSTRUMENTATION'] == 'true'
45
+ return perform_without_apm(req, options)
46
+ end
47
+
48
+ begin
49
+ Hoss.with_event do |event|
50
+ event.request.headers['host'] = req.uri.host
51
+ req.headers.each {|n,v| event.request.headers[n] = v}
52
+ event.request.method = req.verb.to_s.upcase
53
+ event.request.url = req.uri.to_s
54
+ event.request.body = Hoss::Spies::HTTPSpy::copy_request_body(req.body)
55
+ event.request.received_at = DateTime.now.strftime('%Q').to_i
56
+ begin
57
+ result = perform_without_apm(req, options)
58
+ if result
59
+ event.response = Hoss::Event::Response.new
60
+ event.response.received_at = DateTime.now.strftime('%Q').to_i
61
+ event.response.status_code = result.code.to_i
62
+ result.headers.each {|n,v| event.response.headers[n] = v}
63
+ event.response.body = result.body.to_s
64
+ end
65
+ result
66
+ rescue HTTP::TimeoutError => e
67
+ event.error = Hoss::Event::Error.new(Hoss::Event::Error::ConnectionTimeout)
68
+ event.error.received_at = DateTime.now.strftime('%Q').to_i
69
+ raise
70
+ rescue Exception => e
71
+ event.error = Hoss::Event::Error.new(Hoss::Event::Error::ConnectionError)
72
+ event.error.received_at = DateTime.now.strftime('%Q').to_i
73
+ raise
74
+ end
75
+ end
76
+ rescue
77
+ return perform_without_apm(req, options)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ register 'HTTP', 'http', HTTPSpy.new
85
+ end
86
+ end
@@ -0,0 +1,101 @@
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 Spies
23
+ # @api private
24
+ class NetHTTPSpy
25
+ KEY = :__hoss_net_http_disabled
26
+ # TYPE = 'ext'
27
+ # SUBTYPE = 'net_http'
28
+
29
+ class << self
30
+ def disabled=(disabled)
31
+ Thread.current[KEY] = disabled
32
+ end
33
+
34
+ def disabled?
35
+ Thread.current[KEY] ||= false
36
+ end
37
+
38
+ def disable_in
39
+ self.disabled = true
40
+
41
+ begin
42
+ yield
43
+ ensure
44
+ self.disabled = false
45
+ end
46
+ end
47
+ end
48
+
49
+ # rubocop:disable Metrics/CyclomaticComplexity
50
+ def install
51
+ Net::HTTP.class_eval do
52
+ alias request_without_apm request
53
+
54
+ def request(req, body = nil, &block)
55
+ if req['HOSS-SKIP-INSTRUMENTATION'] == 'true' || Hoss::Spies::NetHTTPSpy.disabled?
56
+ return request_without_apm(req, body, &block)
57
+ end
58
+
59
+ begin
60
+ host = req['host']&.split(':')&.first || address
61
+ method = req.method.to_s.upcase
62
+ path, query = req.path.split('?')
63
+
64
+ url = use_ssl? ? +'https://' : +'http://'
65
+ url << host
66
+ url << ":#{port}" if port
67
+ url << path
68
+ url << "?#{query}" if query
69
+ uri = URI(url)
70
+
71
+ Hoss.with_event do |event|
72
+ # Record request
73
+ event.request.headers['host'] = uri.hostname
74
+ req.each_header {|n,v| event.request.headers[n] = v}
75
+ event.request.method = method
76
+ event.request.url = uri.to_s
77
+ event.request.body = req.body
78
+ event.request.received_at = DateTime.now.strftime('%Q').to_i
79
+
80
+ result = request_without_apm(req, body, &block)
81
+ if result
82
+ event.response = Hoss::Event::Response.new
83
+ event.response.received_at = DateTime.now.strftime('%Q').to_i
84
+ event.response.status_code = result.code.to_i
85
+ result.each_header {|n,v| event.response.headers[n] = v}
86
+ event.response.body = result.body
87
+ end
88
+ result
89
+ end
90
+ rescue
91
+ return request_without_apm(req, body, &block)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ # rubocop:enable Metrics/CyclomaticComplexity
97
+ end
98
+
99
+ register 'Net::HTTP', 'net/http', NetHTTPSpy.new
100
+ end
101
+ end