elastic-apm 4.4.0 → 4.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bd0b20acebcfa91bc6e5d4e580c6a1a5f8da35b06152a4fe949c76380667e58
4
- data.tar.gz: 85935357d8a1831233cb8ab837564b38ff800e29a0cf1891fd4ba6263bb1a0d2
3
+ metadata.gz: c396bc981217532f2d6775de89e9a2aa7a1bcd501100588bbd700d0f4f2eb590
4
+ data.tar.gz: 94f859585a09fc99f02a4bbd261193e0ed1f74bdb9463968692a16f277b1e4f0
5
5
  SHA512:
6
- metadata.gz: 6eabaee4c14440607bdc5b5a0e53fd8b3844e0756a16cf6ef0da4b2a5431e7c1b441d0ef6329baf7d89f165f44031aa6a8396541930576acf54b254a787994cc
7
- data.tar.gz: e601985643738456350d49ba9c261ccc17a0259651830230cf353d20c241403846f73c3ce371ab5b6f1a97b1223ac797ddfc1b764333e51055ff53104becd212
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('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?(?:benchmark\\W+)?tests(?:\\W+please)?.*')
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.')
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.2.16.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.1.17.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 6a22f7bf8fef1a52530a9c9781a9d374ad07bbbef0d3d8e2af0ff5cbead0dfd5
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.2.14.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
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'
@@ -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. Theis is an opt-in field, as they are used are for a multitude of things.
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 = 'elastic-apm'
24
- spec.version = ElasticAPM::VERSION
25
- spec.authors = ['Mikkel Malmberg']
26
- spec.email = ['mikkel@elastic.co']
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 = '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'
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|
@@ -26,10 +26,9 @@ module ElasticAPM
26
26
  class Socket
27
27
  def initialize(req)
28
28
  @remote_addr = req.env['REMOTE_ADDR']
29
- @encrypted = req.scheme == 'https'
30
29
  end
31
30
 
32
- attr_reader :remote_addr, :encrypted
31
+ attr_reader :remote_addr
33
32
  end
34
33
  end
35
34
  end
@@ -25,7 +25,7 @@ module ElasticAPM
25
25
  # class MyThing
26
26
  # include Fields
27
27
  # field :name
28
- # field :address, optional: true
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
- self.send(:"#{key}=", value)
52
+ send(:"#{key}=", value)
40
53
  end
41
54
 
42
55
  super()
43
56
  end
44
57
 
45
58
  def empty?
46
- self.class.fields.each do |key|
47
- next if send(key)
48
- next if optionals.include?(key)
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
- false
64
+ true
54
65
  end
55
66
 
56
67
  def to_h
57
- self.class.fields.each_with_object({}) do |key, fields|
58
- fields[key] = send(key)
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 optionals
65
- self.class.optionals
75
+ def schema
76
+ self.class.schema
66
77
  end
67
78
  end
68
79
 
69
80
  module ClassMethods
70
- def field(key, optional: false)
71
- attr_accessor(key)
81
+ def field(key, default: nil)
82
+ field = Field.new(key, default: default)
83
+ schema[key] = field
72
84
 
73
- fields.push(key)
74
- optionals.push(key) if optional
85
+ attr_accessor(key)
75
86
  end
76
87
 
77
- attr_reader :fields, :optionals
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(:@fields, [])
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 || Util.git_sha
52
+ @version = @config.service_version
53
53
  end
54
54
 
55
55
  attr_reader :name, :node_name, :environment, :agent, :framework,
@@ -50,7 +50,8 @@ module ElasticAPM
50
50
  private
51
51
 
52
52
  def detect_hostname
53
- `hostname`.chomp
53
+ Socket.gethostname.chomp
54
+ rescue
54
55
  end
55
56
  end
56
57
  end
@@ -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
 
@@ -57,6 +57,8 @@ module ElasticAPM
57
57
  @mutex.synchronize do
58
58
  collected = @value
59
59
 
60
+ return nil if collected.is_a?(Float) && !collected.finite?
61
+
60
62
  @value = initial_value if reset_on_collect?
61
63
 
62
64
  return nil if reset_on_collect? && collected == 0
@@ -33,8 +33,8 @@ module ElasticAPM
33
33
  class Service
34
34
  include Fields
35
35
 
36
- field :name
37
- field :type
36
+ field :name, default: ''
37
+ field :type, default: ''
38
38
  field :resource
39
39
  end
40
40
 
@@ -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
- service: { resource: "#{SUBTYPE}/#{config.region}" },
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: operation_name,
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
- @events = {}
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
@@ -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
- service: { resource: resource },
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
  )
@@ -77,8 +77,7 @@ module ElasticAPM
77
77
  return unless socket
78
78
 
79
79
  {
80
- remote_addr: socket.remote_addr,
81
- encrypted: socket.encrypted
80
+ remote_addr: socket.remote_addr
82
81
  }
83
82
  end
84
83
 
@@ -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
- unless destination.service&.empty?
104
- base[:service] = destination.service.to_h
103
+ if (service = destination.service) && !service.empty?
104
+ base[:service] = service.to_h
105
105
  end
106
106
 
107
- unless destination.cloud&.empty?
108
- base[:cloud] = destination.cloud.to_h
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
- HTTP::Request::USER_AGENT,
41
- [
42
- service.runtime.name,
43
- service.runtime.version
44
- ].join('/')
45
- ].join(' ')
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
@@ -18,5 +18,5 @@
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module ElasticAPM
21
- VERSION = '4.4.0'
21
+ VERSION = '4.5.0'
22
22
  end
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.0
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-10-25 00:00:00.000000000 Z
11
+ date: 2021-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby