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.
@@ -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