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 +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