elastic-apm 4.4.0 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ci/Jenkinsfile +1 -1
- data/.ci/docker/jruby/11-jdk/Dockerfile +1 -1
- data/.ci/docker/jruby/12-jdk/Dockerfile +1 -1
- data/.ci/docker/jruby/13-jdk/Dockerfile +1 -1
- data/.ci/docker/jruby/7-jdk/Dockerfile +1 -1
- data/.ci/docker/jruby/8-jdk/Dockerfile +1 -1
- data/CHANGELOG.asciidoc +16 -0
- data/Gemfile +3 -4
- data/docs/configuration.asciidoc +2 -2
- data/elastic-apm.gemspec +8 -8
- data/lib/elastic_apm/context/request/socket.rb +1 -2
- data/lib/elastic_apm/fields.rb +29 -19
- data/lib/elastic_apm/instrumenter.rb +13 -17
- data/lib/elastic_apm/metadata/service_info.rb +1 -1
- data/lib/elastic_apm/metadata/system_info.rb +2 -1
- data/lib/elastic_apm/metrics/cpu_mem_set.rb +3 -0
- data/lib/elastic_apm/metrics/metric.rb +2 -0
- data/lib/elastic_apm/span/context/destination.rb +2 -2
- data/lib/elastic_apm/span.rb +8 -16
- data/lib/elastic_apm/spies/dynamo_db.rb +8 -3
- data/lib/elastic_apm/spies/mongo.rb +11 -3
- data/lib/elastic_apm/spies/s3.rb +10 -1
- data/lib/elastic_apm/transport/serializers/context_serializer.rb +1 -2
- data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +6 -2
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +4 -4
- data/lib/elastic_apm/transport/user_agent.rb +12 -6
- data/lib/elastic_apm/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c396bc981217532f2d6775de89e9a2aa7a1bcd501100588bbd700d0f4f2eb590
|
4
|
+
data.tar.gz: 94f859585a09fc99f02a4bbd261193e0ed1f74bdb9463968692a16f277b1e4f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae66a68c4651f695361b2028bf76132e8bc257fc141d3ad2872786bbab10b8e19bc3609a84478de53e56ad0c15695ad7debbcf62c4af8997833d0cf7cc7f6d23
|
7
|
+
data.tar.gz: c6b9fd6f80de4139cac0dd040e45d56f869eb2ff8b3db38df376e898490b8c2a5523a9bb3f1ba73a8583356ae5647ec67835b994597dc3db8d72c62ee506794f
|
data/.ci/Jenkinsfile
CHANGED
@@ -39,7 +39,7 @@ pipeline {
|
|
39
39
|
quietPeriod(10)
|
40
40
|
}
|
41
41
|
triggers {
|
42
|
-
issueCommentTrigger(
|
42
|
+
issueCommentTrigger("(${obltGitHubComments()}|^run benchmark tests)")
|
43
43
|
}
|
44
44
|
parameters {
|
45
45
|
booleanParam(name: 'Run_As_Master_Branch', defaultValue: false, description: 'Allow to run any steps on a PR, some steps normally only run on master branch.')
|
data/CHANGELOG.asciidoc
CHANGED
@@ -35,6 +35,22 @@ endif::[]
|
|
35
35
|
[[release-notes-4.x]]
|
36
36
|
=== Ruby Agent version 4.x
|
37
37
|
|
38
|
+
[[release-notes-4.5.0]]
|
39
|
+
==== 4.5.0
|
40
|
+
|
41
|
+
[float]
|
42
|
+
===== Changed
|
43
|
+
|
44
|
+
- Stop collecting the field `http.request.socket.encrypted` {pull}1181[#1181]
|
45
|
+
|
46
|
+
[float]
|
47
|
+
===== Fixed
|
48
|
+
|
49
|
+
- Fixed MongoDB spy thread safety {pull}1202[#1202]
|
50
|
+
- Fixed span context fields for DynamoDB instrumentation {pull}1178[#1178]
|
51
|
+
- Fixed span context fields for S3 instrumentation {pull}1179[#1179]
|
52
|
+
- Update user agent info to match spec {pull}1182[#1182]
|
53
|
+
|
38
54
|
[[release-notes-4.4.0]]
|
39
55
|
==== 4.4.0
|
40
56
|
|
data/Gemfile
CHANGED
@@ -62,10 +62,6 @@ gem 'sucker_punch', '~> 2.0', require: nil
|
|
62
62
|
gem 'yard', require: nil
|
63
63
|
gem 'yarjuf'
|
64
64
|
|
65
|
-
# See issue #6547 in the JRuby repo. When that bug is fixed,
|
66
|
-
# we can use the latest version of the i18n gem.
|
67
|
-
gem 'i18n', '< 1.8.8'
|
68
|
-
|
69
65
|
## Install Framework
|
70
66
|
GITHUB_REPOS = {
|
71
67
|
'grape' => 'ruby-grape/grape',
|
@@ -101,6 +97,9 @@ if frameworks_versions.key?('rails')
|
|
101
97
|
end
|
102
98
|
|
103
99
|
if RUBY_PLATFORM == 'java'
|
100
|
+
# See issue #6547 in the JRuby repo. It is fixed in JRuby 9.3
|
101
|
+
gem 'i18n', '< 1.8.8' if JRUBY_VERSION < '9.3'
|
102
|
+
|
104
103
|
case rails = frameworks_versions['rails']
|
105
104
|
when 'main'
|
106
105
|
gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter', glob: 'activerecord-jdbcsqlite3-adapter/*.gemspec'
|
data/docs/configuration.asciidoc
CHANGED
@@ -240,7 +240,7 @@ NOTE: This feature requires APM Server and Kibana >= 7.3.
|
|
240
240
|
<<dynamic-configuration, image:./images/dynamic-config.svg[] >>
|
241
241
|
|
242
242
|
|============
|
243
|
-
| Environment | `Config` key | Default | Example
|
243
|
+
| Environment | `Config` key | Default | Example
|
244
244
|
| `ELASTIC_APM_CAPTURE_BODY` | `capture_body` | `"off"` | `"all"`
|
245
245
|
|============
|
246
246
|
|
@@ -502,7 +502,7 @@ Use this option to ignore certain URL patterns such as healthchecks or admin sec
|
|
502
502
|
| `ELASTIC_APM_INSTRUMENTED_RAKE_TASKS` | `instrumented_rake_tasks` | `[]` | `['my_task']`
|
503
503
|
|============
|
504
504
|
|
505
|
-
Elastic APM can instrument your Rake tasks.
|
505
|
+
Elastic APM can instrument your Rake tasks. This is an opt-in field, as they are used are for a multitude of things.
|
506
506
|
|
507
507
|
[float]
|
508
508
|
[[config-log-ecs-formatting]]
|
data/elastic-apm.gemspec
CHANGED
@@ -20,15 +20,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
20
20
|
require 'elastic_apm/version'
|
21
21
|
|
22
22
|
Gem::Specification.new do |spec|
|
23
|
-
spec.name
|
24
|
-
spec.version
|
25
|
-
spec.authors
|
26
|
-
spec.email
|
23
|
+
spec.name = 'elastic-apm'
|
24
|
+
spec.version = ElasticAPM::VERSION
|
25
|
+
spec.authors = ['Mikkel Malmberg']
|
26
|
+
spec.email = ['mikkel@elastic.co']
|
27
27
|
|
28
|
-
spec.summary
|
29
|
-
spec.homepage
|
30
|
-
spec.metadata
|
31
|
-
spec.license
|
28
|
+
spec.summary = 'The official Elastic APM agent for Ruby'
|
29
|
+
spec.homepage = 'https://github.com/elastic/apm-agent-ruby'
|
30
|
+
spec.metadata = { 'source_code_uri' => 'https://github.com/elastic/apm-agent-ruby' }
|
31
|
+
spec.license = 'Apache-2.0'
|
32
32
|
spec.required_ruby_version = ">= 2.3.0"
|
33
33
|
|
34
34
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
data/lib/elastic_apm/fields.rb
CHANGED
@@ -25,7 +25,7 @@ module ElasticAPM
|
|
25
25
|
# class MyThing
|
26
26
|
# include Fields
|
27
27
|
# field :name
|
28
|
-
# field :address,
|
28
|
+
# field :address, default: 'There'
|
29
29
|
# end
|
30
30
|
#
|
31
31
|
# MyThing.new(name: 'AJ').to_h
|
@@ -33,56 +33,66 @@ module ElasticAPM
|
|
33
33
|
# MyThing.new().empty?
|
34
34
|
# # => true
|
35
35
|
module Fields
|
36
|
+
class Field
|
37
|
+
def initialize(key, default: nil)
|
38
|
+
@key = key
|
39
|
+
@default = default
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :key, :default
|
43
|
+
end
|
44
|
+
|
36
45
|
module InstanceMethods
|
37
46
|
def initialize(**attrs)
|
47
|
+
schema.each do |key, field|
|
48
|
+
send(:"#{key}=", field.default)
|
49
|
+
end
|
50
|
+
|
38
51
|
attrs.each do |key, value|
|
39
|
-
|
52
|
+
send(:"#{key}=", value)
|
40
53
|
end
|
41
54
|
|
42
55
|
super()
|
43
56
|
end
|
44
57
|
|
45
58
|
def empty?
|
46
|
-
self.class.
|
47
|
-
next if send(key)
|
48
|
-
|
49
|
-
|
50
|
-
return true
|
59
|
+
self.class.schema.each do |key, field|
|
60
|
+
next if send(key).nil?
|
61
|
+
return false
|
51
62
|
end
|
52
63
|
|
53
|
-
|
64
|
+
true
|
54
65
|
end
|
55
66
|
|
56
67
|
def to_h
|
57
|
-
|
58
|
-
|
68
|
+
schema.each_with_object({}) do |(key, field), hsh|
|
69
|
+
hsh[key] = send(key)
|
59
70
|
end
|
60
71
|
end
|
61
72
|
|
62
73
|
private
|
63
74
|
|
64
|
-
def
|
65
|
-
self.class.
|
75
|
+
def schema
|
76
|
+
self.class.schema
|
66
77
|
end
|
67
78
|
end
|
68
79
|
|
69
80
|
module ClassMethods
|
70
|
-
def field(key,
|
71
|
-
|
81
|
+
def field(key, default: nil)
|
82
|
+
field = Field.new(key, default: default)
|
83
|
+
schema[key] = field
|
72
84
|
|
73
|
-
|
74
|
-
optionals.push(key) if optional
|
85
|
+
attr_accessor(key)
|
75
86
|
end
|
76
87
|
|
77
|
-
attr_reader :
|
88
|
+
attr_reader :schema
|
78
89
|
end
|
79
90
|
|
80
91
|
def self.included(cls)
|
81
92
|
cls.extend(ClassMethods)
|
82
93
|
cls.include(InstanceMethods)
|
83
94
|
|
84
|
-
cls.instance_variable_set(:@
|
85
|
-
cls.instance_variable_set(:@optionals, [])
|
95
|
+
cls.instance_variable_set(:@schema, {})
|
86
96
|
end
|
87
97
|
end
|
88
98
|
end
|
@@ -179,7 +179,8 @@ module ElasticAPM
|
|
179
179
|
context: nil,
|
180
180
|
trace_context: nil,
|
181
181
|
parent: nil,
|
182
|
-
sync: nil
|
182
|
+
sync: nil,
|
183
|
+
exit_span: nil
|
183
184
|
)
|
184
185
|
|
185
186
|
transaction =
|
@@ -197,6 +198,15 @@ module ElasticAPM
|
|
197
198
|
|
198
199
|
parent ||= (current_span || current_transaction)
|
199
200
|
|
201
|
+
# To not mess with breakdown metric stats, exit spans MUST not add
|
202
|
+
# sub-spans unless they share the same type and subtype.
|
203
|
+
if parent && parent.is_a?(Span) && parent.exit_span?
|
204
|
+
if parent.type != type || parent.subtype != subtype
|
205
|
+
debug "Skipping new span '#{name}' as its parent is an exit_span"
|
206
|
+
return
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
200
210
|
span = Span.new(
|
201
211
|
name: name,
|
202
212
|
subtype: subtype,
|
@@ -207,7 +217,8 @@ module ElasticAPM
|
|
207
217
|
type: type,
|
208
218
|
context: context,
|
209
219
|
stacktrace_builder: stacktrace_builder,
|
210
|
-
sync: sync
|
220
|
+
sync: sync,
|
221
|
+
exit_span: exit_span
|
211
222
|
)
|
212
223
|
|
213
224
|
if backtrace && transaction.span_frames_min_duration
|
@@ -279,24 +290,9 @@ module ElasticAPM
|
|
279
290
|
'transaction.type': transaction.type
|
280
291
|
}
|
281
292
|
|
282
|
-
@metrics.get(:transaction).timer(
|
283
|
-
:'transaction.duration.sum.us',
|
284
|
-
tags: tags, reset_on_collect: true
|
285
|
-
).update(transaction.duration)
|
286
|
-
|
287
|
-
@metrics.get(:transaction).counter(
|
288
|
-
:'transaction.duration.count',
|
289
|
-
tags: tags, reset_on_collect: true
|
290
|
-
).inc!
|
291
|
-
|
292
293
|
return unless transaction.sampled?
|
293
294
|
return unless transaction.breakdown_metrics
|
294
295
|
|
295
|
-
@metrics.get(:breakdown).counter(
|
296
|
-
:'transaction.breakdown.count',
|
297
|
-
tags: tags, reset_on_collect: true
|
298
|
-
).inc!
|
299
|
-
|
300
296
|
span_tags = tags.merge('span.type': 'app')
|
301
297
|
|
302
298
|
@metrics.get(:breakdown).timer(
|
@@ -49,7 +49,7 @@ module ElasticAPM
|
|
49
49
|
)
|
50
50
|
@language = Language.new(name: 'ruby', version: RUBY_VERSION)
|
51
51
|
@runtime = lookup_runtime
|
52
|
-
@version = @config.service_version
|
52
|
+
@version = @config.service_version
|
53
53
|
end
|
54
54
|
|
55
55
|
attr_reader :name, :node_name, :environment, :agent, :framework,
|
@@ -116,6 +116,9 @@ module ElasticAPM
|
|
116
116
|
process_cpu_usage =
|
117
117
|
current.process_cpu_usage - previous.process_cpu_usage
|
118
118
|
|
119
|
+
# No change / avoid dividing by 0
|
120
|
+
return [0, 0] if system_cpu_total == 0
|
121
|
+
|
119
122
|
cpu_usage_pct = system_cpu_usage.to_f / system_cpu_total
|
120
123
|
cpu_process_pct = process_cpu_usage.to_f / system_cpu_total
|
121
124
|
|
data/lib/elastic_apm/span.rb
CHANGED
@@ -49,7 +49,8 @@ module ElasticAPM
|
|
49
49
|
action: nil,
|
50
50
|
context: nil,
|
51
51
|
stacktrace_builder: nil,
|
52
|
-
sync: nil
|
52
|
+
sync: nil,
|
53
|
+
exit_span: false
|
53
54
|
)
|
54
55
|
@name = name
|
55
56
|
|
@@ -68,6 +69,8 @@ module ElasticAPM
|
|
68
69
|
|
69
70
|
@context = context || Span::Context.new(sync: sync)
|
70
71
|
@stacktrace_builder = stacktrace_builder
|
72
|
+
|
73
|
+
@exit_span = exit_span
|
71
74
|
end
|
72
75
|
# rubocop:enable Metrics/ParameterLists
|
73
76
|
|
@@ -75,6 +78,7 @@ module ElasticAPM
|
|
75
78
|
|
76
79
|
attr_accessor(
|
77
80
|
:action,
|
81
|
+
:exit_span,
|
78
82
|
:name,
|
79
83
|
:original_backtrace,
|
80
84
|
:outcome,
|
@@ -93,6 +97,8 @@ module ElasticAPM
|
|
93
97
|
:transaction
|
94
98
|
)
|
95
99
|
|
100
|
+
alias :exit_span? :exit_span
|
101
|
+
|
96
102
|
# life cycle
|
97
103
|
|
98
104
|
def start(clock_start = Util.monotonic_micros)
|
@@ -107,17 +113,6 @@ module ElasticAPM
|
|
107
113
|
@parent.child_stopped
|
108
114
|
@self_time = @duration - child_durations.duration
|
109
115
|
|
110
|
-
if exit_span?
|
111
|
-
context.destination ||= Context::Destination.new
|
112
|
-
context.destination.service ||= Context::Destination::Service.new
|
113
|
-
context.destination.service.resource ||= (subtype || type)
|
114
|
-
|
115
|
-
# Deprecated fields but required by some versions of APM Server, so
|
116
|
-
# we auto-infer them from existing fields
|
117
|
-
context.destination.service.name ||= (subtype || type)
|
118
|
-
context.destination.service.type ||= type
|
119
|
-
end
|
120
|
-
|
121
116
|
self
|
122
117
|
end
|
123
118
|
|
@@ -158,6 +153,7 @@ module ElasticAPM
|
|
158
153
|
" type:#{type.inspect}" \
|
159
154
|
" subtype:#{subtype.inspect}" \
|
160
155
|
" action:#{action.inspect}" \
|
156
|
+
" exit_span:#{exit_span.inspect}" \
|
161
157
|
'>'
|
162
158
|
end
|
163
159
|
|
@@ -180,9 +176,5 @@ module ElasticAPM
|
|
180
176
|
|
181
177
|
duration >= min_duration
|
182
178
|
end
|
183
|
-
|
184
|
-
def exit_span?
|
185
|
-
context.destination || context.db || context.message || context.http
|
186
|
-
end
|
187
179
|
end
|
188
180
|
end
|
@@ -22,9 +22,9 @@ module ElasticAPM
|
|
22
22
|
module Spies
|
23
23
|
# @api private
|
24
24
|
class DynamoDBSpy
|
25
|
-
NAME = 'dynamodb'
|
26
25
|
TYPE = 'db'
|
27
26
|
SUBTYPE = 'dynamodb'
|
27
|
+
ACTION = 'query'
|
28
28
|
|
29
29
|
@@formatted_op_names = Concurrent::Map.new
|
30
30
|
|
@@ -63,7 +63,12 @@ module ElasticAPM
|
|
63
63
|
statement: params[:key_condition_expression]
|
64
64
|
},
|
65
65
|
destination: {
|
66
|
-
|
66
|
+
address: config.endpoint.host,
|
67
|
+
port: config.endpoint.port,
|
68
|
+
service: {
|
69
|
+
name: SUBTYPE,
|
70
|
+
type: TYPE,
|
71
|
+
resource: SUBTYPE },
|
67
72
|
cloud: { region: config.region }
|
68
73
|
}
|
69
74
|
)
|
@@ -72,7 +77,7 @@ module ElasticAPM
|
|
72
77
|
ElasticAPM::Spies::DynamoDBSpy.span_name(operation_name, params),
|
73
78
|
TYPE,
|
74
79
|
subtype: SUBTYPE,
|
75
|
-
action:
|
80
|
+
action: ACTION,
|
76
81
|
context: context
|
77
82
|
) do
|
78
83
|
ElasticAPM::Spies::DynamoDBSpy.without_net_http do
|
@@ -35,8 +35,16 @@ module ElasticAPM
|
|
35
35
|
SUBTYPE = 'mongodb'
|
36
36
|
ACTION = 'query'
|
37
37
|
|
38
|
+
EVENT_KEY = :__elastic_instrumenter_mongo_events_key
|
39
|
+
|
40
|
+
class Collection
|
41
|
+
def events
|
42
|
+
Thread.current[EVENT_KEY] ||= {}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
38
46
|
def initialize
|
39
|
-
@
|
47
|
+
@collection = Collection.new
|
40
48
|
end
|
41
49
|
|
42
50
|
def started(event)
|
@@ -89,11 +97,11 @@ module ElasticAPM
|
|
89
97
|
context: build_context(event)
|
90
98
|
)
|
91
99
|
|
92
|
-
@events[event.operation_id] = span
|
100
|
+
@collection.events[event.operation_id] = span
|
93
101
|
end
|
94
102
|
|
95
103
|
def pop_event(event)
|
96
|
-
span = @events.delete(event.operation_id)
|
104
|
+
span = @collection.events.delete(event.operation_id)
|
97
105
|
return unless (curr = ElasticAPM.current_span)
|
98
106
|
|
99
107
|
curr == span && ElasticAPM.end_span
|
data/lib/elastic_apm/spies/s3.rb
CHANGED
@@ -87,8 +87,17 @@ module ElasticAPM
|
|
87
87
|
|
88
88
|
resource = "#{SUBTYPE}/#{bucket_name || 'unknown-bucket'}"
|
89
89
|
context = ElasticAPM::Span::Context.new(
|
90
|
+
db: {
|
91
|
+
instance: config.region,
|
92
|
+
type: SUBTYPE
|
93
|
+
},
|
90
94
|
destination: {
|
91
|
-
|
95
|
+
address: config.endpoint.host,
|
96
|
+
port: config.endpoint.port,
|
97
|
+
service: {
|
98
|
+
name: SUBTYPE,
|
99
|
+
type: TYPE,
|
100
|
+
resource: resource },
|
92
101
|
cloud: { region: region }
|
93
102
|
}
|
94
103
|
)
|
@@ -80,14 +80,18 @@ module ElasticAPM
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def build_system(system)
|
83
|
-
{
|
84
|
-
detected_hostname: keyword_field(system.detected_hostname),
|
83
|
+
base = {
|
85
84
|
configured_hostname: keyword_field(system.configured_hostname),
|
86
85
|
architecture: keyword_field(system.architecture),
|
87
86
|
platform: keyword_field(system.platform),
|
88
87
|
kubernetes: keyword_object(system.kubernetes),
|
89
88
|
container: keyword_object(system.container)
|
90
89
|
}
|
90
|
+
if system.detected_hostname
|
91
|
+
base[:detected_hostname] = keyword_field(system.detected_hostname)
|
92
|
+
end
|
93
|
+
|
94
|
+
base
|
91
95
|
end
|
92
96
|
|
93
97
|
def build_cloud(cloud)
|
@@ -100,12 +100,12 @@ module ElasticAPM
|
|
100
100
|
port: destination.port
|
101
101
|
}
|
102
102
|
|
103
|
-
|
104
|
-
base[:service] =
|
103
|
+
if (service = destination.service) && !service.empty?
|
104
|
+
base[:service] = service.to_h
|
105
105
|
end
|
106
106
|
|
107
|
-
|
108
|
-
base[:cloud] =
|
107
|
+
if (cloud = destination.cloud) && !cloud.empty?
|
108
|
+
base[:cloud] = cloud.to_h
|
109
109
|
end
|
110
110
|
|
111
111
|
base
|
@@ -37,12 +37,18 @@ module ElasticAPM
|
|
37
37
|
|
38
38
|
[
|
39
39
|
"elastic-apm-ruby/#{@version}",
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
formatted_service_info(service)
|
41
|
+
].compact.join(' ')
|
42
|
+
end
|
43
|
+
|
44
|
+
def formatted_service_info(service)
|
45
|
+
if service.name
|
46
|
+
"(#{[
|
47
|
+
service.name,
|
48
|
+
service.version
|
49
|
+
].compact.join(' ')
|
50
|
+
})"
|
51
|
+
end
|
46
52
|
end
|
47
53
|
end
|
48
54
|
end
|
data/lib/elastic_apm/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elastic-apm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikkel Malmberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|