hoss-agent 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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