prefab-cloud-ruby 1.1.1 → 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: 4207ba2ec83b02534caa1d8f23b75c2120c8d277253312a5bd98898c38baf156
4
- data.tar.gz: 8698e869c65017759a56c01f3ff57bc16b9597cc4d1edc219fc282f0ae371e0c
3
+ metadata.gz: 5bfaef7482dfdd1946bd0b0dee2cd0f366f5661e275bb8b906fb207d9b241d14
4
+ data.tar.gz: ed36c67d8039d2250fab5c6b1ff08da23cde51f696c256daaa943639205f2d0e
5
5
  SHA512:
6
- metadata.gz: 31eb4a0e5e132dd3136a48f9afa5f7d7decb73eccb43cc74329e9fc2b3c5a5b7109bc53a3c468e8272ae0d4ad04e3f85fb516eb16b6590325e23046e6f1c9d7b
7
- data.tar.gz: 9ea6c49f4ffe05d15654143c98dc7f74d405ac00bd919d3ae43547d9aa3ba4c3adcfa54626a0c393fa766cb420af29231032f07dd4da1c53d71c12da2cfc273a
6
+ metadata.gz: 209f9cc828ec0eccd3b34162d9513c53695bf36838b60601103fd699748fad78af28cb622c1d057ea63162abd143f4db176395726a52517426885483789c7a3c
7
+ data.tar.gz: 3d368dc2aab1e941607e437d889dd51c8d5aaf9e65208d81552f930cfa34db0eff02fba1b7f2e7a3a08ab87a23e11fad67a0634861e586649fbc466a4f2d3629
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
7
+ ## 1.1.2 - 2023-10-13
8
+
9
+ - Add `cloud.prefab.client.criteria_evaluator` `debug` logging of evaluations (#150)
10
+ - Add `x_use_local_cache` for local caching (#148)
11
+ - Tests run in RubyMine (#147)
12
+
3
13
  ## 1.1.1 - 2023-10-11
4
14
 
5
15
  - Migrate happy-path client-initialization logging to `DEBUG` level rather than `INFO` (#144)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.1
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)
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
@@ -5,13 +5,15 @@ module Prefab
5
5
  RECONNECT_WAIT = 5
6
6
  DEFAULT_CHECKPOINT_FREQ_SEC = 60
7
7
  SSE_READ_TIMEOUT = 300
8
+ STALE_CACHE_WARN_HOURS = 5
8
9
  AUTH_USER = 'authuser'
9
10
  LOGGING_KEY_PREFIX = "#{Prefab::LoggerClient::BASE_KEY}#{Prefab::LoggerClient::SEP}".freeze
11
+ LOG = Prefab::InternalLogger.new(ConfigClient)
10
12
 
11
13
  def initialize(base_client, timeout)
12
14
  @base_client = base_client
13
15
  @options = base_client.options
14
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient'
16
+ LOG.debug 'Initialize ConfigClient'
15
17
  @timeout = timeout
16
18
 
17
19
  @stream_lock = Concurrent::ReadWriteLock.new
@@ -22,9 +24,9 @@ module Prefab
22
24
  @config_resolver = Prefab::ConfigResolver.new(@base_client, @config_loader)
23
25
 
24
26
  @initialization_lock = Concurrent::ReadWriteLock.new
25
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquireWriteLock'
27
+ LOG.debug 'Initialize ConfigClient: AcquireWriteLock'
26
28
  @initialization_lock.acquire_write_lock
27
- @base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquiredWriteLock'
29
+ LOG.debug 'Initialize ConfigClient: AcquiredWriteLock'
28
30
  @initialized_future = Concurrent::Future.execute { @initialization_lock.acquire_read_lock }
29
31
 
30
32
  if @options.local_only?
@@ -95,8 +97,7 @@ module Prefab
95
97
  raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
96
98
  end
97
99
 
98
- @base_client.log_internal ::Logger::WARN,
99
- "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")
100
101
  @initialization_lock.release_write_lock
101
102
  end
102
103
 
@@ -112,7 +113,11 @@ module Prefab
112
113
 
113
114
  return if success
114
115
 
115
- @base_client.log_internal ::Logger::WARN, 'No success loading checkpoints'
116
+ success = load_cache
117
+
118
+ return if success
119
+
120
+ LOG.warn 'No success loading checkpoints'
116
121
  end
117
122
 
118
123
  def load_checkpoint_api_cdn
@@ -130,13 +135,14 @@ module Prefab
130
135
  if resp.status == 200
131
136
  configs = PrefabProto::Configs.decode(resp.body)
132
137
  load_configs(configs, source)
138
+ cache_configs(configs)
133
139
  true
134
140
  else
135
- @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}"
136
142
  false
137
143
  end
138
144
  rescue StandardError => e
139
- @base_client.log_internal ::Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
145
+ LOG.warn "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
140
146
  false
141
147
  end
142
148
 
@@ -161,27 +167,66 @@ module Prefab
161
167
  @config_loader.set(config, source)
162
168
  end
163
169
  if @config_loader.highwater_mark > starting_highwater_mark
164
- @base_client.log_internal ::Logger::DEBUG,
165
- "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}'")
166
171
  else
