beaver 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,131 +1,108 @@
1
1
  module Beaver
2
2
  module Parsers
3
- # This appears to work with Rails 3 logs
3
+ # Parser for Rails log entries. See the Request class for more log entry attributes.
4
4
  class Rails < Request
5
- # Tell the Request class to use this parser to parse logs
6
- Request << self
7
-
8
- REGEX_METHOD = /^Started ([A-Z]+)/
9
- REGEX_METHOD_OVERRIDE = /"_method"=>"([A-Z]+)"/i
10
- REGEX_CONTROLLER = /Processing by (\w+Controller)#/
11
- REGEX_ACTION = /Processing by \w+Controller#(\w+) as/
12
- REGEX_COMPLETED = /Completed (\d+)/
13
- REGEX_PATH = /^Started \w{3,4} "([^"]+)"/
14
- REGEX_PARAMS_STR = / Parameters: (\{.+\})$/
15
- REGEX_IP = /" for (\d+[\d.]+) at /
16
- REGEX_FORMAT = /Processing by .+ as (\w+)$/
17
- REGEX_MS = / in (\d+)ms/
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 given lines look like a Rails request
24
- def self.match?(lines)
25
- REGEX_METHOD =~ lines
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 true, always
29
- def valid?; true; end
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 true if/when we have the final line of the multi-line Rails request
32
- def completed?
33
- REGEX_COMPLETED =~ @lines
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
- m = REGEX_PATH.match(@lines)
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(@lines)
47
- m = REGEX_METHOD.match(@lines) if m.nil?
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
- m = REGEX_COMPLETED.match(@lines)
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
- m = REGEX_PARAMS_STR.match(@lines)
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
- m = REGEX_IP.match(@lines)
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
- m = REGEX_TIME.match(@lines)
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
- m = REGEX_TIME.match(@lines)
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
@@ -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.<<(type)
15
- @types << type
14
+ # Add a child Request parser
15
+ def self.inherited(klass)
16
+ @types << klass
16
17
  end
17
18
 
18
- # Returns the correct Request parser class for the given log lines. If one cannot be found,
19
- # the BadRequest class is returned, which the caller will want to ignore.
20
- def self.for(lines)
21
- @types.select { |t| t.match? lines }.first || BadRequest
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
- # Accepts a String of log lines, presumably ones which belong to a single request.
25
- def initialize(lines=nil)
26
- @lines = lines || ''
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
- # Returns the log lines that make up this Request.
31
- def to_s; @lines; end
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 true if this is a "good" request.
34
- def good?; true; end
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
- @lines << line
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