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 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