beaver 1.2.0 → 1.3.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 +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
|