request-log-analyzer 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/DESIGN +14 -0
  2. data/HACKING +7 -0
  3. data/LICENSE +20 -0
  4. data/README.textile +36 -0
  5. data/Rakefile +5 -0
  6. data/bin/request-log-analyzer +123 -0
  7. data/lib/cli/bashcolorizer.rb +60 -0
  8. data/lib/cli/command_line_arguments.rb +301 -0
  9. data/lib/cli/progressbar.rb +236 -0
  10. data/lib/request_log_analyzer.rb +14 -0
  11. data/lib/request_log_analyzer/aggregator/base.rb +45 -0
  12. data/lib/request_log_analyzer/aggregator/database.rb +148 -0
  13. data/lib/request_log_analyzer/aggregator/echo.rb +25 -0
  14. data/lib/request_log_analyzer/aggregator/summarizer.rb +116 -0
  15. data/lib/request_log_analyzer/controller.rb +201 -0
  16. data/lib/request_log_analyzer/file_format.rb +81 -0
  17. data/lib/request_log_analyzer/file_format/merb.rb +33 -0
  18. data/lib/request_log_analyzer/file_format/rails.rb +90 -0
  19. data/lib/request_log_analyzer/filter/base.rb +29 -0
  20. data/lib/request_log_analyzer/filter/field.rb +36 -0
  21. data/lib/request_log_analyzer/filter/timespan.rb +32 -0
  22. data/lib/request_log_analyzer/line_definition.rb +159 -0
  23. data/lib/request_log_analyzer/log_parser.rb +173 -0
  24. data/lib/request_log_analyzer/log_processor.rb +121 -0
  25. data/lib/request_log_analyzer/request.rb +95 -0
  26. data/lib/request_log_analyzer/source/base.rb +42 -0
  27. data/lib/request_log_analyzer/source/log_file.rb +170 -0
  28. data/lib/request_log_analyzer/tracker/base.rb +54 -0
  29. data/lib/request_log_analyzer/tracker/category.rb +71 -0
  30. data/lib/request_log_analyzer/tracker/duration.rb +81 -0
  31. data/lib/request_log_analyzer/tracker/hourly_spread.rb +80 -0
  32. data/lib/request_log_analyzer/tracker/timespan.rb +54 -0
  33. data/spec/controller_spec.rb +40 -0
  34. data/spec/database_inserter_spec.rb +101 -0
  35. data/spec/file_format_spec.rb +78 -0
  36. data/spec/file_formats/spec_format.rb +26 -0
  37. data/spec/filter_spec.rb +137 -0
  38. data/spec/fixtures/merb.log +84 -0
  39. data/spec/fixtures/multiple_files_1.log +5 -0
  40. data/spec/fixtures/multiple_files_2.log +2 -0
  41. data/spec/fixtures/rails_1x.log +59 -0
  42. data/spec/fixtures/rails_22.log +12 -0
  43. data/spec/fixtures/rails_22_cached.log +10 -0
  44. data/spec/fixtures/rails_unordered.log +24 -0
  45. data/spec/fixtures/syslog_1x.log +5 -0
  46. data/spec/fixtures/test_file_format.log +13 -0
  47. data/spec/fixtures/test_language_combined.log +14 -0
  48. data/spec/fixtures/test_order.log +16 -0
  49. data/spec/line_definition_spec.rb +124 -0
  50. data/spec/log_parser_spec.rb +68 -0
  51. data/spec/log_processor_spec.rb +57 -0
  52. data/spec/merb_format_spec.rb +38 -0
  53. data/spec/rails_format_spec.rb +76 -0
  54. data/spec/request_spec.rb +72 -0
  55. data/spec/spec_helper.rb +67 -0
  56. data/spec/summarizer_spec.rb +9 -0
  57. data/tasks/github-gem.rake +177 -0
  58. data/tasks/request_log_analyzer.rake +10 -0
  59. data/tasks/rspec.rake +6 -0
  60. metadata +135 -0
