prefab-cloud-ruby 1.0.1 → 1.1.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: eb91b8be5a01c3bab3312d277a27117c0449713623e1605863486c3809cff0cd
4
- data.tar.gz: 3deaeb9fcb458ef461771865463c5163f8b5c6833a45d1e7af4ec3474f5d04bd
3
+ metadata.gz: e3a735317d9c319aace8ce4cbc99c28a99ed83d23bda5a401c781a6e4e370c51
4
+ data.tar.gz: c9299c6f257d48b07f90a427d2e6b80b2db8c1daf5e3be5c92df34b3c13d76c3
5
5
  SHA512:
6
- metadata.gz: 2555fc0364fef33c11e3e7866a00ab5df766e859c73baedbb39712ddceb07ec96d0216c5e79dd7336e40951de85b773100864356fd24423f3257a8e3ab40e073
7
- data.tar.gz: 44c9813ede3c95db9820e0247c71c7e4a1e0e21b5981d074025baf546e5f3e7de6362744acedd831437e3b10ca747cdb73ec596ec1394b82ff7e70481ab57a52
6
+ metadata.gz: 722133d87e4a67ccce34e18d417b40e904da68e686b128badbde3d2a98a5b2b9e0a90f68c001de63cfc9877edf43982f4ffd59bc4dde0dcc6c10e43dc6a3cd83
7
+ data.tar.gz: 4673e84c936cf72f502402956b846ec14c8cbaaf90d2a90c794e0db4a352f8000cbb1477bd2d43abe685937879cdf6263dc63cfcf0218e7b15265876990c6a84
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.1.0 - 2023-09-18
6
+
7
+ - Add support for structured logging (#143)
8
+ - Ability to pass a hash of key/value context pairs to any of the user-facing log methods
9
+
10
+
3
11
  ## 1.0.1 - 2023-08-17
4
12
 
5
13
  - Bug fix for StringList w/ ExampleContextsAggregator (#141)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.0
data/lib/prefab/client.rb CHANGED
@@ -99,8 +99,8 @@ module Prefab
99
99
  resolver.on_update(&block)
100
100
  end
101
101
 
102
- def log_internal(level, msg, path = nil)
103
- log.log_internal msg, path, nil, level
102
+ def log_internal(level, msg, path = nil, **tags)
103
+ log.log_internal msg, path, nil, level, tags
104
104
  end
105
105
 
106
106
  def enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
@@ -54,7 +54,7 @@ module Prefab
54
54
  end
55
55
  )
56
56
 
57
- result = @client.post('/api/v1/context-shapes', shapes)
57
+ result = post('/api/v1/context-shapes', shapes)
58
58
 
59
59
  log_internal "Uploaded #{to_ship.values.size} shapes: #{result.status}"
60
60
  end
@@ -55,7 +55,7 @@ module Prefab
55
55
  summaries: summaries(to_ship)
56
56
  )
57
57
 
58
- result = @client.post('/api/v1/telemetry', events(summaries_proto))
58
+ result = post('/api/v1/telemetry', events(summaries_proto))
59
59
 
60
60
  log_internal "Uploaded #{to_ship.size} summaries: #{result.status}"
61
61
  end
@@ -45,7 +45,7 @@ module Prefab
45
45
  pool.post do
46
46
  log_internal "Flushing #{to_ship.size} examples"
47
47
 
48
- result = @client.post('/api/v1/telemetry', events(to_ship))
48
+ result = post('/api/v1/telemetry', events(to_ship))
49
49
 
50
50
  log_internal "Uploaded #{to_ship.size} examples: #{result.status}"
51
51
  end
@@ -25,6 +25,9 @@ module Prefab
25
25
 
26
26
  @data = Concurrent::Map.new
27
27
 
28
+ @last_data_sent = nil
29
+ @last_request = nil
30
+
28
31
  start_periodic_sync(sync_interval)
29
32
  end
30
33
 
