logster 2.3.1 → 2.3.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +9 -0
- data/lib/logster.rb +2 -0
- data/lib/logster/base_store.rb +6 -0
- data/lib/logster/configuration.rb +15 -2
- data/lib/logster/middleware/debug_exceptions.rb +9 -6
- data/lib/logster/middleware/reporter.rb +19 -3
- data/lib/logster/rails/railtie.rb +5 -1
- data/lib/logster/redis_rate_limiter.rb +110 -0
- data/lib/logster/redis_store.rb +20 -107
- data/lib/logster/version.rb +3 -1
- data/test/logster/middleware/test_reporter.rb +41 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2f37eb7a03d711e0162719afed21c5d2b085f641764a9cec168eba16761d0ff
|
4
|
+
data.tar.gz: 1398a1d9baea28cdb3015b863075d0d242ab423f11b78db3b7fd074df9de9d03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 893b198161a8c729c8e8290de1e82568f1b0caa2facb06e149216b456cdf8ce9ed76ea489296f632e431985ce769cbd3bb4c9fe90acb8c1641a2a48522d01c91
|
7
|
+
data.tar.gz: b7a1460c5d42d07bffe1df6fab527fd4ee6a733e10364f212fd8ee19e4180893b03e2b3b669ba9e7ef2efb01cbb91857ce4162e37f17b5a3efa18428a9595d48
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -35,6 +35,15 @@ To run logster in other environments, in `config/application.rb`
|
|
35
35
|
Logster.set_environments([:development, :staging, :production])
|
36
36
|
```
|
37
37
|
|
38
|
+
### Configuration
|
39
|
+
|
40
|
+
Logster can be configured using `Logster.config`:
|
41
|
+
|
42
|
+
- `Logster.config.application_version`: set to a unique identifier denoting version of your app. The "solve" function takes this version into account when suppressing errors.
|
43
|
+
- `Logster.config.enable_js_error_reporting` : enable js error reporting from clients
|
44
|
+
- `Logster.config.rate_limit_error_reporting` : controls automatic 1 minute rate limiting for JS error reporting.
|
45
|
+
- `Logster.config.web_title` : `<title>` tag for logster error page.
|
46
|
+
|
38
47
|
### Tracking Error Rate
|
39
48
|
Logster allows you to register a callback when the rate of errors has exceeded
|
40
49
|
a given limit.
|
data/lib/logster.rb
CHANGED
data/lib/logster/base_store.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Logster
|
2
4
|
class BaseStore
|
3
5
|
|
@@ -115,6 +117,10 @@ module Logster
|
|
115
117
|
{}
|
116
118
|
end
|
117
119
|
|
120
|
+
def rate_limited?(ip_address, perform: false, limit: 60)
|
121
|
+
not_implemented
|
122
|
+
end
|
123
|
+
|
118
124
|
def report(severity, progname, msg, opts = {})
|
119
125
|
return if (!msg || (String === msg && msg.empty?)) && skip_empty
|
120
126
|
return if level && severity < level
|
@@ -1,7 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Logster
|
2
4
|
class Configuration
|
3
|
-
attr_accessor
|
4
|
-
:
|
5
|
+
attr_accessor(
|
6
|
+
:allow_grouping,
|
7
|
+
:application_version,
|
8
|
+
:current_context,
|
9
|
+
:env_expandable_keys,
|
10
|
+
:enable_custom_patterns_via_ui,
|
11
|
+
:enable_js_error_reporting,
|
12
|
+
:environments,
|
13
|
+
:rate_limit_error_reporting,
|
14
|
+
:web_title
|
15
|
+
)
|
5
16
|
|
6
17
|
attr_writer :subdirectory
|
7
18
|
|
@@ -12,6 +23,8 @@ module Logster
|
|
12
23
|
@subdirectory = nil
|
13
24
|
@env_expandable_keys = []
|
14
25
|
@enable_custom_patterns_via_ui = false
|
26
|
+
@rate_limit_error_reporting = true
|
27
|
+
@enable_js_error_reporting = true
|
15
28
|
|
16
29
|
@allow_grouping = false
|
17
30
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Logster::Middleware::DebugExceptions < ActionDispatch::DebugExceptions
|
2
4
|
private
|
3
5
|
|
@@ -13,13 +15,14 @@ class Logster::Middleware::DebugExceptions < ActionDispatch::DebugExceptions
|
|
13
15
|
|
14
16
|
Logster.config.current_context.call(env) do
|
15
17
|
location = exception.backtrace[0]
|
16
|
-
exception_string = exception.to_s
|
17
18
|
|
18
|
-
Logster.logger.add_with_opts(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
Logster.logger.add_with_opts(
|
20
|
+
::Logger::Severity::FATAL,
|
21
|
+
"#{exception.class} (#{exception})\n#{location}",
|
22
|
+
"web-exception",
|
23
|
+
backtrace: exception.backtrace.join("\n"),
|
24
|
+
env: env
|
25
|
+
)
|
23
26
|
end
|
24
27
|
|
25
28
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Logster
|
2
4
|
module Middleware
|
3
5
|
class Reporter
|
4
6
|
|
5
|
-
PATH_INFO = "PATH_INFO"
|
6
|
-
SCRIPT_NAME = "SCRIPT_NAME"
|
7
|
+
PATH_INFO = "PATH_INFO"
|
8
|
+
SCRIPT_NAME = "SCRIPT_NAME"
|
7
9
|
|
8
10
|
def initialize(app, config = {})
|
9
11
|
@app = app
|
@@ -21,7 +23,18 @@ module Logster
|
|
21
23
|
end
|
22
24
|
|
23
25
|
if path == @error_path
|
26
|
+
|
27
|
+
if !Logster.config.enable_js_error_reporting
|
28
|
+
return [403, {}, "Access Denied"]
|
29
|
+
end
|
30
|
+
|
24
31
|
Logster.config.current_context.call(env) do
|
32
|
+
if Logster.config.rate_limit_error_reporting
|
33
|
+
req = Rack::Request.new(env)
|
34
|
+
if Logster.store.rate_limited?(req.ip, perform: true)
|
35
|
+
return [429, {}, "Rate Limited"]
|
36
|
+
end
|
37
|
+
end
|
25
38
|
report_js_error(env)
|
26
39
|
end
|
27
40
|
return [200, {}, ["OK"]]
|
@@ -34,9 +47,10 @@ module Logster
|
|
34
47
|
|
35
48
|
def report_js_error(env)
|
36
49
|
req = Rack::Request.new(env)
|
50
|
+
|
37
51
|
params = req.params
|
38
52
|
|
39
|
-
message = params["message"] || ""
|
53
|
+
message = (params["message"] || "").dup
|
40
54
|
message << "\nUrl: " << params["url"] if params["url"]
|
41
55
|
message << "\nLine: " << params["line"] if params["line"]
|
42
56
|
message << "\nColumn: " << params["column"] if params["column"]
|
@@ -48,6 +62,8 @@ module Logster
|
|
48
62
|
message,
|
49
63
|
backtrace: backtrace,
|
50
64
|
env: env)
|
65
|
+
|
66
|
+
true
|
51
67
|
end
|
52
68
|
|
53
69
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Logster::Rails
|
2
4
|
|
3
5
|
# this magically registers logster.js in the asset pipeline
|
@@ -30,7 +32,9 @@ module Logster::Rails
|
|
30
32
|
return unless Logster.config.environments.include?(Rails.env.to_sym)
|
31
33
|
|
32
34
|
if Logster::Logger === Rails.logger
|
33
|
-
|
35
|
+
if Logster.config.enable_js_error_reporting
|
36
|
+
app.middleware.insert_before ActionDispatch::ShowExceptions, Logster::Middleware::Reporter
|
37
|
+
end
|
34
38
|
|
35
39
|
if Rails::VERSION::MAJOR == 3
|
36
40
|
app.middleware.insert_before ActionDispatch::DebugExceptions, Logster::Middleware::DebugExceptions
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logster
|
4
|
+
class RedisRateLimiter
|
5
|
+
BUCKETS = 6
|
6
|
+
PREFIX = "__LOGSTER__RATE_LIMIT".freeze
|
7
|
+
|
8
|
+
attr_reader :duration, :callback
|
9
|
+
|
10
|
+
def self.clear_all(redis, redis_prefix = nil)
|
11
|
+
prefix = key_prefix(redis_prefix)
|
12
|
+
|
13
|
+
redis.eval "
|
14
|
+
local keys = redis.call('keys', '*#{prefix}*')
|
15
|
+
if (table.getn(keys) > 0) then
|
16
|
+
redis.call('del', unpack(keys))
|
17
|
+
end
|
18
|
+
"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(redis, severities, limit, duration, redis_prefix = nil, callback = nil)
|
22
|
+
@severities = severities
|
23
|
+
@limit = limit
|
24
|
+
@duration = duration
|
25
|
+
@callback = callback
|
26
|
+
@redis_prefix = redis_prefix
|
27
|
+
@redis = redis
|
28
|
+
@bucket_range = @duration / BUCKETS
|
29
|
+
@mget_keys = (0..(BUCKETS - 1)).map { |i| "#{key}:#{i}" }
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve_rate
|
33
|
+
@redis.mget(@mget_keys).reduce(0) { |sum, value| sum + value.to_i }
|
34
|
+
end
|
35
|
+
|
36
|
+
def check(severity)
|
37
|
+
return unless @severities.include?(severity)
|
38
|
+
time = Time.now.to_i
|
39
|
+
num = bucket_number(time)
|
40
|
+
redis_key = "#{key}:#{num}"
|
41
|
+
|
42
|
+
current_rate = @redis.eval <<-LUA
|
43
|
+
local bucket_number = #{num}
|
44
|
+
local bucket_count = redis.call("INCR", "#{redis_key}")
|
45
|
+
|
46
|
+
if bucket_count == 1 then
|
47
|
+
redis.call("EXPIRE", "#{redis_key}", "#{bucket_expiry(time)}")
|
48
|
+
redis.call("DEL", "#{callback_key}")
|
49
|
+
end
|
50
|
+
|
51
|
+
local function retrieve_rate ()
|
52
|
+
local sum = 0
|
53
|
+
local values = redis.call("MGET", #{mget_keys(num)})
|
54
|
+
for index, value in ipairs(values) do
|
55
|
+
if value ~= false then sum = sum + value end
|
56
|
+
end
|
57
|
+
return sum
|
58
|
+
end
|
59
|
+
|
60
|
+
return (retrieve_rate() + bucket_count)
|
61
|
+
LUA
|
62
|
+
|
63
|
+
if !@redis.get(callback_key) && (current_rate >= @limit)
|
64
|
+
@callback.call(current_rate) if @callback
|
65
|
+
@redis.set(callback_key, 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
current_rate
|
69
|
+
end
|
70
|
+
|
71
|
+
def key
|
72
|
+
# "_LOGSTER_RATE_LIMIT:012:20:30"
|
73
|
+
# Triggers callback when log levels of :debug, :info and :warn occurs 20 times within 30 secs
|
74
|
+
"#{key_prefix}:#{@severities.join("")}:#{@limit}:#{@duration}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def callback_key
|
78
|
+
"#{key}:callback_triggered"
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def self.key_prefix(redis_prefix)
|
84
|
+
if redis_prefix
|
85
|
+
"#{redis_prefix.call}:#{PREFIX}"
|
86
|
+
else
|
87
|
+
PREFIX
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def key_prefix
|
93
|
+
self.class.key_prefix(@redis_prefix)
|
94
|
+
end
|
95
|
+
|
96
|
+
def mget_keys(bucket_num)
|
97
|
+
keys = @mget_keys.dup
|
98
|
+
keys.delete_at(bucket_num)
|
99
|
+
keys.map { |key| "'#{key}'" }.join(', ')
|
100
|
+
end
|
101
|
+
|
102
|
+
def bucket_number(time)
|
103
|
+
(time % @duration) / @bucket_range
|
104
|
+
end
|
105
|
+
|
106
|
+
def bucket_expiry(time)
|
107
|
+
@duration - ((time % @duration) % @bucket_range)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/logster/redis_store.rb
CHANGED
@@ -1,114 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'logster/base_store'
|
5
|
+
require 'logster/redis_rate_limiter'
|
3
6
|
|
4
7
|
module Logster
|
5
|
-
class RedisRateLimiter
|
6
|
-
BUCKETS = 6
|
7
|
-
PREFIX = "__LOGSTER__RATE_LIMIT".freeze
|
8
|
-
|
9
|
-
attr_reader :duration, :callback
|
10
|
-
|
11
|
-
def self.clear_all(redis, redis_prefix = nil)
|
12
|
-
prefix = key_prefix(redis_prefix)
|
13
|
-
|
14
|
-
redis.eval "
|
15
|
-
local keys = redis.call('keys', '*#{prefix}*')
|
16
|
-
if (table.getn(keys) > 0) then
|
17
|
-
redis.call('del', unpack(keys))
|
18
|
-
end
|
19
|
-
"
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(redis, severities, limit, duration, redis_prefix = nil, callback = nil)
|
23
|
-
@severities = severities
|
24
|
-
@limit = limit
|
25
|
-
@duration = duration
|
26
|
-
@callback = callback
|
27
|
-
@redis_prefix = redis_prefix
|
28
|
-
@redis = redis
|
29
|
-
@bucket_range = @duration / BUCKETS
|
30
|
-
@mget_keys = (0..(BUCKETS - 1)).map { |i| "#{key}:#{i}" }
|
31
|
-
end
|
32
|
-
|
33
|
-
def retrieve_rate
|
34
|
-
@redis.mget(@mget_keys).reduce(0) { |sum, value| sum + value.to_i }
|
35
|
-
end
|
36
|
-
|
37
|
-
def check(severity)
|
38
|
-
return unless @severities.include?(severity)
|
39
|
-
time = Time.now.to_i
|
40
|
-
num = bucket_number(time)
|
41
|
-
redis_key = "#{key}:#{num}"
|
42
|
-
|
43
|
-
current_rate = @redis.eval <<-LUA
|
44
|
-
local bucket_number = #{num}
|
45
|
-
local bucket_count = redis.call("INCR", "#{redis_key}")
|
46
|
-
|
47
|
-
if bucket_count == 1 then
|
48
|
-
redis.call("EXPIRE", "#{redis_key}", "#{bucket_expiry(time)}")
|
49
|
-
redis.call("DEL", "#{callback_key}")
|
50
|
-
end
|
51
|
-
|
52
|
-
local function retrieve_rate ()
|
53
|
-
local sum = 0
|
54
|
-
local values = redis.call("MGET", #{mget_keys(num)})
|
55
|
-
for index, value in ipairs(values) do
|
56
|
-
if value ~= false then sum = sum + value end
|
57
|
-
end
|
58
|
-
return sum
|
59
|
-
end
|
60
|
-
|
61
|
-
return (retrieve_rate() + bucket_count)
|
62
|
-
LUA
|
63
|
-
|
64
|
-
if !@redis.get(callback_key) && (current_rate >= @limit)
|
65
|
-
@callback.call(current_rate) if @callback
|
66
|
-
@redis.set(callback_key, 1)
|
67
|
-
end
|
68
|
-
|
69
|
-
current_rate
|
70
|
-
end
|
71
|
-
|
72
|
-
def key
|
73
|
-
# "_LOGSTER_RATE_LIMIT:012:20:30"
|
74
|
-
# Triggers callback when log levels of :debug, :info and :warn occurs 20 times within 30 secs
|
75
|
-
"#{key_prefix}:#{@severities.join("")}:#{@limit}:#{@duration}"
|
76
|
-
end
|
77
|
-
|
78
|
-
def callback_key
|
79
|
-
"#{key}:callback_triggered"
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def self.key_prefix(redis_prefix)
|
85
|
-
if redis_prefix
|
86
|
-
"#{redis_prefix.call}:#{PREFIX}"
|
87
|
-
else
|
88
|
-
PREFIX
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
def key_prefix
|
94
|
-
self.class.key_prefix(@redis_prefix)
|
95
|
-
end
|
96
|
-
|
97
|
-
def mget_keys(bucket_num)
|
98
|
-
keys = @mget_keys.dup
|
99
|
-
keys.delete_at(bucket_num)
|
100
|
-
keys.map { |key| "'#{key}'" }.join(', ')
|
101
|
-
end
|
102
|
-
|
103
|
-
def bucket_number(time)
|
104
|
-
(time % @duration) / @bucket_range
|
105
|
-
end
|
106
|
-
|
107
|
-
def bucket_expiry(time)
|
108
|
-
@duration - ((time % @duration) % @bucket_range)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
8
|
class RedisStore < BaseStore
|
113
9
|
|
114
10
|
attr_accessor :redis, :max_backlog, :redis_raw_connection
|
@@ -278,6 +174,7 @@ module Logster
|
|
278
174
|
end
|
279
175
|
@redis.keys.each do |key|
|
280
176
|
@redis.del(key) if key.include?(Logster::RedisRateLimiter::PREFIX)
|
177
|
+
@redis.del(key) if key.start_with?(ip_rate_limit_key(""))
|
281
178
|
end
|
282
179
|
end
|
283
180
|
|
@@ -373,6 +270,18 @@ module Logster
|
|
373
270
|
@redis.hgetall(ignored_logs_count_key)
|
374
271
|
end
|
375
272
|
|
273
|
+
def rate_limited?(ip_address, perform: false, limit: 60)
|
274
|
+
key = ip_rate_limit_key(ip_address)
|
275
|
+
|
276
|
+
limited = @redis.exists(key)
|
277
|
+
|
278
|
+
if perform && !limited
|
279
|
+
@redis.setex key, limit, ""
|
280
|
+
end
|
281
|
+
|
282
|
+
limited
|
283
|
+
end
|
284
|
+
|
376
285
|
protected
|
377
286
|
|
378
287
|
def clear_solved(count = nil)
|
@@ -583,6 +492,10 @@ module Logster
|
|
583
492
|
@ignored_logs_count_key ||= "__LOGSTER__IGNORED_LOGS_COUNT_KEY__MAP"
|
584
493
|
end
|
585
494
|
|
495
|
+
def ip_rate_limit_key(ip_address)
|
496
|
+
"__LOGSTER__IP_RATE_LIMIT_#{ip_address}"
|
497
|
+
end
|
498
|
+
|
586
499
|
private
|
587
500
|
|
588
501
|
def register_rate_limit(severities, limit, duration, callback)
|
data/lib/logster/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../../test_helper'
|
2
4
|
require 'rack'
|
3
5
|
require 'logster/redis_store'
|
@@ -5,15 +7,53 @@ require 'logster/middleware/reporter'
|
|
5
7
|
|
6
8
|
class TestReporter < Minitest::Test
|
7
9
|
|
10
|
+
def setup
|
11
|
+
Logster.store = Logster::RedisStore.new
|
12
|
+
Logster.store.clear_all
|
13
|
+
Logster.config.enable_js_error_reporting = true
|
14
|
+
end
|
15
|
+
|
8
16
|
def test_logs_errors
|
9
|
-
|
17
|
+
reporter = Logster::Middleware::Reporter.new(nil)
|
18
|
+
env = Rack::MockRequest.env_for("/logs/report_js_error?message=hello")
|
19
|
+
status, = reporter.call(env)
|
20
|
+
|
21
|
+
assert_equal(200, status)
|
22
|
+
assert_equal(1, Logster.store.count)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_respects_ban_on_errors
|
26
|
+
Logster.config.enable_js_error_reporting = false
|
10
27
|
|
11
28
|
reporter = Logster::Middleware::Reporter.new(nil)
|
12
29
|
env = Rack::MockRequest.env_for("/logs/report_js_error?message=hello")
|
13
30
|
status, = reporter.call(env)
|
14
31
|
|
32
|
+
assert_equal(403, status)
|
33
|
+
assert_equal(0, Logster.store.count)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_rate_limiting
|
37
|
+
reporter = Logster::Middleware::Reporter.new(nil)
|
38
|
+
env = Rack::MockRequest.env_for("/logs/report_js_error?message=hello")
|
39
|
+
status, = reporter.call(env)
|
40
|
+
|
15
41
|
assert_equal(200, status)
|
16
42
|
assert_equal(1, Logster.store.count)
|
43
|
+
|
44
|
+
reporter = Logster::Middleware::Reporter.new(nil)
|
45
|
+
env = Rack::MockRequest.env_for("/logs/report_js_error?message=hello2")
|
46
|
+
status, = reporter.call(env)
|
47
|
+
|
48
|
+
assert_equal(429, status)
|
49
|
+
assert_equal(1, Logster.store.count)
|
50
|
+
|
51
|
+
reporter = Logster::Middleware::Reporter.new(nil)
|
52
|
+
env = Rack::MockRequest.env_for("/logs/report_js_error?message=hello2", "REMOTE_ADDR" => "100.1.1.2")
|
53
|
+
status, = reporter.call(env)
|
54
|
+
|
55
|
+
assert_equal(200, status)
|
56
|
+
assert_equal(2, Logster.store.count)
|
17
57
|
end
|
18
58
|
|
19
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- UI for viewing logs in Rack
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -248,6 +248,7 @@ files:
|
|
248
248
|
- lib/logster/middleware/viewer.rb
|
249
249
|
- lib/logster/pattern.rb
|
250
250
|
- lib/logster/rails/railtie.rb
|
251
|
+
- lib/logster/redis_rate_limiter.rb
|
251
252
|
- lib/logster/redis_store.rb
|
252
253
|
- lib/logster/scheduler.rb
|
253
254
|
- lib/logster/suppression_pattern.rb
|