logster 2.1.2 → 2.2.0

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