logster 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -19
  3. data/.rubocop.yml +1 -1
  4. data/.travis.yml +16 -16
  5. data/CHANGELOG.md +224 -172
  6. data/Gemfile +4 -4
  7. data/Guardfile +8 -8
  8. data/LICENSE.txt +22 -22
  9. data/README.md +99 -99
  10. data/Rakefile +21 -21
  11. data/assets/fonts/FontAwesome.otf +0 -0
  12. data/assets/fonts/fontawesome-webfont.eot +0 -0
  13. data/assets/fonts/fontawesome-webfont.svg +639 -639
  14. data/assets/fonts/fontawesome-webfont.ttf +0 -0
  15. data/assets/fonts/fontawesome-webfont.woff +0 -0
  16. data/assets/fonts/fontawesome-webfont.woff2 +0 -0
  17. data/assets/images/Icon-144_rounded.png +0 -0
  18. data/assets/images/Icon-144_square.png +0 -0
  19. data/assets/images/icon_144x144.png +0 -0
  20. data/assets/images/icon_64x64.png +0 -0
  21. data/assets/javascript/client-app.js +115 -106
  22. data/assets/stylesheets/client-app.css +1 -1
  23. data/build_client_app.sh +0 -0
  24. data/client-app/.editorconfig +20 -20
  25. data/client-app/.ember-cli +9 -9
  26. data/client-app/.eslintignore +19 -19
  27. data/client-app/.eslintrc.js +46 -46
  28. data/client-app/.gitignore +23 -23
  29. data/client-app/.travis.yml +27 -27
  30. data/client-app/.watchmanconfig +3 -3
  31. data/client-app/README.md +57 -57
  32. data/client-app/app/app.js +0 -0
  33. data/client-app/app/components/actions-menu.js +43 -43
  34. data/client-app/app/components/env-tab.js +80 -80
  35. data/client-app/app/components/message-info.js +0 -0
  36. data/client-app/app/components/message-row.js +0 -0
  37. data/client-app/app/components/panel-resizer.js +0 -0
  38. data/client-app/app/components/patterns-list.js +109 -0
  39. data/client-app/app/components/tab-contents.js +27 -27
  40. data/client-app/app/components/tabbed-section.js +0 -0
  41. data/client-app/app/components/time-formatter.js +0 -0
  42. data/client-app/app/components/update-time.js +0 -0
  43. data/client-app/app/controllers/index.js +22 -6
  44. data/client-app/app/controllers/show.js +0 -0
  45. data/client-app/app/helpers/logster-url.js +12 -0
  46. data/client-app/app/helpers/or.js +7 -0
  47. data/client-app/app/index.html +30 -29
  48. data/client-app/app/initializers/app-init.js +67 -67
  49. data/client-app/app/lib/preload.js +20 -20
  50. data/client-app/app/lib/utilities.js +150 -149
  51. data/client-app/app/models/message-collection.js +0 -0
  52. data/client-app/app/models/message.js +100 -100
  53. data/client-app/app/models/pattern-item.js +25 -0
  54. data/client-app/app/resolver.js +0 -0
  55. data/client-app/app/router.js +1 -0
  56. data/client-app/app/routes/index.js +2 -9
  57. data/client-app/app/routes/settings.js +15 -0
  58. data/client-app/app/routes/show.js +0 -0
  59. data/client-app/app/styles/app.css +633 -527
  60. data/client-app/app/templates/application.hbs +2 -2
  61. data/client-app/app/templates/components/actions-menu.hbs +12 -12
  62. data/client-app/app/templates/components/env-tab.hbs +10 -10
  63. data/client-app/app/templates/components/message-info.hbs +41 -41
  64. data/client-app/app/templates/components/message-row.hbs +15 -15
  65. data/client-app/app/templates/components/panel-resizer.hbs +3 -3
  66. data/client-app/app/templates/components/patterns-list.hbs +25 -0
  67. data/client-app/app/templates/components/tabbed-section.hbs +10 -10
  68. data/client-app/app/templates/components/time-formatter.hbs +1 -1
  69. data/client-app/app/templates/index.hbs +65 -58
  70. data/client-app/app/templates/settings.hbs +26 -0
  71. data/client-app/app/templates/show.hbs +7 -7
  72. data/client-app/config/environment.js +51 -51
  73. data/client-app/config/optional-features.json +3 -3
  74. data/client-app/config/targets.js +18 -18
  75. data/client-app/ember-cli-build.js +29 -29
  76. data/client-app/package-lock.json +11357 -11365
  77. data/client-app/package.json +57 -56
  78. data/client-app/public/assets/images/icon_144x144.png +0 -0
  79. data/client-app/public/assets/images/icon_64x64.png +0 -0
  80. data/client-app/testem.js +25 -25
  81. data/client-app/tests/index.html +34 -34
  82. data/client-app/tests/integration/components/env-tab-test.js +134 -123
  83. data/client-app/tests/integration/components/message-info-test.js +111 -111
  84. data/client-app/tests/integration/components/patterns-list-test.js +56 -0
  85. data/client-app/tests/test-helper.js +8 -8
  86. data/client-app/tests/unit/controllers/index-test.js +12 -12
  87. data/client-app/tests/unit/controllers/show-test.js +12 -12
  88. data/client-app/tests/unit/initializers/app-init-test.js +31 -31
  89. data/client-app/tests/unit/routes/index-test.js +11 -11
  90. data/client-app/tests/unit/routes/show-test.js +11 -11
  91. data/lib/examples/sidekiq_logster_reporter.rb +21 -21
  92. data/lib/logster.rb +59 -54
  93. data/lib/logster/base_store.rb +169 -141
  94. data/lib/logster/cache.rb +20 -0
  95. data/lib/logster/configuration.rb +27 -26
  96. data/lib/logster/defer_logger.rb +14 -14
  97. data/lib/logster/ignore_pattern.rb +65 -65
  98. data/lib/logster/logger.rb +113 -113
  99. data/lib/logster/message.rb +212 -212
  100. data/lib/logster/middleware/debug_exceptions.rb +26 -26
  101. data/lib/logster/middleware/reporter.rb +55 -55
  102. data/lib/logster/middleware/viewer.rb +297 -222
  103. data/lib/logster/pattern.rb +95 -0
  104. data/lib/logster/rails/railtie.rb +63 -63
  105. data/lib/logster/redis_store.rb +584 -566
  106. data/lib/logster/scheduler.rb +54 -54
  107. data/lib/logster/suppression_pattern.rb +17 -0
  108. data/lib/logster/version.rb +3 -3
  109. data/lib/logster/web.rb +14 -14
  110. data/logster.gemspec +35 -35
  111. data/test/examples/test_sidekiq_reporter_example.rb +46 -46
  112. data/test/fake_data/Gemfile +4 -4
  113. data/test/fake_data/generate.rb +10 -10
  114. data/test/logster/middleware/test_reporter.rb +19 -19
  115. data/test/logster/middleware/test_viewer.rb +267 -96
  116. data/test/logster/test_base_store.rb +147 -147
  117. data/test/logster/test_cache.rb +38 -0
  118. data/test/logster/test_defer_logger.rb +34 -34
  119. data/test/logster/test_ignore_pattern.rb +41 -41
  120. data/test/logster/test_logger.rb +100 -86
  121. data/test/logster/test_message.rb +119 -119
  122. data/test/logster/test_pattern.rb +152 -0
  123. data/test/logster/test_redis_rate_limiter.rb +230 -230
  124. data/test/logster/test_redis_store.rb +689 -720
  125. data/test/test_helper.rb +38 -38
  126. data/vendor/assets/javascripts/logster.js.erb +39 -39
  127. metadata +24 -7
