prefab-cloud-ruby 1.1.2 → 1.2.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: 0d3348f914c10f7f7d45814ff1be70a067e227cbef499b6f44736f10aa5cc30c
4
- data.tar.gz: 52699e5fee8f4f6ea26a89d3e3381cc6220d25d4720dcb69d7aee8de8f79e1c9
3
+ metadata.gz: 5bfaef7482dfdd1946bd0b0dee2cd0f366f5661e275bb8b906fb207d9b241d14
4
+ data.tar.gz: ed36c67d8039d2250fab5c6b1ff08da23cde51f696c256daaa943639205f2d0e
5
5
  SHA512:
6
- metadata.gz: c960267ea9f3e09664e48965fb470c1a3093061b1e6906f8f73b4c80a396ab80e0b383b3e42209e6cc95d5fab778c6221a2b9292a2015603139a874586eddf22
7
- data.tar.gz: 98cc728c27f1b1f6267c4b01b754d07f23fa60f8dfad95edd9e9288de666f6fc92539bf7124e85337023bf772946422c8919c75ebf487e9999a60251734a4d61
6
+ metadata.gz: 209f9cc828ec0eccd3b34162d9513c53695bf36838b60601103fd699748fad78af28cb622c1d057ea63162abd143f4db176395726a52517426885483789c7a3c
7
+ data.tar.gz: 3d368dc2aab1e941607e437d889dd51c8d5aaf9e65208d81552f930cfa34db0eff02fba1b7f2e7a3a08ab87a23e11fad67a0634861e586649fbc466a4f2d3629
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 - 2023-10-30
4
+ - Add `Prefab.get('key')` style usage after a `Prefab.init()` call (#151)
5
+ - Add `add_context_keys` and `with_context_keys` method for LoggerClient (#145)
6
+
3
7
  ## 1.1.2 - 2023-10-13
4
8
 
5
9
  - Add `cloud.prefab.client.criteria_evaluator` `debug` logging of evaluations (#150)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0
data/lib/prefab/client.rb CHANGED
@@ -6,6 +6,7 @@ module Prefab
6
6
  class Client
7
7
  MAX_SLEEP_SEC = 10
8
8
  BASE_SLEEP_SEC = 0.5
9
+ LOG = Prefab::InternalLogger.new(Client)
9
10
 
10
11
  attr_reader :namespace, :interceptor, :api_key, :prefab_api_url, :options, :instance_hash
11
12
 
@@ -14,15 +15,19 @@ module Prefab
14
15
  @namespace = @options.namespace
15
16
  @stubs = {}
16
17
  @instance_hash = UUID.new.generate
18
+ Prefab::LoggerClient.new(@options.logdev, formatter: @options.log_formatter,
19
+ prefix: @options.log_prefix,
20
+ log_path_aggregator: log_path_aggregator
21
+ )
17
22
 
18
23
  if @options.local_only?
19
- log_internal ::Logger::DEBUG, 'Prefab Running in Local Mode'
24
+ LOG.debug 'Prefab Running in Local Mode'
20
25
  else
21
26
  @api_key = @options.api_key
22
27
  raise Prefab::Errors::InvalidApiKeyError, @api_key if @api_key.nil? || @api_key.empty? || api_key.count('-') < 1
23
28
 
24
29
  @prefab_api_url = @options.prefab_api_url
25
- log_internal ::Logger::DEBUG, "Prefab Connecting to: #{@prefab_api_url}"
30
+ LOG.debug "Prefab Connecting to: #{@prefab_api_url}"
26
31
  end
27
32
 
28
33
  context.clear
@@ -54,9 +59,7 @@ module Prefab
54
59
  end
55
60
 
56
61
  def log
57
- @logger_client ||= Prefab::LoggerClient.new(@options.logdev, formatter: @options.log_formatter,
58
- prefix: @options.log_prefix,
59
- log_path_aggregator: log_path_aggregator)
62
+ Prefab::LoggerClient.instance
60
63
  end
61
64
 
62
65
  def context_shape_aggregator
@@ -99,10 +102,6 @@ module Prefab
99
102
  resolver.on_update(&block)
100
103
  end
101
104
 
102
- def log_internal(level, msg, path = nil, **tags)
103
- log.log_internal msg, path, nil, level, tags
104
- end
105
-
106
105
  def enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
107
106
  feature_flag_client.feature_is_on_for?(feature_name, jit_context)
108
107
  end
@@ -133,7 +132,10 @@ module Prefab
133
132
  # $prefab.set_rails_loggers
134
133
  # end
135
134
  def fork
136
- Prefab::Client.new(@options.for_fork)
135
+ log_options = self.log.context_keys.to_a # get keys pre-fork
136
+ Prefab::Client.new(@options.for_fork).tap do |client|
137
+ client.log.add_context_keys(*log_options)
138
+ end
137
139
  end
138
140
 
139
141
  private
@@ -8,11 +8,12 @@ module Prefab
8
8
  STALE_CACHE_WARN_HOURS = 5
9
9
  AUTH_USER = 'authuser'
10
10
  LOGGING_KEY_PREFIX = "#{Prefab::LoggerClient::BASE_KEY}#{Prefab::LoggerClient::SEP}".freeze
11
+ LOG = Prefab::InternalLogger.new(ConfigClient)
11
12
 
12
13
  def initialize(base_client, timeout)
13
14
  @base_client = base_client
14
15
  @options = base_client.options
15
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient'
16
+ LOG.debug 'Initialize ConfigClient'
16
17
  @timeout = timeout
17
18
 
18
19
  @stream_lock = Concurrent::ReadWriteLock.new
@@ -23,9 +24,9 @@ module Prefab
23
24
  @config_resolver = Prefab::ConfigResolver.new(@base_client, @config_loader)
24
25
 
25
26
  @initialization_lock = Concurrent::ReadWriteLock.new
26
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquireWriteLock'
27
+ LOG.debug 'Initialize ConfigClient: AcquireWriteLock'
27
28
  @initialization_lock.acquire_write_lock
28
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquiredWriteLock'
29
+ LOG.debug 'Initialize ConfigClient: AcquiredWriteLock'
29
30
  @initialized_future = Concurrent::Future.execute { @initialization_lock.acquire_read_lock }
30
31
 
31
32
  if @options.local_only?
@@ -96,8 +97,7 @@ module Prefab
96
97
  raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
97
98
  end
98
99
 
99
- @base_client.log_internal ::Logger::WARN,
100
- "Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
100
+ LOG.warn("Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have")
101
101
  @initialization_lock.release_write_lock
102
102
  end
103
103
 
@@ -117,7 +117,7 @@ module Prefab
117
117
 
118
118
  return if success
119
119
 
120
- @base_client.log_internal ::Logger::WARN, 'No success loading checkpoints'
120
+ LOG.warn 'No success loading checkpoints'
121
121
  end
122
122
 
123
123
  def load_checkpoint_api_cdn
@@ -138,11 +138,11 @@ module Prefab
138
138
  cache_configs(configs)
139
139
  true
140
140
  else
141
- @base_client.log_internal ::Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
141
+ LOG.info "Checkpoint #{source} failed to load. Response #{resp.status}"
142
142
  false
143
143
  end
144
144
  rescue StandardError => e
145
- @base_client.log_internal ::Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
145
+ LOG.warn "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
146
146
  false
147
147
  end
148
148
 
@@ -167,11 +167,9 @@ module Prefab
167
167
  @config_loader.set(config, source)
168
168
  end
169
169
  if @config_loader.highwater_mark > starting_highwater_mark
170
- @base_client.log_internal ::Logger::DEBUG,
171
- "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
170
+ LOG.debug("Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'")
172
171
  else
173
- @base_client.log_internal ::Logger::DEBUG,
174
- "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", 'load_configs'
172
+ LOG.debug("Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.")
175
173
  end
176
174
  @config_resolver.update
177
175
  finish_init!(source, project_id)
@@ -196,9 +194,9 @@ module Prefab
196
194
  f.flock(File::LOCK_EX)
197
195
  f.write(PrefabProto::Configs.encode_json(configs))
198
196
  end
199
- @base_client.log_internal ::Logger::DEBUG, "Cached configs to #{cache_path}"
197
+ LOG.debug "Cached configs to #{cache_path}"
200
198
  rescue => e
201
- @base_client.log_internal ::Logger::DEBUG, "Failed to cache configs to #{cache_path} #{e}"
199
+ LOG.debug "Failed to cache configs to #{cache_path} #{e}"
202
200
  end
203
201
 
204
202
  def load_cache
@@ -210,11 +208,11 @@ module Prefab
210
208
 
211
209
  hours_old = ((Time.now - File.mtime(f)) / 60 / 60).round(2)
212
210
  if hours_old > STALE_CACHE_WARN_HOURS
213
- @base_client.log_internal ::Logger::INFO, "Stale Cache Load: #{hours_old} hours old"
211
+ LOG.info "Stale Cache Load: #{hours_old} hours old"
214
212
  end
215
213
  end
216
214
  rescue => e
217
- @base_client.log_internal ::Logger::DEBUG, "Failed to read cached configs at #{cache_path}. #{e}"
215
+ LOG.debug "Failed to read cached configs at #{cache_path}. #{e}"
218
216
  false
219
217
  end
220
218
 
@@ -222,13 +220,13 @@ module Prefab
222
220
  def start_checkpointing_thread
223
221
  Thread.new do
224
222
  loop do
225
- load_checkpoint
226
-
227
223
  started_at = Time.now
228
224
  delta = @checkpoint_freq_secs - (Time.now - started_at)
229
225
  sleep(delta) if delta > 0
226
+
227
+ load_checkpoint
230
228
  rescue StandardError => e
231
- @base_client.log_internal ::Logger::DEBUG, "Issue Checkpointing #{e.message}"
229
+ LOG.debug "Issue Checkpointing #{e.message}"
232
230
  end
233
231
  end
234
232
  end
@@ -236,9 +234,10 @@ module Prefab
236
234
  def finish_init!(source, project_id)
237
235
  return unless @initialization_lock.write_locked?
238
236
 
239
- @base_client.log_internal ::Logger::DEBUG, "Unlocked Config via #{source}"
237
+ LOG.debug "Unlocked Config via #{source}"
240
238
  @initialization_lock.release_write_lock
241
- @base_client.log.config_client = self
239
+
240
+ Prefab::LoggerClient.instance.config_client = self
242
241
  presenter = Prefab::ConfigClientPresenter.new(
243
242
  size: @config_resolver.local_store.size,
244
243
  source: source,
@@ -246,8 +245,8 @@ module Prefab
246
245
  project_env_id: @config_resolver.project_env_id,
247
246
  api_key_id: @base_client.options.api_key_id
248
247
  )
249
- @base_client.log_internal ::Logger::INFO, presenter.to_s
250
- @base_client.log_internal ::Logger::DEBUG, to_s
248
+ LOG.info presenter.to_s
249
+ LOG.debug to_s
251
250
  end
252
251
 
253
252
  def start_sse_streaming_connection_thread(start_at_id)
@@ -259,11 +258,11 @@ module Prefab
259
258
  'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
260
259
  }
261
260
  url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
262
- @base_client.log_internal ::Logger::DEBUG, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
261
+ LOG.debug "SSE Streaming Connect to #{url} start_at #{start_at_id}"
263
262
  @streaming_thread = SSE::Client.new(url,
264
263
  headers: headers,
265
264
  read_timeout: SSE_READ_TIMEOUT,
266
- logger: Prefab::SseLogger.new(@base_client.log)) do |client|
265
+ logger: Prefab::SseLogger.new) do |client|
267
266
  client.on_event do |event|
268
267
  configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
269
268
  load_configs(configs, :sse)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Prefab
4
4
  class ConfigLoader
5
+ LOG = Prefab::InternalLogger.new(ConfigLoader)
6
+
5
7
  attr_reader :highwater_mark
6
8
 
7
9
  def initialize(base_client)
@@ -29,8 +31,8 @@ module Prefab
29
31
  @api_config.delete(config.key)
30
32
  else
31
33
  if @api_config[config.key]
32
- @base_client.log_internal ::Logger::DEBUG,
33
- "Replace #{config.key} with value from #{source} #{@api_config[config.key][:config].id} -> #{config.id}"
34
+ LOG.debug(
35
+ "Replace #{config.key} with value from #{source} #{@api_config[config.key][:config].id} -> #{config.id}")
34
36
  end
35
37
  @api_config[config.key] = { source: source, config: config }
36
38
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Prefab
4
4
  class ConfigValueUnwrapper
5
+ LOG = Prefab::InternalLogger.new(ConfigValueUnwrapper)
5
6
  attr_reader :value, :weighted_value_index
6
7
 
7
8
  def initialize(value, weighted_value_index = nil)
@@ -16,6 +17,7 @@ module Prefab
16
17
  when :string_list
17
18
  value.string_list.values
18
19
  else
20
+ LOG.error "Unknown type: #{config_value.type}"
19
21
  raise "Unknown type: #{config_value.type}"
20
22
  end
21
23
  end
@@ -6,6 +6,8 @@ module Prefab
6
6
  class ContextShapeAggregator
7
7
  include Prefab::PeriodicSync
8
8
 
9
+ LOG = Prefab::InternalLogger.new(ContextShapeAggregator)
10
+
9
11
  attr_reader :data
10
12
 
11
13
  def initialize(client:, max_shapes:, sync_interval:)
@@ -43,7 +45,7 @@ module Prefab
43
45
 
44
46
  def flush(to_ship, _)
45
47
  pool.post do
46
- log_internal "Uploading context shapes for #{to_ship.values.size}"
48
+ LOG.debug "Uploading context shapes for #{to_ship.values.size}"
47
49
 
48
50
  shapes = PrefabProto::ContextShapes.new(
49
51
  shapes: to_ship.map do |name, shape|
@@ -56,7 +58,7 @@ module Prefab
56
58
 
57
59
  result = post('/api/v1/context-shapes', shapes)
58
60
 
59
- log_internal "Uploaded #{to_ship.values.size} shapes: #{result.status}"
61
+ LOG.debug "Uploaded #{to_ship.values.size} shapes: #{result.status}"
60
62
  end
61
63
  end
62
64
  end
@@ -7,6 +7,7 @@ module Prefab
7
7
  # This class evaluates a config's criteria. `evaluate` returns the value of
8
8
  # the first match based on the provided properties.
9
9
  class CriteriaEvaluator
10
+ LOG = Prefab::InternalLogger.new(CriteriaEvaluator)
10
11
  NAMESPACE_KEY = 'NAMESPACE'
11
12
  NO_MATCHING_ROWS = [].freeze
12
13
 
@@ -21,7 +22,7 @@ module Prefab
21
22
  def evaluate(properties)
22
23
  rtn = evaluate_for_env(@project_env_id, properties) ||
23
24
  evaluate_for_env(0, properties)
24
- @base_client.log_internal ::Logger::DEBUG, "Eval Key #{@config.key} Result #{rtn&.value} with #{properties.to_h}", :criteria_evaluator unless @config.config_type == :LOG_LEVEL
25
+ LOG.debug "Eval Key #{@config.key} Result #{rtn&.value} with #{properties.to_h}" unless @config.config_type == :LOG_LEVEL
25
26
  rtn
26
27
  end
27
28
 
@@ -9,6 +9,8 @@ module Prefab
9
9
  class EvaluationSummaryAggregator
10
10
  include Prefab::PeriodicSync
11
11
 
12
+ LOG = Prefab::InternalLogger.new(EvaluationSummaryAggregator)
13
+
12
14
  attr_reader :data
13
15
 
14
16
  def initialize(client:, max_keys:, sync_interval:)
@@ -47,7 +49,7 @@ module Prefab
47
49
 
48
50
  def flush(to_ship, start_at_was)
49
51
  pool.post do
50
- log_internal "Flushing #{to_ship.size} summaries"
52
+ LOG.debug "Flushing #{to_ship.size} summaries"
51
53
 
52
54
  summaries_proto = PrefabProto::ConfigEvaluationSummaries.new(
53
55
  start: start_at_was,
@@ -57,7 +59,7 @@ module Prefab
57
59
 
58
60
  result = post('/api/v1/telemetry', events(summaries_proto))
59
61
 
60
- log_internal "Uploaded #{to_ship.size} summaries: #{result.status}"
62
+ LOG.debug "Uploaded #{to_ship.size} summaries: #{result.status}"
61
63
  end
62
64
  end
63
65
 
@@ -10,6 +10,8 @@ module Prefab
10
10
  class ExampleContextsAggregator
11
11
  include Prefab::PeriodicSync
12
12
 
13
+ LOG = Prefab::InternalLogger.new(ExampleContextsAggregator)
14
+
13
15
  attr_reader :data, :cache
14
16
 
15
17
  ONE_HOUR = 60 * 60
@@ -43,11 +45,11 @@ module Prefab
43
45
 
44
46
  def flush(to_ship, _)
45
47
  pool.post do
46
- log_internal "Flushing #{to_ship.size} examples"
48
+ LOG.debug "Flushing #{to_ship.size} examples"
47
49
 
48
50
  result = post('/api/v1/telemetry', events(to_ship))
49
51
 
50
- log_internal "Uploaded #{to_ship.size} examples: #{result.status}"
52
+ LOG.debug "Uploaded #{to_ship.size} examples: #{result.status}"
51
53
  end
52
54
  end
53
55
 
@@ -2,29 +2,32 @@
2
2
 
3
3
  module Prefab
4
4
  class InternalLogger < ::Logger
5
- def initialize(path, logger)
6
- @path = path
7
- @logger = logger
5
+ def initialize(path)
6
+ if path.is_a?(Class)
7
+ @path = path.name.split('::').last.downcase
8
+ else
9
+ @path = path
10
+ end
8
11
  end
9
12
 
10
- def debug(progname = nil)
11
- @logger.log_internal yield, @path, progname, DEBUG
13
+ def debug msg
14
+ Prefab::LoggerClient.instance.log_internal ::Logger::DEBUG, msg, @path
12
15
  end
13
16
 
14
- def info(progname = nil)
15
- @logger.log_internal yield, @path, progname, INFO
17
+ def info msg
18
+ Prefab::LoggerClient.instance.log_internal ::Logger::INFO, msg, @path
16
19
  end
17
20
 
18
- def warn(progname = nil)
19
- @logger.log_internal yield, @path, progname, WARN
21
+ def warn msg
22
+ Prefab::LoggerClient.instance.log_internal ::Logger::WARN, msg, @path
20
23
  end
21
24
 
22
- def error(progname = nil)
23
- @logger.log_internal yield, @path, progname, ERROR
25
+ def error msg
26
+ Prefab::LoggerClient.instance.log_internal ::Logger::ERROR, msg, @path
24
27
  end
25
28
 
26
- def fatal(progname = nil)
27
- @logger.log_internal yield, @path, progname, FATAL
29
+ def fatal msg
30
+ Prefab::LoggerClient.instance.log_internal ::Logger::FATAL, msg, @path
28
31
  end
29
32
  end
30
33
  end
@@ -4,6 +4,8 @@ require_relative 'periodic_sync'
4
4
 
5
5
  module Prefab
6
6
  class LogPathAggregator
7
+ LOG = Prefab::InternalLogger.new(LogPathAggregator)
8
+
7
9
  include Prefab::PeriodicSync
8
10
 
9
11
  INCREMENT = ->(count) { (count || 0) + 1 }
@@ -41,7 +43,7 @@ module Prefab
41
43
 
42
44
  def flush(to_ship, start_at_was)
43
45
  pool.post do
44
- log_internal "Uploading stats for #{to_ship.size} paths"
46
+ LOG.debug "Uploading stats for #{to_ship.size} paths"
45
47
 
46
48
  aggregate = Hash.new { |h, k| h[k] = PrefabProto::Logger.new }
47
49
 
@@ -60,7 +62,7 @@ module Prefab
60
62
 
61
63
  result = post('/api/v1/known-loggers', loggers)
62
64
 
63
- log_internal "Uploaded #{to_ship.size} paths: #{result.status}"
65
+ LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
64
66
  end
65
67
  end
66
68
  end
@@ -17,7 +17,13 @@ module Prefab
17
17
  PrefabProto::LogLevel::FATAL => ::Logger::FATAL
18
18
  }.freeze
19
19
 
20
- def initialize(logdev, log_path_aggregator: nil, formatter: nil, prefix: nil)
20
+ def self.instance
21
+ @@shared_instance ||= LoggerClient.new($stdout)
22
+ end
23
+
24
+ attr_reader :context_keys
25
+
26
+ def initialize(logdev, log_path_aggregator: nil, formatter: Options::DEFAULT_LOG_FORMATTER, prefix: nil)
21
27
  super(logdev)
22
28
  self.formatter = formatter
23
29
  @config_client = BootstrappingConfigClient.new
@@ -25,9 +31,28 @@ module Prefab
25
31
  @recurse_check = Concurrent::Map.new(initial_capacity: 2)
26
32
  @prefix = "#{prefix}#{prefix && '.'}"
27
33
 
34
+ @context_keys = Concurrent::Set.new
35
+
28
36
  @log_path_aggregator = log_path_aggregator
37
+ @@shared_instance = self
29
38
  end
30
39
 
40
+ def add_context_keys(*keys)
41
+ @context_keys += keys
42
+ end
43
+
44
+ def with_context_keys(*keys)
45
+ @context_keys += keys
46
+ yield
47
+ ensure
48
+ @context_keys -= keys
49
+ end
50
+
51
+ def internal_logger(path=nil)
52
+ InternalLogger.new(path, self)
53
+ end
54
+
55
+ # InternalLoggers Will Call This
31
56
  def add_internal(severity, message, progname, loc, log_context={}, &block)
32
57
  path_loc = get_loc_path(loc)
33
58
  path = @prefix + path_loc
@@ -37,7 +62,7 @@ module Prefab
37
62
  log(message, path, progname, severity, log_context, &block)
38
63
  end
39
64
 
40
- def log_internal(message, path, progname, severity, log_context={}, &block)
65
+ def log_internal(severity, message, path, log_context={}, &block)
41
66
  return if @recurse_check[local_log_id]
42
67
  @recurse_check[local_log_id] = true
43
68
 
@@ -47,7 +72,7 @@ module Prefab
47
72
  INTERNAL_PREFIX
48
73
  end
49
74
  begin
50
- log(message, path, progname, severity, log_context, &block)
75
+ log(message, path, nil, severity, log_context, &block)
51
76
  ensure
52
77
  @recurse_check[local_log_id] = false
53
78
  end
@@ -55,7 +80,6 @@ module Prefab
55
80
 
56
81
  def log(message, path, progname, severity, log_context={})
57
82
  severity ||= ::Logger::UNKNOWN
58
-
59
83
  return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
60
84
 
61
85
  progname = @progname if progname.nil?
@@ -70,7 +94,7 @@ module Prefab
70
94
  end
71
95
 
72
96
  @logdev.write(
73
- format_message(format_severity(severity), Time.now, progname, message, path, log_context)
97
+ format_message(format_severity(severity), Time.now, progname, message, path, stringify_keys(log_context.merge(fetch_context_for_context_keys)))
74
98
  )
75
99
  true
76
100
  end
@@ -138,6 +162,17 @@ module Prefab
138
162
 
139
163
  NO_DEFAULT = nil
140
164
 
165
+ def stringify_keys(hash)
166
+ Hash[hash.map { |k, v| [k.to_s, v] }]
167
+ end
168
+
169
+ def fetch_context_for_context_keys
170
+ context = Prefab::Context.current.to_h
171
+ Hash[@context_keys.map do |key|
172
+ [key, context.dig(*key.split("."))]
173
+ end]
174
+ end
175
+
141
176
  # Find the closest match to 'log_level.path' in config
142
177
  def level_of(path)
143
178
  closest_log_level_match = nil
@@ -29,7 +29,9 @@ module Prefab
29
29
 
30
30
  progname = (progname.nil? || progname.empty?) ? path : "#{progname}: #{path}"
31
31
 
32
- formatted_log_context = log_context.sort.map{|k, v| "#{k}=#{v}" }.join(" ")
32
+ formatted_log_context = log_context.sort.map do |k, v|
33
+ v.nil? ? nil : "#{k}=#{v}"
34
+ end.compact.join(" ")
33
35
  "#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}#{log_context.any? ? " " + formatted_log_context : ""}\n"
34
36
  }
35
37
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Prefab
4
4
  module PeriodicSync
5
+ LOG = Prefab::InternalLogger.new("periodsync")
5
6
  def sync
6
7
  return if @data.size.zero?
7
8
 
8
- log_internal "Syncing #{@data.size} items"
9
+ LOG.debug "Syncing #{@data.size} items"
9
10
 
10
11
  start_at_was = @start_at
11
12
  @start_at = Prefab::TimeHelpers.now_in_ms
@@ -36,7 +37,7 @@ module Prefab
36
37
  @sync_interval = calculate_sync_interval(sync_interval)
37
38
 
38
39
  Thread.new do
39
- log_internal "Initialized #{@name} instance_hash=#{@client.instance_hash}"
40
+ LOG.debug "Initialized #{@name} instance_hash=#{@client.instance_hash}"
40
41
 
41
42
  loop do
42
43
  sleep @sync_interval.call
@@ -45,10 +46,6 @@ module Prefab
45
46
  end
46
47
  end
47
48
 
48
- def log_internal(message)
49
- @client.log.log_internal message, @name, nil, ::Logger::DEBUG
50
- end
51
-
52
49
  def pool
53
50
  @pool ||= Concurrent::ThreadPoolExecutor.new(
54
51
  fallback_policy: :discard,
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ @@lock = Concurrent::ReadWriteLock.new
5
+
6
+ def self.init(options = Prefab::Options.new)
7
+ unless @singleton.nil?
8
+ Prefab::LoggerClient.instance.warn 'Prefab already initialized.'
9
+ return @singleton
10
+ end
11
+
12
+ @@lock.with_write_lock {
13
+ @singleton = Prefab::Client.new(options)
14
+ }
15
+ end
16
+
17
+ def self.fork
18
+ ensure_initialized
19
+ @@lock.with_write_lock {
20
+ @singleton = @singleton.fork
21
+ }
22
+ end
23
+
24
+ def self.set_rails_loggers
25
+ ensure_initialized
26
+ @singleton.set_rails_loggers
27
+ end
28
+
29
+ def self.get(key, properties = NO_DEFAULT_PROVIDED)
30
+ ensure_initialized
31
+ @singleton.get(key, properties)
32
+ end
33
+
34
+ def self.enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
35
+ ensure_initialized
36
+ @singleton.enabled?(feature_name, jit_context)
37
+ end
38
+
39
+ def self.with_context(properties, &block)
40
+ ensure_initialized
41
+ @singleton.with_context(properties, &block)
42
+ end
43
+
44
+ def self.instance
45
+ ensure_initialized
46
+ @singleton
47
+ end
48
+
49
+ private
50
+
51
+ def self.ensure_initialized
52
+ if not defined? @singleton or @singleton.nil?
53
+ raise "Use Prefab.initialize before calling Prefab.get"
54
+ end
55
+ end
56
+ end
@@ -1,14 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prefab
4
- class SseLogger < InternalLogger
5
- def initialize(logger)
6
- super('sse', logger)
4
+ class SseLogger < ::Logger
5
+ def initialize()
6
+ @path = "sse"
7
+ end
8
+
9
+ def debug(progname = nil, &block)
10
+ Prefab::LoggerClient.instance.log_internal ::Logger::DEBUG, progname, @path, &block
11
+ end
12
+
13
+ def info(progname = nil, &block)
14
+ Prefab::LoggerClient.instance.log_internal ::Logger::INFO, progname, @path, &block
7
15
  end
8
16
 
9
17
  # The SSE::Client warns on a perfectly normal stream disconnect, recast to info
10
- def warn(progname = nil)
11
- @logger.log_internal yield, @path, progname, INFO
18
+ def warn(progname = nil, &block)
19
+ Prefab::LoggerClient.instance.log_internal ::Logger::INFO, progname, @path, &block
20
+ end
21
+
22
+ def error(progname = nil, &block)
23
+ Prefab::LoggerClient.instance.log_internal ::Logger::ERROR, progname, @path, &block
24
+ end
25
+
26
+ def fatal(progname = nil, &block)
27
+ Prefab::LoggerClient.instance.log_internal ::Logger::FATAL, progname, @path, &block
12
28
  end
13
29
  end
14
30
  end
@@ -2,6 +2,8 @@ require 'yaml'
2
2
 
3
3
  module Prefab
4
4
  class YAMLConfigParser
5
+ LOG = Prefab::InternalLogger.new(YAMLConfigParser)
6
+
5
7
  def initialize(file, client)
6
8
  @file = file
7
9
  @client = client
@@ -21,10 +23,10 @@ module Prefab
21
23
 
22
24
  def load
23
25
  if File.exist?(@file)
24
- @client.log_internal ::Logger::INFO, "Load #{@file}"
26
+ LOG.info "Load #{@file}"
25
27
  YAML.load_file(@file)
26
28
  else
27
- @client.log_internal ::Logger::INFO, "No file #{@file}"
29
+ LOG.info "No file #{@file}"
28
30
  {}
29
31
  end
30
32
  end
@@ -43,4 +43,5 @@ require 'prefab/client'
43
43
  require 'prefab/config_client_presenter'
44
44
  require 'prefab/config_client'
45
45
  require 'prefab/feature_flag_client'
46
+ require 'prefab/prefab'
46
47
  require 'prefab/murmer3'
@@ -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.1.2 ruby lib
5
+ # stub: prefab-cloud-ruby 1.2.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "1.1.2"
9
+ s.version = "1.2.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-10-13"
14
+ s.date = "2023-10-30"
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]
@@ -65,6 +65,7 @@ Gem::Specification.new do |s|
65
65
  "lib/prefab/murmer3.rb",
66
66
  "lib/prefab/options.rb",
67
67
  "lib/prefab/periodic_sync.rb",
68
+ "lib/prefab/prefab.rb",
68
69
  "lib/prefab/rate_limit_cache.rb",
69
70
  "lib/prefab/resolved_config_presenter.rb",
70
71
  "lib/prefab/sse_logger.rb",
@@ -100,7 +101,9 @@ Gem::Specification.new do |s|
100
101
  "test/test_local_config_parser.rb",
101
102
  "test/test_log_path_aggregator.rb",
102
103
  "test/test_logger.rb",
104
+ "test/test_logger_initialization.rb",
103
105
  "test/test_options.rb",
106
+ "test/test_prefab.rb",
104
107
  "test/test_rate_limit_cache.rb",
105
108
  "test/test_weighted_value_resolver.rb"
106
109
  ]
