dry-monitor 0.0.1 → 0.0.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 +13 -0
- data/lib/dry/monitor.rb +3 -83
- data/lib/dry/monitor/notifications.rb +79 -0
- data/lib/dry/monitor/rack/logger.rb +109 -0
- data/lib/dry/monitor/rack/middleware.rb +12 -107
- data/lib/dry/monitor/sql/logger.rb +4 -1
- data/lib/dry/monitor/version.rb +1 -1
- data/spec/integration/rack_middleware_spec.rb +15 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6454d8e5127c145f85a2f9e35dc21e7a84bd6037
|
4
|
+
data.tar.gz: c3f01289d91fa96c7aa3316b866b74fbd2524ccf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ac158bd1e1b591de60c7e8afa817ea79a2ea437e5776cf8290fbdb12d5ac2c1309ee48e72078f579c702b0f288fdcac19de152fac8ccec215f369930381bfff
|
7
|
+
data.tar.gz: 4e52db2286bdb3cb5e94b412b767c7ea2d88f0eb9f3d77f9e17fe359e89653f2d917dd9bd1290f591fe0db45accd367dbf8f6d5d8190f7d1fe0964e373c5e896
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
# v0.0.2 2017-02-02
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* `Dry::Monitor::Rack::Middleware#on` shortcut (solnic)
|
6
|
+
* `Dry::Monitor::Rack::Middleware#instrument` shortcut (solnic)
|
7
|
+
* `Dry::Monitor::SQL::Logger` can be configured with a custom message template (solnic)
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
* `Dry::Monitor::Rack::Logger#{subscribe=>attach}` and now it accepts a rack middleware instance (solnic)
|
12
|
+
* `Dry::Monitor::Rack::Logger` appends new lines when logging `:rack.request.stop` events (solnic)
|
13
|
+
|
1
14
|
# v0.0.1 2017-02-01
|
2
15
|
|
3
16
|
First public release
|
data/lib/dry/monitor.rb
CHANGED
@@ -1,85 +1,5 @@
|
|
1
|
-
require 'dry-equalizer'
|
2
|
-
|
3
|
-
require 'dry/monitor/rack/middleware'
|
4
1
|
require 'dry/monitor/logger'
|
2
|
+
require 'dry/monitor/notifications'
|
3
|
+
require 'dry/monitor/rack/middleware'
|
4
|
+
require 'dry/monitor/rack/logger'
|
5
5
|
require 'dry/monitor/sql/logger'
|
6
|
-
|
7
|
-
module Dry
|
8
|
-
module Monitor
|
9
|
-
class Clock
|
10
|
-
def measure(&block)
|
11
|
-
start = current
|
12
|
-
result = block.()
|
13
|
-
stop = current
|
14
|
-
[result, ((stop - start) * 1000).round(2)]
|
15
|
-
end
|
16
|
-
|
17
|
-
def current
|
18
|
-
Time.now
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
CLOCK = Clock.new
|
23
|
-
|
24
|
-
class Event
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
attr_reader :info
|
28
|
-
|
29
|
-
def initialize(id, info = {})
|
30
|
-
@id = id
|
31
|
-
@info = info
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Notifications
|
36
|
-
attr_reader :id
|
37
|
-
attr_reader :events
|
38
|
-
attr_reader :listeners
|
39
|
-
attr_reader :clock
|
40
|
-
|
41
|
-
def initialize(id)
|
42
|
-
@id = id
|
43
|
-
@listeners = Hash.new { |h, k| h[k] = [] }
|
44
|
-
@events = {}
|
45
|
-
@clock = CLOCK
|
46
|
-
end
|
47
|
-
|
48
|
-
def event(id, info = {})
|
49
|
-
events[id] = Event.new(id, info)
|
50
|
-
self
|
51
|
-
end
|
52
|
-
|
53
|
-
def subscribe(event_id, listener = nil, &block)
|
54
|
-
listeners[event_id] << (listener || block)
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
def start(event_id, payload)
|
59
|
-
instrument(event_id, payload)
|
60
|
-
end
|
61
|
-
|
62
|
-
def stop(event_id, payload)
|
63
|
-
instrument(event_id, payload)
|
64
|
-
end
|
65
|
-
|
66
|
-
def instrument(event_id, payload = nil, &block)
|
67
|
-
event = events[event_id]
|
68
|
-
|
69
|
-
if block
|
70
|
-
result, time = clock.measure(&block)
|
71
|
-
end
|
72
|
-
|
73
|
-
listeners[event_id].each do |listener|
|
74
|
-
if time
|
75
|
-
listener.(time, event.id, payload.merge(event.info))
|
76
|
-
else
|
77
|
-
listener.(event.id, payload.merge(event.info))
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
result
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Dry
|
2
|
+
module Monitor
|
3
|
+
class Clock
|
4
|
+
def measure(&block)
|
5
|
+
start = current
|
6
|
+
result = block.()
|
7
|
+
stop = current
|
8
|
+
[result, ((stop - start) * 1000).round(2)]
|
9
|
+
end
|
10
|
+
|
11
|
+
def current
|
12
|
+
Time.now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
CLOCK = Clock.new
|
17
|
+
|
18
|
+
class Event
|
19
|
+
attr_reader :id
|
20
|
+
|
21
|
+
attr_reader :info
|
22
|
+
|
23
|
+
def initialize(id, info = {})
|
24
|
+
@id = id
|
25
|
+
@info = info
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Notifications
|
30
|
+
attr_reader :id
|
31
|
+
attr_reader :events
|
32
|
+
attr_reader :listeners
|
33
|
+
attr_reader :clock
|
34
|
+
|
35
|
+
def initialize(id)
|
36
|
+
@id = id
|
37
|
+
@listeners = Hash.new { |h, k| h[k] = [] }
|
38
|
+
@events = {}
|
39
|
+
@clock = CLOCK
|
40
|
+
end
|
41
|
+
|
42
|
+
def event(id, info = {})
|
43
|
+
events[id] = Event.new(id, info) unless events.key?(id)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def subscribe(event_id, listener = nil, &block)
|
48
|
+
listeners[event_id] << (listener || block)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def start(event_id, payload)
|
53
|
+
instrument(event_id, payload)
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop(event_id, payload)
|
57
|
+
instrument(event_id, payload)
|
58
|
+
end
|
59
|
+
|
60
|
+
def instrument(event_id, payload = nil, &block)
|
61
|
+
event = events[event_id]
|
62
|
+
|
63
|
+
if block
|
64
|
+
result, time = clock.measure(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
listeners[event_id].each do |listener|
|
68
|
+
if time
|
69
|
+
listener.(time, event.id, payload.merge(event.info))
|
70
|
+
else
|
71
|
+
listener.(event.id, payload.merge(event.info))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'dry/configurable'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Monitor
|
5
|
+
module Rack
|
6
|
+
class Logger
|
7
|
+
extend Dry::Configurable
|
8
|
+
|
9
|
+
setting :filtered_params, %w[_csrf password]
|
10
|
+
|
11
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
12
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
13
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
14
|
+
RACK_INPUT = 'rack.input'.freeze
|
15
|
+
QUERY_PARAMS = 'QUERY_PARAMS'.freeze
|
16
|
+
|
17
|
+
START_MSG = %(Started %s "%s" for %s at %s).freeze
|
18
|
+
STOP_MSG = %(Finished %s "%s" for %s in %sms [Status: %s]\n).freeze
|
19
|
+
PARAMS_MSG = %( Parameters %s).freeze
|
20
|
+
QUERY_MSG = %( Query parameters %s).freeze
|
21
|
+
FILTERED = '[FILTERED]'.freeze
|
22
|
+
|
23
|
+
attr_reader :logger
|
24
|
+
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
def initialize(logger, config = self.class.config)
|
28
|
+
@logger = logger
|
29
|
+
@config = config
|
30
|
+
end
|
31
|
+
|
32
|
+
def attach(rack_monitor)
|
33
|
+
rack_monitor.on(:start) do |id, payload|
|
34
|
+
log_start_request(payload[:env])
|
35
|
+
end
|
36
|
+
|
37
|
+
rack_monitor.on(:stop) do |id, payload|
|
38
|
+
log_stop_request(payload[:env], payload[:status], payload[:time])
|
39
|
+
end
|
40
|
+
|
41
|
+
rack_monitor.on(:error) do |id, payload|
|
42
|
+
log_exception(payload[:exception], payload[:name])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def log_exception(e, app_name)
|
47
|
+
logger.error e.message
|
48
|
+
logger.error filter_backtrace(e.backtrace, app_name).join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
def log_start_request(request)
|
52
|
+
info START_MSG % [
|
53
|
+
request[REQUEST_METHOD],
|
54
|
+
request[PATH_INFO],
|
55
|
+
request[REMOTE_ADDR],
|
56
|
+
Time.now
|
57
|
+
]
|
58
|
+
log_request_params(request)
|
59
|
+
end
|
60
|
+
|
61
|
+
def log_stop_request(request, status, time)
|
62
|
+
info STOP_MSG % [
|
63
|
+
request[REQUEST_METHOD],
|
64
|
+
request[PATH_INFO],
|
65
|
+
request[REMOTE_ADDR],
|
66
|
+
time,
|
67
|
+
status
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
def log_request_params(request)
|
72
|
+
with_http_params(request[QUERY_PARAMS]) do |params|
|
73
|
+
info QUERY_MSG % [params.inspect]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def info(*args)
|
78
|
+
logger.info(*args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def with_http_params(params)
|
82
|
+
params = ::Rack::Utils.parse_nested_query(params)
|
83
|
+
if params.size > 0
|
84
|
+
yield(filter_params(params))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def filter_backtrace(backtrace, app_name)
|
89
|
+
# TODO: what do we want to do with this?
|
90
|
+
backtrace.reject { |l| l.include?('gems') }
|
91
|
+
end
|
92
|
+
|
93
|
+
def filter_params(params)
|
94
|
+
params.each_with_object({}) do |(k, v), h|
|
95
|
+
if v.is_a?(Hash)
|
96
|
+
h.update(k => filter_params(v))
|
97
|
+
elsif v.is_a?(Array)
|
98
|
+
h.update(k => v.map { |m| filter_params(m) })
|
99
|
+
elsif config.filtered_params.include?(k)
|
100
|
+
h.update(k => FILTERED)
|
101
|
+
else
|
102
|
+
h[k] = v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -1,115 +1,12 @@
|
|
1
|
-
require 'dry/configurable'
|
2
1
|
require 'rack/utils'
|
3
2
|
|
4
3
|
module Dry
|
5
4
|
module Monitor
|
6
5
|
module Rack
|
7
|
-
class Logger
|
8
|
-
extend Dry::Configurable
|
9
|
-
|
10
|
-
setting :filtered_params, %w[_csrf password]
|
11
|
-
|
12
|
-
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
13
|
-
PATH_INFO = 'PATH_INFO'.freeze
|
14
|
-
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
15
|
-
RACK_INPUT = 'rack.input'.freeze
|
16
|
-
QUERY_PARAMS = 'QUERY_PARAMS'.freeze
|
17
|
-
|
18
|
-
START_MSG = %(Started %s "%s" for %s at %s).freeze
|
19
|
-
STOP_MSG = %(Finished %s "%s" for %s in %sms [Status: %s]).freeze
|
20
|
-
PARAMS_MSG = %( Parameters %s).freeze
|
21
|
-
QUERY_MSG = %( Query parameters %s).freeze
|
22
|
-
FILTERED = '[FILTERED]'.freeze
|
23
|
-
|
24
|
-
attr_reader :logger
|
25
|
-
|
26
|
-
attr_reader :config
|
27
|
-
|
28
|
-
def initialize(logger, config = self.class.config)
|
29
|
-
@logger = logger
|
30
|
-
@config = config
|
31
|
-
end
|
32
|
-
|
33
|
-
def subscribe(notifications)
|
34
|
-
notifications.subscribe(Middleware::REQUEST_START) do |id, payload|
|
35
|
-
log_start_request(payload[:env])
|
36
|
-
end
|
37
|
-
|
38
|
-
notifications.subscribe(Middleware::REQUEST_STOP) do |id, payload|
|
39
|
-
log_stop_request(payload[:env], payload[:status], payload[:time])
|
40
|
-
end
|
41
|
-
|
42
|
-
notifications.subscribe(Middleware::APP_ERROR) do |id, payload|
|
43
|
-
log_exception(payload[:exception], payload[:name])
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def log_exception(e, app_name)
|
48
|
-
logger.error e.message
|
49
|
-
logger.error filter_backtrace(e.backtrace, app_name).join("\n")
|
50
|
-
end
|
51
|
-
|
52
|
-
def log_start_request(request)
|
53
|
-
info START_MSG % [
|
54
|
-
request[REQUEST_METHOD],
|
55
|
-
request[PATH_INFO],
|
56
|
-
request[REMOTE_ADDR],
|
57
|
-
Time.now
|
58
|
-
]
|
59
|
-
log_request_params(request)
|
60
|
-
end
|
61
|
-
|
62
|
-
def log_stop_request(request, status, time)
|
63
|
-
info STOP_MSG % [
|
64
|
-
request[REQUEST_METHOD],
|
65
|
-
request[PATH_INFO],
|
66
|
-
request[REMOTE_ADDR],
|
67
|
-
time,
|
68
|
-
status
|
69
|
-
]
|
70
|
-
end
|
71
|
-
|
72
|
-
def log_request_params(request)
|
73
|
-
with_http_params(request[QUERY_PARAMS]) do |params|
|
74
|
-
info QUERY_MSG % [params.inspect]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def info(*args)
|
79
|
-
logger.info(*args)
|
80
|
-
end
|
81
|
-
|
82
|
-
def with_http_params(params)
|
83
|
-
params = ::Rack::Utils.parse_nested_query(params)
|
84
|
-
if params.size > 0
|
85
|
-
yield(filter_params(params))
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def filter_backtrace(backtrace, app_name)
|
90
|
-
# TODO: what do we want to do with this?
|
91
|
-
backtrace.reject { |l| l.include?('gems') }
|
92
|
-
end
|
93
|
-
|
94
|
-
def filter_params(params)
|
95
|
-
params.each_with_object({}) do |(k, v), h|
|
96
|
-
if v.is_a?(Hash)
|
97
|
-
h.update(k => filter_params(v))
|
98
|
-
elsif v.is_a?(Array)
|
99
|
-
h.update(k => v.map { |m| filter_params(m) })
|
100
|
-
elsif config.filtered_params.include?(k)
|
101
|
-
h.update(k => FILTERED)
|
102
|
-
else
|
103
|
-
h[k] = v
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
6
|
class Middleware
|
110
|
-
REQUEST_START = :'request.start'
|
111
|
-
REQUEST_STOP = :'request.stop'
|
112
|
-
|
7
|
+
REQUEST_START = :'rack.request.start'
|
8
|
+
REQUEST_STOP = :'rack.request.stop'
|
9
|
+
REQUEST_ERROR = :'rack.request.error'
|
113
10
|
|
114
11
|
attr_reader :app
|
115
12
|
attr_reader :notifications
|
@@ -119,13 +16,21 @@ module Dry
|
|
119
16
|
|
120
17
|
notifications.event(REQUEST_START)
|
121
18
|
notifications.event(REQUEST_STOP)
|
122
|
-
notifications.event(
|
19
|
+
notifications.event(REQUEST_ERROR)
|
123
20
|
end
|
124
21
|
|
125
22
|
def new(app)
|
126
23
|
self.class.new(notifications, app)
|
127
24
|
end
|
128
25
|
|
26
|
+
def on(event_id, &block)
|
27
|
+
notifications.subscribe(:"rack.request.#{event_id}", &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def instrument(event_id, *args, &block)
|
31
|
+
notifications.instrument(:"rack.request.#{event_id}", *args, &block)
|
32
|
+
end
|
33
|
+
|
129
34
|
def call(env)
|
130
35
|
notifications.start(REQUEST_START, env: env)
|
131
36
|
response, time = CLOCK.measure { app.call(env) }
|
@@ -9,17 +9,20 @@ module Dry
|
|
9
9
|
|
10
10
|
setting :theme, Rouge::Themes::Gruvbox
|
11
11
|
setting :colorize, true
|
12
|
+
setting :message_template, %( Loaded %s in %sms %s).freeze
|
12
13
|
|
13
14
|
attr_reader :config
|
14
15
|
attr_reader :logger
|
15
16
|
attr_reader :formatter
|
16
17
|
attr_reader :lexer
|
18
|
+
attr_reader :template
|
17
19
|
|
18
20
|
def initialize(logger, config = self.class.config)
|
19
21
|
@logger = logger
|
20
22
|
@config = config
|
21
23
|
@formatter = Rouge::Formatters::Terminal256.new(config.theme)
|
22
24
|
@lexer = Rouge::Lexers::SQL.new
|
25
|
+
@template = config.message_template
|
23
26
|
end
|
24
27
|
|
25
28
|
def subscribe(notifications)
|
@@ -29,7 +32,7 @@ module Dry
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def log_query(time, name, query)
|
32
|
-
logger.info
|
35
|
+
logger.info template % [name.inspect, time, colorize(query)]
|
33
36
|
end
|
34
37
|
|
35
38
|
private
|
data/lib/dry/monitor/version.rb
CHANGED
@@ -30,7 +30,7 @@ RSpec.describe Dry::Monitor::Rack::Middleware do
|
|
30
30
|
|
31
31
|
before do
|
32
32
|
File.open(log_file_path, 'w').close
|
33
|
-
rack_logger.
|
33
|
+
rack_logger.attach(middleware)
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'triggers start/stop events for with a rack request' do
|
@@ -48,4 +48,18 @@ RSpec.describe Dry::Monitor::Rack::Middleware do
|
|
48
48
|
expect(log_file_content).to include('Query parameters {"_csrf"=>"[FILTERED]", "password"=>"[FILTERED]", "user"=>{"password"=>"[FILTERED]"}, "other"=>[{"password"=>"[FILTERED]"}, {"password"=>"[FILTERED]"}], "foo"=>"bar", "one"=>"1"}')
|
49
49
|
end
|
50
50
|
end
|
51
|
+
|
52
|
+
describe '#on' do
|
53
|
+
it 'subscribe a listener to a specific request event' do
|
54
|
+
captured = []
|
55
|
+
|
56
|
+
middleware.on(:error) do |id, payload|
|
57
|
+
captured << payload
|
58
|
+
end
|
59
|
+
|
60
|
+
middleware.instrument(:error, exception: 'oops')
|
61
|
+
|
62
|
+
expect(captured).to eql([exception: 'oops'])
|
63
|
+
end
|
64
|
+
end
|
51
65
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-monitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rouge
|
@@ -115,6 +115,8 @@ files:
|
|
115
115
|
- lib/dry-monitor.rb
|
116
116
|
- lib/dry/monitor.rb
|
117
117
|
- lib/dry/monitor/logger.rb
|
118
|
+
- lib/dry/monitor/notifications.rb
|
119
|
+
- lib/dry/monitor/rack/logger.rb
|
118
120
|
- lib/dry/monitor/rack/middleware.rb
|
119
121
|
- lib/dry/monitor/sql/logger.rb
|
120
122
|
- lib/dry/monitor/version.rb
|