prefab-cloud-ruby 0

Sign up to get free protection for your applications and to get access to all the features.
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