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.
Files changed (40) hide show
  1. data/README.rdoc +4 -3
  2. data/bin/request-log-analyzer +4 -5
  3. data/lib/cli/command_line_arguments.rb +2 -2
  4. data/lib/request_log_analyzer.rb +28 -6
  5. data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +5 -1
  6. data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -3
  7. data/lib/request_log_analyzer/controller.rb +11 -16
  8. data/lib/request_log_analyzer/file_format.rb +71 -38
  9. data/lib/request_log_analyzer/file_format/merb.rb +32 -26
  10. data/lib/request_log_analyzer/file_format/rails.rb +73 -71
  11. data/lib/request_log_analyzer/file_format/rails_development.rb +93 -95
  12. data/lib/request_log_analyzer/filter.rb +38 -0
  13. data/lib/request_log_analyzer/filter/anonimize.rb +1 -1
  14. data/lib/request_log_analyzer/line_definition.rb +1 -1
  15. data/lib/request_log_analyzer/output.rb +6 -8
  16. data/lib/request_log_analyzer/output/fixed_width.rb +133 -117
  17. data/lib/request_log_analyzer/output/html.rb +138 -60
  18. data/lib/request_log_analyzer/request.rb +3 -1
  19. data/lib/request_log_analyzer/{source/base.rb → source.rb} +5 -0
  20. data/lib/request_log_analyzer/source/{log_file.rb → log_parser.rb} +15 -6
  21. data/lib/request_log_analyzer/tracker.rb +58 -0
  22. data/lib/request_log_analyzer/tracker/category.rb +7 -8
  23. data/lib/request_log_analyzer/tracker/duration.rb +15 -12
  24. data/lib/request_log_analyzer/tracker/hourly_spread.rb +8 -8
  25. data/lib/request_log_analyzer/tracker/timespan.rb +10 -10
  26. data/spec/controller_spec.rb +5 -4
  27. data/spec/database_inserter_spec.rb +5 -8
  28. data/spec/file_format_spec.rb +2 -2
  29. data/spec/file_formats/spec_format.rb +2 -1
  30. data/spec/filter_spec.rb +0 -3
  31. data/spec/log_parser_spec.rb +6 -6
  32. data/spec/merb_format_spec.rb +38 -38
  33. data/spec/rails_format_spec.rb +2 -2
  34. data/spec/request_spec.rb +2 -2
  35. data/spec/spec_helper.rb +3 -37
  36. data/tasks/github-gem.rake +2 -1
  37. metadata +7 -8
  38. data/lib/request_log_analyzer/filter/base.rb +0 -32
  39. data/lib/request_log_analyzer/log_parser.rb +0 -173
  40. data/lib/request_log_analyzer/tracker/base.rb +0 -54
@@ -1,114 +1,112 @@
1
- class RequestLogAnalyzer::FileFormat::RailsDevelopment < RequestLogAnalyzer::FileFormat
1
+ module RequestLogAnalyzer::FileFormat
2
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
3
+ class RailsDevelopment < 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
- # 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
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
- # Rendered layouts/_footer (2.9ms)
22
- line_definition :rendered do |line|
23
- line.teaser = /Rendered /
24
- line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
25
- line.captures << { :name => :render_file, :type => :string } \
26
- << { :name => :render_duration, :type => :msec }
27
- end
23
+ # Rendered layouts/_footer (2.9ms)
24
+ line_definition :rendered do |line|
25
+ line.teaser = /Rendered /
26
+ line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
27
+ line.captures << { :name => :render_file, :type => :string } \
28
+ << { :name => :render_duration, :type => :msec }
29
+ end
28
30
 