@@ -1,720 +1,689 @@
1
- require_relative '../test_helper'
2
- require 'logster/redis_store'
3
- require 'rack'
4
-
5
- class TestRedisStore < Minitest::Test
6
-
7
- def setup
8
- @store = Logster::RedisStore.new(Redis.new)
9
- @store.clear_all
10
- end
11
-
12
- def teardown
13
- @store.clear_all
14
- end
15
-
16
- def test_delete
17
- env = { test_env: "this is env" }
18
- msg = @store.report(Logger::WARN, "test", "testing", env: env)
19
- @store.delete(msg)
20
- latest = @store.latest
21
-
22
- assert_equal(0, latest.length)
23
- assert_nil(@store.get_env(msg.key))
24
- end
25
-
26
- def test_latest
27
- @store.report(Logger::WARN, "test", "IGNORE")
28
- @store.report(Logger::WARN, "test", "This is a warning")
29
- @store.report(Logger::WARN, "test", "This is another warning")
30
-
31
- latest = @store.latest(limit: 2)
32
-
33
- assert_equal(2, latest.length)
34
- assert_equal("This is a warning", latest[0].message)
35
- assert_equal("This is another warning", latest[1].message)
36
- assert_equal(Logger::WARN, latest[1].severity)
37
- assert_equal("test", latest[1].progname)
38
- assert(!latest[1].key.nil?)
39
- end
40
-
41
- def test_latest_after
42
- 10.times do |i|
43
- @store.report(Logger::WARN, "test", "A#{i}")
44
- end
45
-
46
- message = @store.latest[-1]
47
-
48
- 3.times do |i|
49
- @store.report(Logger::WARN, "test", i.to_s)
50
- end
51
-
52
- message = @store.latest(after: message.key, limit: 3)[0]
53
-
54
- assert_equal("0", message.message)
55
- end
56
-
57
- def test_latest_before
58
- 10.times do
59
- @store.report(Logger::WARN, "test", "A")
60
- end
61
- 10.times do
62
- @store.report(Logger::WARN, "test", "B")
63
- end
64
- 10.times do
65
- @store.report(Logger::WARN, "test", "C")
66
- end
67
-
68
- messages = @store.latest(limit: 10)
69
- assert_equal("C", messages[0].message)
70
- assert_equal(10, messages.length)
71
-
72
- messages = @store.latest(limit: 10, before: messages[0].key)
73
- assert_equal("B", messages[0].message)
74
- assert_equal(10, messages.length)
75
-
76
- messages = @store.latest(limit: 10, before: messages[0].key)
77
- assert_equal("A", messages[0].message)
78
- assert_equal(10, messages.length)
79
-
80
- end
81
-
82
- def test_get
83
- a_env = { "a_message" => "A MESSAGE" }
84
- a_message = @store.report(Logger::WARN, "test", "A", env: a_env)
85
- b_message = @store.report(Logger::WARN, "test", "B")
86
- @store.report(Logger::WARN, "test", "C")
87
-
88
- a_message = @store.get(a_message.key)
89
- assert_equal("A", a_message.message)
90
- assert_equal("B", b_message.message)
91
- assert(a_env <= a_message.env)
92
-
93
- a_message = @store.get(a_message.key, load_env: false)
94
- assert_equal("A", a_message.message)
95
- assert_nil(a_message.env)
96
- end
97
-
98
- def test_save_saves_env_separately
99
- env = { "myenv" => "thisisenv" }
100
- message = @store.report(Logger::WARN, "test", "title", env: env)
101
- message = @store.get(message.key, load_env: false)
102
- assert_nil(message.env)
103
-
104
- message = @store.get(message.key)
105
- assert(env <= message.env)
106
-
107
- assert(env <= @store.get_env(message.key))
108
- end
109
-
110
- def test_bulk_get
111
- keys = []
112
-
113
- 5.times do |n|
114
- env = n == 0 ? nil : { "test_#{n}" => "envsss" }
115
- keys << @store.report(Logger::WARN, "progname", "test_#{n}", env: env).key
116
- end
117
-
118
- messages = @store.bulk_get(keys)
119
-
120
- 5.times do |n|
121
- msg = messages[n]
122
- assert_equal("test_#{n}", msg.message)
123
- if n == 0
124
- assert_equal(Logster::Message.default_env, msg.env)
125
- else
126
- assert({ "test_#{n}" => "envsss" } <= msg.env)
127
- end
128
- end
129
- end
130
-
131
- def test_get_env
132
- env = { "my_little_env" => "some value" }
133
- message = @store.report(Logger::WARN, "test", "A", env: env)
134
- assert(env <= @store.get_env(message.key))
135
- assert_nil(@store.get_env("nonexistentkey"))
136
- end
137
-
138
- def test_replace_and_bump
139
- old_env = { "old_env" => "old value" }
140
- message = @store.report(Logger::WARN, "test", "A", env: old_env)
141
-
142
- unsaved_env = { "unsaved_env" => "lost value" }
143
- message.env = unsaved_env
144
-
145
- @store.replace_and_bump(message, save_env: false)
146
-
147
- message = @store.get(message.key)
148
- assert(old_env <= message.env)
149
- refute(unsaved_env <= message.env)
150
-
151
- saved_env = { "saved_env" => "saved value!" }
152
- message.env = saved_env
153
-
154
- @store.replace_and_bump(message)
155
-
156
- message = @store.get(message.key)
157
- assert(saved_env == message.env)
158
- end
159
-
160
- def test_backward_compatibility_no_loss_of_data
161
- # previously we were storing env samples as a part of the main message json
162
- # now we've switched to storing samples separately from the main message
163
- # we need to make we don't lose env data of messages stored the old way
164
- # when we migrate to the new system
165
-
166
- # it probably makes sense to remove this test after a while (say 6-12 months)
167
-
168
- Logster.config.allow_grouping = true
169
- backtrace = "fake backtrace"
170
- env = { "some_env" => "some env" }
171
- message = Logster::Message.new(Logger::WARN, "", "title", count: 60)
172
- message.env = env
173
- message.backtrace = backtrace
174
-
175
- @store.save(message)
176
-
177
- # hack to force env to be stored with the main message json
178
- @store.redis.hset(@store.send("hash_key"), message.key, message.to_json(exclude_env: false))
179
-
180
- another_env = { "another_env" => "more env" }
181
- message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: another_env)
182
- message = @store.get(message.key)
183
-
184
- assert(env <= message.env)
185
- assert_equal(61, message.count)
186
- # another_env is not merged cause count is 60, only the count is updated
187
-
188
- # make sure we are now storing env samples separately
189
- message = @store.get(message.key, load_env: false)
190
- assert_nil(message.env)
191
- ensure
192
- Logster.config.allow_grouping = false
193
- end
194
-
195
- def test_backward_compatibility_no_loss_of_data_2
196
- # same story as the test above, just a bit different
197
-
198
- Logster.config.allow_grouping = true
199
- backtrace = "fake backtrace"
200
- env = { "some_env" => "some env" }
201
- message = Logster::Message.new(Logger::WARN, "", "title")
202
- message.env = env
203
- message.backtrace = backtrace
204
-
205
- @store.save(message)
206
-
207
- # hack to force env to be stored with the main message json
208
- @store.redis.hset(@store.send("hash_key"), message.key, message.to_json(exclude_env: false))
209
-
210
- another_env = { "another_env" => "more env" }
211
- message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: another_env)
212
- message = @store.get(message.key)
213
-
214
- assert_instance_of(Array, message.env)
215
- assert(env <= message.env[0])
216
- assert(another_env <= message.env[1])
217
- assert_equal(2, message.env.size)
218
- assert_equal(2, message.count)
219
-
220
- # make sure we are now storing env samples separately
221
- message = @store.get(message.key, load_env: false)
222
- assert_nil(message.env)
223
- ensure
224
- Logster.config.allow_grouping = false
225
- end
226
-
227
- def test_merging_performance
228
- Logster.config.allow_grouping = true
229
- backtrace = "fake backtrace"
230
- env = { "some_env" => "some env" }
231
- another_env = { "another_env" => "more env" }
232
- yet_another_env = { "moaar_env" => "more env" }
233
-
234
- @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: env, count: 49)
235
-
236
- message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: another_env)
237
- assert_instance_of(Array, message.env)
238
- assert_equal(2, message.env.size)
239
- assert(env <= message.env[0])
240
- assert(another_env <= message.env[1])
241
-
242
- message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: yet_another_env)
243
- # we don't need to load env from redis cause we don't
244
- # need to merge new env samples if count is 50 or more
245
- assert_nil(message.env)
246
- ensure
247
- Logster.config.allow_grouping = false
248
- end
249
-
250
- def test_backlog
251
- env = { "backlog_test" => "BACKLOG" }
252
- @store.max_backlog = 1
253
- deleted_msg = @store.report(Logger::WARN, "test", "A")
254
- @store.report(Logger::WARN, "test", "A")
255
- @store.report(Logger::WARN, "test", "A")
256
- @store.report(Logger::WARN, "test", "B", env: env)
257
-
258
- latest = @store.latest
259
-
260
- assert_equal(1, latest.length)
261
- assert_equal("B", latest[0].message)
262
- assert(env <= latest[0].env)
263
- assert_nil(@store.get(deleted_msg.key))
264
- assert_nil(@store.get_env(deleted_msg.key))
265
- end
266
-
267
- def test_save_unsave
268
- @store.max_backlog = 3
269
- @store.report(Logger::WARN, "test", "A")
270
- b_message = @store.report(Logger::WARN, "test", "B")
271
- @store.protect b_message.key
272
- c_message = @store.report(Logger::WARN, "test", "C")
273
- @store.protect c_message.key
274
- @store.report(Logger::WARN, "test", "D")
275
-
276
- latest = @store.latest
277
-
278
- assert_equal(3, latest.length)
279
- assert_equal("B", latest[0].message)
280
- assert_equal("C", latest[1].message)
281
- assert_equal(true, latest[1].protected)
282
- assert_equal("D", latest[2].message)
283
-
284
- # Saved messages still accessible by key
285
- assert_equal("B", @store.get(b_message.key).message)
286
- assert_equal(true, @store.get(b_message.key).protected)
287
-
288
- # Unsave does not delete message if still recent
289
- @store.unprotect c_message.key
290
- assert_equal("C", @store.get(c_message.key).message)
291
- assert_equal(false, @store.get(c_message.key).protected)
292
- end
293
-
294
- def test_clear
295
- env = { "clear_env" => "cllleear" }
296
- @store.max_backlog = 25
297
- a_message = @store.report(Logger::WARN, "test", "A", timestamp: Time.now - (24 * 60 * 60), env: env)
298
- @store.protect a_message.key
299
- 20.times do
300
- @store.report(Logger::WARN, "test", "B", env: env)
301
- end
302
- c_message = @store.report(Logger::WARN, "test", "C", timestamp: Time.now + (24 * 60 * 60), env: env)
303
- @store.protect c_message.key
304
- d_message = @store.report(Logger::WARN, "test", "D", env: env)
305
- 10.times do
306
- @store.report(Logger::WARN, "test", "E", env: env)
307
- end
308
-
309
- latest = @store.latest
310
- assert_equal(25, latest.length)
311
-
312
- @store.clear
313
-
314
- # Protected messages are still accessible by their key
315
- assert_equal("C", @store.get(c_message.key).message)
316
- assert(env <= @store.get_env(c_message.key))
317
- # Unprotected messages are gone
318
- assert_nil(@store.get(d_message.key))
319
- assert_nil(@store.get_env(d_message.key))
320
-
321
- # The latest list is rebuilt with protected messages, earliest first
322
- # Including messages that previously fell off (A)
323
- latest = @store.latest
324
- assert_equal(2, latest.length)
325
- assert_equal("A", latest[0].message)
326
- assert_equal("C", latest[1].message)
327
- assert(env <= latest[0].env)
328
- assert(env <= latest[1].env)
329
- end
330
-
331
- def test_hash_cleanup
332
- @store.max_backlog = 2
333
- a_message = @store.report(Logger::WARN, "test", "A")
334
- @store.report(Logger::WARN, "test", "B")
335
- @store.report(Logger::WARN, "test", "C")
336
-
337
- assert_nil(@store.get(a_message.key))
338
- end
339
-
340
- def test_filter_latest
341
- @store.report(Logger::INFO, "test", "A")
342
- @store.report(Logger::WARN, "test", "B")
343
-
344
- messages = @store.latest
345
- assert_equal(2, messages.length)
346
-
347
- messages = @store.latest(after: messages.last.key)
348
- assert_equal(0, messages.length)
349
-
350
- 10.times do
351
- @store.report(Logger::INFO, "test", "A")
352
- end
353
- @store.report(Logger::ERROR, "test", "C")
354
- 10.times do
355
- @store.report(Logger::INFO, "test", "A")
356
- end
357
-
358
- latest = @store.latest(severity: [Logger::ERROR, Logger::WARN], limit: 2)
359
-
360
- assert_equal(2, latest.length)
361
- assert_equal("B", latest[0].message)
362
- assert_equal("C", latest[1].message)
363
-
364
- @store.report(Logger::ERROR, "test", "E")
365
- # respects after
366
- latest = @store.latest(severity: [Logger::ERROR, Logger::WARN], limit: 2, after: latest[1].key)
367
- assert_equal(1, latest.length)
368
- end
369
-
370
- def test_search
371
- @store.report(Logger::INFO, "test", "ABCDEFG")
372
- @store.report(Logger::INFO, "test", "TUVWXYZ")
373
-
374
- messages = @store.latest
375
- assert_equal(2, messages.length)
376
-
377
- latest = @store.latest(search: "TUVWXYZ")
378
-
379
- assert_equal(1, latest.length)
380
- assert_equal("TUVWXYZ", latest[0].message)
381
- end
382
-
383
- def test_search_exclude_results
384
- @store.report(Logger::INFO, "test", "ABCDEFG")
385
- @store.report(Logger::INFO, "test", "TUVWXYZ")
386
-
387
- messages = @store.latest
388
- assert_equal(2, messages.length)
389
-
390
- latest = @store.latest(search: "-ABCD")
391
-
392
- assert_equal(1, latest.length)
393
- assert_equal("TUVWXYZ", latest[0].message)
394
- end
395
-
396
- def test_regex_search
397
- @store.report(Logger::INFO, "test", "pattern_1")
398
- @store.report(Logger::INFO, "test", "pattern_2")
399
-
400
- messages = @store.latest
401
- assert_equal(2, messages.length)
402
-
403
- latest = @store.latest(search: /^pattern_[1]$/)
404
-
405
- assert_equal(1, latest.length)
406
- end
407
-
408
- def test_env_search
409
- @store.report(Logger::INFO, "test", "message ABCD", env: { cluster: "business5" })
410
- @store.report(Logger::INFO, "test", "message WXYZ", env: { cluster: "business7" })
411
-
412
- messages = @store.latest
413
- assert_equal(2, messages.length)
414
-
415
- latest = @store.latest(search: "business5")
416
-
417
- assert_equal(1, latest.length)
418
- assert_equal("message ABCD", latest[0].message)
419
-
420
- latest = @store.latest(search: "-business5")
421
-
422
- assert_equal(1, latest.length)
423
- assert_equal("message WXYZ", latest[0].message)
424
-
425
- latest = @store.latest(search: /business/)
426
-
427
- assert_equal(2, latest.length)
428
- assert_equal(["message ABCD", "message WXYZ"], latest.map(&:message).sort)
429
- end
430
-
431
- def test_array_env_search_preserve_env
432
- m1_original_env = [{ cluster: "business5" }, { cluster: "standard3" }]
433
- m2_original_env = [{ cluster: "business2" }, { cluster: "standard7" }]
434
-
435
- @store.report(Logger::INFO, "test", "message ABCD", env: m1_original_env, count: 2)
436
- @store.report(Logger::INFO, "test", "message WXYZ", env: m2_original_env, count: 2)
437
-
438
- messages = @store.latest
439
- assert_equal(2, messages.length)
440
-
441
- m1_key = messages[0].key
442
- m2_key = messages[1].key
443
-
444
- messages = @store.latest(search: "business")
445
- assert_equal(2, messages.size)
446
-
447
- # any hashes that don't match should be stripped from the env
448
- # array but only temporarily until it's sent to the client
449
- # env array should remain untouched in redis memory
450
- assert_equal(["business5"], messages[0].env.map { |env| env["cluster"] })
451
- assert_equal(1, messages[0].count)
452
- assert_equal(["business2"], messages[1].env.map { |env| env["cluster"] })
453
- assert_equal(1, messages[1].count)
454
-
455
- m1 = @store.get(m1_key)
456
- m2 = @store.get(m2_key)
457
- # original env should preserved in redis memory
458
- assert_equal(["business5", "standard3"], m1.env.map { |env| env["cluster"] })
459
- assert_equal(["business2", "standard7"], m2.env.map { |env| env["cluster"] })
460
- end
461
-
462
- def test_both_env_and_title_match_search
463
- @store.report(Logger::INFO, "test", "message", env: [{ cluster: "business15" }])
464
- @store.report(Logger::INFO, "test", "message2", env: { cluster: "business15" })
465
-
466
- messages = @store.latest
467
- assert_equal(2, messages.size)
468
-
469
- messages = @store.latest(search: "-business15")
470
- assert_equal(0, messages.size)
471
- end
472
-
473
- def test_data_kept_intact_on_report_when_env_matches_an_ignore_pattern
474
- begin
475
- Logster.config.allow_grouping = true
476
- backtrace = caller
477
- message = @store.report(Logger::WARN, "", "my error", env: { whatever: "something", backtrace: backtrace })
478
-
479
- @store.ignore = [
480
- Logster::IgnorePattern.new("business")
481
- ]
482
- @store.report(Logger::WARN, "", "my error", env: { cluster: "business17", backtrace: backtrace })
483
-
484
- message = @store.get(message.key)
485
- assert(Array === message.env)
486
- assert_equal(2, message.env.size)
487
- # message2 shouldn't vanish even if
488
- # its env matches an ignore pattern
489
- # however it should be merged with message1
490
- assert_equal("business17", message.env[1]["cluster"])
491
- ensure
492
- # reset so it doesn't affect other tests
493
- @store.ignore = nil
494
- Logster.config.allow_grouping = false
495
- end
496
- end
497
-
498
- def test_array_env_negative_search
499
- @store.report(Logger::INFO, "test", "message ABCD", env: [{ cluster: "business5" }, { cluster: "standard3" }], count: 2)
500
- @store.report(Logger::INFO, "test", "message WXYZ", env: [{ cluster: "business2" }, { cluster: "standard7" }], count: 2)
501
-
502
- messages = @store.latest
503
- assert_equal(2, messages.length)
504
-
505
- messages = @store.latest(search: "-business")
506
- assert_equal(2, messages.size)
507
-
508
- assert_equal(["standard3"], messages[0].env.map { |env| env["cluster"] })
509
- assert_equal(1, messages[0].count)
510
- assert_equal(["standard7"], messages[1].env.map { |env| env["cluster"] })
511
- assert_equal(1, messages[1].count)
512
- end
513
-
514
- def test_negative_search_MUST_not_match_title_in_order_to_include_message
515
- @store.report(Logger::INFO, "test", "message ABCD", env: [{ cluster: "business5" }, { cluster: "standard3" }], count: 2)
516
-
517
- messages = @store.latest(search: "-ABCD")
518
- assert_equal(0, messages.size) # cause title has ABCD
519
- end
520
-
521
- def test_positive_search_looks_at_title_OR_env
522
- @store.report(Logger::INFO, "test", "message", env: [{ cluster: "business5 ABCDEFG" }, { cluster: "standard3" }], count: 2)
523
-
524
- messages = @store.latest(search: "ABCDEFG")
525
- assert_equal(1, messages.size)
526
- assert_equal(1, messages[0].env.size)
527
- assert_equal("business5 ABCDEFG", messages[0].env[0]["cluster"])
528
- end
529
-
530
- def test_backtrace
531
- @store.report(Logger::INFO, "test", "pattern_1")
532
- message = @store.latest(limit: 1).first
533
- assert_match("test_backtrace", message.backtrace)
534
- end
535
-
536
- def test_ignore
537
- @store.ignore = [/^test/]
538
- @store.report(Logger::INFO, "test", "test it")
539
- @store.report(Logger::INFO, "test", " test it")
540
-
541
- assert_equal(1, @store.latest.count)
542
- end
543
-
544
- def test_solve
545
- Logster.config.application_version = "abc"
546
-
547
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
548
- m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1")
549
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace2")
550
-
551
- assert_equal(3, @store.latest.count)
552
-
553
- @store.solve(m.key)
554
-
555
- assert_equal(1, @store.latest.count)
556
-
557
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
558
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
559
-
560
- assert_equal(2, @store.latest.count)
561
-
562
- ensure
563
- Logster.config.application_version = nil
564
- end
565
-
566
- def test_solve_grouped
567
- Logster.config.allow_grouping = true
568
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
569
- m = @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "efg" })
570
-
571
- assert_equal(1, @store.latest.count)
572
-
573
- @store.solve(m.key)
574
-
575
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
576
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "efg" })
577
-
578
- assert_equal(0, @store.latest.count)
579
-
580
- ensure
581
- Logster.config.allow_grouping = false
582
- end
583
-
584
- def test_clears_solved
585
- m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1", env: { "application_version" => "abc" })
586
- @store.solve(m.key)
587
-
588
- assert_equal(1, @store.solved.length)
589
-
590
- @store.clear
591
- assert_equal(0, @store.solved.length)
592
- end
593
-
594
- def test_solving_with_some_missing_version
595
-
596
- m = @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
597
- @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
598
-
599
- @store.solve(m.key)
600
-
601
- assert_equal(1, @store.latest.count)
602
- end
603
-
604
- def test_env
605
- env = Rack::MockRequest.env_for("/test").merge(
606
- "HTTP_HOST" => "www.site.com",
607
- "HTTP_USER_AGENT" => "SOME WHERE"
608
- )
609
- orig = env.dup
610
- orig["test"] = "tests"
611
- orig["test1"] = "tests1"
612
- Logster.add_to_env(env, "test", "tests")
613
- Logster.add_to_env(env, "test1", "tests1")
614
-
615
- orig.delete_if do |k, v|
616
- !%w{
617
- HTTP_HOST
618
- REQUEST_METHOD
619
- HTTP_USER_AGENT
620
- test
621
- test1
622
- }.include? k
623
- end
624
-
625
- @store.report(Logger::INFO, "test", "test", env: env)
626
-
627
- env = @store.latest.last.env
628
-
629
- env.delete "hostname"
630
- env.delete "process_id"
631
-
632
- assert_equal(orig, env)
633
- end
634
-
635
- def test_rate_limits
636
- %w{minute hour}.each do |duration|
637
- begin
638
- called = false
639
-
640
- assert_instance_of(
641
- Logster::RedisRateLimiter,
642
- @store.public_send("register_rate_limit_per_#{duration}", Logger::WARN, 0) do
643
- called = true
644
- end
645
- )
646
-
647
- @store.report(Logger::WARN, "test", "test")
648
- assert called
649
- ensure
650
- reset_redis
651
- end
652
- end
653
- end
654
-
655
- def test_rate_limits_only_checks_when_message_is_bumped_or_saved
656
- Logster.config.allow_grouping = true
657
- Logster.config.application_version = 'abc'
658
-
659
- @store.ignore = [/^ActiveRecord::RecordNotFound/]
660
- rate_limit = @store.register_rate_limit_per_minute(Logger::WARN, 0)
661
-
662
- message = @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
663
- assert_equal(1, rate_limit.retrieve_rate)
664
-
665
- @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
666
- assert_equal(2, rate_limit.retrieve_rate)
667
-
668
- @store.solve(message.key)
669
- @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
670
- assert_equal(2, rate_limit.retrieve_rate)
671
-
672
- @store.report(Logger::WARN, 'message 2', "Error!")
673
- assert_equal(3, rate_limit.retrieve_rate)
674
-
675
- @store.report(Logger::WARN, 'message 3', "ActiveRecord::RecordNotFound")
676
- assert_equal(3, rate_limit.retrieve_rate)
677
- ensure
678
- Logster.config.allow_grouping = false
679
- Logster.config.application_version = nil
680
- reset_redis
681
- end
682
-
683
- def test_rate_limits_with_prefix
684
- begin
685
- time = Time.now
686
- Timecop.freeze(time)
687
- current_namespace = 'first'
688
- @store.redis_prefix = Proc.new { current_namespace }
689
-
690
- called_first = 0
691
- called_second = 0
692
-
693
- @store.register_rate_limit_per_minute(Logger::WARN, 0) { called_first += 1 }
694
- @store.report(Logger::WARN, "test", "test")
695
- assert_equal(1, called_first)
696
-
697
- current_namespace = 'second'
698
- @store.register_rate_limit_per_minute(Logger::WARN, 0) { called_second += 1 }
699
- @store.report(Logger::WARN, "test", "test")
700
- assert_equal(1, called_first)
701
- assert_equal(1, called_second)
702
-
703
- Timecop.freeze(time + 10) do
704
- current_namespace = 'first'
705
- @store.report(Logger::WARN, "test", "test")
706
-
707
- assert_equal(2, called_first)
708
- assert_equal(1, called_second)
709
- end
710
- ensure
711
- reset_redis
712
- end
713
- end
714
-
715
- private
716
-
717
- def reset_redis
718
- @store.redis.flushall
719
- end
720
- end
1
+ require_relative '../test_helper'
2
+ require 'logster/redis_store'
3
+ require 'rack'
4
+
5
+ class TestRedisStore < Minitest::Test
6
+
7
+ def setup
8
+ @store = Logster::RedisStore.new(Redis.new)
9
+ @store.clear_all
10
+ end
11
+
12
+ def teardown
13
+ @store.clear_all
14
+ end
15
+
16
+ def test_delete
17
+ env = { test_env: "this is env" }
18
+ msg = @store.report(Logger::WARN, "test", "testing", env: env)
19
+ @store.delete(msg)
20
+ latest = @store.latest
21
+
22
+ assert_equal(0, latest.length)
23
+ assert_nil(@store.get_env(msg.key))
24
+ end
25
+
26
+ def test_latest
27
+ @store.report(Logger::WARN, "test", "IGNORE")
28
+ @store.report(Logger::WARN, "test", "This is a warning")
29
+ @store.report(Logger::WARN, "test", "This is another warning")
30
+
31
+ latest = @store.latest(limit: 2)
32
+
33
+ assert_equal(2, latest.length)
34
+ assert_equal("This is a warning", latest[0].message)
35
+ assert_equal("This is another warning", latest[1].message)
36
+ assert_equal(Logger::WARN, latest[1].severity)
37
+ assert_equal("test", latest[1].progname)
38
+ assert(!latest[1].key.nil?)
39
+ end
40
+
41
+ def test_latest_after
42
+ 10.times do |i|
43
+ @store.report(Logger::WARN, "test", "A#{i}")
44
+ end
45
+
46
+ message = @store.latest[-1]
47
+
48
+ 3.times do |i|
49
+ @store.report(Logger::WARN, "test", i.to_s)
50
+ end
51
+
52
+ message = @store.latest(after: message.key, limit: 3)[0]
53
+
54
+ assert_equal("0", message.message)
55
+ end
56
+
57
+ def test_latest_before
58
+ 10.times do
59
+ @store.report(Logger::WARN, "test", "A")
60
+ end
61
+ 10.times do
62
+ @store.report(Logger::WARN, "test", "B")
63
+ end
64
+ 10.times do
65
+ @store.report(Logger::WARN, "test", "C")
66
+ end
67
+
68
+ messages = @store.latest(limit: 10)
69
+ assert_equal("C", messages[0].message)
70
+ assert_equal(10, messages.length)
71
+
72
+ messages = @store.latest(limit: 10, before: messages[0].key)
73
+ assert_equal("B", messages[0].message)
74
+ assert_equal(10, messages.length)
75
+
76
+ messages = @store.latest(limit: 10, before: messages[0].key)
77
+ assert_equal("A", messages[0].message)
78
+ assert_equal(10, messages.length)
79
+
80
+ end
81
+
82
+ def test_get
83
+ a_env = { "a_message" => "A MESSAGE" }
84
+ a_message = @store.report(Logger::WARN, "test", "A", env: a_env)
85
+ b_message = @store.report(Logger::WARN, "test", "B")
86
+ @store.report(Logger::WARN, "test", "C")
87
+
88
+ a_message = @store.get(a_message.key)
89
+ assert_equal("A", a_message.message)
90
+ assert_equal("B", b_message.message)
91
+ assert(a_env <= a_message.env)
92
+
93
+ a_message = @store.get(a_message.key, load_env: false)
94
+ assert_equal("A", a_message.message)
95
+ assert_nil(a_message.env)
96
+ end
97
+
98
+ def test_save_saves_env_separately
99
+ env = { "myenv" => "thisisenv" }
100
+ message = @store.report(Logger::WARN, "test", "title", env: env)
101
+ message = @store.get(message.key, load_env: false)
102
+ assert_nil(message.env)
103
+
104
+ message = @store.get(message.key)
105
+ assert(env <= message.env)
106
+
107
+ assert(env <= @store.get_env(message.key))
108
+ end
109
+
110
+ def test_bulk_get
111
+ keys = []
112
+
113
+ 5.times do |n|
114
+ env = n == 0 ? nil : { "test_#{n}" => "envsss" }
115
+ keys << @store.report(Logger::WARN, "progname", "test_#{n}", env: env).key
116
+ end
117
+
118
+ messages = @store.bulk_get(keys)
119
+
120
+ 5.times do |n|
121
+ msg = messages[n]
122
+ assert_equal("test_#{n}", msg.message)
123
+ if n == 0
124
+ assert_equal(Logster::Message.default_env, msg.env)
125
+ else
126
+ assert({ "test_#{n}" => "envsss" } <= msg.env)
127
+ end
128
+ end
129
+ end
130
+
131
+ def test_get_env
132
+ env = { "my_little_env" => "some value" }
133
+ message = @store.report(Logger::WARN, "test", "A", env: env)
134
+ assert(env <= @store.get_env(message.key))
135
+ assert_nil(@store.get_env("nonexistentkey"))
136
+ end
137
+
138
+ def test_replace_and_bump
139
+ old_env = { "old_env" => "old value" }
140
+ message = @store.report(Logger::WARN, "test", "A", env: old_env)
141
+
142
+ unsaved_env = { "unsaved_env" => "lost value" }
143
+ message.env = unsaved_env
144
+
145
+ @store.replace_and_bump(message, save_env: false)
146
+
147
+ message = @store.get(message.key)
148
+ assert(old_env <= message.env)
149
+ refute(unsaved_env <= message.env)
150
+
151
+ saved_env = { "saved_env" => "saved value!" }
152
+ message.env = saved_env
153
+
154
+ @store.replace_and_bump(message)
155
+
156
+ message = @store.get(message.key)
157
+ assert(saved_env == message.env)
158
+ end
159
+
160
+ def test_merging_performance
161
+ Logster.config.allow_grouping = true
162
+ backtrace = "fake backtrace"
163
+ env = { "some_env" => "some env" }
164
+ another_env = { "another_env" => "more env" }
165
+ yet_another_env = { "moaar_env" => "more env" }
166
+
167
+ @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: env, count: 49)
168
+
169
+ message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: another_env)
170
+ assert_instance_of(Array, message.env)
171
+ assert_equal(2, message.env.size)
172
+ assert(env <= message.env[0])
173
+ assert(another_env <= message.env[1])
174
+
175
+ message = @store.report(Logger::WARN, "", "title", backtrace: backtrace, env: yet_another_env)
176
+ # we don't need to load env from redis cause we don't
177
+ # need to merge new env samples if count is 50 or more
178
+ assert_nil(message.env)
179
+ ensure
180
+ Logster.config.allow_grouping = false
181
+ end
182
+
183
+ def test_backlog
184
+ env = { "backlog_test" => "BACKLOG" }
185
+ @store.max_backlog = 1
186
+ deleted_msg = @store.report(Logger::WARN, "test", "A")
187
+ @store.report(Logger::WARN, "test", "A")
188
+ @store.report(Logger::WARN, "test", "A")
189
+ @store.report(Logger::WARN, "test", "B", env: env)
190
+
191
+ latest = @store.latest
192
+
193
+ assert_equal(1, latest.length)
194
+ assert_equal("B", latest[0].message)
195
+ assert(env <= latest[0].env)
196
+ assert_nil(@store.get(deleted_msg.key))
197
+ assert_nil(@store.get_env(deleted_msg.key))
198
+ end
199
+
200
+ def test_save_unsave
201
+ @store.max_backlog = 3
202
+ @store.report(Logger::WARN, "test", "A")
203
+ b_message = @store.report(Logger::WARN, "test", "B")
204
+ @store.protect b_message.key
205
+ c_message = @store.report(Logger::WARN, "test", "C")
206
+ @store.protect c_message.key
207
+ @store.report(Logger::WARN, "test", "D")
208
+
209
+ latest = @store.latest
210
+
211
+ assert_equal(3, latest.length)
212
+ assert_equal("B", latest[0].message)
213
+ assert_equal("C", latest[1].message)
214
+ assert_equal(true, latest[1].protected)
215
+ assert_equal("D", latest[2].message)
216
+
217
+ # Saved messages still accessible by key
218
+ assert_equal("B", @store.get(b_message.key).message)
219
+ assert_equal(true, @store.get(b_message.key).protected)
220
+
221
+ # Unsave does not delete message if still recent
222
+ @store.unprotect c_message.key
223
+ assert_equal("C", @store.get(c_message.key).message)
224
+ assert_equal(false, @store.get(c_message.key).protected)
225
+ end
226
+
227
+ def test_clear
228
+ env = { "clear_env" => "cllleear" }
229
+ @store.max_backlog = 25
230
+ a_message = @store.report(Logger::WARN, "test", "A", timestamp: Time.now - (24 * 60 * 60), env: env)
231
+ @store.protect a_message.key
232
+ 20.times do
233
+ @store.report(Logger::WARN, "test", "B", env: env)
234
+ end
235
+ c_message = @store.report(Logger::WARN, "test", "C", timestamp: Time.now + (24 * 60 * 60), env: env)
236
+ @store.protect c_message.key
237
+ d_message = @store.report(Logger::WARN, "test", "D", env: env)
238
+ 10.times do
239
+ @store.report(Logger::WARN, "test", "E", env: env)
240
+ end
241
+
242
+ latest = @store.latest
243
+ assert_equal(25, latest.length)
244
+
245
+ @store.clear
246
+
247
+ # Protected messages are still accessible by their key
248
+ assert_equal("C", @store.get(c_message.key).message)
249
+ assert(env <= @store.get_env(c_message.key))
250
+ # Unprotected messages are gone
251
+ assert_nil(@store.get(d_message.key))
252
+ assert_nil(@store.get_env(d_message.key))
253
+
254
+ # The latest list is rebuilt with protected messages, earliest first
255
+ # Including messages that previously fell off (A)
256
+ latest = @store.latest
257
+ assert_equal(2, latest.length)
258
+ assert_equal("A", latest[0].message)
259
+ assert_equal("C", latest[1].message)
260
+ assert(env <= latest[0].env)
261
+ assert(env <= latest[1].env)
262
+ end
263
+
264
+ def test_hash_cleanup
265
+ @store.max_backlog = 2
266
+ a_message = @store.report(Logger::WARN, "test", "A")
267
+ @store.report(Logger::WARN, "test", "B")
268
+ @store.report(Logger::WARN, "test", "C")
269
+
270
+ assert_nil(@store.get(a_message.key))
271
+ end
272
+
273
+ def test_filter_latest
274
+ @store.report(Logger::INFO, "test", "A")
275
+ @store.report(Logger::WARN, "test", "B")
276
+
277
+ messages = @store.latest
278
+ assert_equal(2, messages.length)
279
+
280
+ messages = @store.latest(after: messages.last.key)
281
+ assert_equal(0, messages.length)
282
+
283
+ 10.times do
284
+ @store.report(Logger::INFO, "test", "A")
285
+ end
286
+ @store.report(Logger::ERROR, "test", "C")
287
+ 10.times do
288
+ @store.report(Logger::INFO, "test", "A")
289
+ end
290
+
291
+ latest = @store.latest(severity: [Logger::ERROR, Logger::WARN], limit: 2)
292
+
293
+ assert_equal(2, latest.length)
294
+ assert_equal("B", latest[0].message)
295
+ assert_equal("C", latest[1].message)
296
+
297
+ @store.report(Logger::ERROR, "test", "E")
298
+ # respects after
299
+ latest = @store.latest(severity: [Logger::ERROR, Logger::WARN], limit: 2, after: latest[1].key)
300
+ assert_equal(1, latest.length)
301
+ end
302
+
303
+ def test_search
304
+ @store.report(Logger::INFO, "test", "ABCDEFG")
305
+ @store.report(Logger::INFO, "test", "TUVWXYZ")
306
+
307
+ messages = @store.latest
308
+ assert_equal(2, messages.length)
309
+
310
+ latest = @store.latest(search: "TUVWXYZ")
311
+
312
+ assert_equal(1, latest.length)
313
+ assert_equal("TUVWXYZ", latest[0].message)
314
+ end
315
+
316
+ def test_search_exclude_results
317
+ @store.report(Logger::INFO, "test", "ABCDEFG")
318
+ @store.report(Logger::INFO, "test", "TUVWXYZ")
319
+
320
+ messages = @store.latest
321
+ assert_equal(2, messages.length)
322
+
323
+ latest = @store.latest(search: "-ABCD")
324
+
325
+ assert_equal(1, latest.length)
326
+ assert_equal("TUVWXYZ", latest[0].message)
327
+ end
328
+
329
+ def test_regex_search
330
+ @store.report(Logger::INFO, "test", "pattern_1")
331
+ @store.report(Logger::INFO, "test", "pattern_2")
332
+
333
+ messages = @store.latest
334
+ assert_equal(2, messages.length)
335
+
336
+ latest = @store.latest(search: /^pattern_[1]$/)
337
+
338
+ assert_equal(1, latest.length)
339
+ end
340
+
341
+ def test_env_search
342
+ @store.report(Logger::INFO, "test", "message ABCD", env: { cluster: "business5" })
343
+ @store.report(Logger::INFO, "test", "message WXYZ", env: { cluster: "business7" })
344
+
345
+ messages = @store.latest
346
+ assert_equal(2, messages.length)
347
+
348
+ latest = @store.latest(search: "business5")
349
+
350
+ assert_equal(1, latest.length)
351
+ assert_equal("message ABCD", latest[0].message)
352
+
353
+ latest = @store.latest(search: "-business5")
354
+
355
+ assert_equal(1, latest.length)
356
+ assert_equal("message WXYZ", latest[0].message)
357
+
358
+ latest = @store.latest(search: /business/)
359
+
360
+ assert_equal(2, latest.length)
361
+ assert_equal(["message ABCD", "message WXYZ"], latest.map(&:message).sort)
362
+ end
363
+
364
+ def test_array_env_search_preserve_env
365
+ m1_original_env = [{ cluster: "business5" }, { cluster: "standard3" }]
366
+ m2_original_env = [{ cluster: "business2" }, { cluster: "standard7" }]
367
+
368
+ @store.report(Logger::INFO, "test", "message ABCD", env: m1_original_env, count: 2)
369
+ @store.report(Logger::INFO, "test", "message WXYZ", env: m2_original_env, count: 2)
370
+
371
+ messages = @store.latest
372
+ assert_equal(2, messages.length)
373
+
374
+ m1_key = messages[0].key
375
+ m2_key = messages[1].key
376
+
377
+ messages = @store.latest(search: "business")
378
+ assert_equal(2, messages.size)
379
+
380
+ # any hashes that don't match should be stripped from the env
381
+ # array but only temporarily until it's sent to the client
382
+ # env array should remain untouched in redis memory
383
+ assert_equal(["business5"], messages[0].env.map { |env| env["cluster"] })
384
+ assert_equal(1, messages[0].count)
385
+ assert_equal(["business2"], messages[1].env.map { |env| env["cluster"] })
386
+ assert_equal(1, messages[1].count)
387
+
388
+ m1 = @store.get(m1_key)
389
+ m2 = @store.get(m2_key)
390
+ # original env should preserved in redis memory
391
+ assert_equal(["business5", "standard3"], m1.env.map { |env| env["cluster"] })
392
+ assert_equal(["business2", "standard7"], m2.env.map { |env| env["cluster"] })
393
+ end
394
+
395
+ def test_both_env_and_title_match_search
396
+ @store.report(Logger::INFO, "test", "message", env: [{ cluster: "business15" }])
397
+ @store.report(Logger::INFO, "test", "message2", env: { cluster: "business15" })
398
+
399
+ messages = @store.latest
400
+ assert_equal(2, messages.size)
401
+
402
+ messages = @store.latest(search: "-business15")
403
+ assert_equal(0, messages.size)
404
+ end
405
+
406
+ def test_data_kept_intact_on_report_when_env_matches_an_ignore_pattern
407
+ begin
408
+ Logster.config.allow_grouping = true
409
+ backtrace = caller
410
+ message = @store.report(Logger::WARN, "", "my error", env: { whatever: "something", backtrace: backtrace })
411
+
412
+ @store.ignore = [
413
+ Logster::IgnorePattern.new("business")
414
+ ]
415
+ @store.report(Logger::WARN, "", "my error", env: { cluster: "business17", backtrace: backtrace })
416
+
417
+ message = @store.get(message.key)
418
+ assert(Array === message.env)
419
+ assert_equal(2, message.env.size)
420
+ # message2 shouldn't vanish even if
421
+ # its env matches an ignore pattern
422
+ # however it should be merged with message1
423
+ assert_equal("business17", message.env[1]["cluster"])
424
+ ensure
425
+ # reset so it doesn't affect other tests
426
+ @store.ignore = nil
427
+ Logster.config.allow_grouping = false
428
+ end
429
+ end
430
+
431
+ def test_array_env_negative_search
432
+ @store.report(Logger::INFO, "test", "message ABCD", env: [{ cluster: "business5" }, { cluster: "standard3" }], count: 2)
433
+ @store.report(Logger::INFO, "test", "message WXYZ", env: [{ cluster: "business2" }, { cluster: "standard7" }], count: 2)
434
+
435
+ messages = @store.latest
436
+ assert_equal(2, messages.length)
437
+
438
+ messages = @store.latest(search: "-business")
439
+ assert_equal(2, messages.size)
440
+
441
+ assert_equal(["standard3"], messages[0].env.map { |env| env["cluster"] })
442
+ assert_equal(1, messages[0].count)
443
+ assert_equal(["standard7"], messages[1].env.map { |env| env["cluster"] })
444
+ assert_equal(1, messages[1].count)
445
+ end
446
+
447
+ def test_negative_search_MUST_not_match_title_in_order_to_include_message
448
+ @store.report(Logger::INFO, "test", "message ABCD", env: [{ cluster: "business5" }, { cluster: "standard3" }], count: 2)
449
+
450
+ messages = @store.latest(search: "-ABCD")
451
+ assert_equal(0, messages.size) # cause title has ABCD
452
+ end
453
+
454
+ def test_positive_search_looks_at_title_OR_env
455
+ @store.report(Logger::INFO, "test", "message", env: [{ cluster: "business5 ABCDEFG" }, { cluster: "standard3" }], count: 2)
456
+
457
+ messages = @store.latest(search: "ABCDEFG")
458
+ assert_equal(1, messages.size)
459
+ assert_equal(1, messages[0].env.size)
460
+ assert_equal("business5 ABCDEFG", messages[0].env[0]["cluster"])
461
+ end
462
+
463
+ def test_backtrace
464
+ @store.report(Logger::INFO, "test", "pattern_1")
465
+ message = @store.latest(limit: 1).first
466
+ assert_match("test_backtrace", message.backtrace)
467
+ end
468
+
469
+ def test_ignore
470
+ @store.ignore = [/^test/]
471
+ @store.report(Logger::INFO, "test", "test it")
472
+ @store.report(Logger::INFO, "test", " test it")
473
+
474
+ assert_equal(1, @store.latest.count)
475
+ end
476
+
477
+ def test_solve
478
+ Logster.config.application_version = "abc"
479
+
480
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
481
+ m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1")
482
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace2")
483
+
484
+ assert_equal(3, @store.latest.count)
485
+
486
+ @store.solve(m.key)
487
+
488
+ assert_equal(1, @store.latest.count)
489
+
490
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
491
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
492
+
493
+ assert_equal(2, @store.latest.count)
494
+
495
+ ensure
496
+ Logster.config.application_version = nil
497
+ end
498
+
499
+ def test_solve_grouped
500
+ Logster.config.allow_grouping = true
501
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
502
+ m = @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "efg" })
503
+
504
+ assert_equal(1, @store.latest.count)
505
+
506
+ @store.solve(m.key)
507
+
508
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
509
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "efg" })
510
+
511
+ assert_equal(0, @store.latest.count)
512
+
513
+ ensure
514
+ Logster.config.allow_grouping = false
515
+ end
516
+
517
+ def test_clears_solved
518
+ m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1", env: { "application_version" => "abc" })
519
+ @store.solve(m.key)
520
+
521
+ assert_equal(1, @store.solved.length)
522
+
523
+ @store.clear
524
+ assert_equal(0, @store.solved.length)
525
+ end
526
+
527
+ def test_solving_with_some_missing_version
528
+
529
+ m = @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz" })
530
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
531
+
532
+ @store.solve(m.key)
533
+
534
+ assert_equal(1, @store.latest.count)
535
+ end
536
+
537
+ def test_env
538
+ env = Rack::MockRequest.env_for("/test").merge(
539
+ "HTTP_HOST" => "www.site.com",
540
+ "HTTP_USER_AGENT" => "SOME WHERE"
541
+ )
542
+ orig = env.dup
543
+ orig["test"] = "tests"
544
+ orig["test1"] = "tests1"
545
+ Logster.add_to_env(env, "test", "tests")
546
+ Logster.add_to_env(env, "test1", "tests1")
547
+
548
+ orig.delete_if do |k, v|
549
+ !%w{
550
+ HTTP_HOST
551
+ REQUEST_METHOD
552
+ HTTP_USER_AGENT
553
+ test
554
+ test1
555
+ }.include? k
556
+ end
557
+
558
+ @store.report(Logger::INFO, "test", "test", env: env)
559
+
560
+ env = @store.latest.last.env
561
+
562
+ env.delete "hostname"
563
+ env.delete "process_id"
564
+
565
+ assert_equal(orig, env)
566
+ end
567
+
568
+ def test_custom_ignore_patterns_work_with_per_store_config
569
+ Logster.config.enable_custom_patterns_via_ui = false
570
+ @store.allow_custom_patterns = true
571
+ Logster::SuppressionPattern.new("/testtesttest/", store: @store).save
572
+ @store.report(Logger::INFO, "test", "testtesttesttest")
573
+ latest = @store.latest
574
+ assert_equal(0, latest.size)
575
+
576
+ @store.allow_custom_patterns = false
577
+ @store.report(Logger::INFO, "test", "testtesttesttest")
578
+ latest = @store.latest
579
+ assert_equal(1, latest.size)
580
+ assert_equal("testtesttesttest", latest.first.message)
581
+ end
582
+
583
+ def test_rate_limits
584
+ %w{minute hour}.each do |duration|
585
+ begin
586
+ called = false
587
+
588
+ assert_instance_of(
589
+ Logster::RedisRateLimiter,
590
+ @store.public_send("register_rate_limit_per_#{duration}", Logger::WARN, 0) do
591
+ called = true
592
+ end
593
+ )
594
+
595
+ @store.report(Logger::WARN, "test", "test")
596
+ assert called
597
+ ensure
598
+ reset_redis
599
+ end
600
+ end
601
+ end
602
+
603
+ def test_rate_limits_only_checks_when_message_is_bumped_or_saved
604
+ Logster.config.allow_grouping = true
605
+ Logster.config.application_version = 'abc'
606
+
607
+ @store.ignore = [/^ActiveRecord::RecordNotFound/]
608
+ rate_limit = @store.register_rate_limit_per_minute(Logger::WARN, 0)
609
+
610
+ message = @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
611
+ assert_equal(1, rate_limit.retrieve_rate)
612
+
613
+ @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
614
+ assert_equal(2, rate_limit.retrieve_rate)
615
+
616
+ @store.solve(message.key)
617
+ @store.report(Logger::WARN, 'message 1', "Error!", backtrace: 'here')
618
+ assert_equal(2, rate_limit.retrieve_rate)
619
+
620
+ @store.report(Logger::WARN, 'message 2', "Error!")
621
+ assert_equal(3, rate_limit.retrieve_rate)
622
+
623
+ @store.report(Logger::WARN, 'message 3', "ActiveRecord::RecordNotFound")
624
+ assert_equal(3, rate_limit.retrieve_rate)
625
+ ensure
626
+ Logster.config.allow_grouping = false
627
+ Logster.config.application_version = nil
628
+ reset_redis
629
+ end
630
+
631
+ def test_rate_limits_with_prefix
632
+ begin
633
+ time = Time.now
634
+ Timecop.freeze(time)
635
+ current_namespace = 'first'
636
+ @store.redis_prefix = Proc.new { current_namespace }
637
+
638
+ called_first = 0
639
+ called_second = 0
640
+
641
+ @store.register_rate_limit_per_minute(Logger::WARN, 0) { called_first += 1 }
642
+ @store.report(Logger::WARN, "test", "test")
643
+ assert_equal(1, called_first)
644
+
645
+ current_namespace = 'second'
646
+ @store.register_rate_limit_per_minute(Logger::WARN, 0) { called_second += 1 }
647
+ @store.report(Logger::WARN, "test", "test")
648
+ assert_equal(1, called_first)
649
+ assert_equal(1, called_second)
650
+
651
+ Timecop.freeze(time + 10) do
652
+ current_namespace = 'first'
653
+ @store.report(Logger::WARN, "test", "test")
654
+
655
+ assert_equal(2, called_first)
656
+ assert_equal(1, called_second)
657
+ end
658
+ ensure
659
+ reset_redis
660
+ end
661
+ end
662
+
663
+ def test_suppression_patterns_are_cached
664
+ @store.allow_custom_patterns = true
665
+ rec = Logster::SuppressionPattern.new(/forest/, store: @store)
666
+ rec.save
667
+
668
+ @store.report(Logger::INFO, "test", "littleforest")
669
+ latest = @store.latest
670
+ assert_equal(0, latest.size)
671
+
672
+ rec.destroy(clear_cache: false)
673
+ @store.report(Logger::INFO, "test", "anotherforest")
674
+ assert_equal(0, @store.latest.size)
675
+
676
+ Process.stub :clock_gettime, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 3 do
677
+ @store.report(Logger::INFO, "test", "myforest")
678
+ latest = @store.latest
679
+ assert_equal(1, latest.size)
680
+ assert_equal("myforest", latest.first.message)
681
+ end
682
+ end
683
+
684
+ private
685
+
686
+ def reset_redis
687
+ @store.clear_all
688
+ end
689
+ end