beaver 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 }
@@ -8,7 +8,7 @@
8
8
  # end
9
9
  #
10
10
  module Beaver
11
- MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION = 1, 1, 0, nil
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
@@ -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
- set_matchers(matchers)
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 set_matchers(matchers)
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
@@ -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 = /^Completed (\d+)/
12
+ REGEX_COMPLETED = /Completed (\d+)/
13
13
  REGEX_PATH = /^Started \w{3,4} "([^"]+)"/
14
- REGEX_PARAMS_STR = /^ Parameters: (\{.+\})$/
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
@@ -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.
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 1.1.0
9
+ version: 1.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jordan Hollinger