@@ -0,0 +1,116 @@
1
+ require File.dirname(__FILE__) + '/../tracker/base'
2
+
3
+ module RequestLogAnalyzer::Aggregator
4
+
5
+ class Summarizer < Base
6
+
7
+ class Definer
8
+
9
+ attr_reader :trackers
10
+
11
+ def initialize
12
+ @trackers = []
13
+ end
14
+
15
+ def method_missing(tracker_method, *args)
16
+ track(tracker_method, args.first)
17
+ end
18
+
19
+ def category(category_field, options = {})
20
+ if category_field.kind_of?(Symbol)
21
+ track(:category, options.merge(:category => category_field))
22
+ elsif category_field.kind_of?(Hash)
23
+ track(:category, category_field.merge(options))
24
+ end
25
+ end
26
+
27
+ def duration(duration_field, options = {})
28
+ if duration_field.kind_of?(Symbol)
29
+ track(:duration, options.merge(:duration => duration_field))
30
+ elsif duration_field.kind_of?(Hash)
31
+ track(:duration, duration_field.merge(options))
32
+ end
33
+ end
34
+
35
+ def track(tracker_klass, options = {})
36
+ require "#{File.dirname(__FILE__)}/../tracker/#{tracker_klass}"
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)
38
+ @trackers << tracker_klass.new(options)
39
+ end
40
+ end
41
+
42
+ attr_reader :trackers
43
+ attr_reader :warnings_encountered
44
+
45
+ def initialize(source, options = {})
46
+ super(source, options)
47
+ @warnings_encountered = {}
48
+ @trackers = source.file_format.report_trackers
49
+ setup
50
+ end
51
+
52
+ def setup
53
+ end
54
+
55
+ def prepare
56
+ raise "No trackers set up in Summarizer!" if @trackers.nil? || @trackers.empty?
57
+ @trackers.each { |tracker| tracker.prepare }
58
+ end
59
+
60
+ def aggregate(request)
61
+ @trackers.each do |tracker|
62
+ tracker.update(request) if tracker.should_update?(request)
63
+ end
64
+ end
65
+
66
+ def finalize
67
+ @trackers.each { |tracker| tracker.finalize }
68
+ end
69
+
70
+ def report(output=STDOUT, report_width = 80, color = false)
71
+ report_header(output, report_width, color)
72
+ if source.parsed_requests > 0
73
+ @trackers.each { |tracker| tracker.report(output, report_width, color) }
74
+ else
75
+ output << "\n"
76
+ output << "There were no requests analyzed.\n"
77
+ end
78
+ report_footer(output, report_width, color)
79
+ end
80
+
81
+ def report_header(output=STDOUT, report_width = 80, color = false)
82
+ output << "Request summary\n"
83
+ output << green("━" * report_width, color) + "\n"
84
+ output << "Parsed lines: #{green(source.parsed_lines, color)}\n"
85
+ output << "Parsed requests: #{green(source.parsed_requests, color)}\n"
86
+ output << "Skipped lines: #{green(source.skipped_lines, color)}\n" if source.skipped_lines > 0
87
+ if has_warnings?
88
+ output << "Warnings: " + @warnings_encountered.map { |(key, value)| "#{key.inspect}: #{blue(value, color)}" }.join(', ') + "\n"
89
+ end
90
+ output << "\n"
91
+ end
92
+
93
+ def report_footer(output=STDOUT, report_width = 80, color = false)
94
+ output << "\n"
95
+ if has_serious_warnings?
96
+ output << green("━" * report_width, color) + "\n"
97
+ output << "Multiple warnings were encountered during parsing. Possibly, your logging " + "\n"
98
+ output << "is not setup correctly. Visit this website for logging configuration tips:" + "\n"
99
+ output << blue("http://github.com/wvanbergen/request-log-analyzer/wikis/configure-logging", color) + "\n"
100
+ end
101
+ end
102
+
103
+ def has_warnings?
104
+ @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 0
105
+ end
106
+
107
+ def has_serious_warnings?
108
+ @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 10
109
+ end
110
+
111
+ def warning(type, message, lineno)
112
+ @warnings_encountered[type] ||= 0
113
+ @warnings_encountered[type] += 1
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,201 @@
1
+ module RequestLogAnalyzer
2
+
3
+ # The RequestLogAnalyzer::Controller class creates a LogParser instance for the
4
+ # requested file format, and connect it with sources and aggregators.
5
+ #
6
+ # Sources are streams or files from which the requests will be parsed.
7
+ # Aggregators will handle every passed request to yield a meaningfull results.
8
+ #
9
+ # - Use the build-function to build a controller instance using command line arguments.
10
+ # - Use add_aggregator to register a new aggregator
11
+ # - Use add_source to register a new aggregator
12
+ # - Use the run! method to start the parser and send the requests to the aggregators.
13
+ #
14
+ # Note that the order of sources can be imported if you have log files than succeed
15
+ # eachother. Requests that span over succeeding files will be parsed correctly if the
16
+ # sources are registered in the correct order. This can be helpful to parse requests
17
+ # from several logrotated log files.
18
+ class Controller
19
+
20
+ include RequestLogAnalyzer::FileFormat::Awareness
21
+
22
+ attr_reader :aggregators
23
+ attr_reader :filters
24
+ attr_reader :log_parser
25
+ attr_reader :source
26
+ attr_reader :output
27
+ attr_reader :options
28
+
29
+ # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
30
+ # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
31
+ # <rr>report_with</tt> Width of the report. Defaults to 80.
32
+ def self.build(arguments, report_width = 80)
33
+
34
+ options = { :report_width => arguments[:report_width].to_i, :output => STDOUT}
35
+
36
+ options[:database] = arguments[:database] if arguments[:database]
37
+ options[:debug] = arguments[:debug]
38
+ options[:colorize] = !arguments[:boring]
39
+
40
+ if arguments[:file]
41
+ options[:output] = File.new(arguments[:file], "w+")
42
+ options[:colorize] = false
43
+ end
44
+
45
+ # Create the controller with the correct file format
46
+ file_format = RequestLogAnalyzer::FileFormat.load(arguments[:format])
47
+
48
+ # register sources
49
+ if arguments.parameters.length == 1
50
+ file = arguments.parameters[0]
51
+ if file == '-' || file == 'STDIN'
52
+ options.store(:source_files, $stdin)
53
+ elsif File.exist?(file)
54
+ options.store(:source_files, file)
55
+ else
56
+ puts "File not found: #{file}"
57
+ exit(0)
58
+ end
59
+ else
60
+ options.store(:source_files, arguments.parameters)
61
+ end
62
+
63
+ controller = Controller.new(RequestLogAnalyzer::Source::LogFile.new(file_format, options), options)
64
+
65
+ options[:assume_correct_order] = arguments[:assume_correct_order]
66
+
67
+ # register filters
68
+ if arguments[:after] || arguments[:before]
69
+ filter_options = {}
70
+ filter_options[:after] = DateTime.parse(arguments[:after])
71
+ filter_options[:before] = DateTime.parse(arguments[:before]) if arguments[:before]
72
+ controller.add_filter(:timespan, filter_options)
73
+ end
74
+
75
+ arguments[:reject].each do |(field, value)|
76
+ controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
77
+ end
78
+
79
+ arguments[:select].each do |(field, value)|
80
+ controller.add_filter(:field, :mode => :select, :field => field, :value => value)
81
+ end
82
+
83
+ # register aggregators
84
+ arguments[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
85
+
86
+ # register the database
87
+ controller.add_aggregator(:database) if arguments[:database] && !arguments[:aggregator].include?('database')
88
+ controller.add_aggregator(:summarizer) if arguments[:aggregator].empty?
89
+
90
+ # register the echo aggregator in debug mode
91
+ controller.add_aggregator(:echo) if arguments[:debug]
92
+
93
+ file_format.setup_environment(controller)
94
+
95
+ return controller
96
+ end
97
+
98
+ # Builds a new Controller for the given log file format.
99
+ # <tt>format</tt> Logfile format. Defaults to :rails
100
+ # Options are passd on to the LogParser.
101
+ # * <tt>:aggregator</tt> Aggregator array.
102
+ # * <tt>:database</tt> Database the controller should use.
103
+ # * <tt>:echo</tt> Output debug information.
104
+ # * <tt>:silent</tt> Do not output any warnings.
105
+ # * <tt>:colorize</tt> Colorize output
106
+ # * <tt>:output</tt> All report outputs get << through this output.
107
+ def initialize(source, options = {})
108
+
109
+ @source = source
110
+ @options = options
111
+ @aggregators = []
112
+ @filters = []
113
+ @output = options[:output]
114
+
115
+ # Requester format through RequestLogAnalyzer::FileFormat and construct the parser
116
+ register_file_format(@source.file_format)
117
+
118
+ # Pass all warnings to every aggregator so they can do something useful with them.
119
+ @source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } } if @source
120
+
121
+ # Handle progress messagess
122
+ @source.progress = lambda { |message, value| handle_progress(message, value) } if @source
123
+ end
124
+
125
+ # Progress function.
126
+ # Expects :started with file, :progress with current line and :finished or :interrupted when done.
127
+ # <tt>message</tt> Current state (:started, :finished, :interupted or :progress).
128
+ # <tt>value</tt> File or current line.
129
+ def handle_progress(message, value = nil)
130
+ case message
131
+ when :started
132
+ @progress_bar = ProgressBar.new(green(File.basename(value), options[:colorize]), File.size(value))
133
+ when :finished
134
+ @progress_bar.finish
135
+ @progress_bar = nil
136
+ when :interrupted
137
+ if @progress_bar
138
+ @progress_bar.halt
139
+ @progress_bar = nil
140
+ end
141
+ when :progress
142
+ @progress_bar.set(value)
143
+ end
144
+ end
145
+
146
+ # Adds an aggregator to the controller. The aggregator will be called for every request
147
+ # that is parsed from the provided sources (see add_source)
148
+ def add_aggregator(agg)
149
+ if agg.kind_of?(Symbol)
150
+ require File.dirname(__FILE__) + "/aggregator/#{agg}"
151
+ agg = RequestLogAnalyzer::Aggregator.const_get(agg.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
152
+ end
153
+
154
+ @aggregators << agg.new(@source, @options)
155
+ end
156
+
157
+ alias :>> :add_aggregator
158
+
159
+ # Adds a request filter to the controller.
160
+ def add_filter(filter, filter_options = {})
161
+ if filter.kind_of?(Symbol)
162
+ require File.dirname(__FILE__) + "/filter/#{filter}"
163
+ filter = RequestLogAnalyzer::Filter.const_get(filter.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
164
+ end
165
+
166
+ @filters << filter.new(file_format, @options.merge(filter_options))
167
+ end
168
+
169
+ # Runs RequestLogAnalyzer
170
+ # 1. Call prepare on every aggregator
171
+ # 2. Generate requests from source object
172
+ # 3. Filter out unwanted requests
173
+ # 4. Call aggregate for remaning requests on every aggregator
174
+ # 4. Call finalize on every aggregator
175
+ # 5. Call report on every aggregator
176
+ # 6. Finalize Source
177
+ def run!
178
+
179
+ @filters.each { |filter| filter.prepare }
180
+ @aggregators.each { |agg| agg.prepare }
181
+
182
+ begin
183
+ @source.requests do |request|
184
+ #@filters.each { |filter| request = filter.filter(request) }
185
+ @aggregators.each { |agg| agg.aggregate(request) } if request
186
+ end
187
+ rescue Interrupt => e
188
+ handle_progress(:interrupted)
189
+ puts "Caught interrupt! Stopped parsing."
190
+ end
191
+
192
+ puts "\n"
193
+
194
+ @aggregators.each { |agg| agg.finalize }
195
+ @aggregators.each { |agg| agg.report(@output, options[:report_width], options[:colorize]) }
196
+
197
+ @source.finalize
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,81 @@
1
+ module RequestLogAnalyzer
2
+
3
+ class FileFormat
4
+
5
+ # Makes classes aware of a file format by registering the file_format variable
6
+ module Awareness
7
+
8
+ def self.included(base)
9
+ base.send(:attr_reader, :file_format)
10
+ end
11
+
12
+ def register_file_format(format)
13
+ @file_format = format
14
+ end
15
+ end
16
+
17
+ def self.inherited(subclass)
18
+ subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
19
+ subclass.class_eval { class << self; attr_accessor :line_definer; end }
20
+ subclass.class_eval { class << self; attr_accessor :report_definer; end }
21
+ end
22
+
23
+ def self.line_definition(name, &block)
24
+ @line_definer.send(name, &block)
25
+ end
26
+
27
+ def self.format_definition(&block)
28
+ if block_given?
29
+ yield(@line_definer)
30
+ else
31
+ return @line_definer
32
+ end
33
+ end
34
+
35
+ def self.report(&block)
36
+ @report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
37
+ yield(@report_definer)
38
+ end
39
+
40
+ def self.load(file_format)
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
+
65
+ def line_definitions
66
+ @line_definitions ||= self.class.line_definer.line_definitions
67
+ end
68
+
69
+ def report_trackers
70
+ self.class.instance_variable_get(:@report_definer).trackers rescue []
71
+ end
72
+
73
+ def valid?
74
+ line_definitions.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
75
+ end
76
+
77
+ def setup_environment(controller)
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,33 @@
1
+ module RequestLogAnalyzer::FileFormat::Merb
2
+
3
+ LINE_DEFINITIONS = {
4
+
5
+ # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
6
+ :started => {
7
+ :header => true,
8
+ :teaser => /Started/,
9
+ :regexp => /Started request handling\:\ (.+)/,
10
+ :captures => [{ :name => :timestamp, :type => :timestamp, :anonymize => :slightly }]
11
+ },
12
+
13
+ # ~ Params: {"action"=>"create", "controller"=>"session"}
14
+ # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
15
+ :params => {
16
+ :teaser => /Params/,
17
+ :regexp => /Params\:\ \{(.+)\}/,
18
+ :captures => [{ :name => :raw_hash, :type => :string}]
19
+ },
20
+
21
+ # ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
22
+ :completed => {
23
+ :footer => true,
24
+ :teaser => /\{:dispatch_time/,
25
+ :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
+ :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
+ }
31
+ }
32
+
33
+ end
@@ -0,0 +1,90 @@
1
+ class RequestLogAnalyzer::FileFormat::Rails < RequestLogAnalyzer::FileFormat
2
+
3
+ # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
4
+ line_definition :processing do |line|
5
+ line.header = true # this line is the first log line for a request
6
+ line.teaser = /Processing /
7
+ 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]+)\]/
8
+ line.captures << { :name => :controller, :type => :string } \
9
+ << { :name => :action, :type => :string } \
10
+ << { :name => :format, :type => :string } \
11
+ << { :name => :ip, :type => :string, :anonymize => :ip } \
12
+ << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
13
+ << { :name => :method, :type => :string }
14
+ end
15
+
16
+ # 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.
17
+ line_definition :cache_hit do |line|
18
+ line.regexp = /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter:.+>\] rendered_or_redirected/
19
+ end
20
+
21
+ # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
22
+ line_definition :failed do |line|
23
+ line.footer = true
24
+ line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
25
+ line.captures << { :name => :error, :type => :string } \
26
+ << { :name => :message, :type => :string } \
27
+ << { :name => :line, :type => :integer } \
28
+ << { :name => :file, :type => :string } \
29
+ << { :name => :stack_trace, :type => :string, :anonymize => true }
30
+ end
31
+
32
+
33
+ # Rails < 2.1 completed line example
34
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
35
+ 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
+
37
+ # Rails > 2.1 completed line example
38
+ # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
39
+ RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
40
+
41
+ # The completed line uses a kind of hack to ensure that both old style logs and new style logs
42
+ # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
43
+ # but the line contains exactly the same information.
44
+ line_definition :completed do |line|
45
+
46
+ line.footer = true
47
+ line.teaser = /Completed in /
48
+ line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
49
+
50
+ line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
51
+ << { :name => :view, :type => :sec, :anonymize => :slightly } \
52
+ << { :name => :db, :type => :sec, :anonymize => :slightly } \
53
+ << { :name => :status, :type => :integer } \
54
+ << { :name => :url, :type => :string, :anonymize => :url } # Old variant
55
+
56
+ line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
57
+ << { :name => :view, :type => :msec, :anonymize => :slightly } \
58
+ << { :name => :db, :type => :msec, :anonymize => :slightly } \
59
+ << { :name => :status, :type => :integer} \
60
+ << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
61
+ end
62
+
63
+
64
+
65
+ REQUEST_CATEGORIZER = Proc.new do |request|
66
+ format = request[:format] || 'html'
67
+ "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
68
+ end
69
+
70
+ report do |analyze|
71
+ analyze.timespan :line_type => :processing
72
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
73
+ analyze.category :method, :title => 'HTTP methods'
74
+ analyze.category :status, :title => 'HTTP statuses returned'
75
+ analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
76
+
77
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
78
+ analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
79
+ analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
80
+
81
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
82
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
83
+
84
+ analyze.hourly_spread :line_type => :processing
85
+ analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
86
+ end
87
+
88
+
89
+
90
+ end