@@ -55,7 +58,7 @@ module Prefab
55
58
  namespace: @client.namespace
56
59
  )
57
60
 
58
- result = @client.post('/api/v1/known-loggers', loggers)
61
+ result = post('/api/v1/known-loggers', loggers)
59
62
 
60
63
  log_internal "Uploaded #{to_ship.size} paths: #{result.status}"
61
64
  end
@@ -27,26 +27,26 @@ module Prefab
27
27
  @log_path_aggregator = log_path_aggregator
28
28
  end
29
29
 
30
- def add_internal(severity, message, progname, loc, &block)
30
+ def add_internal(severity, message, progname, loc, log_context={}, &block)
31
31
  path_loc = get_loc_path(loc)
32
32
  path = @prefix + path_loc
33
33
 
34
34
  @log_path_aggregator&.push(path_loc, severity)
35
35
 
36
- log(message, path, progname, severity, &block)
36
+ log(message, path, progname, severity, log_context, &block)
37
37
  end
38
38
 
39
- def log_internal(message, path, progname, severity, &block)
39
+ def log_internal(message, path, progname, severity, log_context={}, &block)
40
40
  path = if path
41
41
  "#{INTERNAL_PREFIX}.#{path}"
42
42
  else
43
43
  INTERNAL_PREFIX
44
44
  end
45
45
 
46
- log(message, path, progname, severity, &block)
46
+ log(message, path, progname, severity, log_context, &block)
47
47
  end
48
48
 
49
- def log(message, path, progname, severity)
49
+ def log(message, path, progname, severity, log_context={})
50
50
  severity ||= ::Logger::UNKNOWN
51
51
 
52
52
  return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
@@ -63,29 +63,29 @@ module Prefab
63
63
  end
64
64
 
65
65
  @logdev.write(
66
- format_message(format_severity(severity), Time.now, progname, message, path)
66
+ format_message(format_severity(severity), Time.now, progname, message, path, log_context)
67
67
  )
68
68
  true
69
69
  end
70
70
 
71
- def debug(progname = nil, &block)
72
- add_internal(DEBUG, nil, progname, caller_locations(1, 1)[0], &block)
71
+ def debug(progname = nil, **log_context, &block)
72
+ add_internal(DEBUG, nil, progname, caller_locations(1, 1)[0], log_context, &block)
73
73
  end
74
74
 
75
- def info(progname = nil, &block)
76
- add_internal(INFO, nil, progname, caller_locations(1, 1)[0], &block)
75
+ def info(progname = nil, **log_context, &block)
76
+ add_internal(INFO, nil, progname, caller_locations(1, 1)[0], log_context, &block)
77
77
  end
78
78
 
79
- def warn(progname = nil, &block)
80
- add_internal(WARN, nil, progname, caller_locations(1, 1)[0], &block)
79
+ def warn(progname = nil, **log_context, &block)
80
+ add_internal(WARN, nil, progname, caller_locations(1, 1)[0], log_context, &block)
81
81
  end
82
82
 
83
- def error(progname = nil, &block)
84
- add_internal(ERROR, nil, progname, caller_locations(1, 1)[0], &block)
83
+ def error(progname = nil, **log_context, &block)
84
+ add_internal(ERROR, nil, progname, caller_locations(1, 1)[0], log_context, &block)
85
85
  end
86
86
 
87
- def fatal(progname = nil, &block)
88
- add_internal(FATAL, nil, progname, caller_locations(1, 1)[0], &block)
87
+ def fatal(progname = nil, **log_context, &block)
88
+ add_internal(FATAL, nil, progname, caller_locations(1, 1)[0], log_context, &block)
89
89
  end
90
90
 
91
91
  def debug?
@@ -168,18 +168,17 @@ module Prefab
168
168
  path
169
169
  end
170
170
 
171
- def format_message(severity, datetime, progname, msg, path = nil)
171
+ def format_message(severity, datetime, progname, msg, path = nil, log_context={})
172
172
  formatter = (@formatter || @default_formatter)