167
- @base_client.log_internal ::Logger::DEBUG,
168
- "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.")
169
173
  end
170
174
  @config_resolver.update
171
175
  finish_init!(source, project_id)
172
176
  end
173
177
 
178
+ def cache_path
179
+ return @cache_path unless @cache_path.nil?
180
+ @cache_path ||= calc_cache_path
181
+ FileUtils.mkdir_p(File.dirname(@cache_path))
182
+ @cache_path
183
+ end
184
+
185
+ def calc_cache_path
186
+ file_name = "prefab.cache.#{@base_client.options.api_key_id}.json"
187
+ dir = ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache'))
188
+ File.join(dir, file_name)
189
+ end
190
+
191
+ def cache_configs(configs)
192
+ return unless @options.use_local_cache && !@options.is_fork
193
+ File.open(cache_path, "w") do |f|
194
+ f.flock(File::LOCK_EX)
195
+ f.write(PrefabProto::Configs.encode_json(configs))
196
+ end
197
+ LOG.debug "Cached configs to #{cache_path}"
198
+ rescue => e
199
+ LOG.debug "Failed to cache configs to #{cache_path} #{e}"
200
+ end
201
+
202
+ def load_cache
203
+ return false unless @options.use_local_cache
204
+ File.open(cache_path) do |f|
205
+ f.flock(File::LOCK_SH)
206
+ configs = PrefabProto::Configs.decode_json(f.read)
207
+ load_configs(configs, :cache)
208
+
209
+ hours_old = ((Time.now - File.mtime(f)) / 60 / 60).round(2)
210
+ if hours_old > STALE_CACHE_WARN_HOURS
211
+ LOG.info "Stale Cache Load: #{hours_old} hours old"
212
+ end
213
+ end
214
+ rescue => e
215
+ LOG.debug "Failed to read cached configs at #{cache_path}. #{e}"
216
+ false
217
+ end
218
+
174
219
  # A thread that checks for a checkpoint
175
220
  def start_checkpointing_thread
176
221
  Thread.new do
177
222
  loop do
178
- load_checkpoint
179
-
180
223
  started_at = Time.now
181
224
  delta = @checkpoint_freq_secs - (Time.now - started_at)
182
225
  sleep(delta) if delta > 0
226
+
227
+ load_checkpoint
183
228
  rescue StandardError => e
184
- @base_client.log_internal ::Logger::DEBUG, "Issue Checkpointing #{e.message}"
229
+ LOG.debug "Issue Checkpointing #{e.message}"
185
230
  end
186
231
  end
187
232
  end
@@ -189,18 +234,19 @@ module Prefab
189
234
  def finish_init!(source, project_id)
190
235
  return unless @initialization_lock.write_locked?
191
236
 
192
- @base_client.log_internal ::Logger::DEBUG, "Unlocked Config via #{source}"
237
+ LOG.debug "Unlocked Config via #{source}"
193
238
  @initialization_lock.release_write_lock
