prefab-cloud-ruby 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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc.sample +3 -0
  3. data/.github/workflows/ruby.yml +46 -0
  4. data/.gitmodules +3 -0
  5. data/.rubocop.yml +13 -0
  6. data/.tool-versions +1 -0
  7. data/CHANGELOG.md +169 -0
  8. data/CODEOWNERS +1 -0
  9. data/Gemfile +26 -0
  10. data/Gemfile.lock +188 -0
  11. data/LICENSE.txt +20 -0
  12. data/README.md +94 -0
  13. data/Rakefile +50 -0
  14. data/VERSION +1 -0
  15. data/bin/console +21 -0
  16. data/compile_protos.sh +18 -0
  17. data/lib/prefab/client.rb +153 -0
  18. data/lib/prefab/config_client.rb +292 -0
  19. data/lib/prefab/config_client_presenter.rb +18 -0
  20. data/lib/prefab/config_loader.rb +84 -0
  21. data/lib/prefab/config_resolver.rb +77 -0
  22. data/lib/prefab/config_value_unwrapper.rb +115 -0
  23. data/lib/prefab/config_value_wrapper.rb +18 -0
  24. data/lib/prefab/context.rb +179 -0
  25. data/lib/prefab/context_shape.rb +20 -0
  26. data/lib/prefab/context_shape_aggregator.rb +65 -0
  27. data/lib/prefab/criteria_evaluator.rb +136 -0
  28. data/lib/prefab/encryption.rb +65 -0
  29. data/lib/prefab/error.rb +6 -0
  30. data/lib/prefab/errors/env_var_parse_error.rb +11 -0
  31. data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
  32. data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
  33. data/lib/prefab/errors/missing_default_error.rb +13 -0
  34. data/lib/prefab/errors/missing_env_var_error.rb +11 -0
  35. data/lib/prefab/errors/uninitialized_error.rb +13 -0
  36. data/lib/prefab/evaluation.rb +52 -0
  37. data/lib/prefab/evaluation_summary_aggregator.rb +87 -0
  38. data/lib/prefab/example_contexts_aggregator.rb +78 -0
  39. data/lib/prefab/exponential_backoff.rb +21 -0
  40. data/lib/prefab/feature_flag_client.rb +42 -0
  41. data/lib/prefab/http_connection.rb +41 -0
  42. data/lib/prefab/internal_logger.rb +16 -0
  43. data/lib/prefab/local_config_parser.rb +151 -0
  44. data/lib/prefab/log_path_aggregator.rb +69 -0
  45. data/lib/prefab/logger_client.rb +264 -0
  46. data/lib/prefab/murmer3.rb +50 -0
  47. data/lib/prefab/options.rb +208 -0
  48. data/lib/prefab/periodic_sync.rb +69 -0
  49. data/lib/prefab/prefab.rb +56 -0
  50. data/lib/prefab/rate_limit_cache.rb +41 -0
  51. data/lib/prefab/resolved_config_presenter.rb +86 -0
  52. data/lib/prefab/time_helpers.rb +7 -0
  53. data/lib/prefab/weighted_value_resolver.rb +42 -0
  54. data/lib/prefab/yaml_config_parser.rb +34 -0
  55. data/lib/prefab-cloud-ruby.rb +57 -0
  56. data/lib/prefab_pb.rb +93 -0
  57. data/prefab-cloud-ruby.gemspec +155 -0
  58. data/test/.prefab.default.config.yaml +2 -0
  59. data/test/.prefab.unit_tests.config.yaml +28 -0
  60. data/test/integration_test.rb +150 -0
  61. data/test/integration_test_helpers.rb +151 -0
  62. data/test/support/common_helpers.rb +180 -0
  63. data/test/support/mock_base_client.rb +42 -0
  64. data/test/support/mock_config_client.rb +19 -0
  65. data/test/support/mock_config_loader.rb +1 -0
  66. data/test/test_client.rb +444 -0
  67. data/test/test_config_client.rb +109 -0
  68. data/test/test_config_loader.rb +117 -0
  69. data/test/test_config_resolver.rb +430 -0
  70. data/test/test_config_value_unwrapper.rb +224 -0
  71. data/test/test_config_value_wrapper.rb +42 -0
  72. data/test/test_context.rb +203 -0
  73. data/test/test_context_shape.rb +50 -0
  74. data/test/test_context_shape_aggregator.rb +147 -0
  75. data/test/test_criteria_evaluator.rb +726 -0
  76. data/test/test_encryption.rb +16 -0
  77. data/test/test_evaluation_summary_aggregator.rb +162 -0
  78. data/test/test_example_contexts_aggregator.rb +238 -0
  79. data/test/test_exponential_backoff.rb +18 -0
  80. data/test/test_feature_flag_client.rb +48 -0
  81. data/test/test_helper.rb +17 -0
  82. data/test/test_integration.rb +58 -0
  83. data/test/test_local_config_parser.rb +147 -0
  84. data/test/test_log_path_aggregator.rb +62 -0
  85. data/test/test_logger.rb +621 -0
  86. data/test/test_logger_initialization.rb +12 -0
  87. data/test/test_options.rb +75 -0
  88. data/test/test_prefab.rb +12 -0
  89. data/test/test_rate_limit_cache.rb +44 -0
  90. data/test/test_weighted_value_resolver.rb +71 -0
  91. metadata +337 -0
