dry-monitor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e21a185d78c3e50cd1c99ddf4d211ed3650b298f
4
- data.tar.gz: e103e5a459c69901f96423978e55fdf325f85f54
3
+ metadata.gz: 6454d8e5127c145f85a2f9e35dc21e7a84bd6037
4
+ data.tar.gz: c3f01289d91fa96c7aa3316b866b74fbd2524ccf
5
5
  SHA512:
6
- metadata.gz: 7539abd470e9413b7882ff41f0674f2fc3bfcf860c394e4e53991611316a636f2ba76cea514d2f6178ede7677ae6b782b3844e44f806d95f6719cc906beb38b8
7
- data.tar.gz: 8d97ebaba61a89e895669d1ecfa88266fe596f44c92590bffa68a5acf4dc5dcdc1fc8ff03f24f65d1966d06b38c9cb1ba28b6aa04e5ad58926304b868ca7d565
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
- APP_ERROR = :'app.error'
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(APP_ERROR)
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 " Loaded #{name.inspect} in #{time}ms #{colorize(query)}"
35
+ logger.info template % [name.inspect, time, colorize(query)]
33
36
  end
34
37
 
35
38
  private
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Monitor
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.0.2'.freeze
4
4
  end
5
5
  end
@@ -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.subscribe(notifications)
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.1
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-01 00:00:00.000000000 Z
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