@@ -44,24 +44,26 @@ module CommonHelpers
44
44
  }.freeze
45
45
 
46
46
  def new_client(overrides = {})
47
- $logs ||= StringIO.new
48
47
 
49
48
  config = overrides.delete(:config)
50
49
  project_env_id = overrides.delete(:project_env_id)
51
50
 
52
- options = Prefab::Options.new(
53
- **DEFAULT_NEW_CLIENT_OPTIONS.merge(
54
- overrides.merge(logdev: $logs)
55
- )
56
- )
57
-
58
- Prefab::Client.new(options).tap do |client|
51
+ Prefab::Client.new(prefab_options(overrides)).tap do |client|
59
52
  inject_config(client, config) if config
60
53
 
61
54
  client.resolver.project_env_id = project_env_id if project_env_id
62
55
  end
63
56
  end
64
57
 
58
+ def prefab_options(overrides = {})
59
+ $logs ||= StringIO.new
60
+ Prefab::Options.new(
61
+ **DEFAULT_NEW_CLIENT_OPTIONS.merge(
62
+ overrides.merge(logdev: $logs)
63
+ )
64
+ )
65
+ end
66
+
65
67
  def string_list(values)
66
68
  PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
67
69
  end
@@ -145,7 +147,7 @@ module CommonHelpers
145
147
  end