29
- # User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) 
30
- line_definition :query_executed do |line|
31
- line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?(.+) (?:\e\[0m)?/
32
- line.captures << { :name => :query_class, :type => :string } \
33
- << { :name => :query_duration, :type => :msec } \
34
- << { :name => :query_sql, :type => :string }
35
- end
31
+ # User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) 
32
+ line_definition :query_executed do |line|
33
+ line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?(.+) (?:\e\[0m)?/
34
+ line.captures << { :name => :query_class, :type => :string } \
35
+ << { :name => :query_duration, :type => :msec } \
36
+ << { :name => :query_sql, :type => :string }
37
+ end
36
38
 
37
- # CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) 
38
- line_definition :query_cached do |line|
39
- line.teaser = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
40
- line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
41
- line.captures << { :name => :cached_duration, :type => :msec } \
42
- << { :name => :cached_sql, :type => :string }
43
- end
39
+ # CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) 
40
+ line_definition :query_cached do |line|
41
+ line.teaser = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
42
+ line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
43
+ line.captures << { :name => :cached_duration, :type => :msec } \
44
+ << { :name => :cached_sql, :type => :string }
45
+ end
44
46
 
45
- # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
46
- line_definition :failed do |line|
47
- line.footer = true
48
- line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
49
- line.captures << { :name => :error, :type => :string } \
50
- << { :name => :message, :type => :string } \
51
- << { :name => :line, :type => :integer } \
52
- << { :name => :file, :type => :string } \
53
- << { :name => :stack_trace, :type => :string, :anonymize => true }
54
- end
47
+ # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
48
+ line_definition :failed do |line|
49
+ line.footer = true
50
+ line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
51
+ line.captures << { :name => :error, :type => :string } \
52
+ << { :name => :message, :type => :string } \
53
+ << { :name => :line, :type => :integer } \
54
+ << { :name => :file, :type => :string } \
55
+ << { :name => :stack_trace, :type => :string, :anonymize => true }
56
+ end
55
57
 
56
58
 
57
- # Rails < 2.1 completed line example
58
- # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
59
- 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.+)\]/
59
+ # Rails < 2.1 completed line example
60
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
61
+ 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.+)\]/
60
62
 
61
- # Rails > 2.1 completed line example
62
- # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
63
- RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
63
+ # Rails > 2.1 completed line example
64
+ # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
65
+ RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
64
66
 
65
- # The completed line uses a kind of hack to ensure that both old style logs and new style logs
66
- # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
67
- # but the line contains exactly the same information.
68
- line_definition :completed do |line|
67
+ # The completed line uses a kind of hack to ensure that both old style logs and new style logs
68
+ # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
69
+ # but the line contains exactly the same information.
70
+ line_definition :completed do |line|
69
71
 
70
- line.footer = true
71
- line.teaser = /Completed in /
72
- line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
72
+ line.footer = true
73
+ line.teaser = /Completed in /
74
+ line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
73
75
 
74
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
75
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
76
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
77
- << { :name => :status, :type => :integer } \
78
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
76
+ line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
77
+ << { :name => :view, :type => :sec, :anonymize => :slightly } \
78
+ << { :name => :db, :type => :sec, :anonymize => :slightly } \
79
+ << { :name => :status, :type => :integer } \
80
+ << { :name => :url, :type => :string, :anonymize => :url } # Old variant
79
81
 
80
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
81
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
82
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
83
- << { :name => :status, :type => :integer} \
84
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
85
- end
82
+ line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
83
+ << { :name => :view, :type => :msec, :anonymize => :slightly } \
84
+ << { :name => :db, :type => :msec, :anonymize => :slightly } \
85
+ << { :name => :status, :type => :integer} \
86
+ << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
87
+ end
86
88
 
89
+ REQUEST_CATEGORIZER = Proc.new do |request|
90
+ format = request[:format] || 'html'
91
+ "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
92
+ end
87
93
 
88
-
89
- REQUEST_CATEGORIZER = Proc.new do |request|
90
- format = request[:format] || 'html'
91
- "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
92
- end
93
-
94
- report do |analyze|
95
- analyze.timespan :line_type => :processing
96
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
- analyze.category :method, :title => 'HTTP methods'
98
- analyze.category :status, :title => 'HTTP statuses returned'
99
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
94
+ report do |analyze|
95
+ analyze.timespan :line_type => :processing
96
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
+ analyze.category :method, :title => 'HTTP methods'
98
+ analyze.category :status, :title => 'HTTP statuses returned'
99
+ analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
100
100
 
