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 +4 -4
- data/CHANGELOG.md +10 -0
- data/VERSION +1 -1
- data/lib/prefab/client.rb +12 -10
- data/lib/prefab/config_client.rb +68 -22
- data/lib/prefab/config_client_presenter.rb +2 -2
- data/lib/prefab/config_loader.rb +4 -2
- data/lib/prefab/config_value_unwrapper.rb +2 -0
- data/lib/prefab/context_shape_aggregator.rb +4 -2
- data/lib/prefab/criteria_evaluator.rb +4 -1
- data/lib/prefab/evaluation_summary_aggregator.rb +4 -2
- data/lib/prefab/example_contexts_aggregator.rb +4 -2
- data/lib/prefab/internal_logger.rb +16 -13
- data/lib/prefab/log_path_aggregator.rb +4 -2
- data/lib/prefab/logger_client.rb +48 -6
- data/lib/prefab/options.rb +19 -2
- data/lib/prefab/periodic_sync.rb +3 -6
- data/lib/prefab/prefab.rb +56 -0
- data/lib/prefab/sse_logger.rb +21 -5
- data/lib/prefab/yaml_config_parser.rb +4 -2
- data/lib/prefab-cloud-ruby.rb +1 -0
- data/prefab-cloud-ruby.gemspec +6 -4
- data/test/support/common_helpers.rb +11 -9
- data/test/support/mock_base_client.rb +1 -3
- data/test/test_client.rb +12 -3
- data/test/test_config_client.rb +34 -1
- data/test/test_context_shape_aggregator.rb +2 -2
- data/test/test_criteria_evaluator.rb +2 -11
- data/test/test_helper.rb +1 -1
- data/test/test_log_path_aggregator.rb +1 -1
- data/test/test_logger.rb +103 -5
- data/test/test_logger_initialization.rb +12 -0
- data/test/test_prefab.rb +12 -0
- metadata +5 -3
- data/.envrc +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bfaef7482dfdd1946bd0b0dee2cd0f366f5661e275bb8b906fb207d9b241d14
|
4
|
+
data.tar.gz: ed36c67d8039d2250fab5c6b1ff08da23cde51f696c256daaa943639205f2d0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/prefab/config_client.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
27
|
+
LOG.debug 'Initialize ConfigClient: AcquireWriteLock'
|
26
28
|
@initialization_lock.acquire_write_lock
|
27
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
141
|
+
LOG.info "Checkpoint #{source} failed to load. Response #{resp.status}"
|
136
142
|
false
|
137
143
|
end
|
138
144
|
rescue StandardError => e
|
139
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
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
|
-
|
237
|
+
LOG.debug "Unlocked Config via #{source}"
|
193
238
|
@initialization_lock.release_write_lock
|
194
|
-
|
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
|
-
|
246
|
+
api_key_id: @base_client.options.api_key_id
|
201
247
|
)
|
202
|
-
|
203
|
-
|
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
|
-
|
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
|
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:,
|
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 =
|
10
|
+
@api_key_id = api_key_id
|
11
11
|
end
|
12
12
|
|
13
13
|
def to_s
|
data/lib/prefab/config_loader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
48
|
+
LOG.debug "Flushing #{to_ship.size} examples"
|
47
49
|
|
48
50
|
result = post('/api/v1/telemetry', events(to_ship))
|
49
51
|
|
50
|
-
|
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
|
6
|
-
|
7
|
-
|
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
|
11
|
-
|
13
|
+
def debug msg
|
14
|
+
Prefab::LoggerClient.instance.log_internal ::Logger::DEBUG, msg, @path
|
12
15
|
end
|
13
16
|
|
14
|
-
def info
|
15
|
-
|
17
|
+
def info msg
|
18
|
+
Prefab::LoggerClient.instance.log_internal ::Logger::INFO, msg, @path
|
16
19
|
end
|
17
20
|
|
18
|
-
def warn
|
19
|
-
|
21
|
+
def warn msg
|
22
|
+
Prefab::LoggerClient.instance.log_internal ::Logger::WARN, msg, @path
|
20
23
|
end
|
21
24
|
|
22
|
-
def error
|
23
|
-
|
25
|
+
def error msg
|
26
|
+
Prefab::LoggerClient.instance.log_internal ::Logger::ERROR, msg, @path
|
24
27
|
end
|
25
28
|
|
26
|
-
def fatal
|
27
|
-
|
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
|
-
|
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
|
-
|
65
|
+
LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
|
64
66
|
end
|
65
67
|
end
|
66
68
|
end
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -17,16 +17,42 @@ module Prefab
|
|
17
17
|
PrefabProto::LogLevel::FATAL => ::Logger::FATAL
|
18
18
|
}.freeze
|
19
19
|
|
20
|
-
def
|
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,
|
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
|
-
|
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
|
data/lib/prefab/options.rb
CHANGED
@@ -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
|
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)
|
data/lib/prefab/periodic_sync.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/prefab/sse_logger.rb
CHANGED
@@ -1,14 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Prefab
|
4
|
-
class SseLogger <
|
5
|
-
def initialize(
|
6
|
-
|
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
|
-
|
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
|
-
|
26
|
+
LOG.info "Load #{@file}"
|
25
27
|
YAML.load_file(@file)
|
26
28
|
else
|
27
|
-
|
29
|
+
LOG.info "No file #{@file}"
|
28
30
|
{}
|
29
31
|
end
|
30
32
|
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
data/prefab-cloud-ruby.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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
|
-
|
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::
|
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 :
|
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
|
data/test/test_config_client.rb
CHANGED
@@ -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
|
724
|
-
|
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
@@ -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'
|
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'
|
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'
|
103
|
+
prefab.log.log_internal(::Logger::WARN, 'should not log', 'test.path')
|
104
104
|
end
|
105
|
-
prefab.log.log_internal('should log', 'test.path'
|
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',
|
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)
|
data/test/test_prefab.rb
ADDED
@@ -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.
|
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
|
+
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