146
148
 
147
149
  def assert_only_expected_logs
148
- assert_equal "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints\n", $logs.string
150
+ assert_equal "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints\n", $logs.string
149
151
  # mark nil to indicate we handled it
150
152
  $logs = nil
151
153
  end
@@ -9,8 +9,8 @@ class MockBaseClient
9
9
  def initialize(options = Prefab::Options.new)
10
10
  @options = options
11
11
  @namespace = namespace
12
- @logger = Prefab::LoggerClient.new($stdout)
13
12
  @config_client = MockConfigClient.new
13
+ Prefab::LoggerClient.new(options.logdev)
14
14
  @posts = []
15
15
  end
16
16
 
@@ -30,8 +30,6 @@ class MockBaseClient
30
30
  @logger
31
31
  end
32
32
 
33
- def log_internal(level, msg, path = nil, **tags); end
34
-
35
33
  def context_shape_aggregator; end
36
34
 
37
35
  def evaluation_summary_aggregator; end
data/test/test_client.rb CHANGED
@@ -185,7 +185,7 @@ class TestClient < Minitest::Test
185
185
  collect_evaluation_summaries: true).evaluation_summary_aggregator.class
186
186
 
187
187
  assert_logged [
188
- "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints"
188
+ "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints"
189
189
  ]