194
- @base_client.log.config_client = self
239
+
240
+ Prefab::LoggerClient.instance.config_client = self
195
241
  presenter = Prefab::ConfigClientPresenter.new(
196
242
  size: @config_resolver.local_store.size,
197
243
  source: source,
198
244
  project_id: project_id,
199
245
  project_env_id: @config_resolver.project_env_id,
200
- api_key: @base_client.options.api_key
246
+ api_key_id: @base_client.options.api_key_id
201
247
  )
202
- @base_client.log_internal ::Logger::INFO, presenter.to_s
203
- @base_client.log_internal ::Logger::DEBUG, to_s
248
+ LOG.info presenter.to_s
249
+ LOG.debug to_s
204
250
  end
205
251
 
206
252
  def start_sse_streaming_connection_thread(start_at_id)
@@ -212,11 +258,11 @@ module Prefab
212
258
  'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
213
259
  }
214
260
  url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
215
- @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}"
216
262
  @streaming_thread = SSE::Client.new(url,
217
263
  headers: headers,
218
264
  read_timeout: SSE_READ_TIMEOUT,
219
- logger: Prefab::SseLogger.new(@base_client.log)) do |client|
265
+ logger: Prefab::SseLogger.new) do |client|
220
266
  client.on_event do |event|
221
267
  configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
222
268
  load_configs(configs, :sse)
@@ -2,12 +2,12 @@
2
2
 
3
3
  module Prefab
4
4
  class ConfigClientPresenter
5
- def initialize(size:, source:, project_id:, project_env_id:, api_key:)
5
+ def initialize(size:, source:, project_id:, project_env_id:, api_key_id:)
6
6
  @size = size
7
7
  @source = source
8
8
  @project_id = project_id
9
9
  @project_env_id = project_env_id
10
- @api_key_id = api_key&.split("-")&.first
10
+ @api_key_id = api_key_id
11
11
  end
12
12
 
13
13
  def to_s
@@ -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
 
@@ -19,8 +20,10 @@ module Prefab
19
20
  end
20
21
 
21
22
  def evaluate(properties)
22
- evaluate_for_env(@project_env_id, properties) ||
23
+ rtn = evaluate_for_env(@project_env_id, properties) ||
23
24
  evaluate_for_env(0, properties)
25
+ LOG.debug "Eval Key #{@config.key} Result #{rtn&.value} with #{properties.to_h}" unless @config.config_type == :LOG_LEVEL
26
+ rtn
24
27
  end
25
28
 
26
29
  def all_criteria_match?(conditional_value, props)
@@ -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,16 +17,42 @@ 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
24
30
  @silences = Concurrent::Map.new(initial_capacity: 2)
31
+ @recurse_check = Concurrent::Map.new(initial_capacity: 2)
25
32
  @prefix = "#{prefix}#{prefix && '.'}"
26
33
 
34
+ @context_keys = Concurrent::Set.new
35
+
27
36
  @log_path_aggregator = log_path_aggregator
37
+ @@shared_instance = self
38
+ end
39
+
40
+ def add_context_keys(*keys)
41
+ @context_keys += keys
28
42
  end
29
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
30
56
  def add_internal(severity, message, progname, loc, log_context={}, &block)
31
57
  path_loc = get_loc_path(loc)
32
58
  path = @prefix + path_loc
@@ -36,19 +62,24 @@ module Prefab
36
62
  log(message, path, progname, severity, log_context, &block)
37
63
  end
38
64
 
39
- def log_internal(message, path, progname, severity, log_context={}, &block)
65
+ def log_internal(severity, message, path, log_context={}, &block)
66
+ return if @recurse_check[local_log_id]
67
+ @recurse_check[local_log_id] = true
68
+
40
69
  path = if path
41
70
  "#{INTERNAL_PREFIX}.#{path}"
42
71
  else
43
72
  INTERNAL_PREFIX
44
73
  end