173
173
 
174
- if formatter.arity == 5
175
- formatter.call(severity, datetime, progname, msg, path)
176
- else
177
- formatter.call(severity, datetime, join_path_and_progname(path, progname), msg)
178
- end
179
- end
180
-
181
- def join_path_and_progname(path, progname)
182
- (progname.nil? || progname.empty?) ? path : "#{progname}: #{path}"
174
+ formatter.call(
175
+ severity: severity,
176
+ datetime: datetime,
177
+ progname: progname,
178
+ path: path,
179
+ message: msg,
180
+ log_context: log_context
181
+ )
183
182
  end
184
183
  end
185
184
 
@@ -17,17 +17,23 @@ module Prefab
17
17
  attr_reader :prefab_envs
18
18
  attr_reader :collect_sync_interval
19
19
 
20
- DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
21
- "#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}\n"
20
+ DEFAULT_LOG_FORMATTER = proc { |data|
21
+ severity = data[:severity]
22
+ datetime = data[:datetime]
23
+ progname = data[:progname]
24
+ path = data[:path]
25
+ msg = data[:message]
26
+ log_context = data[:log_context]
27
+
28
+ progname = (progname.nil? || progname.empty?) ? path : "#{progname}: #{path}"
29
+
30
+ formatted_log_context = log_context.sort.map{|k, v| "#{k}=#{v}" }.join(" ")
31
+ "#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}#{log_context.any? ? " " + formatted_log_context : ""}\n"
22
32
  }
23
- JSON_LOG_FORMATTER = proc { |severity, datetime, progname, msg, path|
24
- {
25
- type: severity,
26
- time: datetime,
27
- progname: progname,
28
- message: msg,
29
- path: path
30
- }.compact.to_json << "\n"
33
+
34
+ JSON_LOG_FORMATTER = proc { |data|
35
+ log_context = data.delete(:log_context)
36
+ data.merge(log_context).compact.to_json << "\n"
31
37
  }
32
38
 
33
39
  module ON_INITIALIZATION_FAILURE
@@ -26,6 +26,10 @@ module Prefab
26
26
  # noop -- override as you wish
27
27
  end
28
28
 
29
+ def post(url, data)
30
+ @client.post(url, data)
31
+ end
32
+
29
33
  def start_periodic_sync(sync_interval)
30
34
  @start_at = Prefab::TimeHelpers.now_in_ms
31
35
 
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 1.0.1 ruby lib
5
+ # stub: prefab-cloud-ruby 1.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "1.0.1"
9
+ s.version = "1.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2023-08-17"
14
+ s.date = "2023-09-18"
15
15
  s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.executables = ["console".freeze]
@@ -1,18 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class IntegrationTest
4
- attr_reader :func, :input, :expected, :test_client
4
+ attr_reader :func, :input, :expected, :data, :expected_data, :aggregator, :endpoint, :test_client
5
5
 
6
6
  def initialize(test_data)
7
7
  @client_overrides = parse_client_overrides(test_data['client_overrides'])
8
8
  @func = parse_function(test_data['function'])
9
9
  @input = parse_input(test_data['input'])
10
10
  @expected = parse_expected(test_data['expected'])
11
- @test_client = base_client
11
+ @data = test_data['data']
12
+ @expected_data = test_data['expected_data']
13
+ @aggregator = test_data['aggregator']
14
+ @endpoint = test_data['endpoint']
15
+ @test_client = capture_telemetry(base_client)
12
16
  end
13
17
 
14
18
  def test_type
15
- if @input[0] && @input[0].start_with?('log-level.')
19
+ if @data
20
+ :telemetry
21
+ elsif @input[0] && @input[0].start_with?('log-level.')
16
22
  :log_level
17
23
  elsif @expected[:status] == 'raise'
18
24
  :raise
@@ -23,6 +29,18 @@ class IntegrationTest
23
29
  end
24
30
  end
25
31
 
32
+ def last_data_sent
33
+ test_client.last_data_sent
34
+ end
35
+
36
+ def last_post_result
37
+ test_client.last_post_result
38
+ end
39
+
40
+ def last_post_endpoint
41
+ test_client.last_post_endpoint
42
+ end
43
+
26
44
  private
27
45
 
28
46
  def parse_client_overrides(overrides)
@@ -42,6 +60,8 @@ class IntegrationTest
42
60
  end
43
61
 
44
62
  def parse_input(input)
63
+ return nil if input.nil?
64
+
45
65
  if input['key']
46
66
  parse_config_input(input)
47
67
  elsif input['flag']
@@ -62,6 +82,8 @@ class IntegrationTest
62
82
  end
63
83
 
64
84
  def parse_expected(expected)
85
+ return {} if expected.nil?
86
+
65
87
  {
66
88
  status: expected['status'],
67
89
  error: parse_error_type(expected['error']),
@@ -73,6 +95,7 @@ class IntegrationTest
73
95
  def parse_error_type(error_type)
74
96
  case error_type
75
97
  when 'missing_default' then Prefab::Errors::MissingDefaultError
98
+ when 'initialization_timeout' then Prefab::Errors::InitializationTimeoutError
76
99
  end
77
100
  end
78
101
 
@@ -87,7 +110,34 @@ class IntegrationTest
87
110
  prefab_envs: ['unit_tests'],
88
111
  prefab_datasources: Prefab::Options::DATASOURCES::ALL,
89
112
  api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'],
90
- prefab_api_url: 'https://api.staging-prefab.cloud'
113
+ prefab_api_url: 'https://api.staging-prefab.cloud',
91
114
  }.merge(@client_overrides))