190
190
  end
191
191
 
@@ -395,7 +395,7 @@ class TestClient < Minitest::Test
395
395
  values: [
396
396
  PrefabProto::ConditionalValue.new(
397
397
  criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
398
- value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::DEBUG)
398
+ value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::INFO)
399
399
  )
400
400
  ]
401
401
  )
@@ -405,12 +405,21 @@ class TestClient < Minitest::Test
405
405
  client = new_client(config: config, project_env_id: PROJECT_ENV_ID,
406
406
  collect_evaluation_summaries: true, allow_telemetry_in_local_mode: true)
407
407
 
408
- assert_equal :DEBUG, client.get(config.key, IRRELEVANT)
408
+ assert_equal :INFO, client.get(config.key, IRRELEVANT)
409
409
 
410
410
  # nothing is summarized for log levels
411
411
  assert_summary client, {}
412
412
  end
413
413
 
414
+ def test_fork_includes_logger_context_keys
415
+ client = new_client
416
+ client.log.add_context_keys "user.name"
417
+
418
+ forked = client.fork
419
+
420
+ assert forked.log.context_keys.to_a == %w(user.name)
421
+ end
422
+
414
423
  private
415
424
 
416
425
  def basic_value_config
@@ -124,8 +124,8 @@ class TestContextShapeAggregator < Minitest::Test
124
124
 
