beaver 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +31 -34
- data/bin/beaver +32 -9
- data/lib/beaver/beaver.rb +10 -3
- data/lib/beaver/dam.rb +15 -5
- data/lib/beaver/parsers/rails.rb +8 -1
- data/lib/beaver/request.rb +11 -1
- metadata +5 -5
data/README.rdoc
CHANGED
@@ -7,21 +7,14 @@ Beaver is a light DSL for parsing Rails production logs back into usable data. I
|
|
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
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Read the full documentation at http://jordanhollinger.com/docs/beaver/. The Beaver::Dam and Beaver::Request classes
|
15
|
-
are probably what you're after.
|
10
|
+
Read the full documentation at {jordanhollinger.com/docs/beaver/}[http://jordanhollinger.com/docs/beaver/].
|
11
|
+
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
|
12
|
+
of the "hits" array in a "dam" block, see the Beaver::Request class.
|
16
13
|
|
17
14
|
== Installation
|
18
|
-
Beaver is still somewhat experimental, but it has proved stable and robust enough for a non-beta release.
|
19
|
-
|
20
15
|
[sudo] gem install beaver
|
21
16
|
|
22
|
-
==
|
23
|
-
Write your beaver file:
|
24
|
-
|
17
|
+
== Run a DSL file with beaver
|
25
18
|
hit :failed_login, :method => :post, :path => '/login', :status => 401
|
26
19
|
|
27
20
|
hit :new_widgets, :path => '/widgets', :method => :post, :status => 302 do
|
@@ -56,23 +49,7 @@ Run ''beaver'' from the command line, passing in your beaver file and some logs:
|
|
56
49
|
|
57
50
|
beaver my_beaver_file.rb /var/www/rails-app/log/production.log*
|
58
51
|
|
59
|
-
== Example use with Logwatch
|
60
|
-
This assumes 1) you're rotating your Rails logs daily and 2) you're running logwatch daily.
|
61
|
-
|
62
|
-
Check your beaver DSL file into your Rails app, maybe under /script.
|
63
|
-
|
64
|
-
Add a logwatch config file for your new service at /etc/logwatch/conf/services/your_app.conf:
|
65
|
-
|
66
|
-
Title = "Your Rails App"
|
67
|
-
LogFile = NONE
|
68
|
-
|
69
|
-
In /etc/logwatch/scripts/services/your_app:
|
70
|
-
|
71
|
-
beaver /var/www/your_app/script/beaver.rb --yesterday /var/www/your_app/log/production.log{,.1}
|
72
|
-
|
73
52
|
== Use your own Beaver
|
74
|
-
Use Beaver like a library:
|
75
|
-
|
76
53
|
require 'rubygems'
|
77
54
|
require 'beaver'
|
78
55
|
|
@@ -82,23 +59,43 @@ Use Beaver like a library:
|
|
82
59
|
|
83
60
|
# Parse the logs, but only include requests from yesterday
|
84
61
|
Beaver.parse logs, :on => Date.today-1 do
|
85
|
-
# Same DSL as above
|
86
62
|
hit :failed_login, :method => :post, :path => '/login', :status => 401
|
87
63
|
...
|
88
64
|
end
|
89
65
|
|
66
|
+
== Use beaver like grep for Rails logs
|
67
|
+
|
68
|
+
It's pretty difficult to grep through a multi-line log format and output each matching multi-line event. But Beaver is up to it:
|
69
|
+
|
70
|
+
beaver --path="/widgets.*" --method=post,put /var/www/rails-app/log/production.log
|
71
|
+
|
72
|
+
Or format the output to a single line:
|
73
|
+
|
74
|
+
beaver --path="/widgets.*" --method=post,put /var/www/rails-app/log/production.log --print "%{ip} hit %{path} using %{method}"
|
75
|
+
|
76
|
+
The command-line matchers are nearly identical to the DSL's. See more details with 'beaver --help'.
|
77
|
+
|
78
|
+
== Example use with Logwatch
|
79
|
+
This assumes 1) you're rotating your Rails logs daily and 2) you're running logwatch daily.
|
80
|
+
|
81
|
+
Check your beaver DSL file into your Rails app, maybe under /script.
|
82
|
+
|
83
|
+
Add a logwatch config file for your new service at /etc/logwatch/conf/services/your_app.conf:
|
84
|
+
|
85
|
+
Title = "Your Rails App"
|
86
|
+
LogFile = NONE
|
87
|
+
|
88
|
+
In /etc/logwatch/scripts/services/your_app:
|
89
|
+
|
90
|
+
beaver /var/www/your_app/script/beaver.rb --yesterday /var/www/your_app/log/production.log{,.1}
|
91
|
+
|
90
92
|
== Your Rails app should return appropriate HTTP statuses
|
91
93
|
Rails does a lot of great things for us, but one thing largely up to us are
|
92
94
|
HTTP status codes. For example, your failed logins are probably returning
|
93
|
-
200 when they should arguably be returning 400 or 401.
|
95
|
+
200 when they should arguably be returning 400 or 401. It's easy to do, and very useful to Beaver.
|
94
96
|
|
95
97
|
render :action => :login, :status => 401
|
96
98
|
|
97
|
-
See, it's easy. And very useful to Beaver.
|
98
|
-
|
99
|
-
== TODO
|
100
|
-
* Add support for Apache/Nginx/Rack::CommonLogger
|
101
|
-
|
102
99
|
== License
|
103
100
|
Copyright 2011 Jordan Hollinger
|
104
101
|
|
data/bin/beaver
CHANGED
@@ -4,10 +4,6 @@ require 'optparse'
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'beaver'
|
6
6
|
|
7
|
-
#
|
8
|
-
# This application was installed by RubyGems as part of the 'beaver' gem.
|
9
|
-
#
|
10
|
-
|
11
7
|
# Parse a string from the command-line into a Date object
|
12
8
|
def parse_date(date)
|
13
9
|
case date
|
@@ -19,21 +15,48 @@ end
|
|
19
15
|
|
20
16
|
options = {}
|
21
17
|
o = OptionParser.new do |opts|
|
22
|
-
opts.banner = 'Usage: beaver
|
18
|
+
opts.banner = 'Usage: beaver [options] [dsl.rb...] /rails/production.log...'
|
19
|
+
opts.on('--path PATH', 'URL path, string or regex') { |path| options[:path] = Regexp.new(path, Regexp::IGNORECASE) }
|
20
|
+
opts.on('--method METHOD', 'HTTP request method(s), e.g. get or post,put') { |methods| options[:method] = methods.split(',').map { |m| m.downcase.to_sym } }
|
21
|
+
opts.on('--status STATUS', 'HTTP status(es), e.g. 200 or 500..503') { |s| options[:status] = s =~ /(\.+)/ ? Range.new(*s.split($1).map(&:to_i)) : s.to_i }
|
22
|
+
opts.on('--ip IP', 'IP address, string or regex') { |ip| options[:ip] = Regexp.new(ip) }
|
23
|
+
opts.on('--params PARAMS', 'Request parameters string (a Ruby Hash), string or regex') { |params| options[:params_str] = Regexp.new(params, Regexp::IGNORECASE) }
|
24
|
+
opts.on('--format FORMAT', 'Response format(s), e.g. html or json,xml') { |f| options[:format] = f.split(',').map(&:to_sym) }
|
25
|
+
opts.on('--longer-than MS', 'Minimum response time in ms') { |ms| options[:longer_than] = ms.to_i }
|
26
|
+
opts.on('--shorter-than MS', 'Maximum response time in ms') { |ms| options[:shorter_than] = ms.to_i }
|
23
27
|
opts.on('--on DATE', 'Only include log entries from the given date (yyyy-mm-dd or -n days)') { |d| options[:on] = parse_date(d) }
|
24
28
|
opts.on('--after DATE', 'Only include log entries from after the given date (yyyy-mm-dd or -n days)') { |d| options[:after] = parse_date(d) }
|
25
29
|
opts.on('--before DATE', 'Only include log entries from before the given date (yyyy-mm-dd or -n days)') { |d| options[:before] = parse_date(d) }
|
26
30
|
opts.on('--today', 'Alias to --on=-0') { options[:on] = Date.today }
|
27
31
|
opts.on('--yesterday', 'Alias to --on=-1') { options[:on] = Date.today-1 }
|
32
|
+
opts.on('--regex REGEX', 'A regex string to be matched against the entire request') { |r| options[:match] = Regexp.new(r, Regexp::IGNORECASE) }
|
33
|
+
opts.on('--print FORMAT', 'Formatted request string, e.g. "%{ip} went to %{path} passing %{params[:email]}"') { |hit| options[:hit] = hit }
|
34
|
+
opts.on('-v', '--version', 'Show version') { puts "beaver #{Beaver::VERSION}"; exit }
|
28
35
|
end
|
29
36
|
o.parse!
|
30
37
|
|
31
|
-
|
38
|
+
# Separate the beaver files and log files
|
39
|
+
beaver_files, log_files = ARGV.uniq.partition { |a| a =~ /\.rb$/ }
|
40
|
+
|
41
|
+
# There have to be log files
|
42
|
+
if log_files.empty?
|
32
43
|
puts o.banner
|
33
44
|
exit 1
|
34
45
|
end
|
35
46
|
|
36
|
-
|
37
|
-
|
38
|
-
|
47
|
+
# Run logs through the DSL file(s)
|
48
|
+
if beaver_files.any?
|
49
|
+
STDERR.puts "WARNING --hit is ignored when running Beaver DSL files" if options[:hit]
|
50
|
+
|
51
|
+
beaver_files.map! { |b| File.open(b, &:read) }
|
52
|
+
Beaver.parse *[log_files, options].flatten do
|
53
|
+
beaver_files.each { |b| eval b }
|
54
|
+
end
|
55
|
+
# Run logs through the CLI options
|
56
|
+
else
|
57
|
+
Beaver.parse *log_files do
|
58
|
+
hit :cli, options do
|
59
|
+
puts options[:hit] ? eval('"'+options[:hit].gsub(/%\{/, '#{')+'"') : to_s << $/
|
60
|
+
end
|
61
|
+
end
|
39
62
|
end
|
data/lib/beaver/beaver.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Not specifically a performance analyzer (like https://github.com/wvanbergen/request-log-analyzer/wiki)
|
2
2
|
# Rather, a DSL for finding out how people are using your Rails app (which could include performance).
|
3
3
|
module Beaver
|
4
|
+
MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION = 1, 0, 0, nil
|
5
|
+
VERSION = [MAJOR_VERSION, MINOR_VERSION, TINY_VERSION, PRE_VERSION].compact.join '.'
|
6
|
+
|
4
7
|
# Alias to creating a new Beaver, parsing the files, and filtering them
|
5
8
|
def self.parse(*args, &blk)
|
6
9
|
raise ArgumentError, 'You must pass a block to Beaver#parse' unless block_given?
|
@@ -37,14 +40,14 @@ module Beaver
|
|
37
40
|
# Creates a new Dam and appends it to this Beaver. name should be a unique symbol.
|
38
41
|
# See Beaver::Dam for available options.
|
39
42
|
def hit(dam_name, matchers={}, &callback)
|
40
|
-
|
43
|
+
STDERR.puts "WARNING Overwriting existing hit '#{dam_name}'" if @dams.has_key? dam_name
|
41
44
|
matchers = @global_matchers.merge matchers
|
42
45
|
@dams[dam_name] = Dam.new(dam_name, matchers, &callback)
|
43
46
|
end
|
44
47
|
|
45
48
|
# Define a sumarry for a Dam
|
46
49
|
def dam(name, &callback)
|
47
|
-
|
50
|
+
STDERR.puts "WARNING Overwriting existing dam '#{name}'" if @sums.has_key? name
|
48
51
|
@sums[name] = callback
|
49
52
|
end
|
50
53
|
|
@@ -63,7 +66,11 @@ module Beaver
|
|
63
66
|
end
|
64
67
|
end
|
65
68
|
@sums.each do |dam_name, callback|
|
66
|
-
|
69
|
+
if @dams.has_key? dam_name
|
70
|
+
@dams[dam_name].instance_eval(&callback) if @dams[dam_name].hits.any?
|
71
|
+
else
|
72
|
+
STDERR.puts "WARNING You have defined a dam for '#{dam_name}', but there is no hit defined for '#{dam_name}'"
|
73
|
+
end
|
67
74
|
end
|
68
75
|
end
|
69
76
|
|
data/lib/beaver/dam.rb
CHANGED
@@ -6,11 +6,13 @@ module Beaver
|
|
6
6
|
#
|
7
7
|
# :path => String for exact match, or Regex
|
8
8
|
#
|
9
|
-
# :method => A Symbol of :get, :post, :
|
9
|
+
# :method => A Symbol of :get, :post, :put or :delete, or any array of any (reads the magic _method field if present)
|
10
10
|
#
|
11
11
|
# :status => A Fixnum like 404 or a Range like (500..503)
|
12
12
|
#
|
13
13
|
# :ip => String for exact match, or Regex
|
14
|
+
#
|
15
|
+
# :format => A symbol or array of symbols of response formats like :html, :json
|
14
16
|
#
|
15
17
|
# :longer_than => Fixnum n. Matches any request which took longer than n milliseconds to complete.
|
16
18
|
#
|
@@ -65,6 +67,8 @@ module Beaver
|
|
65
67
|
return false unless @match_status.nil? or @match_status === request.status
|
66
68
|
return false unless @match_ip_s.nil? or @match_ip_s == request.ip
|
67
69
|
return false unless @match_ip_r.nil? or @match_ip_r =~ request.ip
|
70
|
+
return false unless @match_format_s.nil? or @match_format_s == request.format
|
71
|
+
return false unless @match_format_a.nil? or @match_format_a.include? request.format
|
68
72
|
return false unless @match_before.nil? or @match_before > request.time
|
69
73
|
return false unless @match_after.nil? or @match_after < request.time
|
70
74
|
return false unless @match_on.nil? or (@match_on.year == request.time.year and @match_on.month == request.time.month and @match_on.day == request.time.day)
|
@@ -108,8 +112,8 @@ module Beaver
|
|
108
112
|
end if matchers[:path]
|
109
113
|
|
110
114
|
case matchers[:method].class.name
|
111
|
-
when Symbol.name then @match_method_s = matchers[:method]
|
112
|
-
when Array.name then @match_method_a = matchers[:method]
|
115
|
+
when Symbol.name then @match_method_s = matchers[:method].to_s.downcase.to_sym
|
116
|
+
when Array.name then @match_method_a = matchers[:method].map { |m| m.to_s.downcase.to_sym }
|
113
117
|
else raise ArgumentError, "Method must be a Symbol or an Array (it's a #{matchers[:method].class.name})"
|
114
118
|
end if matchers[:method]
|
115
119
|
|
@@ -119,11 +123,17 @@ module Beaver
|
|
119
123
|
end if matchers[:status]
|
120
124
|
|
121
125
|
case matchers[:ip].class.name
|
122
|
-
when String.name then @
|
123
|
-
when Regexp.name then @
|
126
|
+
when String.name then @match_ip_s = matchers[:ip]
|
127
|
+
when Regexp.name then @match_ip_r = matchers[:ip]
|
124
128
|
else raise ArgumentError, "IP must be a String or a Regexp (it's a #{matchers[:ip].class.name})"
|
125
129
|
end if matchers[:ip]
|
126
130
|
|
131
|
+
case matchers[:format].class.name
|
132
|
+
when Symbol.name then @match_format_s = matchers[:format].to_s.downcase.to_sym
|
133
|
+
when Array.name then @match_format_a = matchers[:format].map { |f| f.to_s.downcase.to_sym }
|
134
|
+
else raise ArgumentError, "Format must be a Symbol or an Array (it's a #{matchers[:format].class.name})"
|
135
|
+
end if matchers[:format]
|
136
|
+
|
127
137
|
case matchers[:longer_than].class.name
|
128
138
|
when Fixnum.name then @match_longer = matchers[:longer_than]
|
129
139
|
else raise ArgumentError, "longer_than must be a Fixnum (it's a #{matchers[:longer_than].class.name})"
|
data/lib/beaver/parsers/rails.rb
CHANGED
@@ -11,6 +11,7 @@ module Beaver
|
|
11
11
|
REGEX_PATH = /^Started \w{3,4} "([^"]+)"/
|
12
12
|
REGEX_PARAMS_STR = /^ Parameters: (\{.+\})$/
|
13
13
|
REGEX_IP = /" for (\d+[\d.]+) at /
|
14
|
+
REGEX_FORMAT = /Processing by .+ as (\w+)$/
|
14
15
|
REGEX_MS = / in (\d+)ms/
|
15
16
|
# Depending on the version of Rails, the time format may be wildly different
|
16
17
|
REGEX_TIME = / at ([a-z0-9:\+\- ]+)$/i
|
@@ -40,7 +41,7 @@ module Beaver
|
|
40
41
|
def parse_method
|
41
42
|
m = REGEX_METHOD_OVERRIDE.match(@lines)
|
42
43
|
m = REGEX_METHOD.match(@lines) if m.nil?
|
43
|
-
m ? m.captures.first.downcase.to_sym : :
|
44
|
+
m ? m.captures.first.downcase.to_sym : :unknown
|
44
45
|
end
|
45
46
|
|
46
47
|
# Parses and returns the response status
|
@@ -67,6 +68,12 @@ module Beaver
|
|
67
68
|
m ? m.captures.first : BLANK_STR
|
68
69
|
end
|
69
70
|
|
71
|
+
# Parses and returns the respones format
|
72
|
+
def parse_format
|
73
|
+
m = REGEX_FORMAT.match(@lines)
|
74
|
+
m ? m.captures.first.to_s.downcase.to_sym : :unknown
|
75
|
+
end
|
76
|
+
|
70
77
|
# Parses and returns the number of milliseconds it took for the request to complete
|
71
78
|
def parse_ms
|
72
79
|
m = REGEX_MS.match(@lines)
|
data/lib/beaver/request.rb
CHANGED
@@ -36,7 +36,7 @@ module Beaver
|
|
36
36
|
|
37
37
|
# Append a log line
|
38
38
|
def <<(line)
|
39
|
-
@lines << line
|
39
|
+
@lines << line
|
40
40
|
end
|
41
41
|
|
42
42
|
# Returns the request path
|
@@ -69,6 +69,11 @@ module Beaver
|
|
69
69
|
@ip ||= parse_ip
|
70
70
|
end
|
71
71
|
|
72
|
+
# Returns the responses format (html, json, etc)
|
73
|
+
def format
|
74
|
+
@format ||= parse_format
|
75
|
+
end
|
76
|
+
|
72
77
|
# Returns the number of milliseconds it took for the request to complete
|
73
78
|
def ms
|
74
79
|
@ms ||= parse_ms
|
@@ -131,6 +136,11 @@ module Beaver
|
|
131
136
|
BLANK_STR
|
132
137
|
end
|
133
138
|
|
139
|
+
# Parses and returns the respones format
|
140
|
+
def parse_format
|
141
|
+
:format
|
142
|
+
end
|
143
|
+
|
134
144
|
# Parses and returns the number of milliseconds it took for the request to complete
|
135
145
|
def parse_ms
|
136
146
|
0
|
metadata
CHANGED
@@ -3,10 +3,10 @@ name: beaver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
|
+
- 1
|
6
7
|
- 0
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.1
|
9
|
+
version: 1.0.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jordan Hollinger
|
@@ -14,11 +14,11 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-
|
17
|
+
date: 2011-12-03 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
21
|
-
description: A simple DSL
|
21
|
+
description: A simple DSL and command-line tool for discovering what people are up to in your Rails app
|
22
22
|
email: jordan@jordanhollinger.com
|
23
23
|
executables:
|
24
24
|
- beaver
|
@@ -67,6 +67,6 @@ rubyforge_project:
|
|
67
67
|
rubygems_version: 1.3.7
|
68
68
|
signing_key:
|
69
69
|
specification_version: 3
|
70
|
-
summary: Rails
|
70
|
+
summary: Rails log parser
|
71
71
|
test_files: []
|
72
72
|
|