hoss-agent 1.0.11

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 +117 -0
  51. data/lib/hoss/spies/http.rb +93 -0
  52. data/lib/hoss/spies/net_http.rb +113 -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,139 @@
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/transport/connection/proxy_pipe'
21
+
22
+ module Hoss
23
+ module Transport
24
+ class Connection
25
+ # @api private
26
+ class Http
27
+ include Logging
28
+
29
+ def initialize(config, headers: nil)
30
+ @config = config
31
+ @headers = headers || Headers.new(config)
32
+ @client = build_client
33
+ @closed = Concurrent::AtomicBoolean.new(true)
34
+ end
35
+
36
+ def open(url)
37
+ @closed.make_false
38
+ @rd, @wr = ProxyPipe.pipe(compress: @config.http_compression?)
39
+ @request = open_request_in_thread(url)
40
+ end
41
+
42
+ def self.open(config, url)
43
+ new(config).tap do |http|
44
+ http.open(url)
45
+ end
46
+ end
47
+
48
+ def post(url, body: nil, headers: nil)
49
+ request(:post, url, body: body, headers: headers)
50
+ end
51
+
52
+ def get(url, headers: nil)
53
+ request(:get, url, headers: headers)
54
+ end
55
+
56
+ def request(method, url, body: nil, headers: nil)
57
+ @client.send(
58
+ method,
59
+ url,
60
+ body: body,
61
+ headers: (headers ? @headers.merge(headers) : @headers).to_h,
62
+ ssl_context: @config.ssl_context
63
+ ).flush
64
+ end
65
+
66
+ def write(str)
67
+ @wr.write(str)
68
+ @wr.bytes_sent
69
+ end
70
+
71
+ def close(reason)
72
+ return if closed?
73
+
74
+ debug '%s: Closing request with reason %s', thread_str, reason
75
+ @closed.make_true
76
+
77
+ @wr&.close(reason)
78
+ return if @request.nil? || @request&.join(5)
79
+
80
+ error(
81
+ '%s: APM Server not responding in time, terminating request',
82
+ thread_str
83
+ )
84
+ @request.kill
85
+ end
86
+
87
+ def closed?
88
+ @closed.true?
89
+ end
90
+
91
+ def inspect
92
+ format(
93
+ '%s closed: %s>',
94
+ super.split.first,
95
+ closed?
96
+ )
97
+ end
98
+
99
+ private
100
+
101
+ def thread_str
102
+ format('[THREAD:%s]', Thread.current.object_id)
103
+ end
104
+
105
+ def open_request_in_thread(url)
106
+ debug '%s: Opening new request', thread_str
107
+ Thread.new do
108
+ begin
109
+ resp = post(url, body: @rd, headers: @headers.chunked.to_h)
110
+
111
+ if resp&.status == 202
112
+ debug 'APM Server responded with status 202'
113
+ elsif resp
114
+ error "APM Server responded with an error:\n%p", resp.body.to_s
115
+ end
116
+ rescue Exception => e
117
+ error(
118
+ "Couldn't establish connection to APM Server:\n%p", e.inspect
119
+ )
120
+ end
121
+ end
122
+ end
123
+
124
+ def build_client
125
+ client = HTTP.headers(@headers)
126
+ return client unless @config.proxy_address && @config.proxy_port
127
+
128
+ client.via(
129
+ @config.proxy_address,
130
+ @config.proxy_port,
131
+ @config.proxy_username,
132
+ @config.proxy_password,
133
+ @config.proxy_headers
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,94 @@
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
+ class Connection
23
+ # @api private
24
+ class ProxyPipe
25
+ def initialize(enc = nil, compress: true)
26
+ rd, wr = IO.pipe(enc)
27
+
28
+ @read = rd
29
+ @write = Write.new(wr, compress: compress)
30
+
31
+ # Http.rb<4 calls rewind on the request bodies, but IO::Pipe raises
32
+ # ~mikker
33
+ return if HTTP::VERSION.to_i >= 4
34
+ def rd.rewind; end
35
+ end
36
+
37
+ attr_reader :read, :write
38
+
39
+ # @api private
40
+ class Write
41
+ include Logging
42
+
43
+ def initialize(io, compress: true)
44
+ @io = io
45
+ @compress = compress
46
+ @bytes_sent = Concurrent::AtomicFixnum.new(0)
47
+ @config = Hoss.agent&.config # this is silly, fix Logging
48
+
49
+ return unless compress
50
+ enable_compression!
51
+ ObjectSpace.define_finalizer(self, self.class.finalize(@io))
52
+ end
53
+
54
+ def self.finalize(io)
55
+ proc { io.close }
56
+ end
57
+
58
+ attr_reader :io
59
+
60
+ def enable_compression!
61
+ io.binmode
62
+ @io = Zlib::GzipWriter.new(io)
63
+ end
64
+
65
+ def close(reason = nil)
66
+ debug("Closing writer with reason #{reason}")
67
+ io.close
68
+ end
69
+
70
+ def closed?
71
+ io.closed?
72
+ end
73
+
74
+ def write(str)
75
+ io.puts(str).tap do
76
+ @bytes_sent.update do |curr|
77
+ @compress ? io.tell : curr + str.bytesize
78
+ end
79
+ end
80
+ end
81
+
82
+ def bytes_sent
83
+ @bytes_sent.value
84
+ end
85
+ end
86
+
87
+ def self.pipe(**args)
88
+ pipe = new(**args)
89
+ [pipe.read, pipe.write]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,60 @@
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/transport/filters/secrets_filter'
21
+
22
+ module Hoss
23
+ module Transport
24
+ # @api private
25
+ module Filters
26
+ SKIP = :skip
27
+
28
+ def self.new(config)
29
+ Container.new(config)
30
+ end
31
+
32
+ # @api private
33
+ class Container
34
+ def initialize(config)
35
+ @filters = { secrets: SecretsFilter.new(config) }
36
+ end
37
+
38
+ def add(key, filter)
39
+ @filters = @filters.merge(key => filter)
40
+ end
41
+
42
+ def remove(key)
43
+ @filters.delete(key)
44
+ end
45
+
46
+ def apply!(payload)
47
+ @filters.reduce(payload) do |result, (_key, filter)|
48
+ result = filter.call(result)
49
+ break SKIP if result.nil?
50
+ result
51
+ end
52
+ end
53
+
54
+ def length
55
+ @filters.length
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,77 @@
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
+ module Filters
23
+ class HashSanitizer
24
+ FILTERED = '[FILTERED]'
25
+
26
+ KEY_FILTERS = [
27
+ /passw(or)?d/i,
28
+ /auth/i,
29
+ /^pw$/,
30
+ /secret/i,
31
+ /token/i,
32
+ /api[-._]?key/i,
33
+ /session[-._]?id/i,
34
+ /(set[-_])?cookie/i
35
+ ].freeze
36
+
37
+ VALUE_FILTERS = [
38
+ # (probably) credit card number
39
+ /^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$/
40
+ ].freeze
41
+
42
+ attr_accessor :key_filters
43
+
44
+ def initialize
45
+ @key_filters = KEY_FILTERS
46
+ end
47
+
48
+ def strip_from!(obj, key_filters = KEY_FILTERS)
49
+ return unless obj&.is_a?(Hash)
50
+
51
+ obj.each do |k, v|
52
+ if filter_key?(k)
53
+ next obj[k] = FILTERED
54
+ end
55
+
56
+ case v
57
+ when Hash
58
+ strip_from!(v)
59
+ when String
60
+ if filter_value?(v)
61
+ obj[k] = FILTERED
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def filter_key?(key)
68
+ @key_filters.any? { |regex| regex.match(key) }
69
+ end
70
+
71
+ def filter_value?(value)
72
+ VALUE_FILTERS.any? { |regex| regex.match(value) }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
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/transport/filters/hash_sanitizer'
21
+
22
+ module Hoss
23
+ module Transport
24
+ module Filters
25
+ # @api private
26
+ class SecretsFilter
27
+ def initialize(config)
28
+ @config = config
29
+ @sanitizer = HashSanitizer.new
30
+ @sanitizer.key_filters += config.custom_key_filters +
31
+ config.sanitize_field_names
32
+ end
33
+
34
+ def call(payload)
35
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :headers)
36
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :env)
37
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :cookies)
38
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :response, :headers)
39
+ @sanitizer.strip_from! payload.dig(:error, :context, :request, :headers)
40
+ @sanitizer.strip_from! payload.dig(:error, :context, :response, :headers)
41
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :body)
42
+
43
+ payload
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,74 @@
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 Headers
24
+ HEADERS = {
25
+ 'Content-Type' => 'application/x-ndjson',
26
+ 'Transfer-Encoding' => 'chunked'
27
+ }.freeze
28
+ GZIP_HEADERS = HEADERS.merge(
29
+ 'Content-Encoding' => 'gzip'
30
+ ).freeze
31
+
32
+ def initialize(config, initial: {})
33
+ @config = config
34
+ @hash = build!(initial)
35
+ end
36
+
37
+ attr_accessor :hash
38
+
39
+ def [](key)
40
+ @hash[key]
41
+ end
42
+
43
+ def []=(key, value)
44
+ @hash[key] = value
45
+ end
46
+
47
+ def merge(other)
48
+ self.class.new(@config, initial: @hash.merge(other))
49
+ end
50
+
51
+ def merge!(other)
52
+ @hash.merge!(other)
53
+ self
54
+ end
55
+
56
+ def to_h
57
+ @hash
58
+ end
59
+
60
+ def chunked
61
+ merge(
62
+ @config.http_compression? ? GZIP_HEADERS : HEADERS
63
+ )
64
+ end
65
+
66
+ private
67
+
68
+ def build!(headers)
69
+ headers[:'User-Agent'] = UserAgent.new(@config).to_s
70
+ headers
71
+ end
72
+ end
73
+ end
74
+ end