beaver 0.0.1 → 1.0.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 +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
|
|