92
115
  end
116
+
117
+ def capture_telemetry(client)
118
+ client.define_singleton_method(:post) do |url, data|
119
+ client.instance_variable_set(:@last_data_sent, data)
120
+ client.instance_variable_set(:@last_post_endpoint, url)
121
+
122
+ result = super(url, data)
123
+
124
+ client.instance_variable_set(:@last_post_result, result)
125
+
126
+ result
127
+ end
128
+
129
+ client.define_singleton_method(:last_data_sent) do
130
+ client.instance_variable_get(:@last_data_sent)
131
+ end
132
+
133
+ client.define_singleton_method(:last_post_endpoint) do
134
+ client.instance_variable_get(:@last_post_endpoint)
135
+ end
136
+
137
+ client.define_singleton_method(:last_post_result) do
138
+ client.instance_variable_get(:@last_post_result)
139
+ end
140
+
141
+ client
142
+ end
93
143
  end
@@ -33,4 +33,117 @@ module IntegrationTestHelpers
33
33
  .select { |file| file =~ /\.ya?ml$/ }
34
34
  end
35
35
  end
36
+
37
+ def self.prepare_post_data(it)
38
+ case it.aggregator
39
+ when "log_path"
40
+ aggregator = it.test_client.log_path_aggregator
41
+
42
+ it.data.each do |(path, data)|
43
+ data.each_with_index do |count, severity|
44
+ count.times { aggregator.push(path, severity) }
45
+ end
46
+ end
47
+
48
+ expected_loggers = Hash.new { |h, k| h[k] = PrefabProto::Logger.new }
49
+
50
+ it.expected_data.each do |data|
51
+ data["counts"].each do |(severity, count)|
52
+ expected_loggers[data["logger_name"]][severity] = count
53
+ expected_loggers[data["logger_name"]]["logger_name"] = data["logger_name"]
54
+ end
55
+ end
56
+
57
+ [aggregator, ->(data) { data.loggers }, expected_loggers.values]
58
+ when "context_shape"
59
+ aggregator = it.test_client.context_shape_aggregator
60
+
61
+ context = Prefab::Context.new(it.data)
62
+
63
+ aggregator.push(context)
64
+
65
+ expected = it.expected_data.map do |data|
66
+ PrefabProto::ContextShape.new(
67
+ name: data["name"],
68
+ field_types: data["field_types"]
69
+ )
70
+ end
71
+
72
+ [aggregator, ->(data) { data.shapes }, expected]
73
+ when "evaluation_summary"
74
+ aggregator = it.test_client.evaluation_summary_aggregator
75
+
76
+ aggregator.instance_variable_set("@data", Concurrent::Hash.new)
77
+
78
+ it.data.each do |key|
79
+ it.test_client.get(key)
80
+ end
81
+
82
+ expected_data = []
83
+ it.expected_data.each do |data|
84
+ value = if data["value_type"] == "string_list"
85
+ PrefabProto::StringList.new(values: data["value"])
86
+ else
87
+ data["value"]
88
+ end
89
+ expected_data << PrefabProto::ConfigEvaluationSummary.new(
90
+ key: data["key"],
91
+ type: data["type"].to_sym,
92
+ counters: [
93
+ PrefabProto::ConfigEvaluationCounter.new(
94
+ count: data["count"],
95
+ config_id: 0,
96
+ selected_value: PrefabProto::ConfigValue.new(data["value_type"] => value),
97
+ config_row_index: data["summary"]["config_row_index"],
98
+ conditional_value_index: data["summary"]["conditional_value_index"] || 0,
99
+ weighted_value_index: data["summary"]["weighted_value_index"],
100
+ reason: :UNKNOWN
101
+ )
102
+ ]
103
+ )
104
+ end
105
+
106
+ [aggregator, ->(data) {
107
+ data.events[0].summaries.summaries.each { |e|
108
+ e.counters.each { |c|
109
+ c.config_id = 0
110
+ }
111
+ }
112
+ }, expected_data]
113
+ when "example_contexts"
114
+ aggregator = it.test_client.example_contexts_aggregator
115
+
116
+ it.data.each do |hash|
117
+ aggregator.record(Prefab::Context.new(hash))
118
+ end
119
+
120
+ expected_data = []
121
+ it.expected_data.each do |data|
122
+ expected_data << PrefabProto::ExampleContext.new(
123
+ timestamp: 0,
124
+ contextSet: PrefabProto::ContextSet.new(
125
+ contexts: data.map do |(k, vs)|
126
+ PrefabProto::Context.new(
127
+ type: k,
128
+ values: vs.map do |v|
129
+ [v["key"], PrefabProto::ConfigValue.new(v["value_type"] => v["value"])]
130
+ end.to_h
131
+ )
132
+ end
133
+ )
134
+ )
135
+ end
136
+ [aggregator, ->(data) { data.events[0].example_contexts.examples.each { |e| e.timestamp = 0 } }, expected_data]
137
+ else
138
+ puts "unknown aggregator #{it.aggregator}"
139
+ end
140
+ end
141
+
142
+ def self.with_parent_context_maybe(context, &block)
143
+ if context
144
+ Prefab::Context.with_context(context, &block)
145
+ else
146
+ yield
147
+ end
148
+ end
36
149
  end
