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