45
-
46
- log(message, path, progname, severity, log_context, &block)
74
+ begin
75
+ log(message, path, nil, severity, log_context, &block)
76
+ ensure
77
+ @recurse_check[local_log_id] = false
78
+ end
47
79
  end
48
80
 
49
81
  def log(message, path, progname, severity, log_context={})
50
82
  severity ||= ::Logger::UNKNOWN
51
-
52
83
  return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
53
84
 
54
85
  progname = @progname if progname.nil?
@@ -63,7 +94,7 @@ module Prefab
63
94
  end
64
95
 
65
96
  @logdev.write(
66
- 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)))
67
98
  )
68
99
  true
69
100
  end
@@ -131,6 +162,17 @@ module Prefab
131
162
 
132
163
  NO_DEFAULT = nil
133
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
+
134
176
  # Find the closest match to 'log_level.path' in config
135
177
  def level_of(path)
136
178
  closest_log_level_match = nil
@@ -16,6 +16,8 @@ module Prefab
16
16
  attr_reader :prefab_config_classpath_dir
17
17
  attr_reader :prefab_envs
18
18
  attr_reader :collect_sync_interval
19
+ attr_reader :use_local_cache
20
+ attr_accessor :is_fork
19
21
 
20
22
  DEFAULT_LOG_FORMATTER = proc { |data|
21
23
  severity = data[:severity]
@@ -27,7 +29,9 @@ module Prefab
27
29
 
28
30
  progname = (progname.nil? || progname.empty?) ? path : "#{progname}: #{path}"
29
31
 
30
- 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(" ")
31
35
  "#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}#{log_context.any? ? " " + formatted_log_context : ""}\n"
32
36
  }
33
37
 
@@ -77,7 +81,8 @@ module Prefab
77
81
  context_max_size: DEFAULT_MAX_EVAL_SUMMARIES,
78
82
  collect_evaluation_summaries: true,
79
83
  collect_max_evaluation_summaries: DEFAULT_MAX_EVAL_SUMMARIES,
80
- allow_telemetry_in_local_mode: false
84
+ allow_telemetry_in_local_mode: false,
85
+ x_use_local_cache: false
81
86
  )
82
87
  @api_key = api_key
83
88
  @logdev = logdev
@@ -98,6 +103,8 @@ module Prefab
98
103
  @collect_evaluation_summaries = collect_evaluation_summaries
99
104
  @collect_max_evaluation_summaries = collect_max_evaluation_summaries
100
105
  @allow_telemetry_in_local_mode = allow_telemetry_in_local_mode
106
+ @use_local_cache = x_use_local_cache
107
+ @is_fork = false
101
108
 
102
109
  # defaults that may be overridden by context_upload_mode
103
110
  @collect_shapes = false
@@ -156,6 +163,16 @@ module Prefab
156
163
  ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
157
164
  end
158
165
 
166
+ def api_key_id
167
+ @api_key&.split("-")&.first
168
+ end
169
+
170
+ def for_fork
171
+ clone = self.clone
172
+ clone.is_fork = true
173
+ clone
174
+ end
175
+
159
176
  private
160
177
 
161
178
  def telemetry_allowed?(option)
@@ -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.1 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.1"
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-11"
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]
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
21
21
  "README.md"
22
22
  ]
23
23
  s.files = [
24
- ".envrc",
25
24
  ".envrc.sample",
26
25
  ".github/workflows/ruby.yml",
27
26
  ".gitmodules",
@@ -66,6 +65,7 @@ Gem::Specification.new do |s|
66
65
  "lib/prefab/murmer3.rb",
67
66
  "lib/prefab/options.rb",
68
67
  "lib/prefab/periodic_sync.rb",
68
+ "lib/prefab/prefab.rb",
69
69
  "lib/prefab/rate_limit_cache.rb",
70
70
  "lib/prefab/resolved_config_presenter.rb",
71
71
  "lib/prefab/sse_logger.rb",
@@ -101,7 +101,9 @@ Gem::Specification.new do |s|
101
101
  "test/test_local_config_parser.rb",
102
102
  "test/test_log_path_aggregator.rb",
103
103
  "test/test_logger.rb",
104
+ "test/test_logger_initialization.rb",
104
105
  "test/test_options.rb",
106
+ "test/test_prefab.rb",
105
107
  "test/test_rate_limit_cache.rb",
106
108
  "test/test_weighted_value_resolver.rb"
107
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, message); 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
@@ -9,7 +9,8 @@ class TestConfigClient < Minitest::Test
9
9
  prefab_config_override_dir: 'none',