@@ -82,6 +82,16 @@ module CommonHelpers
82
82
 
83
83
  FakeResponse = Struct.new(:status, :body)
84
84
 
85
+ def wait_for(condition, max_wait: 2, sleep_time: 0.01)
86
+ wait_time = 0
87
+ while !condition.call
88
+ wait_time += sleep_time
89
+ sleep sleep_time
90
+
91
+ raise "Waited #{max_wait} seconds for the condition to be true, but it never was" if wait_time > max_wait
92
+ end
93
+ end
94
+
85
95
  def wait_for_post_requests(client, max_wait: 2, sleep_time: 0.01)
86
96
  # we use ivars to avoid re-mocking the post method on subsequent calls
87
97
  client.instance_variable_set("@_requests", [])
@@ -99,13 +109,7 @@ module CommonHelpers
99
109
  yield
100
110
 
101
111
  # let the flush thread run
102
- wait_time = 0
103
- while client.instance_variable_get("@_requests").empty?
104
- wait_time += sleep_time
105
- sleep sleep_time
106
-
107
- raise "Waited #{max_wait} seconds for the flush thread to run, but it never did" if wait_time > max_wait
108
- end
112
+ wait_for -> { client.instance_variable_get("@_requests").size > 0 }, max_wait: max_wait, sleep_time: sleep_time
109
113
 
