prefab-cloud-ruby 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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=