125
125
 
126
126
  assert_logged [
127
- "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints",
128
- "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client Couldn't Initialize In 0. Key some.key. Returning what we have"
127
+ "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints",
128
+ "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient Couldn't Initialize In 0. Key some.key. Returning what we have"
129
129
  ]
130
130
  end
131
131
 
@@ -710,22 +710,11 @@ class TestCriteriaEvaluator < Minitest::Test
710
710
  FakeResolver.new(config, @base_client)
711
711
  end
712
712
 
713
- class FakeLogger
714
- def info(msg)
715
- # loudly complain about unexpected log messages
716
- raise msg
717
- end
718
-
719
- def log_internal(*args); end
720
- end
721
-
722
713
  class FakeBaseClient
723
- def log
724
- FakeLogger.new
714
+ def initialize
715
+ Prefab::LoggerClient.new($stdout)
725
716
  end
726
717
 
727
- def log_internal(level, msg, path = nil, **tags); end
728
-
729
718
  def evaluation_summary_aggregator
730
719
  @evaluation_summary_aggregator ||= Prefab::EvaluationSummaryAggregator.new(client: self, max_keys: 9999, sync_interval: 9999)
731
720
  end
@@ -47,7 +47,7 @@ class TestLogPathAggregator < Minitest::Test
47
47
  ]], requests