110
114
  client.instance_variable_get("@_requests")
111
115
  end
@@ -16,7 +16,7 @@ class TestIntegration < Minitest::Test
16
16
  define_method(:"test_#{test['name']}_#{test_case['name']}") do
17
17
  it = IntegrationTest.new(test_case)
18
18
 
19
- with_parent_context_maybe(parent_context) do
19
+ IntegrationTestHelpers.with_parent_context_maybe(parent_context) do
20
20
  case it.test_type
21
21
  when :raise
22
22
  err = assert_raises(it.expected[:error]) do
@@ -34,6 +34,19 @@ class TestIntegration < Minitest::Test
34
34
  end
35
35
  when :log_level
36
36
  assert_equal it.expected[:value].to_sym, it.test_client.send(it.func, *it.input)
37
+ when :telemetry
38
+ aggregator, get_actual_data, expected = IntegrationTestHelpers.prepare_post_data(it)
39
+ aggregator.sync
40
+
41
+ wait_for -> { it.last_post_result&.status == 200 }
42
+
43
+ assert it.endpoint == it.last_post_endpoint
44
+
45
+ actual = get_actual_data[it.last_data_sent]
46
+
47
+ expected.all? do |expected|
48
+ assert actual.include?(expected)
49
+ end
37
50
  else
38
51
  raise "Unknown test type: #{it.test_type}"
39
52
  end
@@ -42,14 +55,4 @@ class TestIntegration < Minitest::Test
42
55
  end
43
56
  end
44
57
  end
45
-
46
- private
47
-
48
- def with_parent_context_maybe(context, &block)
49
- if context
50
- Prefab::Context.with_context(context, &block)
51
- else
52
- yield
53
- end
54
- end
55
58
  end
data/test/test_logger.rb CHANGED
@@ -404,6 +404,46 @@ class TestLogger < Minitest::Test
404
404
  assert_logged io, 'ERROR', 'test.test_logger.test_logging_with_a_block', message
405
405
  end
406
406
 
407
+ def test_structured_logging
408
+ prefab, io = captured_logger
409
+ message = 'HELLO'
410
+
411
+ prefab.log.error message, user: "michael", id: 123
412
+
413
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logging', "#{message} id=123 user=michael"
414
+ end
415
+
416
+ def test_structured_json_logging
417
+ prefab, io = captured_logger(log_formatter: Prefab::Options::JSON_LOG_FORMATTER)
418
+ message = 'HELLO'
419
+
420
+ prefab.log.error message, user: "michael", id: 123
421
+
422
+ log_data = JSON.parse(io.string)
423
+ assert log_data["message"] == message
424
+ assert log_data["user"] == "michael"
425
+ assert log_data["id"] == 123
426
+ end
427
+
428
+ def test_structured_internal_logging
429
+ prefab, io = captured_logger
430
+
431
+ prefab.log.log_internal('test', 'test.path', '', ::Logger::WARN, user: "michael")
432
+
433
+ assert_logged io, 'WARN', 'cloud.prefab.client.test.path', "test user=michael"
434
+ end
435
+
436
+ def test_structured_block_logger
437
+ prefab, io = captured_logger
438
+ message = 'MY MESSAGE'
439
+
440
+ prefab.log.error user: "michael" do
441
+ message
442
+ end
443
+
444
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_block_logger', "#{message} user=michael"
445
+ end
446
+
407
447
  private
408
448
 
409
449
  def assert_logged(logged_io, level, path, message)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-17 00:00:00.000000000 Z
11
+ date: 2023-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby