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.
@@ -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