beaver 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +26 -28
- data/bin/beaver +54 -23
- data/lib/beaver.rb +2 -0
- data/lib/beaver/beaver.rb +34 -30
- data/lib/beaver/dam.rb +117 -74
- data/lib/beaver/parsers/http.rb +109 -0
- data/lib/beaver/parsers/rails.rb +66 -89
- data/lib/beaver/request.rb +30 -121
- data/lib/beaver/utils.rb +59 -23
- data/lib/beaver/version.rb +4 -0
- metadata +5 -3
@@ -0,0 +1,109 @@
|
|
1
|
+
module Beaver
|
2
|
+
module Parsers
|
3
|
+
# Parser for HTTP Common Log entries. See the Request class for more log entry attributes.
|
4
|
+
class HTTP < Request
|
5
|
+
# The Combined Log Format
|
6
|
+
FORMAT = '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"' # :nodoc:
|
7
|
+
# The Combined Log Format as an array of symbols
|
8
|
+
FORMAT_SYMBOLS = FORMAT.split(' ').map(&:to_sym) # :nodoc:
|
9
|
+
FHOST, FID, FUSER, FTIME, FREQUEST, FSTATUS, FSIZE, FREFERER, FUA = FORMAT_SYMBOLS # :nodoc:
|
10
|
+
|
11
|
+
# Regex matchers keyed by opening quotes, etc.
|
12
|
+
MATCHERS = {'[' => /^\[([^\]]+)\] ?/, '"' => /^"([^"]+)" ?/} # :nodoc:
|
13
|
+
MATCHERS.default = /^([^ ]+) ?/
|
14
|
+
|
15
|
+
# Matches an HTTP Log entry
|
16
|
+
REGEX_MATCH = %r{\[[0-9]{2}/\w+/[0-9:]+ } # :nodoc:
|
17
|
+
# Matches the request method, url and params
|
18
|
+
REGEX_REQUEST = /^([A-Z]+) (\/[^\?]+)\??(.*) HTTP\/1/ # :nodoc:
|
19
|
+
# Matches the request date
|
20
|
+
REGEX_DATE = %r{^([0-9]{2}/[a-z]+/[0-9]+)}i # :nodoc:
|
21
|
+
|
22
|
+
# Partially parse the request
|
23
|
+
def initialize(data)
|
24
|
+
super
|
25
|
+
pre_parse!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the HTTP method as a symbol
|
29
|
+
def method
|
30
|
+
parse_request! if @method.nil?
|
31
|
+
@method
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the request path
|
35
|
+
def path
|
36
|
+
parse_request! if @path.nil?
|
37
|
+
@path
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the url query string
|
41
|
+
def params_str
|
42
|
+
parse_request! if @params_str.nil?
|
43
|
+
@params_str
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the response size (in bytes)
|
47
|
+
def size
|
48
|
+
@size ||= @request[FSIZE].to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the REFERER [sic]
|
52
|
+
def referer
|
53
|
+
@request[FREFERER]
|
54
|
+
end
|
55
|
+
alias_method :referrer, :referer
|
56
|
+
|
57
|
+
# Returns the user agent string
|
58
|
+
def user_agent
|
59
|
+
@request[FUA]
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Parse and cache the request method, URL and any params
|
65
|
+
def parse_request!
|
66
|
+
REGEX_REQUEST.match(@request[FREQUEST])
|
67
|
+
@method = $1 ? $1.downcase.to_sym : :unknown
|
68
|
+
@path = $2 || BLANK_STR
|
69
|
+
@params_str = $3 || BLANK_STR
|
70
|
+
end
|
71
|
+
|
72
|
+
# Parses and returns the response status
|
73
|
+
def parse_status
|
74
|
+
@request[FSTATUS].to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
# Parses and returns the request's IP address
|
78
|
+
def parse_ip
|
79
|
+
@request[FHOST] || BLANK_STR
|
80
|
+
end
|
81
|
+
|
82
|
+
# Parses and returns the time at which the request was made
|
83
|
+
def parse_date
|
84
|
+
REGEX_DATE.match(@request[FTIME]) ? Date.parse($1) : nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parses and returns the time at which the request was made
|
88
|
+
def parse_time
|
89
|
+
time_str = @request[FTIME].gsub(/([0-9]{4}):([0-9]{2})/, "#{$1} #{$2}")
|
90
|
+
Time.parse(time_str) rescue nil
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Break the log line down into sections, store them in @request as a Hash.
|
96
|
+
# Each section may be further parsed on-demand.
|
97
|
+
# XXX This is probably really slow
|
98
|
+
def pre_parse!
|
99
|
+
line = @data.clone
|
100
|
+
request = {}
|
101
|
+
for f in FORMAT_SYMBOLS
|
102
|
+
line.slice!(MATCHERS[line[0,1]])
|
103
|
+
request[f] = $1
|
104
|
+
end
|
105
|
+
@request = request
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/beaver/parsers/rails.rb
CHANGED
@@ -1,131 +1,108 @@
|
|
1
1
|
module Beaver
|
2
2
|
module Parsers
|
3
|
-
#
|
3
|
+
# Parser for Rails log entries. See the Request class for more log entry attributes.
|
4
4
|
class Rails < Request
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
REGEX_TAGS = /^(\[.+\] )+/
|
19
|
-
REGEX_TAG = /\[([^\]]+)\] /
|
5
|
+
REGEX_MATCH = /^Started [A-Z]+/ # :nodoc:
|
6
|
+
REGEX_METHOD = /^Started ([A-Z]+)/ # :nodoc:
|
7
|
+
REGEX_METHOD_OVERRIDE = /"_method"=>"([A-Z]+)"/i # :nodoc:
|
8
|
+
REGEX_CONTROLLER = /Processing by (\w+Controller)#/ # :nodoc:
|
9
|
+
REGEX_ACTION = /Processing by \w+Controller#(\w+) as/ # :nodoc:
|
10
|
+
REGEX_COMPLETED = /Completed (\d+)/ # :nodoc:
|
11
|
+
REGEX_PATH = /^Started \w{3,4} "([^"]+)"/ # :nodoc:
|
12
|
+
REGEX_PARAMS_STR = / Parameters: (\{.+\})$/ # :nodoc:
|
13
|
+
REGEX_IP = /" for ([a-zA-Z0-9:.]+) at / # :nodoc:
|
14
|
+
REGEX_FORMAT = /Processing by .+ as (\w+)$/ # :nodoc:
|
15
|
+
REGEX_MS = / in (\d+)ms/ # :nodoc:
|
16
|
+
REGEX_TAGS = /^(\[.+\] )+/ # :nodoc:
|
17
|
+
REGEX_TAG = /\[([^\]]+)\] / # :nodoc:
|
20
18
|
# Depending on the version of Rails, the time format may be wildly different
|
21
|
-
REGEX_TIME = / at ([a-z0-9:\+\- ]+)$/i
|
19
|
+
REGEX_TIME = / at ([a-z0-9:\+\- ]+)$/i # :nodoc:
|
22
20
|
|
23
|
-
# Returns true if the
|
24
|
-
def
|
25
|
-
|
21
|
+
# Returns true if/when we have the final line of the multi-line Rails request
|
22
|
+
def completed?
|
23
|
+
REGEX_COMPLETED =~ @data
|
26
24
|
end
|
27
25
|
|
28
|
-
# Returns
|
29
|
-
def
|
26
|
+
# Returns the class name of the Rails controller that handled the request
|
27
|
+
def controller
|
28
|
+
@controller ||= REGEX_CONTROLLER.match(@data) ? $1 : BLANK_STR
|
29
|
+
end
|
30
30
|
|
31
|
-
# Returns
|
32
|
-
def
|
33
|
-
|
31
|
+
# Returns the class name of the Rails controller action that handled the request
|
32
|
+
def action
|
33
|
+
@action ||= REGEX_ACTION.match(@data) ? $1.to_sym : :unknown
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the responses format (html, json, etc)
|
37
|
+
def format
|
38
|
+
@format ||= REGEX_FORMAT.match(@data) ? $1.downcase.to_sym : :unknown
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the number of milliseconds it took for the request to complete
|
42
|
+
def ms
|
43
|
+
@ms ||= REGEX_MS.match(@data) ? $1.to_i : 0
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the request parameters as a Hash (this is more expensive than Request#params_str)
|
47
|
+
def params
|
48
|
+
@params ||= params_str.empty? ? BLANK_HASH : Utils.str_to_hash(params_str)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the tags string associated with the request (e.g. "[tag1] [tag2] ")
|
52
|
+
def tags_str
|
53
|
+
@tags_str ||= REGEX_TAGS.match(@data) ? $1 : nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an array of tags associated with the request
|
57
|
+
def tags
|
58
|
+
@tags ||= if t = tags_str
|
59
|
+
tags = t.scan(REGEX_TAG)
|
60
|
+
tags.flatten!
|
61
|
+
tags.uniq!
|
62
|
+
tags.map! &:downcase
|
63
|
+
tags
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
34
67
|
end
|
35
68
|
|
36
69
|
protected
|
37
70
|
|
38
71
|
# Parses and returns the request path
|
39
72
|
def parse_path
|
40
|
-
|
41
|
-
m ? m.captures.first : BLANK_STR
|
73
|
+
REGEX_PATH.match(@data) ? $1 : BLANK_STR
|
42
74
|
end
|
43
75
|
|
44
76
|
# Parses and returns the request method
|
45
77
|
def parse_method
|
46
|
-
m = REGEX_METHOD_OVERRIDE.match(@
|
47
|
-
m = REGEX_METHOD.match(@
|
78
|
+
m = REGEX_METHOD_OVERRIDE.match(@data)
|
79
|
+
m = REGEX_METHOD.match(@data) if m.nil?
|
48
80
|
m ? m.captures.first.downcase.to_sym : :unknown
|
49
81
|
end
|
50
82
|
|
51
|
-
# Parses the name of the Rails controller which handled the request
|
52
|
-
def parse_controller
|
53
|
-
c = REGEX_CONTROLLER.match(@lines) if c.nil?
|
54
|
-
c ? c.captures.first : BLANK_STR
|
55
|
-
end
|
56
|
-
|
57
|
-
# Parses the name of the Rails controller action which handled the request
|
58
|
-
def parse_action
|
59
|
-
a = REGEX_ACTION.match(@lines) if a.nil?
|
60
|
-
a ? a.captures.first.to_sym : :unknown
|
61
|
-
end
|
62
|
-
|
63
83
|
# Parses and returns the response status
|
64
84
|
def parse_status
|
65
|
-
|
66
|
-
m ? m.captures.first.to_i : 0
|
85
|
+
REGEX_COMPLETED.match(@data) ? $1.to_i : 0
|
67
86
|
end
|
68
87
|
|
69
88
|
# Parses and returns the request parameters as a String
|
70
89
|
def parse_params_str
|
71
|
-
|
72
|
-
m ? m.captures.first : BLANK_STR
|
73
|
-
end
|
74
|
-
|
75
|
-
# Parses and returns the request parameters as a Hash (relatively expensive)
|
76
|
-
def parse_params
|
77
|
-
p = params_str
|
78
|
-
p.empty? ? BLANK_HASH : Utils.str_to_hash(p)
|
90
|
+
REGEX_PARAMS_STR.match(@data) ? $1 : BLANK_STR
|
79
91
|
end
|
80
92
|
|
81
93
|
# Parses and returns the request's IP address
|
82
94
|
def parse_ip
|
83
|
-
|
84
|
-
m ? m.captures.first : BLANK_STR
|
85
|
-
end
|
86
|
-
|
87
|
-
# Parses and returns the respones format
|
88
|
-
def parse_format
|
89
|
-
m = REGEX_FORMAT.match(@lines)
|
90
|
-
m ? m.captures.first.to_s.downcase.to_sym : :unknown
|
91
|
-
end
|
92
|
-
|
93
|
-
# Parses and returns the number of milliseconds it took for the request to complete
|
94
|
-
def parse_ms
|
95
|
-
m = REGEX_MS.match(@lines)
|
96
|
-
m ? m.captures.first.to_i : 0
|
95
|
+
REGEX_IP.match(@data) ? $1 : BLANK_STR
|
97
96
|
end
|
98
97
|
|
99
98
|
# Parses and returns the time at which the request was made
|
100
99
|
def parse_date
|
101
|
-
|
102
|
-
m ? Date.parse(m.captures.first) : nil
|
100
|
+
REGEX_TIME.match(@data) ? Date.parse($1) : nil
|
103
101
|
end
|
104
102
|
|
105
103
|
# Parses and returns the time at which the request was made
|
106
104
|
def parse_time
|
107
|
-
|
108
|
-
m ? Time.parse(m.captures.first) : nil
|
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
|
105
|
+
REGEX_TIME.match(@data) ? Time.parse($1) : nil
|
129
106
|
end
|
130
107
|
end
|
131
108
|
end
|
data/lib/beaver/request.rb
CHANGED
@@ -2,42 +2,43 @@ require 'date'
|
|
2
2
|
require 'time'
|
3
3
|
|
4
4
|
module Beaver
|
5
|
-
# Represents a single request from the logs.
|
5
|
+
# Represents a single request from the logs. The base class for Beaver::Parsers::Rails and Beaver::Parsers::HTTP.
|
6
|
+
# Attributes common to both are defined here.
|
6
7
|
class Request
|
7
|
-
BLANK_STR = ''
|
8
|
-
BLANK_HASH = {}
|
8
|
+
BLANK_STR = '' # :nodoc:
|
9
|
+
BLANK_HASH = {} # :nodoc:
|
9
10
|
|
10
11
|
# Holds the Parser classes used to parser requests
|
11
12
|
@types = []
|
12
13
|
|
13
|
-
# Add a child Request parser
|
14
|
-
def self
|
15
|
-
@types <<
|
14
|
+
# Add a child Request parser
|
15
|
+
def self.inherited(klass)
|
16
|
+
@types << klass
|
16
17
|
end
|
17
18
|
|
18
|
-
# Returns
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
# Returns a new Request object for the given log line, or nil if one cannot be found.
|
20
|
+
def self.for(line)
|
21
|
+
klass = @types.detect { |t| t.match? line }
|
22
|
+
klass ? klass.new(line) : nil
|
22
23
|
end
|
23
24
|
|
24
|
-
#
|
25
|
-
def
|
26
|
-
|
27
|
-
@final = false
|
25
|
+
# Returns true if the given line look like a request of this class
|
26
|
+
def self.match?(line)
|
27
|
+
self::REGEX_MATCH =~ line rescue false
|
28
28
|
end
|
29
29
|
|
30
|
-
#
|
31
|
-
def
|
30
|
+
# Accepts a String of log data, presumably ones which belong to a single request.
|
31
|
+
def initialize(data=nil)
|
32
|
+
@data = data || ''
|
33
|
+
@final = false
|
34
|
+
end
|
32
35
|
|
33
|
-
# Returns
|
34
|
-
def
|
35
|
-
# Returns true if this is a "bad" request.
|
36
|
-
def bad?; not good?; end
|
36
|
+
# Returns the log data that make up this Request.
|
37
|
+
def to_s; @data; end
|
37
38
|
|
38
39
|
# Append a log line
|
39
40
|
def <<(line)
|
40
|
-
@
|
41
|
+
@data << line
|
41
42
|
end
|
42
43
|
|
43
44
|
# Returns the request path
|
@@ -50,16 +51,6 @@ module Beaver
|
|
50
51
|
@method ||= parse_method
|
51
52
|
end
|
52
53
|
|
53
|
-
# Returns the class name of the Rails controller that handled the request
|
54
|
-
def controller
|
55
|
-
@controller ||= parse_controller
|
56
|
-
end
|
57
|
-
|
58
|
-
# Returns the class name of the Rails controller action that handled the request
|
59
|
-
def action
|
60
|
-
@action ||= parse_action
|
61
|
-
end
|
62
|
-
|
63
54
|
# Returns the response status
|
64
55
|
def status
|
65
56
|
@status ||= parse_status
|
@@ -70,26 +61,11 @@ module Beaver
|
|
70
61
|
@params_str ||= parse_params_str
|
71
62
|
end
|
72
63
|
|
73
|
-
# Returns the request parameters as a Hash (this is more expensive than Request#params_str)
|
74
|
-
def params
|
75
|
-
@params ||= parse_params
|
76
|
-
end
|
77
|
-
|
78
64
|
# Returns the IP address of the request
|
79
65
|
def ip
|
80
66
|
@ip ||= parse_ip
|
81
67
|
end
|
82
68
|
|
83
|
-
# Returns the responses format (html, json, etc)
|
84
|
-
def format
|
85
|
-
@format ||= parse_format
|
86
|
-
end
|
87
|
-
|
88
|
-
# Returns the number of milliseconds it took for the request to complete
|
89
|
-
def ms
|
90
|
-
@ms ||= parse_ms
|
91
|
-
end
|
92
|
-
|
93
69
|
# Returns the date on which the request was made
|
94
70
|
def date
|
95
71
|
@date ||= parse_date
|
@@ -100,16 +76,6 @@ module Beaver
|
|
100
76
|
@time ||= parse_time
|
101
77
|
end
|
102
78
|
|
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
|
-
|
113
79
|
# When called inside of a Beaver::Dam#hit block, this Request will *not* be matched.
|
114
80
|
def skip!
|
115
81
|
throw :skip
|
@@ -126,86 +92,29 @@ module Beaver
|
|
126
92
|
end
|
127
93
|
|
128
94
|
# Returns true if the request has all the information it needs to be properly parsed
|
129
|
-
def completed
|
130
|
-
true
|
131
|
-
end
|
95
|
+
def completed?; true; end
|
132
96
|
|
133
97
|
protected
|
134
98
|
|
135
99
|
# Parses and returns the request path
|
136
|
-
def parse_path
|
137
|
-
BLANK_STR
|
138
|
-
end
|
100
|
+
def parse_path; BLANK_STR; end
|
139
101
|
|
140
102
|
# Parses and returns the request method
|
141
|
-
def parse_method
|
142
|
-
:unknown
|
143
|
-
end
|
144
|
-
|
145
|
-
# Parses the name of the Rails controller which handled the request
|
146
|
-
def parse_controller
|
147
|
-
BLANK_STR
|
148
|
-
end
|
149
|
-
|
150
|
-
# Parses the name of the Rails controller action which handled the request
|
151
|
-
def parse_action
|
152
|
-
:unknown
|
153
|
-
end
|
103
|
+
def parse_method; :unknown; end
|
154
104
|
|
155
105
|
# Parses and returns the response status
|
156
|
-
def parse_status
|
157
|
-
0
|
158
|
-
end
|
106
|
+
def parse_status; 0; end
|
159
107
|
|
160
108
|
# Parses and returns the request params as a String
|
161
|
-
def parse_params_str
|
162
|
-
BLANK_STR
|
163
|
-
end
|
164
|
-
|
165
|
-
# Parses and returns the request params as a Hash
|
166
|
-
def parse_params
|
167
|
-
BLANK_HASH
|
168
|
-
end
|
109
|
+
def parse_params_str; BLANK_STR; end
|
169
110
|
|
170
111
|
# Parses and returns the request IP address
|
171
|
-
def parse_ip
|
172
|
-
BLANK_STR
|
173
|
-
end
|
174
|
-
|
175
|
-
# Parses and returns the respones format
|
176
|
-
def parse_format
|
177
|
-
:unknown
|
178
|
-
end
|
179
|
-
|
180
|
-
# Parses and returns the number of milliseconds it took for the request to complete
|
181
|
-
def parse_ms
|
182
|
-
0
|
183
|
-
end
|
112
|
+
def parse_ip; BLANK_STR; end
|
184
113
|
|
185
114
|
# Parses and returns the date on which the request was made
|
186
|
-
def parse_date
|
187
|
-
nil
|
188
|
-
end
|
115
|
+
def parse_date; nil; end
|
189
116
|
|
190
117
|
# Parses and returns the time at which the request was made
|
191
|
-
def parse_time
|
192
|
-
nil
|
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
|
204
|
-
end
|
205
|
-
|
206
|
-
# Represents a BadRequest that no parser could figure out.
|
207
|
-
class BadRequest < Request
|
208
|
-
# Returns false, always
|
209
|
-
def good?; false; end
|
118
|
+
def parse_time; nil; end
|
210
119
|
end
|
211
120
|
end
|