@@ -0,0 +1,621 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestLogger < Minitest::Test
6
+ TEST_ENV_ID = 2
7
+ DEFAULT_VALUE = 'FATAL'
8
+ DEFAULT_ENV_VALUE = 'INFO'
9
+ DESIRED_VALUE = 'DEBUG'
10
+ WRONG_ENV_VALUE = 'ERROR'
11
+ PROJECT_ENV_ID = 1
12
+
13
+ DEFAULT_ROW = PrefabProto::ConfigRow.new(
14
+ values: [
15
+ PrefabProto::ConditionalValue.new(
16
+ value: PrefabProto::ConfigValue.new(log_level: DEFAULT_VALUE)
17
+ )
18
+ ]
19
+ )
20
+
21
+ def setup
22
+ super
23
+ Prefab::LoggerClient.send(:public, :get_path)
24
+ Prefab::LoggerClient.send(:public, :get_loc_path)
25
+ Prefab::LoggerClient.send(:public, :level_of)
26
+ @logger = Prefab::LoggerClient.new($stdout)
27
+ end
28
+
29
+ def test_get_path
30
+ assert_equal 'test_l.foo_warn',
31
+ @logger.get_path('/Users/jdwyah/Documents/workspace/RateLimitInc/prefab-cloud-ruby/lib/test_l.rb',
32
+ 'foo_warn')
33
+
34
+ assert_equal 'active_support.log_subscriber.info',
35
+ @logger.get_path('/Users/jdwyah/.rvm/gems/ruby-2.3.3@forcerank/gems/activesupport-4.1.16/lib/active_support/log_subscriber.rb',
36
+ 'info')
37
+ assert_equal 'active_support.log_subscriber.info',
38
+ @logger.get_path("/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'",
39
+ 'info')
40
+ assert_equal 'unknown.info',
41
+ @logger.get_path(nil,
42
+ 'info')
43
+ end
44
+
45
+ def test_loc_resolution
46
+ backtrace_location = Struct.new(:absolute_path, :base_label, :string) do
47
+ def to_s
48
+ string
49
+ end
50
+ end # https://ruby-doc.org/core-3.0.0/Thread/Backtrace/Location.html
51
+
52
+ # verify that even if the Thread::Backtrace::Location does not have an absolute_location, we do our best
53
+ assert_equal 'active_support.log_subscriber.info',
54
+ @logger.get_loc_path(backtrace_location.new(nil,
55
+ 'info',
56
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
57
+ assert_equal 'test_l.info',
58
+ @logger.get_loc_path(backtrace_location.new('/Users/jdwyah/Documents/workspace/RateLimitInc/prefab-cloud-ruby/lib/test_l.rb',
59
+ 'info',
60
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
61
+ end
62
+
63
+ def test_level_of
64
+ with_env('PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL', 'info') do
65
+ # env var overrides the default level
66
+ assert_equal ::Logger::INFO,
67
+ @logger.level_of('app.models.user'), 'PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info'
68
+
69
+ @logger.config_client = MockConfigClient.new({})
70
+ assert_equal ::Logger::WARN,
71
+ @logger.level_of('app.models.user'), 'default is warn'
72
+
73
+ @logger.config_client = MockConfigClient.new('log-level.app' => :INFO)
74
+ assert_equal ::Logger::INFO,
75
+ @logger.level_of('app.models.user')
76
+
77
+ @logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG)
78
+ assert_equal ::Logger::DEBUG,
79
+ @logger.level_of('app.models.user')
80
+
81
+ @logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG,
82
+ 'log-level.app.models' => :ERROR)
83
+ assert_equal ::Logger::ERROR,
84
+ @logger.level_of('app.models.user'), 'test leveling'
85
+ end
86
+ end
87
+
88
+ def test_log_internal
89
+ prefab, io = captured_logger
90
+ prefab.log.log_internal(::Logger::WARN, 'test message', 'cloud.prefab.client.test.path')
91
+ assert_logged io, 'WARN', "cloud.prefab.client.test.path", "test message"
92
+ end
93
+
94
+ def test_log_internal_unknown
95
+ prefab, io = captured_logger
96
+ prefab.log.log_internal(::Logger::UNKNOWN, 'test message', 'cloud.prefab.client.test.path')
97
+ assert_logged io, 'ANY', "cloud.prefab.client.test.path", "test message"
98
+ end
99
+
100
+ def test_log_internal_silencing
101
+ prefab, io = captured_logger
102
+ prefab.log.silence do
103
+ prefab.log.log_internal(::Logger::WARN, 'should not log', 'cloud.prefab.client.test.path')
104
+ end
105
+ prefab.log.log_internal(::Logger::WARN, 'should log', 'cloud.prefab.client.test.path')
106
+ assert_logged io, 'WARN', "cloud.prefab.client.test.path", "should log"
107
+ refute_logged io, 'should not log'
108
+ end
109
+
110
+ def test_log
111
+ prefab, io = captured_logger
112
+ prefab.log.log('test message', 'test.path', '', ::Logger::WARN)
113
+ assert_logged io, 'WARN', "test.path", "test message"
114
+ end
115
+
116
+ def test_log_unknown
117
+ prefab, io = captured_logger
118
+ prefab.log.log('test message', 'test.path', '', ::Logger::UNKNOWN)
119
+ assert_logged io, 'ANY', "test.path", "test message"
120
+ end
121
+
122
+ def test_log_silencing
123
+ prefab, io = captured_logger
124
+ prefab.log.silence do
125
+ prefab.log.log('should not log', 'test.path', '', ::Logger::WARN)
126
+ end
127
+ prefab.log.log('should log', 'test.path', '', ::Logger::WARN)
128
+ assert_logged io, 'WARN', "test.path", "should log"
129
+ refute_logged io, 'should not log'
130
+ end
131
+
132
+ def test_logging_with_prefix
133
+ prefix = 'my.own.prefix'
134
+ message = 'this is a test'
135
+
136
+ prefab, io = captured_logger(log_prefix: prefix)
137
+
138
+ prefixed_logger = prefab.log
139
+ prefixed_logger.error message
140
+
141
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_prefix", message
142
+ end
143
+
144
+ def test_logging_without_a_progname
145
+ prefab, io = captured_logger
146
+ message = 'MY MESSAGE'
147
+
148
+ prefab.log.error message
149
+
150
+ assert_logged io, 'ERROR', 'test.test_logger.test_logging_without_a_progname', message
151
+ end
152
+
153
+ def test_logging_without_a_progname_or_message
154
+ prefab, io = captured_logger
155
+
156
+ prefab.log.error
157
+
158
+ assert_logged io, 'ERROR', 'test.test_logger.test_logging_without_a_progname_or_message', ''
159
+ end
160
+
161
+ def test_logging_with_a_progname
162
+ prefab, io = captured_logger
163
+ message = 'MY MESSAGE'
164
+
165
+ prefab.log.progname = 'MY_PROGNAME'
166
+ prefab.log.error message
167
+
168
+ assert_logged io, 'ERROR', 'MY_PROGNAME: test.test_logger.test_logging_with_a_progname', message
169
+ end
170
+
171
+ def test_logging_with_a_progname_and_no_message
172
+ prefab, io = captured_logger
173
+
174
+ prefab.log.progname = 'MY_PROGNAME'
175
+ prefab.log.error
176
+
177
+ assert_logged io, 'ERROR', 'MY_PROGNAME: test.test_logger.test_logging_with_a_progname_and_no_message', 'MY_PROGNAME'
178
+ end
179
+
180
+ def test_logging_with_criteria_on_top_level_key
181
+ prefix = 'my.own.prefix'
182
+
183
+ config = PrefabProto::Config.new(
184
+ key: 'log-level',
185
+ rows: [
186
+ DEFAULT_ROW,
187
+
188
+ # wrong env
189
+ PrefabProto::ConfigRow.new(
190
+ project_env_id: TEST_ENV_ID,
191
+ values: [
192
+ PrefabProto::ConditionalValue.new(
193
+ criteria: [
194
+ PrefabProto::Criterion.new(
195
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
196
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
197
+ property_name: 'user.email_suffix'
198
+ )
199
+ ],
200
+ value: PrefabProto::ConfigValue.new(log_level: WRONG_ENV_VALUE)
201
+ )
202
+ ]
203
+ ),
204
+
205
+ # correct env
206
+ PrefabProto::ConfigRow.new(
207
+ project_env_id: PROJECT_ENV_ID,
208
+ values: [
209
+ PrefabProto::ConditionalValue.new(
210
+ criteria: [
211
+ PrefabProto::Criterion.new(
212
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
213
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
214
+ property_name: 'user.email_suffix'
215
+ )
216
+ ],
217
+ value: PrefabProto::ConfigValue.new(log_level: DESIRED_VALUE)
218
+ ),
219
+ PrefabProto::ConditionalValue.new(
220
+ value: PrefabProto::ConfigValue.new(log_level: DEFAULT_ENV_VALUE)
221
+ )
222
+ ]
223
+ )
224
+ ]
225
+ )
226
+
227
+ prefab, io = captured_logger(log_prefix: prefix)
228
+
229
+ inject_config(prefab, config)
230
+ inject_project_env_id(prefab, PROJECT_ENV_ID)
231
+
232
+ # without any context, the level should be the default for the env (info)
233
+ prefab.with_context({}) do
234
+ prefab.log.debug 'Test debug'
235
+ refute_logged io, 'Test debug'
236
+
237
+ prefab.log.info 'Test info'
238
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test info'
239
+
240
+ prefab.log.error 'Test error'
241
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test error'
242
+ end
243
+
244
+ reset_io(io)
245
+
246
+ # with the wrong context, the level should be the default for the env (info)
247
+ prefab.with_context(user: { email_suffix: 'yahoo.com' }) do
248
+ prefab.log.debug 'Test debug'
249
+ refute_logged io, 'Test debug'
250
+
251
+ prefab.log.info 'Test info'
252
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test info'
253
+
254
+ prefab.log.error 'Test error'
255
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test error'
256
+ end
257
+
258
+ reset_io(io)
259
+
260
+ # with the correct context, the level should be the desired value (debug)
261
+ prefab.with_context(user: { email_suffix: 'hotmail.com' }) do
262
+ prefab.log.debug 'Test debug'
263
+ assert_logged io, 'DEBUG', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test debug'
264
+
265
+ prefab.log.info 'Test info'
266
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test info'
267
+
268
+ prefab.log.error 'Test error'
269
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_top_level_key", 'Test error'
270
+ end
271
+ end
272
+
273
+ def test_logging_with_criteria_on_key_path
274
+ prefix = 'my.own.prefix'
275
+
276
+ config = PrefabProto::Config.new(
277
+ key: 'log-level.my.own.prefix.test.test_logger',
278
+ rows: [
279
+ DEFAULT_ROW,
280
+
281
+ # wrong env
282
+ PrefabProto::ConfigRow.new(
283
+ project_env_id: TEST_ENV_ID,
284
+ values: [
285
+ PrefabProto::ConditionalValue.new(
286
+ criteria: [
287
+ PrefabProto::Criterion.new(
288
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
289
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
290
+ property_name: 'email_suffix'
291
+ )
292
+ ],
293
+ value: PrefabProto::ConfigValue.new(log_level: WRONG_ENV_VALUE)
294
+ )
295
+ ]
296
+ ),
297
+
298
+ # correct env
299
+ PrefabProto::ConfigRow.new(
300
+ project_env_id: PROJECT_ENV_ID,
301
+ values: [
302
+ PrefabProto::ConditionalValue.new(
303
+ criteria: [
304
+ PrefabProto::Criterion.new(
305
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
306
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
307
+ property_name: 'user.email_suffix'
308
+ )
309
+ ],
310
+ value: PrefabProto::ConfigValue.new(log_level: DESIRED_VALUE)
311
+ ),
312
+
313
+ PrefabProto::ConditionalValue.new(
314
+ criteria: [
315
+ PrefabProto::Criterion.new(
316
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
317
+ value_to_match: string_list(%w[user:4567]),
318
+ property_name: 'user.tracking_id'
319
+ )
320
+ ],
321
+ value: PrefabProto::ConfigValue.new(log_level: DESIRED_VALUE)
322
+ ),
323
+
324
+ PrefabProto::ConditionalValue.new(
325
+ value: PrefabProto::ConfigValue.new(log_level: DEFAULT_ENV_VALUE)
326
+ )
327
+ ]
328
+ )
329
+ ]
330
+ )
331
+
332
+ prefab, io = captured_logger(log_prefix: prefix)
333
+
334
+ inject_config(prefab, config)
335
+ inject_project_env_id(prefab, PROJECT_ENV_ID)
336
+
337
+ # without any context, the level should be the default for the env (info)
338
+ prefab.with_context({}) do
339
+ prefab.log.debug 'Test debug'
340
+ refute_logged io, 'Test debug'
341
+
342
+ prefab.log.info 'Test info'
343
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test info'
344
+
345
+ prefab.log.error 'Test error'
346
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test error'
347
+ end
348
+
349
+ reset_io(io)
350
+
351
+ # with the wrong context, the level should be the default for the env (info)
352
+ prefab.with_context(user: { email_suffix: 'yahoo.com' }) do
353
+ prefab.log.debug 'Test debug'
354
+ refute_logged io, 'Test debug'
355
+
356
+ prefab.log.info 'Test info'
357
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test info'
358
+
359
+ prefab.log.error 'Test error'
360
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test error'
361
+ end
362
+
363
+ reset_io(io)
364
+
365
+ # with the correct context, the level should be the desired value (debug)
366
+ prefab.with_context(user: { email_suffix: 'hotmail.com' }) do
367
+ prefab.log.debug 'Test debug'
368
+ assert_logged io, 'DEBUG', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test debug'
369
+
370
+ prefab.log.info 'Test info'
371
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test info'
372
+
373
+ prefab.log.error 'Test error'
374
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test error'
375
+ end
376
+
377
+ reset_io(io)
378
+
379
+ # with the correct lookup key
380
+ prefab.with_context(user: { tracking_id: 'user:4567' }) do
381
+ prefab.log.debug 'Test debug'
382
+ assert_logged io, 'DEBUG', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test debug'
383
+
384
+ prefab.log.info 'Test info'
385
+ assert_logged io, 'INFO', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test info'
386
+
387
+ prefab.log.error 'Test error'
388
+ assert_logged io, 'ERROR', "#{prefix}.test.test_logger.test_logging_with_criteria_on_key_path", 'Test error'
389
+ end
390
+ end
391
+
392
+ def test_logging_with_a_block
393
+ prefab, io = captured_logger
394
+ message = 'MY MESSAGE'
395
+
396
+ prefab.log.error do
397
+ message
398
+ end
399
+
400
+ prefab.log.info do
401
+ raise 'THIS WILL NEVER BE EVALUATED'
402
+ end
403
+
404
+ assert_logged io, 'ERROR', 'test.test_logger.test_logging_with_a_block', message
405
+ end
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
+
436
+ def test_structured_logging
437
+ prefab, io = captured_logger
438
+ message = 'HELLO'
439
+
440
+ prefab.log.error message, user: "michael", id: 123
441
+
442
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_logging', "#{message} id=123 user=michael"
443
+ end
444
+
445
+ def test_structured_json_logging
446
+ prefab, io = captured_logger(log_formatter: Prefab::Options::JSON_LOG_FORMATTER)
447
+ message = 'HELLO'
448
+
449
+ prefab.log.error message, user: "michael", id: 123
450
+
451
+ log_data = JSON.parse(io.string)
452
+ assert log_data["message"] == message
453
+ assert log_data["user"] == "michael"
454
+ assert log_data["id"] == 123
455
+ end
456
+
457
+ def test_structured_internal_logging
458
+ prefab, io = captured_logger
459
+
460
+ prefab.log.log_internal(::Logger::WARN, 'test', 'cloud.prefab.client.test.path', user: "michael")
461
+
462
+ assert_logged io, 'WARN', 'cloud.prefab.client.test.path', "test user=michael"
463
+ end
464
+
465
+ def test_structured_block_logger
466
+ prefab, io = captured_logger
467
+ message = 'MY MESSAGE'
468
+
469
+ prefab.log.error user: "michael" do
470
+ message
471
+ end
472
+
473
+ assert_logged io, 'ERROR', 'test.test_logger.test_structured_block_logger', "#{message} user=michael"
474
+ end
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
+
545
+ def test_context_key_threads
546
+ prefab, io = captured_logger
547
+
548
+ threads = []
549
+ 1000.times.map do |i|
550
+ threads << Thread.new do
551
+ prefab.with_context({test: {"thread_#{i}": "thread_#{i}"}}) do
552
+ prefab.log.add_context_keys "test.thread_#{i}"
553
+ prefab.log.error "UH OH"
554
+ assert_logged io, 'ERROR', 'test.test_logger.test_context_key_threads',
555
+ "UH OH test.thread_#{i}=thread_#{i}"
556
+ end
557
+ end
558
+ end
559
+ threads.each { |thr| thr.join }
560
+ end
561
+
562
+ def test_tagged
563
+ prefab, io = captured_logger
564
+
565
+ prefab.log.tagged([[]]) do # rails sends some of these
566
+ prefab.log.tagged("outside") do
567
+ prefab.log.tagged("nested", "tag2") do
568
+ prefab.log.error "msg"
569
+ assert_logged io, 'ERROR', 'test.test_logger.test_tagged',
570
+ 'msg log.tags=\["outside", "nested", "tag2"\]'
571
+ end
572
+ end
573
+ end
574
+ end
575
+
576
+ def test_req_tagged
577
+ prefab, io = captured_logger
578
+ prefab.log.tagged("tag-1").error "first"
579
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
580
+ 'first req.tags=\["tag-1"\]'
581
+ reset_io(io)
582
+
583
+ prefab.log.tagged("tag-2").error "2nd"
584
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
585
+ '2nd req.tags=\["tag-1", "tag-2"\]'
586
+ prefab.log.flush
587
+
588
+ prefab.log.tagged("tag-3").error "3rd"
589
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
590
+ '3rd req.tags=\["tag-3"\]'
591
+ prefab.log.flush
592
+ end
593
+
594
+ private
595
+
596
+ def assert_logged(logged_io, level, path, message)
597
+ assert_match(/#{level}\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+]?\d+:\s+#{path} #{message}\n/, logged_io.string)
598
+ end
599
+
600
+ def refute_logged(logged_io, message)
601
+ refute_match(/#{message}/, logged_io.string)
602
+ end
603
+
604
+ def captured_logger(options = {})
605
+ io = StringIO.new
606
+ options = Prefab::Options.new(**options.merge(
607
+ logdev: io,
608
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
609
+ ))
610
+ prefab = Prefab::Client.new(options)
611
+
612
+ [prefab, io]
613
+ end
614
+
615
+ def reset_io(io)
616
+ io.close
617
+ io.reopen
618
+
619
+ assert_equal '', io.string
620
+ end
621
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestLoggerInitialization < Minitest::Test
6
+
7
+ def test_init_out_of_order
8
+ # assert nothing blows up
9
+ Prefab::LoggerClient.instance.info "anything"
10
+ end
11
+
12
+ end