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,33 @@
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
+ class Stacktrace
23
+ attr_accessor :frames
24
+
25
+ def length
26
+ frames.length
27
+ end
28
+
29
+ def to_a
30
+ frames.map(&:to_h)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
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/naively_hashable'
21
+
22
+ module Hoss
23
+ class Stacktrace
24
+ # @api private
25
+ class Frame
26
+ include NaivelyHashable
27
+
28
+ attr_accessor(
29
+ :abs_path,
30
+ :filename,
31
+ :function,
32
+ :vars,
33
+ :pre_context,
34
+ :context_line,
35
+ :post_context,
36
+ :library_frame,
37
+ :lineno,
38
+ :module,
39
+ :colno
40
+ )
41
+ def build_context(context_line_count)
42
+ return unless abs_path && context_line_count > 0
43
+
44
+ padding = (context_line_count - 1) / 2
45
+ from = lineno - padding - 1
46
+ from = 0 if from < 0
47
+ to = lineno + padding - 1
48
+ file_lines = read_lines(abs_path, from..to)
49
+
50
+ return unless file_lines
51
+
52
+ self.context_line = file_lines[padding]
53
+ self.pre_context = file_lines.first(padding)
54
+ self.post_context = file_lines.last(padding)
55
+ end
56
+
57
+ private
58
+
59
+ def read_lines(path, range)
60
+ File.readlines(path)[range]
61
+ rescue Errno::ENOENT
62
+ nil
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,124 @@
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/stacktrace/frame'
21
+ require 'hoss/util/lru_cache'
22
+
23
+ module Hoss
24
+ # @api private
25
+ class StacktraceBuilder
26
+ JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
27
+ RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/.freeze
28
+
29
+ RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}.freeze
30
+ JRUBY_ORG_REGEX = %r{org/jruby}.freeze
31
+
32
+ GEMS_PATH =
33
+ if defined?(Bundler) && Bundler.default_bundle_dir
34
+ Bundler.bundle_path.to_s
35
+ else
36
+ Gem.dir
37
+ end
38
+
39
+ def initialize(config)
40
+ @config = config
41
+ @cache = Util::LruCache.new(2048, &method(:build_frame))
42
+ end
43
+
44
+ attr_reader :config
45
+
46
+ def build(backtrace, type:)
47
+ Stacktrace.new.tap do |s|
48
+ s.frames = backtrace[0...config.stack_trace_limit].map do |line|
49
+ @cache[[line, type]]
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def build_frame(cache, keys)
57
+ line, type = keys
58
+ abs_path, lineno, function, _module_name = parse_line(line)
59
+
60
+ frame = Stacktrace::Frame.new
61
+ frame.abs_path = abs_path
62
+ frame.filename = strip_load_path(abs_path)
63
+ frame.function = function
64
+ frame.lineno = lineno.to_i
65
+ frame.library_frame = library_frame?(config, abs_path)
66
+
67
+ line_count =
68
+ context_lines_for(config, type, library_frame: frame.library_frame)
69
+ frame.build_context line_count
70
+
71
+ cache[[line, type]] = frame
72
+ end
73
+
74
+ def parse_line(line)
75
+ ruby_match = line.match(RUBY_FORMAT)
76
+
77
+ if ruby_match
78
+ _, file, number, method = ruby_match.to_a
79
+ file.sub!(/\.class$/, '.rb')
80
+ module_name = nil
81
+ else
82
+ java_match = line.match(JAVA_FORMAT)
83
+ _, module_name, method, file, number = java_match.to_a
84
+ end
85
+
86
+ [file, number, method, module_name]
87
+ end
88
+
89
+ # rubocop:disable Metrics/CyclomaticComplexity
90
+ def library_frame?(config, abs_path)
91
+ return false unless abs_path
92
+
93
+ return true if abs_path.start_with?(GEMS_PATH)
94
+
95
+ if abs_path.start_with?(config.__root_path)
96
+ return true if abs_path.start_with?(config.__root_path + '/vendor')
97
+ return false
98
+ end
99
+
100
+ return true if abs_path.match(RUBY_VERS_REGEX)
101
+ return true if abs_path.match(JRUBY_ORG_REGEX)
102
+
103
+ false
104
+ end
105
+ # rubocop:enable Metrics/CyclomaticComplexity
106
+
107
+ def strip_load_path(path)
108
+ return nil if path.nil?
109
+
110
+ prefix =
111
+ $LOAD_PATH
112
+ .map(&:to_s)
113
+ .select { |s| path.start_with?(s) }
114
+ .max_by(&:length)
115
+
116
+ prefix ? path[prefix.chomp(File::SEPARATOR).length + 1..-1] : path
117
+ end
118
+
119
+ def context_lines_for(config, type, library_frame:)
120
+ key = "source_lines_#{type}_#{library_frame ? 'library' : 'app'}_frames"
121
+ config.send(key.to_sym)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,191 @@
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/metadata'
21
+ require 'hoss/transport/user_agent'
22
+ require 'hoss/transport/headers'
23
+ require 'hoss/transport/connection'
24
+ require 'hoss/transport/worker'
25
+ require 'hoss/transport/serializers'
26
+ require 'hoss/transport/filters'
27
+ require 'hoss/transport/connection/http'
28
+
29
+ require 'hoss/util/throttle'
30
+
31
+ module Hoss
32
+ module Transport
33
+ # @api private
34
+ class Base
35
+ include Logging
36
+
37
+ WATCHER_EXECUTION_INTERVAL = 5
38
+ WATCHER_TIMEOUT_INTERVAL = 4
39
+ WORKER_JOIN_TIMEOUT = 5
40
+
41
+ def initialize(config)
42
+ @config = config
43
+ @queue = SizedQueue.new(config.max_queue_size)
44
+
45
+ @serializers = Serializers.new(config)
46
+ @filters = Filters.new(config)
47
+
48
+ @stopped = Concurrent::AtomicBoolean.new
49
+ @workers = Array.new(config.pool_size)
50
+
51
+ @worker_mutex = Mutex.new
52
+ end
53
+
54
+ attr_reader :config, :queue, :filters, :workers, :watcher, :stopped
55
+
56
+ def start
57
+ debug '%s: Starting Transport', pid_str
58
+ # Set @stopped to false first, in case transport is restarted;
59
+ # ensure_worker_count requires @stopped to be false
60
+ # ~estolfo
61
+ @stopped.make_false unless @stopped.false?
62
+
63
+ ensure_worker_count
64
+ create_watcher
65
+ end
66
+
67
+ def stop
68
+ debug '%s: Stopping Transport', pid_str
69
+
70
+ @stopped.make_true
71
+
72
+ stop_watcher
73
+ stop_workers
74
+ end
75
+
76
+ def submit(resource)
77
+ if @stopped.true?
78
+ warn '%s: Transport stopping, no new events accepted', pid_str
79
+ debug 'Dropping: %s', resource.inspect
80
+ return false
81
+ end
82
+
83
+ queue.push(resource, true)
84
+
85
+ true
86
+ rescue ThreadError
87
+ throttled_queue_full_warning
88
+ nil
89
+ rescue Exception => e
90
+ error '%s: Failed adding to the transport queue: %p', pid_str, e.inspect
91
+ nil
92
+ end
93
+
94
+ def add_filter(key, callback)
95
+ @filters.add(key, callback)
96
+ end
97
+
98
+ def handle_forking!
99
+ # We can't just stop and start again because the StopMessage
100
+ # will then be the first message processed when the transport is
101
+ # restarted.
102
+ stop_watcher
103
+ ensure_worker_count
104
+ create_watcher
105
+ end
106
+
107
+ private
108
+
109
+ def pid_str
110
+ format('[PID:%s]', Process.pid)
111
+ end
112
+
113
+ def create_watcher
114
+ @watcher = Concurrent::TimerTask.execute(
115
+ execution_interval: WATCHER_EXECUTION_INTERVAL,
116
+ timeout_interval: WATCHER_TIMEOUT_INTERVAL
117
+ ) { ensure_worker_count }
118
+ end
119
+
120
+ def ensure_worker_count
121
+ @worker_mutex.synchronize do
122
+ return if all_workers_alive?
123
+ return if stopped.true?
124
+
125
+ @workers.map! do |thread|
126
+ next thread if thread&.alive?
127
+
128
+ boot_worker
129
+ end
130
+ end
131
+ end
132
+
133
+ def all_workers_alive?
134
+ !!workers.all? { |t| t&.alive? }
135
+ end
136
+
137
+ def boot_worker
138
+ debug '%s: Booting worker...', pid_str
139
+
140
+ Thread.new do
141
+ Worker.new(
142
+ config, queue,
143
+ serializers: @serializers,
144
+ filters: @filters
145
+ ).work_forever
146
+ end
147
+ end
148
+
149
+ def stop_workers
150
+ debug '%s: Stopping workers', pid_str
151
+
152
+ send_stop_messages
153
+
154
+ @worker_mutex.synchronize do
155
+ workers.each do |thread|
156
+ next if thread.nil?
157
+ next if thread.join(WORKER_JOIN_TIMEOUT)
158
+
159
+ debug(
160
+ '%s: Worker did not stop in %ds, killing...',
161
+ pid_str, WORKER_JOIN_TIMEOUT
162
+ )
163
+ thread.kill
164
+ end
165
+
166
+ # Maintain the @worker array size for when transport is restarted
167
+ @workers.fill(nil)
168
+ end
169
+ end
170
+
171
+ def send_stop_messages
172
+ config.pool_size.times { queue.push(Worker::StopMessage.new, true) }
173
+ rescue ThreadError
174
+ warn 'Cannot push stop messages to worker queue as it is full'
175
+ end
176
+
177
+ def stop_watcher
178
+ watcher&.shutdown
179
+ end
180
+
181
+ def throttled_queue_full_warning
182
+ (@queue_full_log ||= Util::Throttle.new(5) do
183
+ warn(
184
+ '%s: Queue is full (%i items), skipping…',
185
+ pid_str, config.max_queue_size
186
+ )
187
+ end).call
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,55 @@
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 Transport
22
+ # @api private
23
+ class Connection
24
+ include Logging
25
+
26
+ def initialize(config)
27
+ @config = config
28
+ @metadata = JSON.fast_generate(
29
+ Serializers::MetadataSerializer.new(config).build(
30
+ Metadata.new(config)
31
+ )
32
+ )
33
+ @url = @config.ingress_host
34
+ @mutex = Mutex.new
35
+ end
36
+
37
+ attr_reader :http
38
+ def write(str)
39
+ debug "Sending report"
40
+ uri = URI(@url)
41
+ req = Net::HTTP::Post.new(
42
+ uri,
43
+ 'Authorization' => "Bearer #{@config.api_key}", #"Basic " + Base64.encode64("#{@config.api_key}:"),
44
+ 'Content-Type' => 'application/json',
45
+ 'HOSS-SKIP-INSTRUMENTATION' => 'true',
46
+ )
47
+ req.body = str
48
+ res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
49
+ http.request(req)
50
+ end
51
+ raise unless res.code == "200"
52
+ end
53
+ end
54
+ end
55
+ end