beaver 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +42 -1
- data/bin/beaver +1 -0
- data/lib/beaver/beaver.rb +1 -1
- data/lib/beaver/dam.rb +53 -4
- data/lib/beaver/parsers/rails.rb +24 -2
- data/lib/beaver/request.rb +20 -0
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -6,6 +6,7 @@ Beaver is a light DSL and command line utility for parsing Rails production logs
|
|
6
6
|
* How many 500, 404, etc. errors yesterday? On what pages?
|
7
7
|
* How many Widgets were created yesterday, and with what data?
|
8
8
|
* Did anyone submit a form with the words "kill them all"? Yikes.
|
9
|
+
* Rails 3.2 tagged logging is cool, but what's a good way to review them?
|
9
10
|
|
10
11
|
Read the full documentation at {jordanhollinger.com/docs/beaver/}[http://jordanhollinger.com/docs/beaver/].
|
11
12
|
For a full list of matchers available to "hit", see the Beaver::Dam class. For a full list of methods available inside a "hit" block, or to members
|
@@ -26,6 +27,10 @@ of the "hits" array in a "dam" block, see the Beaver::Request class.
|
|
26
27
|
puts "#{ip} looked for help at #{path}"
|
27
28
|
end
|
28
29
|
|
30
|
+
hit :tagged => 'user 1' do
|
31
|
+
puts "user 1 was tagged at #{path} - other tags were: #{tags.join(', ')}"
|
32
|
+
end
|
33
|
+
|
29
34
|
hit :server_error, :status => (500..503)
|
30
35
|
|
31
36
|
dam :failed_login do
|
@@ -67,7 +72,7 @@ Run ''beaver'' from the command line, passing in your beaver file and some logs:
|
|
67
72
|
|
68
73
|
It's difficult to grep through a multi-line log format like Rails' and output each matching multi-line event (though I hear Google is working on a 'Context-Free Grep', which may help solve that). Until then, for Rails anyway, beaver is happy to step in.
|
69
74
|
|
70
|
-
beaver --path="/widgets" --method=post,put /var/www/rails-app/log/production.log
|
75
|
+
beaver --path="/widgets" --method=post,put --tagged=tag1,tag2 /var/www/rails-app/log/production.log
|
71
76
|
|
72
77
|
Or format the output to a single line:
|
73
78
|
|
@@ -104,6 +109,42 @@ HTTP status codes. For example, your failed logins are probably returning
|
|
104
109
|
|
105
110
|
render :action => :login, :status => 401
|
106
111
|
|
112
|
+
A detailed description of each status code and when to use it can be found at {www.w3.org}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html].
|
113
|
+
|
114
|
+
== Complex tag querying
|
115
|
+
Beaver supports complex Rails Tagged Logger tag quering, in both DSL and command-line modes.
|
116
|
+
|
117
|
+
=== DSL
|
118
|
+
|
119
|
+
# Matches any request tagged with "foo"
|
120
|
+
hit :tagged, :tagged => 'foo'
|
121
|
+
# Could also be written as ['foo']
|
122
|
+
|
123
|
+
# Matches any request tagged with "foo" AND "bar"
|
124
|
+
hit :tagged_with_all, :tagged => 'foo, bar'
|
125
|
+
# Could also be written ['foo', 'bar']
|
126
|
+
|
127
|
+
# Matches any request tagged with "foo" OR "bar"
|
128
|
+
hit :tagged_with_any, :tagged => [['foo'], ['bar']]
|
129
|
+
|
130
|
+
# Matches any request tagged with ("foo" AND "bar") OR ("bar" AND "baz") OR "yay"
|
131
|
+
hit :tagged_and_or_and_or, :tagged => [['foo', 'foop'], ['bar', 'baz'], ['yay']]
|
132
|
+
# Could also be written ['foo, foop', 'bar, baz', 'yay']
|
133
|
+
|
134
|
+
=== Command-line
|
135
|
+
|
136
|
+
# Matches any request tagged with "foo"
|
137
|
+
beaver --tagged foo production.log
|
138
|
+
|
139
|
+
# Matches any request tagged with "foo" AND "bar"
|
140
|
+
beaver --tagged foo,bar production.log
|
141
|
+
|
142
|
+
# Matches any request tagged with "foo" OR "bar"
|
143
|
+
beaver --tagged foo --tagged bar production.log
|
144
|
+
|
145
|
+
# Matches any request tagged with ("foo" AND "foop") OR ("bar" AND "baz") OR "yay"
|
146
|
+
beaver --tagged foo,foop --taged bar,baz --tagged yay production.log
|
147
|
+
|
107
148
|
== License
|
108
149
|
Copyright 2011 Jordan Hollinger
|
109
150
|
|
data/bin/beaver
CHANGED
@@ -13,6 +13,7 @@ o = OptionParser.new do |opts|
|
|
13
13
|
opts.on('--action CONTROLLER', 'Rails action name or regex') { |a| matchers[:action] = Regexp.new(a, Regexp::IGNORECASE) }
|
14
14
|
opts.on('--status STATUS', 'HTTP status(es), e.g. 200 or 500..503') { |s| matchers[:status] = s =~ /(\.+)/ ? Range.new(*s.split($1).map(&:to_i)) : s.to_i }
|
15
15
|
opts.on('--ip IP', 'IP address, string or regex') { |ip| matchers[:ip] = Regexp.new(ip) }
|
16
|
+
opts.on('--tagged TAGS', 'Comma-separated Rails Tagged Logger tags') { |tags| (matchers[:tagged] ||= []) << tags }
|
16
17
|
opts.on('--params PARAMS', 'Request parameters string (a Ruby Hash), string or regex') { |params| matchers[:params_str] = Regexp.new(params, Regexp::IGNORECASE) }
|
17
18
|
opts.on('--format FORMAT', 'Response format(s), e.g. html or json,xml') { |f| matchers[:format] = f.split(',').map(&:to_sym) }
|
18
19
|
opts.on('--longer-than MS', 'Minimum response time in ms') { |ms| matchers[:longer_than] = ms.to_i }
|
data/lib/beaver/beaver.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# end
|
9
9
|
#
|
10
10
|
module Beaver
|
11
|
-
MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION = 1,
|
11
|
+
MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION = 1, 2, 0, nil
|
12
12
|
VERSION = [MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION].compact.join '.'
|
13
13
|
|
14
14
|
# Creates a new Beaver and immediately filters the log files. This should scale well
|
data/lib/beaver/dam.rb
CHANGED
@@ -31,7 +31,9 @@ module Beaver
|
|
31
31
|
# :params_str => A regular expressing matching the Parameters string
|
32
32
|
#
|
33
33
|
# :params => A Hash of Symbol=>String/Regexp pairs: {:username => 'bob', :email => /@gmail\.com$/}. All must match.
|
34
|
-
#
|
34
|
+
#
|
35
|
+
# :tagged => A comma-separated String or Array of Rails Tagged Logger tags. If you specify multiple tags, a request must have *all* of them.
|
36
|
+
#
|
35
37
|
# :match => A "catch-all" Regex that will be matched against the entire request string
|
36
38
|
#
|
37
39
|
# The last argument may be a block, which will be called everytime this Dam is hit.
|
@@ -47,11 +49,11 @@ module Beaver
|
|
47
49
|
|
48
50
|
# Name should be a unique symbol. Matchers is an options Hash. The callback will be evauluated within
|
49
51
|
# the context of a Beaver::Request.
|
50
|
-
def initialize(name, matchers, &callback)
|
52
|
+
def initialize(name, matchers={}, &callback)
|
51
53
|
@name = name
|
52
54
|
@callback = callback
|
53
55
|
@hits = []
|
54
|
-
|
56
|
+
build matchers
|
55
57
|
end
|
56
58
|
|
57
59
|
# Returns an array of IP address that hit this Dam.
|
@@ -78,12 +80,33 @@ module Beaver
|
|
78
80
|
return false unless @match_on.nil? or @match_on == request.date
|
79
81
|
return false unless @match_params_str.nil? or @match_params_str =~ request.params_str
|
80
82
|
return false unless @match_r.nil? or @match_r =~ request.to_s
|
83
|
+
if @deep_tag_match
|
84
|
+
return false unless @match_tags.nil? or (@match_tags.any? and request.tags_str and deep_matching_tags(@match_tags, request.tags))
|
85
|
+
else
|
86
|
+
return false unless @match_tags.nil? or (@match_tags.any? and request.tags_str and (@match_tags - request.tags).empty?)
|
87
|
+
end
|
81
88
|
return false unless @match_params.nil? or matching_hashes?(@match_params, request.params)
|
82
89
|
return true
|
83
90
|
end
|
84
91
|
|
85
92
|
private
|
86
93
|
|
94
|
+
# Matches tags recursively
|
95
|
+
def deep_matching_tags(matchers, tags)
|
96
|
+
all_tags_matched = nil
|
97
|
+
any_arrays_matched = false
|
98
|
+
for m in matchers
|
99
|
+
if m.is_a? Array
|
100
|
+
matched = deep_matching_tags m, tags
|
101
|
+
any_arrays_matched = true if matched
|
102
|
+
else
|
103
|
+
matched = tags.include? m
|
104
|
+
all_tags_matched = (matched && all_tags_matched != false) ? true : false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return (all_tags_matched or any_arrays_matched)
|
108
|
+
end
|
109
|
+
|
87
110
|
# Recursively compares to Hashes. If all of Hash A is in Hash B, they match.
|
88
111
|
def matching_hashes?(a,b)
|
89
112
|
intersecting_keys = a.keys & b.keys
|
@@ -107,8 +130,10 @@ module Beaver
|
|
107
130
|
end
|
108
131
|
end
|
109
132
|
|
133
|
+
public
|
134
|
+
|
110
135
|
# Parses and checks the validity of the matching options passed to the Dam.
|
111
|
-
def
|
136
|
+
def build(matchers)
|
112
137
|
if matchers[:path].respond_to? :===
|
113
138
|
@match_path = matchers[:path]
|
114
139
|
else
|
@@ -192,10 +217,34 @@ module Beaver
|
|
192
217
|
else raise ArgumentError, "Params must be a String or a Regexp (it's a #{matchers[:params].class.name})"
|
193
218
|
end if matchers[:params]
|
194
219
|
|
220
|
+
if matchers[:tagged]
|
221
|
+
@match_tags = parse_tag_matchers(matchers[:tagged])
|
222
|
+
@deep_tag_match = @match_tags.any? { |t| t.is_a? Array }
|
223
|
+
end
|
224
|
+
|
195
225
|
case matchers[:match].class.name
|
196
226
|
when Regexp.name then @match_r = matchers[:match]
|
197
227
|
else raise ArgumentError, "Match must be a Regexp (it's a #{matchers[:match].class.name})"
|
198
228
|
end if matchers[:match]
|
229
|
+
|
230
|
+
self
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
# Recursively parses a tag match pattern
|
236
|
+
def parse_tag_matchers(matcher)
|
237
|
+
if matcher.is_a? String
|
238
|
+
matcher.split(',').map { |t| t.strip.downcase }.uniq
|
239
|
+
elsif matcher.is_a? Array
|
240
|
+
matcher.map! do |m|
|
241
|
+
if m.is_a?(Array) or (m.is_a?(String) and m =~ /,/)
|
242
|
+
parse_tag_matchers(m)
|
243
|
+
else m; end
|
244
|
+
end
|
245
|
+
else
|
246
|
+
raise ArgumentError, "tagged must be a String or Array (it's a #{matcher.class.name})"
|
247
|
+
end
|
199
248
|
end
|
200
249
|
end
|
201
250
|
end
|
data/lib/beaver/parsers/rails.rb
CHANGED
@@ -9,12 +9,14 @@ module Beaver
|
|
9
9
|
REGEX_METHOD_OVERRIDE = /"_method"=>"([A-Z]+)"/i
|
10
10
|
REGEX_CONTROLLER = /Processing by (\w+Controller)#/
|
11
11
|
REGEX_ACTION = /Processing by \w+Controller#(\w+) as/
|
12
|
-
REGEX_COMPLETED =
|
12
|
+
REGEX_COMPLETED = /Completed (\d+)/
|
13
13
|
REGEX_PATH = /^Started \w{3,4} "([^"]+)"/
|
14
|
-
REGEX_PARAMS_STR =
|
14
|
+
REGEX_PARAMS_STR = / Parameters: (\{.+\})$/
|
15
15
|
REGEX_IP = /" for (\d+[\d.]+) at /
|
16
16
|
REGEX_FORMAT = /Processing by .+ as (\w+)$/
|
17
17
|
REGEX_MS = / in (\d+)ms/
|
18
|
+
REGEX_TAGS = /^(\[.+\] )+/
|
19
|
+
REGEX_TAG = /\[([^\]]+)\] /
|
18
20
|
# Depending on the version of Rails, the time format may be wildly different
|
19
21
|
REGEX_TIME = / at ([a-z0-9:\+\- ]+)$/i
|
20
22
|
|
@@ -105,6 +107,26 @@ module Beaver
|
|
105
107
|
m = REGEX_TIME.match(@lines)
|
106
108
|
m ? Time.parse(m.captures.first) : nil
|
107
109
|
end
|
110
|
+
|
111
|
+
# Parses and returns an array of tags string associated with the request
|
112
|
+
def parse_tags_str
|
113
|
+
t = REGEX_TAGS.match(@lines)
|
114
|
+
t ? t.captures.first : nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# Parses and returns an array of tags associated with the request
|
118
|
+
def parse_tags
|
119
|
+
t = tags_str
|
120
|
+
if t
|
121
|
+
tags = t.scan(REGEX_TAG)
|
122
|
+
tags.flatten!
|
123
|
+
tags.uniq!
|
124
|
+
tags.map! &:downcase
|
125
|
+
tags
|
126
|
+
else
|
127
|
+
[]
|
128
|
+
end
|
129
|
+
end
|
108
130
|
end
|
109
131
|
end
|
110
132
|
end
|
data/lib/beaver/request.rb
CHANGED
@@ -100,6 +100,16 @@ module Beaver
|
|
100
100
|
@time ||= parse_time
|
101
101
|
end
|
102
102
|
|
103
|
+
# Returns the tags string associated with the request (e.g. "[tag1] [tag2] ")
|
104
|
+
def tags_str
|
105
|
+
@tags_str ||= parse_tags_str
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns an array of tags associated with the request
|
109
|
+
def tags
|
110
|
+
@tags ||= parse_tags
|
111
|
+
end
|
112
|
+
|
103
113
|
# When called inside of a Beaver::Dam#hit block, this Request will *not* be matched.
|
104
114
|
def skip!
|
105
115
|
throw :skip
|
@@ -181,6 +191,16 @@ module Beaver
|
|
181
191
|
def parse_time
|
182
192
|
nil
|
183
193
|
end
|
194
|
+
|
195
|
+
# Parses and returns the tags string associated with the request (e.g. "[tag1] [tag2] ")
|
196
|
+
def parse_tags_str
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
|
200
|
+
# Parses and returns any tags associated with the request
|
201
|
+
def parse_tags
|
202
|
+
nil
|
203
|
+
end
|
184
204
|
end
|
185
205
|
|
186
206
|
# Represents a BadRequest that no parser could figure out.
|