101
- analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
102
- analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
103
- analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
101
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
102
+ analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
103
+ analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
104
104
 
105
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
106
- :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
105
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
106
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
107
107
 
108
- analyze.hourly_spread :line_type => :processing
109
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
108
+ analyze.hourly_spread :line_type => :processing
109
+ analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
110
+ end
110
111
  end
111
-
112
-
113
-
114
- end
112
+ end
@@ -0,0 +1,38 @@
1
+ module RequestLogAnalyzer::Filter
2
+
3
+ # Filter class loader using const_missing
4
+ # This function will automatically load the class file based on the name of the class
5
+ def self.const_missing(const)
6
+ RequestLogAnalyzer::load_default_class_file(self, const)
7
+ end
8
+
9
+ # Base filter class used to filter input requests.
10
+ # All filters should interit from this base.
11
+ class Base
12
+
13
+ include RequestLogAnalyzer::FileFormat::Awareness
14
+
15
+ attr_reader :log_parser
16
+ attr_reader :options
17
+
18
+ # Initializer
19
+ # <tt>format</tt> The file format
20
+ # <tt>options</tt> Are passed to the filters.
21
+ def initialize(format, options = {})
22
+ @options = options
23
+ register_file_format(format)
24
+ end
25
+
26
+ # Initialize the filter
27
+ def prepare
28
+ end
29
+
30
+ # Return the request if the request should be kept.
31
+ # Return nil otherwise.
32
+ def filter(request)
33
+ return nil unless request
34
+ return request
35
+ end
36
+ end
37
+
38
+ end
@@ -15,7 +15,7 @@ module RequestLogAnalyzer::Filter
15
15
  end
16
16
 
17
17
  def anonymize_url(value)
