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,319 @@
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/report'
21
+ require 'zlib'
22
+ require 'cgi'
23
+
24
+ module Hoss
25
+ module Transport
26
+ # @api private
27
+ class Worker
28
+ include Logging
29
+
30
+ class << self
31
+ def adapter
32
+ @adapter ||= Connection
33
+ end
34
+
35
+ attr_writer :adapter
36
+ end
37
+
38
+ # @api private
39
+ class StopMessage; end
40
+
41
+ # @api private
42
+ class FlushMessage; end
43
+
44
+ def initialize(
45
+ config,
46
+ queue,
47
+ serializers:,
48
+ filters:
49
+ )
50
+ @config = config
51
+ @queue = queue
52
+
53
+ @serializers = serializers
54
+ @filters = filters
55
+
56
+ @connection = self.class.adapter.new(config)
57
+ end
58
+
59
+ attr_reader :config, :queue, :filters, :name, :connection, :serializers
60
+ def work_forever
61
+
62
+ done = false
63
+ while (!done && msg = queue.pop)
64
+ debug 'working message', msg
65
+
66
+ # wait if we don't have a config
67
+ while config.agentConfig.nil?
68
+ sleep 0.1
69
+ end
70
+
71
+ case msg
72
+ when StopMessage
73
+ debug 'Stopping worker -- %s', self
74
+ done = true
75
+ break
76
+ else
77
+ batch = []
78
+ current_batch_size = 0
79
+
80
+ # Use this as the first message in the batch
81
+ event = msg.filtered ? msg : filter_resource(msg)
82
+ unless event.nil?
83
+ event_size = resource_size(event)
84
+ if current_batch_size + event_size <= @config.batch_size
85
+ unless host_blacklisted(event)
86
+ current_batch_size += event_size
87
+ if event.retries < @config.max_event_retries
88
+ batch.push(event)
89
+ else
90
+ debug "max retries hit for event"
91
+ end
92
+ end
93
+ else
94
+ debug "Event is too large, body needs to be truncated"
95
+ end
96
+ end
97
+
98
+ # Do inner loop reading queue to build report
99
+ requeue_messages = []
100
+ while current_batch_size < @config.batch_size && !queue.empty?
101
+ next_msg = queue.pop
102
+ case next_msg
103
+ when StopMessage
104
+ debug 'Stopping worker -- %s', self
105
+ done = true
106
+ break
107
+ else
108
+ event = next_msg.filtered ? next_msg : filter_resource(next_msg) unless
109
+ unless event.nil?
110
+ event_size = resource_size(event)
111
+ if current_batch_size + event_size <= @config.batch_size
112
+ unless host_blacklisted(event)
113
+ current_batch_size += event_size
114
+ if event.retries < @config.max_event_retries
115
+ batch.push(event)
116
+ else
117
+ debug "max retries hit for event"
118
+ end
119
+ end
120
+ else
121
+ debug "Event too large for this batch, requeue"
122
+ requeue_messages.push(event)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ if batch.length == 0
129
+ debug "batch is empty, breaking"
130
+ break
131
+ end
132
+
133
+ debug "Requeue #{requeue_messages.length} messages" if requeue_messages.length > 0
134
+ requeue_messages.each {|msg| queue.push(msg, false) }
135
+
136
+ report = Report.new
137
+ report.events = batch.map {|event| serializers.serialize(event) }
138
+
139
+ debug "Finished building report"
140
+ data = serializers.serialize(report)
141
+ json = JSON.fast_generate(data)
142
+ begin
143
+ debug json
144
+ rescue Exception => e
145
+ debug 'unable to print body'
146
+ puts json
147
+ end
148
+ begin
149
+ connection.write(json)
150
+ rescue Exception => e
151
+ error format('Failed send report: %s %s', e.inspect, e.backtrace)
152
+ batch.each do |m|
153
+ m.retries += 1
154
+ queue.push(m, false)
155
+ end
156
+ sleep 1
157
+ end
158
+ end
159
+ end
160
+ rescue Exception => e
161
+ warn 'Worker died with exception: %s', e.inspect
162
+ debug e.backtrace.join("\n")
163
+ end
164
+
165
+ private
166
+
167
+ def resource_size(resource)
168
+ size = 20
169
+ size += JSON.fast_generate(resource.request.headers).length if resource.request && resource.request.headers
170
+ size += JSON.fast_generate(resource.response.headers).length if resource.response && resource.response.headers
171
+ size += resource.request.body.length if resource.request && resource.request.body
172
+ size += resource.response.body.length if resource.response && resource.response.body
173
+ size
174
+ end
175
+
176
+ def decompress_body(body, encoding)
177
+ return body unless body
178
+ case (encoding || '').downcase
179
+ when "gzip"
180
+ Zlib::GzipReader.new(StringIO.new(body)).read.to_s
181
+ when "deflate"
182
+ Zlib::Inflate.new.inflate(body)
183
+ else
184
+ body
185
+ end
186
+ end
187
+
188
+ def get_header(headers, name)
189
+ headers[headers.keys.select{|h| h.casecmp(name) == 0}[0]]
190
+ end
191
+
192
+ def filter_resource(resource)
193
+ # Make sure the body is not compressed
194
+ resource.request.body = decompress_body(resource.request.body, get_header(resource.request.headers, "content-encoding")) if resource.request
195
+ resource.response.body = decompress_body(resource.response.body, get_header(resource.response.headers,"content-encoding")) if resource.response
196
+
197
+ # Start applying filters
198
+ config = get_configuration_for_host(resource.request.headers['host'])
199
+ debug "Using config for event #{config.to_s}"
200
+
201
+ # Filter the headers
202
+ filter_headers(resource.request.headers, config['sanitizedHeaders']) if resource.request
203
+ filter_headers(resource.response.headers, config['sanitizedHeaders']) if resource.response
204
+
205
+ # Filter the body
206
+ if config['bodyCapture'] == 'Off' || (config['bodyCapture'] == 'OnError' && !contains_error(resource))
207
+ resource.request.body = nil if resource.request
208
+ resource.response.body = nil if resource.response
209
+ else
210
+ resource.request.body = filter_body(resource.request.body, config['sanitizedBodyFields']) if resource.request
211
+ resource.response.body = filter_body(resource.response.body, config['sanitizedBodyFields']) if resource.response
212
+ end
213
+
214
+ # Filter the query params
215
+ resource.request.url = filter_url(resource.request.url, config['sanitizedQueryParams'])
216
+
217
+ # Record this event as filtered in case of retry
218
+ resource.filtered = true
219
+
220
+ resource
221
+ end
222
+
223
+ def contains_error(resource)
224
+ if !resource.response.nil? && resource.response.status_code >= 400
225
+ return true
226
+ end
227
+ false
228
+ end
229
+
230
+ def filter_url(url, config)
231
+ uri = URI(url)
232
+ if uri.query
233
+ query = CGI::parse(uri.query)
234
+ query.keys.each{|k| query[k] = 'xxx' if contains_string(config, k)}
235
+ uri.query = URI.encode_www_form(query)
236
+ return uri.to_s
237
+ end
238
+ url
239
+ rescue Exception => e
240
+ error "Error filtering url", e
241
+ url
242
+ end
243
+
244
+ def filter_headers(headers, names)
245
+ headers.each { |h| headers[h[0]] = 'xxx' if contains_string(names, h[0]) }
246
+ end
247
+
248
+ def filter_body(body, fields)
249
+ return nil if body.nil?
250
+ json = JSON.parse(body)
251
+ JSON.fast_generate(mask_object_fields(json, fields))
252
+ rescue Exception => e
253
+ body
254
+ end
255
+
256
+ def mask_object_fields(obj, fields)
257
+ field_names = fields.map {|f| f['value'] }
258
+ case obj
259
+ when Hash
260
+ obj.keys.each do |k|
261
+ if contains_string(field_names, k)
262
+ obj[k] = 'xxx'
263
+ else
264
+ obj[k] = mask_object_fields(obj[k], fields)
265
+ end
266
+ end
267
+ when Array
268
+ obj.each_index do |i|
269
+ obj[i] = mask_object_fields(obj[i], fields) if (obj[i].is_a?(Hash) || obj[i].is_a?(Array))
270
+ end
271
+ end
272
+ obj
273
+ end
274
+
275
+ # Get the configuration for a host, either use a defined api or the default account config
276
+ def get_configuration_for_host(host)
277
+ api_config = api_configurations.select{ |api| contains_string(api['hosts'], host)}[0]
278
+ return api_config['configuration'] if api_config
279
+ account_api_configuration
280
+ end
281
+
282
+ # Parse out the host blacklist
283
+ def get_account_host_blacklist
284
+ account_api_configuration['hostBlacklist']
285
+ end
286
+
287
+ # Do a case insensitive search for a string in array
288
+ def contains_string(arr, a)
289
+ !arr.select { |b| b.casecmp(a) == 0 }.empty?
290
+ end
291
+
292
+ # Return 'accountApiConfiguration' or empty object
293
+ def account_api_configuration
294
+ return {} if config.agentConfig.nil? or config.agentConfig['accountApiConfiguration'].nil?
295
+ config.agentConfig['accountApiConfiguration']
296
+ end
297
+
298
+ # Return the 'apis' or empty object
299
+ def api_configurations
300
+ return {} if config.agentConfig.nil? or config.agentConfig['apis'].nil?
301
+ config.agentConfig['apis']
302
+ end
303
+
304
+ def host_blacklisted(event)
305
+ blacklist = get_account_host_blacklist
306
+ return false if blacklist.nil?
307
+ is_blacklisted = false
308
+ unless event.request.nil? || event.request.url.nil?
309
+ url = URI(event.request.url)
310
+ blacklist.each do |host|
311
+ is_blacklisted = true if url.host == host
312
+ is_blacklisted = true if url.host.end_with? '.'+host
313
+ end
314
+ end
315
+ is_blacklisted
316
+ end
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,110 @@
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
+ # rubocop:disable all
22
+ module Util
23
+ # From https://github.com/rails/rails/blob/v5.2.0/activesupport/lib/active_support/inflector/methods.rb#L254-L332
24
+ module Inflector
25
+ extend self
26
+
27
+ #
28
+ # Tries to find a constant with the name specified in the argument string.
29
+ #
30
+ # constantize('Module') # => Module
31
+ # constantize('Foo::Bar') # => Foo::Bar
32
+ #
33
+ # The name is assumed to be the one of a top-level constant, no matter
34
+ # whether it starts with "::" or not. No lexical context is taken into
35
+ # account:
36
+ #
37
+ # C = 'outside'
38
+ # module M
39
+ # C = 'inside'
40
+ # C # => 'inside'
41
+ # constantize('C') # => 'outside', same as ::C
42
+ # end
43
+ #
44
+ # NameError is raised when the name is not in CamelCase or the constant is
45
+ # unknown.
46
+ def constantize(camel_cased_word)
47
+ names = camel_cased_word.split("::".freeze)
48
+
49
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
50
+ Object.const_get(camel_cased_word) if names.empty?
51
+
52
+ # Remove the first blank element in case of '::ClassName' notation.
53
+ names.shift if names.size > 1 && names.first.empty?
54
+
55
+ names.inject(Object) do |constant, name|
56
+ if constant == Object
57
+ constant.const_get(name)
58
+ else
59
+ candidate = constant.const_get(name)
60
+ next candidate if constant.const_defined?(name, false)
61
+ next candidate unless Object.const_defined?(name)
62
+
63
+ # Go down the ancestors to check if it is owned directly. The check
64
+ # stops when we reach Object or the end of ancestors tree.
65
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
66
+ break const if ancestor == Object
67
+ break ancestor if ancestor.const_defined?(name, false)
68
+ const
69
+ end
70
+
71
+ # owner is in Object, so raise
72
+ constant.const_get(name, false)
73
+ end
74
+ end
75
+ end
76
+
77
+ # Tries to find a constant with the name specified in the argument string.
78
+ #
79
+ # safe_constantize('Module') # => Module
80
+ # safe_constantize('Foo::Bar') # => Foo::Bar
81
+ #
82
+ # The name is assumed to be the one of a top-level constant, no matter
83
+ # whether it starts with "::" or not. No lexical context is taken into
84
+ # account:
85
+ #
86
+ # C = 'outside'
87
+ # module M
88
+ # C = 'inside'
89
+ # C # => 'inside'
90
+ # safe_constantize('C') # => 'outside', same as ::C
91
+ # end
92
+ #
93
+ # +nil+ is returned when the name is not in CamelCase or the constant (or
94
+ # part of it) is unknown.
95
+ #
96
+ # safe_constantize('blargle') # => nil
97
+ # safe_constantize('UnknownModule') # => nil
98
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
99
+ def safe_constantize(camel_cased_word)
100
+ constantize(camel_cased_word)
101
+ rescue NameError => e
102
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
103
+ e.name.to_s == camel_cased_word.to_s)
104
+ rescue ArgumentError => e
105
+ raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match(e.message)
106
+ end
107
+ end
108
+ end
109
+ # rubocop:enable all
110
+ end
@@ -0,0 +1,65 @@
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 Util
22
+ # @api private
23
+ class LruCache
24
+ def initialize(max_size = 512, &block)
25
+ @max_size = max_size
26
+ @data = Hash.new(&block)
27
+ @mutex = Mutex.new
28
+ end
29
+
30
+ def [](key)
31
+ @mutex.synchronize do
32
+ val = @data[key]
33
+ return unless val
34
+ add(key, val)
35
+ val
36
+ end
37
+ end
38
+
39
+ def []=(key, val)
40
+ @mutex.synchronize do
41
+ add(key, val)
42
+ end
43
+ end
44
+
45
+ def length
46
+ @data.length
47
+ end
48
+
49
+ def to_a
50
+ @data.to_a
51
+ end
52
+
53
+ private
54
+
55
+ def add(key, val)
56
+ @data.delete(key)
57
+ @data[key] = val
58
+
59
+ return unless @data.length > @max_size
60
+
61
+ @data.delete(@data.first[0])
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,52 @@
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 Util
22
+ # @api private
23
+
24
+ # Usage example:
25
+ # Throttle.new(5) { thing to only do once per 5 secs }
26
+ class Throttle
27
+ def initialize(buffer_secs, &block)
28
+ @buffer_secs = buffer_secs
29
+ @block = block
30
+ end
31
+
32
+ def call
33
+ if @last_call && seconds_since_last_call < @buffer_secs
34
+ return @last_result
35
+ end
36
+
37
+ @last_call = now
38
+ @last_result = @block.call
39
+ end
40
+
41
+ private
42
+
43
+ def now
44
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
+ end
46
+
47
+ def seconds_since_last_call
48
+ now - @last_call
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/hoss/util.rb ADDED
@@ -0,0 +1,54 @@
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 Util
23
+ def self.micros(target = Time.now)
24
+ utc = target.utc
25
+ utc.to_i * 1_000_000 + utc.usec
26
+ end
27
+
28
+ def self.monotonic_micros
29
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
30
+ end
31
+
32
+ def self.git_sha
33
+ sha = `git rev-parse --verify HEAD 2>&1`.chomp
34
+ $?&.success? ? sha : nil
35
+ end
36
+
37
+ def self.hex_to_bits(str)
38
+ str.hex.to_s(2).rjust(str.size * 4, '0')
39
+ end
40
+
41
+ def self.reverse_merge!(first, *others)
42
+ others.reduce(first) do |curr, other|
43
+ curr.merge!(other) { |_, _, new| new }
44
+ end
45
+ end
46
+
47
+ def self.truncate(value, max_length: 1024)
48
+ return unless value
49
+ return value if value.length <= max_length
50
+
51
+ value[0...(max_length - 1)] + '…'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
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
+ VERSION = '1.0.1'
22
+ end