48
48
 
49
49
  assert_logged [
50
- 'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints',
50
+ 'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints',
51
51
  'ERROR 2023-08-09 15:18:12 -0400: test.test_log_path_aggregator.test_sync here is a message'
52
52
  ]
53
53
  end
data/test/test_logger.rb CHANGED
@@ -87,22 +87,22 @@ class TestLogger < Minitest::Test
87
87
 
88
88
  def test_log_internal
89
89
  prefab, io = captured_logger
90
- prefab.log.log_internal('test message', 'test.path', '', ::Logger::WARN)
90
+ prefab.log.log_internal(::Logger::WARN, 'test message', 'test.path')
91
91
  assert_logged io, 'WARN', "cloud.prefab.client.test.path", "test message"
92
92
  end
93
93
 
94
94
  def test_log_internal_unknown
95
95
  prefab, io = captured_logger
96
- prefab.log.log_internal('test message', 'test.path', '', ::Logger::UNKNOWN)
96
+ prefab.log.log_internal(::Logger::UNKNOWN, 'test message', 'test.path')
97
97
  assert_logged io, 'ANY', "cloud.prefab.client.test.path", "test message"
98
98
  end
99
99
 
100
100
  def test_log_internal_silencing
101
101
  prefab, io = captured_logger
102
102
  prefab.log.silence do