18
- return value.sub(/^https?\:\/\/[A-z0-9\.-]+\//, "http://example.com/")
18
+ return value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
19
19
  end
20
20
 
21
21
  def fuzz(value)
@@ -6,7 +6,7 @@ module RequestLogAnalyzer
6
6
  end
7
7
 
8
8
  def anonymizer_for_url(value, capture_definition)
9
- value.sub(/^https?\:\/\/[A-z0-9\.-]+\//, "http://example.com/")
9
+ value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
10
10
  end
11
11
  end
12
12
 
@@ -1,15 +1,13 @@
1
- module RequestLogAnalyzer
1
+ module RequestLogAnalyzer::Output
2
+
3
+ def self.const_missing(const)
4
+ RequestLogAnalyzer::load_default_class_file(self, const)
5
+ end
2
6
 
3
- class Output
7
+ class Base
4
8
 
5
9
  attr_accessor :io, :options, :style
6
10
 
7
- def self.const_missing(const)
8
- filename = const.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
9
- require File.dirname(__FILE__) + '/output/' + filename
10
- self.const_get(const)
11
- end
12
-
13
11
  def initialize(io, options = {})
14
12
  @io = io
15
13
  @options = options
@@ -1,158 +1,174 @@
1
- class RequestLogAnalyzer::Output::FixedWidth < RequestLogAnalyzer::Output
1
+ module RequestLogAnalyzer::Output
2
2
 
3
- module Monochrome
4
- def colorize(text, *options)
5
- text
3
+ class FixedWidth < Base
4
+
5
+ module Monochrome
6
+ def colorize(text, *options)
7
+ text
8
+ end
6
9
  end
7
- end
8
10
 
9
- module Color
11
+ module Color
10
12
 
11
- STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
12
- COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
13
+ STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
14
+ COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
13
15
 
14
- def colorize(text, *options)
16
+ def colorize(text, *options)
15
17
 
16
- font_style = ''
17
- foreground_color = '0'
18
- background_color = ''
18
+ font_style = ''
19
+ foreground_color = '0'
20
+ background_color = ''
19
21
 
20
- options.each do |option|
21
- if option.kind_of?(Symbol)
22
- foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
23
- font_style = "#{STYLES[option]};" if STYLES.include?(option)
24
- elsif option.kind_of?(Hash)
25
- options.each do |key, value|
26
- case key
27
- when :color; foreground_color = "3#{COLORS[value]}" if COLORS.include?(value)
28
- when :background; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
29
- when :on; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
30
- when :style; font_style = "#{STYLES[value]};" if STYLES.include?(value)
22
+ options.each do |option|
23
+ if option.kind_of?(Symbol)
24
+ foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
25
+ font_style = "#{STYLES[option]};" if STYLES.include?(option)
26
+ elsif option.kind_of?(Hash)
27
+ options.each do |key, value|
28
+ case key
29
+ when :color; foreground_color = "3#{COLORS[value]}" if COLORS.include?(value)
30
+ when :background; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
31
+ when :on; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
32
+ when :style; font_style = "#{STYLES[value]};" if STYLES.include?(value)
33
+ end
31
34
  end
32
35
  end
33
36
  end
37
+ return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
34
38
  end
35
- return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
36
- end
37
39
 
38
- end
40
+ end
39
41
 
40
- attr_reader :characters
42
+ attr_reader :characters
41
43
 
42
- CHARACTERS = {
43
- :ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
44
- :utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
45
- }
44
+ CHARACTERS = {
45
+ :ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
46
+ :utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
47
+ }
46
48
 
47
- def initialize(io, options = {})
48
- super(io, options)
49
- @options[:width] ||= 80
50
- @options[:characters] ||= :utf
51
- @characters = CHARACTERS[@options[:characters]]
49
+ def initialize(io, options = {})
50
+ super(io, options)
51
+ @options[:width] ||= 80
52
+ @options[:characters] ||= :utf
53
+ @characters = CHARACTERS[@options[:characters]]
52
54
 
53
- color_module = @options[:color] ? Color : Monochrome
54
- (class << self; self; end).send(:include, color_module)
55
- end
55
+ color_module = @options[:color] ? Color : Monochrome
56
+ (class << self; self; end).send(:include, color_module)
57
+ end
56
58
 
57
- def print(str)
58
- @io << str
59
- end
59
+ def print(str)
60
+ @io << str
61
+ end
60
62
 
61
- alias :<< :print
63
+ alias :<< :print
62
64
 
63
- def puts(str = '')
64
- @io << str << "\n"
65
- end
65
+ def puts(str = '')
66
+ @io << str << "\n"
67
+ end
66
68
 
67
- def title(title)
68
- puts
69
- puts colorize(title, :bold, :white)
70
- line(:green)
71
- end
69
+ def title(title)
70
+ puts
71
+ puts colorize(title, :bold, :white)
72
+ line(:green)
73
+ end
72
74
 
73
- def line(*font)
74
- puts colorize(characters[:horizontal_line] * @options[:width], *font)
75
- end
75
+ def line(*font)
76
+ puts colorize(characters[:horizontal_line] * @options[:width], *font)
77
+ end
76
78
 
77
- def link(text, url = nil)
78
- if url.nil?
79
- colorize(text, :blue, :bold)
80
- else
81
- "#{text} (#{colorize(url, :blue, :bold)})"
79
+ def link(text, url = nil)
80
+ if url.nil?
81
+ colorize(text, :blue, :bold)
82
+ else
83
+ "#{text} (#{colorize(url, :blue, :bold)})"
84
+ end
85
+ end
86
+
87
+ def header
88
+ if io.kind_of?(File)
89
+ puts "Request-log-analyzer summary report"
90
+ line
91
+ puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
92
+ puts "Request-log-analyzer website: http://github.com/wvanbergen/request-log-analyzer"
93
+ end
94
+ end
95
+
96
+ def footer
97
+ puts
98
+ puts "Thanks for using request-log-analyzer!"
82
99
  end
83
- end
84
100
 
85
- def table(*columns, &block)
101
+ def table(*columns, &block)
86
102
 
87
- rows = Array.new
88
- yield(rows)
103
+ rows = Array.new
104
+ yield(rows)
89
105
 
90
- # determine maximum cell widths
91
- max_cell_widths = rows.inject(Array.new(columns.length, 0)) do |result, row|
92
- lengths = row.map { |column| column.to_s.length }
93
- result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
94
- end
95
- columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
106
+ # determine maximum cell widths
107
+ max_cell_widths = rows.inject(Array.new(columns.length, 0)) do |result, row|
108
+ lengths = row.map { |column| column.to_s.length }
109
+ result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
110
+ end
111
+ columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
96
112
 
97
- # determine actual column width
98
- column_widths = columns.map do |column|
99
- if column[:width] == :rest
100
- nil
101
- elsif column[:width]
102
- column[:width]
103
- elsif column[:min_width]
104
- [column[:min_width], column[:actual_width]].max
105
- elsif column[:max_width]
106
- [column[:max_width], column[:actual_width]].min
107
- else
108
- column[:actual_width]
113
+ # determine actual column width
114
+ column_widths = columns.map do |column|
115
+ if column[:width] == :rest
116
+ nil
117
+ elsif column[:width]
118
+ column[:width]
119
+ elsif column[:min_width]
120
+ [column[:min_width], column[:actual_width]].max
121
+ elsif column[:max_width]
122
+ [column[:max_width], column[:actual_width]].min
123
+ else
124
+ column[:actual_width]
125
+ end
109
126
  end
110
- end
111
127
 
112
- if column_widths.include?(nil)
113
- width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
114
- column_widths[column_widths.index(nil)] = width_left
115
- end
116
-
117
- # Print table header
118
- if table_has_header?(columns)
119
- column_titles = []
120
- columns.each_with_index do |column, index|
121
- width = column_widths[index]
122
- alignment = (column[:align] == :right ? '' : '-')
123
- column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
128
+ if column_widths.include?(nil)
129
+ width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
130
+ column_widths[column_widths.index(nil)] = width_left
124
131
  end
132
+
133
+ # Print table header
134
+ if table_has_header?(columns)
135
+ column_titles = []
136
+ columns.each_with_index do |column, index|
137
+ width = column_widths[index]
138
+ alignment = (column[:align] == :right ? '' : '-')
139
+ column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
140
+ end
125
141
 
126
- puts
127
- puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
128
- line(:green)
129
- end
142
+ puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
143
+ line(:green)
144
+ end
130
145
 
131
- rows.each do |row|
132
- row_values = []
133
- columns.each_with_index do |column, index|
134
- width = column_widths[index]
135
- case column[:type]
136
- when :ratio
137
- if width > 4
138
- if column[:treshold] && column[:treshold] < row[index].to_f
139
- bar = ''
140
- bar << characters[:block] * (width.to_f * column[:treshold]).round
141
- bar << colorize(characters[:block] * (width.to_f * (row[index].to_f - column[:treshold])).round, :red)
142
- row_values.push(bar)
146
+ rows.each do |row|
147
+ row_values = []
148
+ columns.each_with_index do |column, index|
149
+ width = column_widths[index]
150
+ case column[:type]
151
+ when :ratio
152
+ if width > 4
153
+ if column[:treshold] && column[:treshold] < row[index].to_f
154
+ bar = ''
155
+ bar << characters[:block] * (width.to_f * column[:treshold]).round
156
+ bar << colorize(characters[:block] * (width.to_f * (row[index].to_f - column[:treshold])).round, :red)
157
+ row_values.push(bar)
158
+ else
159
+ row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
160
+ end
143
161
  else
144
- row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
162
+ row_values.push('')
145
163
  end
146
164
  else
147
- row_values.push('')
165
+ alignment = (columns[index][:align] == :right ? '' : '-')
166
+ row_values.push("%#{alignment}#{width}s" % row[index].to_s[0...width])
148
167
  end
149
- else
150
- alignment = (columns[index][:align] == :right ? '' : '-')
151
- row_values.push("%#{alignment}#{width}s" % row[index].to_s[0...width])
152
168
  end
169
+ puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
153
170
  end
154
- puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
155
171
  end
156
- end
157
172
 
173
+ end
158
174
  end