10
10
  prefab_config_classpath_dir: 'test',
11
11
  prefab_envs: 'unit_tests',
12
- prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
12
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
13
+ x_use_local_cache: true,
13
14
  )
14
15
 
15
16
  @config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
@@ -73,4 +74,36 @@ class TestConfigClient < Minitest::Test
73
74
 
74
75
  assert_match(/format is invalid/, err.message)
75
76
  end
77
+
78
+ def test_caching
79
+ @config_client.send(:cache_configs,
80
+ PrefabProto::Configs.new(configs:
81
+ [PrefabProto::Config.new(key: 'test', id: 1,
82
+ rows: [PrefabProto::ConfigRow.new(
83
+ values: [
84
+ PrefabProto::ConditionalValue.new(
85
+ value: PrefabProto::ConfigValue.new(string: "test value")
86
+ )
87
+ ]
88
+ )])],
89
+ config_service_pointer: PrefabProto::ConfigServicePointer.new(project_id: 3, project_env_id: 5)))
90
+ @config_client.send(:load_cache)
91
+ assert_equal "test value", @config_client.get("test")
92
+ end
93
+
94
+ def test_cache_path_respects_xdg
95
+ options = Prefab::Options.new(
96
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
97
+ x_use_local_cache: true,
98
+ api_key: "123-ENV-KEY-SDK",)
99
+
100
+ config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
101
+ assert_equal "#{Dir.home}/.cache/prefab.cache.123.json", config_client.send(:cache_path)
102
+
103
+ with_env('XDG_CACHE_HOME', '/tmp') do
104
+ config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
105
+ assert_equal "/tmp/prefab.cache.123.json", config_client.send(:cache_path)
106
+ end
107
+ end
108
+
76
109
  end
@@ -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,18 +710,9 @@ 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
718
  def evaluation_summary_aggregator
data/test/test_helper.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/focus'
5
5
  require 'minitest/reporters'
6
- Minitest::Reporters.use!
6
+ Minitest::Reporters.use! unless ENV['RM_INFO']
7
7
 
8
8
  require 'prefab-cloud-ruby'
9
9
 
@@ -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.1
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-11 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
@@ -180,7 +180,6 @@ extra_rdoc_files:
180
180
  - LICENSE.txt
181
181
  - README.md
182
182
  files:
183
- - ".envrc"
184
183
  - ".envrc.sample"
185
184
  - ".github/workflows/ruby.yml"
186
185
  - ".gitmodules"
@@ -225,6 +224,7 @@ files:
225
224
  - lib/prefab/murmer3.rb
226
225
  - lib/prefab/options.rb
227
226
  - lib/prefab/periodic_sync.rb
227
+ - lib/prefab/prefab.rb
228
228
  - lib/prefab/rate_limit_cache.rb
229
229
  - lib/prefab/resolved_config_presenter.rb
230
230
  - lib/prefab/sse_logger.rb
@@ -260,7 +260,9 @@ files:
260
260
  - test/test_local_config_parser.rb
261
261
  - test/test_log_path_aggregator.rb
262
262
  - test/test_logger.rb
263
+ - test/test_logger_initialization.rb
263
264
  - test/test_options.rb
265
+ - test/test_prefab.rb
264
266
  - test/test_rate_limit_cache.rb
265
267
  - test/test_weighted_value_resolver.rb
266
268
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
data/.envrc DELETED
@@ -1,2 +0,0 @@
1
- export AWS_ACCESS_KEY_ID=
2
- export AWS_SECRET_ACCESS_KEY=