103
- prefab.log.log_internal('should not log', 'test.path', '', ::Logger::WARN)
103
+ prefab.log.log_internal(::Logger::WARN, 'should not log', 'test.path')
104
104
  end
105
- prefab.log.log_internal('should log', 'test.path', '', ::Logger::WARN)
105
+ prefab.log.log_internal(::Logger::WARN, 'should log', 'test.path')
106
106
  assert_logged io, 'WARN', "cloud.prefab.client.test.path", "should log"
107
107
  refute_logged io, 'should not log'
108
108
  end
@@ -404,6 +404,35 @@ 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_add_context_keys
408
+ assert @logger.context_keys.empty?
409
+ @logger.add_context_keys("user.name", "role.admin", "company.name")
410
+
411
+ assert @logger.context_keys.to_a == %w(user.name role.admin company.name)
412
+ end
413
+
414
+ def test_context_keys_are_a_set
415
+ @logger.add_context_keys("user.name", "role.admin", "company.name")
416
+
417
+ assert @logger.context_keys.to_a == %w(user.name role.admin company.name)
418
+
419
+ @logger.add_context_keys("user.name", "user.role")
420
+
421
+ assert @logger.context_keys.to_a == %w(user.name role.admin company.name user.role)
422
+ end
423
+
424
+ def test_with_context_keys
425
+ @logger.add_context_keys("company.name")
426
+
427
+ assert @logger.context_keys.to_a == %w(company.name)
428
+
429
+ @logger.with_context_keys("user.name", "role.admin") do
430
+ assert @logger.context_keys.to_a == %w(company.name user.name role.admin)
431
+ end
432
+
433
+ assert @logger.context_keys.to_a == %w(company.name)
434
+ end
435
+
407
436
  def test_structured_logging
