request-log-analyzer 1.1.0 → 1.1.1
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 +4 -3
- data/bin/request-log-analyzer +4 -5
- data/lib/cli/command_line_arguments.rb +2 -2
- data/lib/request_log_analyzer.rb +28 -6
- data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +5 -1
- data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -3
- data/lib/request_log_analyzer/controller.rb +11 -16
- data/lib/request_log_analyzer/file_format.rb +71 -38
- data/lib/request_log_analyzer/file_format/merb.rb +32 -26
- data/lib/request_log_analyzer/file_format/rails.rb +73 -71
- data/lib/request_log_analyzer/file_format/rails_development.rb +93 -95
- data/lib/request_log_analyzer/filter.rb +38 -0
- data/lib/request_log_analyzer/filter/anonimize.rb +1 -1
- data/lib/request_log_analyzer/line_definition.rb +1 -1
- data/lib/request_log_analyzer/output.rb +6 -8
- data/lib/request_log_analyzer/output/fixed_width.rb +133 -117
- data/lib/request_log_analyzer/output/html.rb +138 -60
- data/lib/request_log_analyzer/request.rb +3 -1
- data/lib/request_log_analyzer/{source/base.rb → source.rb} +5 -0
- data/lib/request_log_analyzer/source/{log_file.rb → log_parser.rb} +15 -6
- data/lib/request_log_analyzer/tracker.rb +58 -0
- data/lib/request_log_analyzer/tracker/category.rb +7 -8
- data/lib/request_log_analyzer/tracker/duration.rb +15 -12
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +8 -8
- data/lib/request_log_analyzer/tracker/timespan.rb +10 -10
- data/spec/controller_spec.rb +5 -4
- data/spec/database_inserter_spec.rb +5 -8
- data/spec/file_format_spec.rb +2 -2
- data/spec/file_formats/spec_format.rb +2 -1
- data/spec/filter_spec.rb +0 -3
- data/spec/log_parser_spec.rb +6 -6
- data/spec/merb_format_spec.rb +38 -38
- data/spec/rails_format_spec.rb +2 -2
- data/spec/request_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -37
- data/tasks/github-gem.rake +2 -1
- metadata +7 -8
- data/lib/request_log_analyzer/filter/base.rb +0 -32
- data/lib/request_log_analyzer/log_parser.rb +0 -173
- data/lib/request_log_analyzer/tracker/base.rb +0 -54
data/README.rdoc
CHANGED
@@ -5,7 +5,7 @@ Merb to produce a performance report. Its purpose is to find what actions are be
|
|
5
5
|
|
6
6
|
* Analyzes Rails log files (all versions)
|
7
7
|
* Can combine multiple files (handy if you are using logrotate)
|
8
|
-
* Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and
|
8
|
+
* Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and statuses, Rails action cache statistics, etc.) (Sample output: http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
|
9
9
|
* Low memory footprint (server-safe)
|
10
10
|
* Fast
|
11
11
|
* MIT licensed
|
@@ -32,10 +32,11 @@ To analyze a log file and produce a performance report, run request-log-analyzer
|
|
32
32
|
|
33
33
|
$ request-log-analyzer log/production.log
|
34
34
|
|
35
|
-
For more details and available command line options, see the project's wiki:http://wiki.github.com/wvanbergen/request-log-analyzer/basic-usage
|
35
|
+
For more details and available command line options, see the project's wiki: http://wiki.github.com/wvanbergen/request-log-analyzer/basic-usage
|
36
36
|
|
37
37
|
== Additional information
|
38
38
|
|
39
39
|
* Project wiki at GitHub: http://wiki.github.com/wvanbergen/request-log-analyzer
|
40
|
+
* RDoc documentation: http://wvanbergen.github.com/request-log-analyzer
|
40
41
|
* wvanbergen's blog posts: http://techblog.floorplanner.com/tag/request-log-analyzer
|
41
|
-
*
|
42
|
+
* barttenbrinke's blog posts: http://movesonrails.com/articles/tag/analyzer
|
data/bin/request-log-analyzer
CHANGED
@@ -115,11 +115,10 @@ when :anonymize
|
|
115
115
|
require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
|
116
116
|
RequestLogAnalyzer::LogProcessor.build(:anonymize, arguments).run!
|
117
117
|
else
|
118
|
-
puts "Request
|
119
|
-
|
118
|
+
puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
|
119
|
+
puts "Website: http://github.com/wvanbergen/request-log-analyzer"
|
120
|
+
puts
|
121
|
+
|
120
122
|
# Run the request_log_analyzer!
|
121
123
|
RequestLogAnalyzer::Controller.build(arguments).run!
|
122
|
-
|
123
|
-
puts
|
124
|
-
puts "Thanks for using request-log-analyzer"
|
125
124
|
end
|
@@ -140,8 +140,8 @@ module CommandLine
|
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
143
|
-
OPTION_REGEXP = /^\-\-([A-z0-9-]+)$/;
|
144
|
-
ALIASES_REGEXP = /^\-([A-z0-9]+)$/
|
143
|
+
OPTION_REGEXP = /^\-\-([A-Za-z0-9-]+)$/;
|
144
|
+
ALIASES_REGEXP = /^\-([A-Aa-z0-9]+)$/
|
145
145
|
|
146
146
|
attr_reader :definition
|
147
147
|
attr_reader :tokens
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -1,14 +1,36 @@
|
|
1
1
|
require 'date'
|
2
2
|
require File.dirname(__FILE__) + '/cli/progressbar'
|
3
3
|
|
4
|
+
module RequestLogAnalyzer
|
5
|
+
|
6
|
+
VERSION = '1.1'
|
7
|
+
|
8
|
+
# Function to implement
|
9
|
+
def self.load_default_class_file(base, const)
|
10
|
+
path = to_underscore(base.to_s)
|
11
|
+
basename = to_underscore(const.to_s)
|
12
|
+
filename = "#{File.dirname(__FILE__)}/#{path}/#{basename}"
|
13
|
+
require filename
|
14
|
+
base.const_get(const)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
|
18
|
+
def self.to_underscore(str)
|
19
|
+
str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert a string/symbol in underscores (request_log_analyzer/controller) to camelcase (RequestLogAnalyzer::Controller)
|
23
|
+
def self.to_camelcase(str)
|
24
|
+
str.to_s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
4
28
|
require File.dirname(__FILE__) + '/request_log_analyzer/file_format'
|
5
29
|
require File.dirname(__FILE__) + '/request_log_analyzer/line_definition'
|
6
30
|
require File.dirname(__FILE__) + '/request_log_analyzer/request'
|
7
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/
|
8
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/
|
9
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/aggregator/summarizer'
|
10
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/filter/base'
|
31
|
+
require File.dirname(__FILE__) + '/request_log_analyzer/aggregator'
|
32
|
+
require File.dirname(__FILE__) + '/request_log_analyzer/filter'
|
11
33
|
require File.dirname(__FILE__) + '/request_log_analyzer/controller'
|
12
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/source
|
13
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/source/log_file'
|
34
|
+
require File.dirname(__FILE__) + '/request_log_analyzer/source'
|
14
35
|
require File.dirname(__FILE__) + '/request_log_analyzer/output'
|
36
|
+
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module RequestLogAnalyzer::Aggregator
|
2
|
-
|
2
|
+
|
3
|
+
def self.const_missing(const)
|
4
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
5
|
+
end
|
6
|
+
|
3
7
|
# The base class of an aggregator. This class provides the interface to which
|
4
8
|
# every aggregator should comply (by simply subclassing this class).
|
5
9
|
class Base
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../tracker
|
1
|
+
require File.dirname(__FILE__) + '/../tracker'
|
2
2
|
|
3
3
|
module RequestLogAnalyzer::Aggregator
|
4
4
|
|
@@ -33,8 +33,7 @@ module RequestLogAnalyzer::Aggregator
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def track(tracker_klass, options = {})
|
36
|
-
|
37
|
-
tracker_klass = RequestLogAnalyzer::Tracker.const_get(tracker_klass.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join('')) if tracker_klass.kind_of?(Symbol)
|
36
|
+
tracker_klass = RequestLogAnalyzer::Tracker.const_get(RequestLogAnalyzer::to_camelcase(tracker_klass)) if tracker_klass.kind_of?(Symbol)
|
38
37
|
@trackers << tracker_klass.new(options)
|
39
38
|
end
|
40
39
|
end
|
@@ -62,7 +62,7 @@ module RequestLogAnalyzer
|
|
62
62
|
options.store(:source_files, arguments.parameters)
|
63
63
|
end
|
64
64
|
|
65
|
-
controller = Controller.new(RequestLogAnalyzer::Source::
|
65
|
+
controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
|
66
66
|
|
67
67
|
options[:assume_correct_order] = arguments[:assume_correct_order]
|
68
68
|
|
@@ -147,12 +147,8 @@ module RequestLogAnalyzer
|
|
147
147
|
|
148
148
|
# Adds an aggregator to the controller. The aggregator will be called for every request
|
149
149
|
# that is parsed from the provided sources (see add_source)
|
150
|
-
def add_aggregator(agg)
|
151
|
-
if agg.kind_of?(Symbol)
|
152
|
-
require File.dirname(__FILE__) + "/aggregator/#{agg}"
|
153
|
-
agg = RequestLogAnalyzer::Aggregator.const_get(agg.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
|
154
|
-
end
|
155
|
-
|
150
|
+
def add_aggregator(agg)
|
151
|
+
agg = RequestLogAnalyzer::Aggregator.const_get(RequestLogAnalyzer::to_camelcase(agg)) if agg.kind_of?(Symbol)
|
156
152
|
@aggregators << agg.new(@source, @options)
|
157
153
|
end
|
158
154
|
|
@@ -160,11 +156,7 @@ module RequestLogAnalyzer
|
|
160
156
|
|
161
157
|
# Adds a request filter to the controller.
|
162
158
|
def add_filter(filter, filter_options = {})
|
163
|
-
if filter.kind_of?(Symbol)
|
164
|
-
require File.dirname(__FILE__) + "/filter/#{filter}"
|
165
|
-
filter = RequestLogAnalyzer::Filter.const_get(filter.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
|
166
|
-
end
|
167
|
-
|
159
|
+
filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
|
168
160
|
@filters << filter.new(file_format, @options.merge(filter_options))
|
169
161
|
end
|
170
162
|
|
@@ -178,13 +170,11 @@ module RequestLogAnalyzer
|
|
178
170
|
# 6. Finalize Source
|
179
171
|
def run!
|
180
172
|
|
181
|
-
|
182
|
-
|
183
173
|
@filters.each { |filter| filter.prepare }
|
184
174
|
@aggregators.each { |agg| agg.prepare }
|
185
175
|
|
186
176
|
begin
|
187
|
-
@source.
|
177
|
+
@source.each_request do |request|
|
188
178
|
@filters.each { |filter| request = filter.filter(request) }
|
189
179
|
@aggregators.each { |agg| agg.aggregate(request) } if request
|
190
180
|
end
|
@@ -201,7 +191,12 @@ module RequestLogAnalyzer
|
|
201
191
|
|
202
192
|
@source.finalize
|
203
193
|
|
204
|
-
|
194
|
+
if @output.io.kind_of?(File)
|
195
|
+
puts
|
196
|
+
puts "Report written to: " + File.expand_path(@output.io.path)
|
197
|
+
puts "Thanks for using request-log-analyzer!"
|
198
|
+
@output.io.close
|
199
|
+
end
|
205
200
|
end
|
206
201
|
|
207
202
|
end
|
@@ -1,29 +1,80 @@
|
|
1
|
-
module RequestLogAnalyzer
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
2
2
|
|
3
|
-
|
3
|
+
|
4
|
+
def self.const_missing(const)
|
5
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Loads a FileFormat::Base subclass instance.
|
9
|
+
# You can provide:
|
10
|
+
# * A FileFormat instance (which will return itself)
|
11
|
+
# * A FileFormat class (of which an imstance will be returned)
|
12
|
+
# * A filename (from which the FileFormat class is loaded)
|
13
|
+
# * A symbol of a built-in file format (e.g. :rails)
|
14
|
+
def self.load(file_format)
|
15
|
+
klass = nil
|
16
|
+
if file_format.kind_of?(RequestLogAnalyzer::FileFormat::Base)
|
17
|
+
# this already is a file format! return itself
|
18
|
+
return @current_file_format = file_format
|
19
|
+
|
20
|
+
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
21
|
+
# a usable class is provided. Use this format class.
|
22
|
+
klass = file_format
|
23
|
+
|
24
|
+
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
25
|
+
# load a format from a ruby file
|
26
|
+
require file_format
|
27
|
+
klass = Object.const_get(RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb')))
|
28
|
+
|
29
|
+
else
|
30
|
+
# load a provided file format
|
31
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
32
|
+
end
|
4
33
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
34
|
+
raise if klass.nil?
|
35
|
+
@current_file_format = klass.new # return an instance of the class
|
36
|
+
end
|
37
|
+
|
38
|
+
# Makes classes aware of a file format by registering the file_format variable
|
39
|
+
module Awareness
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.send(:attr_reader, :file_format)
|
15
43
|
end
|
44
|
+
|
45
|
+
def register_file_format(format)
|
46
|
+
@file_format = format
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Base class for all log file format definitions. This class provides functions for subclasses to
|
51
|
+
# define their LineDefinitions and to define a summary report.
|
52
|
+
#
|
53
|
+
# A subclass of this class is instantiated when request-log-analyzer is started and this instance
|
54
|
+
# is shared with all components of the application so they can act on the specifics of the format
|
55
|
+
class Base
|
16
56
|
|
57
|
+
# Registers the line definer instance for a subclass.
|
17
58
|
def self.inherited(subclass)
|
18
59
|
subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
|
19
60
|
subclass.class_eval { class << self; attr_accessor :line_definer; end }
|
20
|
-
subclass.class_eval { class << self; attr_accessor :report_definer; end }
|
61
|
+
subclass.class_eval { class << self; attr_accessor :report_definer; end }
|
62
|
+
|
63
|
+
unless subclass.const_defined?('Request')
|
64
|
+
subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request))
|
65
|
+
end
|
21
66
|
end
|
22
67
|
|
68
|
+
# Specifies a single line defintions.
|
23
69
|
def self.line_definition(name, &block)
|
24
70
|
@line_definer.send(name, &block)
|
25
71
|
end
|
26
72
|
|
73
|
+
def create_request(*hashes)
|
74
|
+
self.class::Request.create(self, *hashes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Specifies multiple line definitions at once using a block
|
27
78
|
def self.format_definition(&block)
|
28
79
|
if block_given?
|
29
80
|
yield(@line_definer)
|
@@ -32,48 +83,30 @@ module RequestLogAnalyzer
|
|
32
83
|
end
|
33
84
|
end
|
34
85
|
|
86
|
+
# Specifies the summary report using a block.
|
35
87
|
def self.report(&block)
|
36
88
|
@report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
|
37
89
|
yield(@report_definer)
|
38
90
|
end
|
39
91
|
|
40
|
-
|
41
|
-
if file_format.kind_of?(RequestLogAnalyzer::FileFormat)
|
42
|
-
# this already is a file format! return itself
|
43
|
-
return file_format
|
44
|
-
|
45
|
-
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat)
|
46
|
-
klass = file_format
|
47
|
-
|
48
|
-
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
49
|
-
# load a format from a ruby file
|
50
|
-
require file_format
|
51
|
-
klass_name = File.basename(file_format, '.rb').split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join('')
|
52
|
-
klass = Object.const_get(klass_name)
|
53
|
-
|
54
|
-
elsif File.exist?("#{File.dirname(__FILE__)}/file_format/#{file_format}.rb")
|
55
|
-
# load a provided file format
|
56
|
-
require "#{File.dirname(__FILE__)}/file_format/#{file_format}"
|
57
|
-
klass_name = file_format.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join('')
|
58
|
-
klass = RequestLogAnalyzer::FileFormat.const_get(klass_name)
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
klass.new # return an instance of the class
|
63
|
-
end
|
64
|
-
|
92
|
+
# Returns all line definitions
|
65
93
|
def line_definitions
|
66
94
|
@line_definitions ||= self.class.line_definer.line_definitions
|
67
95
|
end
|
68
96
|
|
97
|
+
# Returns all the defined trackers for the summary report.
|
69
98
|
def report_trackers
|
70
99
|
self.class.instance_variable_get(:@report_definer).trackers rescue []
|
71
100
|
end
|
72
101
|
|
102
|
+
# Checks whether the line definitions form a valid language.
|
103
|
+
# A file format should have at least a header and a footer line type
|
73
104
|
def valid?
|
74
105
|
line_definitions.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
|
75
106
|
end
|
76
107
|
|
108
|
+
# Function that a file format con implement to monkey patch the environment.
|
109
|
+
# * <tt>controller</tt> The environment is provided as a controller instance
|
77
110
|
def setup_environment(controller)
|
78
111
|
|
79
112
|
end
|
@@ -1,33 +1,39 @@
|
|
1
|
-
module RequestLogAnalyzer::FileFormat
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
2
|
+
|
3
|
+
class Merb < Base
|
2
4
|
|
3
|
-
LINE_DEFINITIONS = {
|
4
|
-
|
5
5
|
# ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
|
6
|
-
:started
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
line_definition :started do |line|
|
7
|
+
line.header = true
|
8
|
+
line.teaser = /Started/
|
9
|
+
line.regexp = /Started request handling\:\ (.+)/
|
10
|
+
line.captures << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly }
|
11
|
+
end
|
12
12
|
|
13
13
|
# ~ Params: {"action"=>"create", "controller"=>"session"}
|
14
14
|
# ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
|
15
|
-
:params
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
line_definition :params do |line|
|
16
|
+
line.teaser = /Params/
|
17
|
+
line.regexp = /Params\:\ \{(.+)\}/
|
18
|
+
line.captures << { :name => :raw_hash, :type => :string}
|
19
|
+
end
|
20
|
+
|
21
21
|
# ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
|
22
|
-
:completed
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
{ :name => :after_filters_time, :type => :sec, :anonymize => :slightly }
|
28
|
-
{ :name => :before_filters_time, :type => :sec, :anonymize => :slightly }
|
29
|
-
{ :name => :action_time, :type => :sec, :anonymize => :slightly }
|
30
|
-
|
31
|
-
}
|
22
|
+
line_definition :completed do |line|
|
23
|
+
line.footer = true
|
24
|
+
line.teaser = /\{:dispatch_time/
|
25
|
+
line.regexp = /\{\:dispatch_time=>(\d+\.\d+(?:e-?\d+)?), (?:\:after_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?(?:\:before_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?\:action_time=>(\d+\.\d+(?:e-?\d+)?)\}/
|
26
|
+
line.captures << { :name => :dispatch_time, :type => :sec, :anonymize => :slightly } \
|
27
|
+
<< { :name => :after_filters_time, :type => :sec, :anonymize => :slightly } \
|
28
|
+
<< { :name => :before_filters_time, :type => :sec, :anonymize => :slightly } \
|
29
|
+
<< { :name => :action_time, :type => :sec, :anonymize => :slightly }
|
30
|
+
end
|
32
31
|
|
33
|
-
|
32
|
+
|
33
|
+
report do |analyze|
|
34
|
+
# FIX ME
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -1,90 +1,92 @@
|
|
1
|
-
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
class Rails < Base
|
4
|
+
|
5
|
+
# Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
|
6
|
+
line_definition :processing do |line|
|
7
|
+
line.header = true # this line is the first log line for a request
|
8
|
+
line.teaser = /Processing /
|
9
|
+
line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
|
10
|
+
line.captures << { :name => :controller, :type => :string } \
|
11
|
+
<< { :name => :action, :type => :string } \
|
12
|
+
<< { :name => :format, :type => :string } \
|
13
|
+
<< { :name => :ip, :type => :string, :anonymize => :ip } \
|
14
|
+
<< { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
|
15
|
+
<< { :name => :method, :type => :string }
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# Filter chain halted as [#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>] rendered_or_redirected.
|
19
|
+
line_definition :cache_hit do |line|
|
20
|
+
line.regexp = /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter:.+>\] rendered_or_redirected/
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
# RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
|
24
|
+
line_definition :failed do |line|
|
25
|
+
line.footer = true
|
26
|
+
line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
|
27
|
+
line.captures << { :name => :error, :type => :string } \
|
28
|
+
<< { :name => :message, :type => :string } \
|
29
|
+
<< { :name => :line, :type => :integer } \
|
30
|
+
<< { :name => :file, :type => :string } \
|
31
|
+
<< { :name => :stack_trace, :type => :string, :anonymize => true }
|
32
|
+
end
|
31
33
|
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
# Rails < 2.1 completed line example
|
36
|
+
# Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
|
37
|
+
RAILS_21_COMPLETED = /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (?:\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(?:\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
# Rails > 2.1 completed line example
|
40
|
+
# Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
|
41
|
+
RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
# The completed line uses a kind of hack to ensure that both old style logs and new style logs
|
44
|
+
# are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
|
45
|
+
# but the line contains exactly the same information.
|
46
|
+
line_definition :completed do |line|
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
line.footer = true
|
49
|
+
line.teaser = /Completed in /
|
50
|
+
line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
|
53
|
+
<< { :name => :view, :type => :sec, :anonymize => :slightly } \
|
54
|
+
<< { :name => :db, :type => :sec, :anonymize => :slightly } \
|
55
|
+
<< { :name => :status, :type => :integer } \
|
56
|
+
<< { :name => :url, :type => :string, :anonymize => :url } # Old variant
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
|
59
|
+
<< { :name => :view, :type => :msec, :anonymize => :slightly } \
|
60
|
+
<< { :name => :db, :type => :msec, :anonymize => :slightly } \
|
61
|
+
<< { :name => :status, :type => :integer} \
|
62
|
+
<< { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
|
63
|
+
end
|
62
64
|
|
63
65
|
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
REQUEST_CATEGORIZER = Proc.new do |request|
|
68
|
+
format = request[:format] || 'html'
|
69
|
+
"#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
|
70
|
+
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
report do |analyze|
|
73
|
+
analyze.timespan :line_type => :processing
|
74
|
+
analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
|
75
|
+
analyze.category :method, :title => 'HTTP methods'
|
76
|
+
analyze.category :status, :title => 'HTTP statuses returned'
|
77
|
+
analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
|
80
|
+
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
|
81
|
+
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
+
analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
|
84
|
+
:if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
analyze.hourly_spread :line_type => :processing
|
87
|
+
analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
|
88
|
+
end
|
88
89
|
|
90
|
+
end
|
89
91
|
|
90
|
-
end
|
92
|
+
end
|