hoss-agent 1.0.1

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 +231 -0
  12. data/lib/hoss/central_config/cache_control.rb +51 -0
  13. data/lib/hoss/central_config.rb +184 -0
  14. data/lib/hoss/child_durations.rb +64 -0
  15. data/lib/hoss/config/bytes.rb +42 -0
  16. data/lib/hoss/config/duration.rb +40 -0
  17. data/lib/hoss/config/options.rb +154 -0
  18. data/lib/hoss/config/regexp_list.rb +30 -0
  19. data/lib/hoss/config/wildcard_pattern_list.rb +54 -0
  20. data/lib/hoss/config.rb +304 -0
  21. data/lib/hoss/context/request/socket.rb +36 -0
  22. data/lib/hoss/context/request/url.rb +59 -0
  23. data/lib/hoss/context/request.rb +28 -0
  24. data/lib/hoss/context/response.rb +47 -0
  25. data/lib/hoss/context/user.rb +59 -0
  26. data/lib/hoss/context.rb +64 -0
  27. data/lib/hoss/context_builder.rb +112 -0
  28. data/lib/hoss/deprecations.rb +39 -0
  29. data/lib/hoss/error/exception.rb +70 -0
  30. data/lib/hoss/error/log.rb +41 -0
  31. data/lib/hoss/error.rb +49 -0
  32. data/lib/hoss/error_builder.rb +90 -0
  33. data/lib/hoss/event.rb +131 -0
  34. data/lib/hoss/instrumenter.rb +107 -0
  35. data/lib/hoss/internal_error.rb +23 -0
  36. data/lib/hoss/logging.rb +70 -0
  37. data/lib/hoss/metadata/process_info.rb +35 -0
  38. data/lib/hoss/metadata/service_info.rb +76 -0
  39. data/lib/hoss/metadata/system_info/container_info.rb +136 -0
  40. data/lib/hoss/metadata/system_info.rb +47 -0
  41. data/lib/hoss/metadata.rb +36 -0
  42. data/lib/hoss/naively_hashable.rb +38 -0
  43. data/lib/hoss/rails.rb +68 -0
  44. data/lib/hoss/railtie.rb +42 -0
  45. data/lib/hoss/report.rb +9 -0
  46. data/lib/hoss/sinatra.rb +53 -0
  47. data/lib/hoss/spies/faraday.rb +102 -0
  48. data/lib/hoss/spies/http.rb +81 -0
  49. data/lib/hoss/spies/net_http.rb +97 -0
  50. data/lib/hoss/spies.rb +104 -0
  51. data/lib/hoss/stacktrace/frame.rb +66 -0
  52. data/lib/hoss/stacktrace.rb +33 -0
  53. data/lib/hoss/stacktrace_builder.rb +124 -0
  54. data/lib/hoss/transport/base.rb +191 -0
  55. data/lib/hoss/transport/connection/http.rb +139 -0
  56. data/lib/hoss/transport/connection/proxy_pipe.rb +94 -0
  57. data/lib/hoss/transport/connection.rb +55 -0
  58. data/lib/hoss/transport/filters/hash_sanitizer.rb +77 -0
  59. data/lib/hoss/transport/filters/secrets_filter.rb +48 -0
  60. data/lib/hoss/transport/filters.rb +60 -0
  61. data/lib/hoss/transport/headers.rb +74 -0
  62. data/lib/hoss/transport/serializers/context_serializer.rb +112 -0
  63. data/lib/hoss/transport/serializers/error_serializer.rb +92 -0
  64. data/lib/hoss/transport/serializers/event_serializer.rb +73 -0
  65. data/lib/hoss/transport/serializers/metadata_serializer.rb +92 -0
  66. data/lib/hoss/transport/serializers/report_serializer.rb +33 -0
  67. data/lib/hoss/transport/serializers.rb +113 -0
  68. data/lib/hoss/transport/user_agent.rb +48 -0
  69. data/lib/hoss/transport/worker.rb +319 -0
  70. data/lib/hoss/util/inflector.rb +110 -0
  71. data/lib/hoss/util/lru_cache.rb +65 -0
  72. data/lib/hoss/util/throttle.rb +52 -0
  73. data/lib/hoss/util.rb +54 -0
  74. data/lib/hoss/version.rb +22 -0
  75. data/lib/hoss-agent.rb +210 -0
  76. data/lib/hoss.rb +21 -0
  77. metadata +147 -0
@@ -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,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,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
@@ -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,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