408
437
  prefab, io = captured_logger
409
438
  message = 'HELLO'
@@ -428,7 +457,7 @@ class TestLogger < Minitest::Test
428
457
  def test_structured_internal_logging
429
458
  prefab, io = captured_logger
430
459
 
431
- prefab.log.log_internal('test', 'test.path', '', ::Logger::WARN, user: "michael")
460
+ prefab.log.log_internal(::Logger::WARN, 'test', 'test.path', user: "michael")
432
461
 
433
462
  assert_logged io, 'WARN', 'cloud.prefab.client.test.path', "test user=michael"
434
463
  end
@@ -444,6 +473,75 @@ class TestLogger < Minitest::Test
444
473
  assert_logged io, 'ERROR', 'test.test_logger.test_structured_block_logger', "#{message} user=michael"
445
474
  end
446
475
 
476
+ def test_structured_logger_with_context_keys
477
+ prefab, io = captured_logger
478
+
479
+ prefab.with_context({user: {name: "michael", job: "developer", admin: false}, company: { name: "Prefab" }}) do
480
+
481
+ prefab.log.add_context_keys "user.name", "company.name", "user.admin"
482
+
483
+ prefab.log.error "UH OH"
484
+
485
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys',
486
+ "UH OH company.name=Prefab user.admin=false user.name=michael"
487
+ end
488
+ end
489
+
490
+ def test_structured_logger_with_context_keys_ignores_nils
491
+ prefab, io = captured_logger
492
+
493
+ prefab.with_context({user: {name: "michael", job: "developer"}, company: { name: "Prefab" }}) do
494
+
495
+ prefab.log.add_context_keys "user.name", "company.name", "user.admin"
496
+
497
+ prefab.log.error "UH OH"
498
+
499
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys_ignores_nils',
500
+ "UH OH company.name=Prefab user.name=michael"
501
+ end
502
+ end
503
+
504
+ def test_structured_logger_with_context_keys_and_log_hash
505
+ prefab, io = captured_logger
506
+
507
+ prefab.with_context({user: {name: "michael", job: "developer", admin: false}, company: { name: "Prefab" }}) do
508
+
509
+ prefab.log.add_context_keys "user.name", "company.name", "user.admin"
510
+
511
+ prefab.log.error "UH OH", user_id: 6
512
+
513
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys_and_log_hash',
514
+ "UH OH company.name=Prefab user.admin=false user.name=michael user_id=6"
515
+ end
516
+
517
+ end
518
+
519
+ def test_structured_logger_with_context_keys_block
520
+ prefab, io = captured_logger
521
+
522
+ prefab.with_context({user: {name: "michael", job: "developer", admin: false}, company: { name: "Prefab" }}) do
523
+
524
+ prefab.log.add_context_keys "user.name"
525
+
526
+ prefab.log.error "UH OH"
527
+
528
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys_block',
529
+ 'UH OH user.name=michael'
530
+
531
+ prefab.log.with_context_keys("company.name") do
532
+ prefab.log.error "UH OH"
533
+
534
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys_block',
535
+ 'UH OH company.name=Prefab user.name=michael'
536
+ end
537
+
538
+ prefab.log.error "UH OH"
539
+
540
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logger_with_context_keys_block',
541
+ 'UH OH user.name=michael'
542
+ end
543
+ end
544
+
447
545
  private
448
546
 
449
547
  def assert_logged(logged_io, level, path, message)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestLoggerInitialization < Minitest::Test
6
+
7
+ def test_init_out_of_order
8
+ # assert nothing blows up
9
+ Prefab::LoggerClient.instance.info "anything"
10
+ end
11
+
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestPrefab < Minitest::Test
6
+ def test_get
7
+ Prefab.init(prefab_options)
8
+ assert_equal 'default', Prefab.get('does.not.exist', 'default')
9
+ assert_equal 'test sample value', Prefab.get('sample')
10
+ assert_equal 123, Prefab.get('sample_int')
11
+ end
12
+ end
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.1.2
4
+ version: 1.2.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-10-13 00:00:00.000000000 Z
11
+ date: 2023-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -224,6 +224,7 @@ files:
224
224
  - lib/prefab/murmer3.rb
225
225
  - lib/prefab/options.rb
226
226
  - lib/prefab/periodic_sync.rb
227
+ - lib/prefab/prefab.rb
227
228
  - lib/prefab/rate_limit_cache.rb
228
229
  - lib/prefab/resolved_config_presenter.rb
229
230
  - lib/prefab/sse_logger.rb
@@ -259,7 +260,9 @@ files:
259
260
  - test/test_local_config_parser.rb
260
261
  - test/test_log_path_aggregator.rb
261
262
  - test/test_logger.rb
263
+ - test/test_logger_initialization.rb
262
264
  - test/test_options.rb
265
+ - test/test_prefab.rb
263
266
  - test/test_rate_limit_cache.rb
264
267
  - test/test_weighted_value_resolver.rb
265
268
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby