ngmoco-request-log-analyzer 1.4.2

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 (112) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +8 -0
  6. data/bin/request-log-analyzer +114 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +213 -0
  11. data/lib/cli/tools.rb +46 -0
  12. data/lib/request_log_analyzer.rb +44 -0
  13. data/lib/request_log_analyzer/aggregator.rb +49 -0
  14. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  15. data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
  16. data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
  17. data/lib/request_log_analyzer/controller.rb +332 -0
  18. data/lib/request_log_analyzer/database.rb +102 -0
  19. data/lib/request_log_analyzer/database/base.rb +115 -0
  20. data/lib/request_log_analyzer/database/connection.rb +38 -0
  21. data/lib/request_log_analyzer/database/request.rb +22 -0
  22. data/lib/request_log_analyzer/database/source.rb +13 -0
  23. data/lib/request_log_analyzer/database/warning.rb +14 -0
  24. data/lib/request_log_analyzer/file_format.rb +160 -0
  25. data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
  26. data/lib/request_log_analyzer/file_format/apache.rb +141 -0
  27. data/lib/request_log_analyzer/file_format/merb.rb +67 -0
  28. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  29. data/lib/request_log_analyzer/file_format/rails.rb +176 -0
  30. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  31. data/lib/request_log_analyzer/filter.rb +30 -0
  32. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  33. data/lib/request_log_analyzer/filter/field.rb +42 -0
  34. data/lib/request_log_analyzer/filter/timespan.rb +45 -0
  35. data/lib/request_log_analyzer/line_definition.rb +111 -0
  36. data/lib/request_log_analyzer/log_processor.rb +99 -0
  37. data/lib/request_log_analyzer/mailer.rb +62 -0
  38. data/lib/request_log_analyzer/output.rb +113 -0
  39. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  40. data/lib/request_log_analyzer/output/html.rb +184 -0
  41. data/lib/request_log_analyzer/request.rb +175 -0
  42. data/lib/request_log_analyzer/source.rb +72 -0
  43. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  44. data/lib/request_log_analyzer/source/log_parser.rb +274 -0
  45. data/lib/request_log_analyzer/tracker.rb +206 -0
  46. data/lib/request_log_analyzer/tracker/duration.rb +104 -0
  47. data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
  48. data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
  49. data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
  50. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  51. data/request-log-analyzer.gemspec +40 -0
  52. data/spec/database.yml +23 -0
  53. data/spec/fixtures/apache_combined.log +5 -0
  54. data/spec/fixtures/apache_common.log +10 -0
  55. data/spec/fixtures/decompression.log +12 -0
  56. data/spec/fixtures/decompression.log.bz2 +0 -0
  57. data/spec/fixtures/decompression.log.gz +0 -0
  58. data/spec/fixtures/decompression.log.zip +0 -0
  59. data/spec/fixtures/decompression.tar.gz +0 -0
  60. data/spec/fixtures/decompression.tgz +0 -0
  61. data/spec/fixtures/header_and_footer.log +6 -0
  62. data/spec/fixtures/merb.log +84 -0
  63. data/spec/fixtures/merb_prefixed.log +9 -0
  64. data/spec/fixtures/multiple_files_1.log +5 -0
  65. data/spec/fixtures/multiple_files_2.log +2 -0
  66. data/spec/fixtures/rails.db +0 -0
  67. data/spec/fixtures/rails_1x.log +59 -0
  68. data/spec/fixtures/rails_22.log +12 -0
  69. data/spec/fixtures/rails_22_cached.log +10 -0
  70. data/spec/fixtures/rails_unordered.log +24 -0
  71. data/spec/fixtures/syslog_1x.log +5 -0
  72. data/spec/fixtures/test_file_format.log +13 -0
  73. data/spec/fixtures/test_language_combined.log +14 -0
  74. data/spec/fixtures/test_order.log +16 -0
  75. data/spec/integration/command_line_usage_spec.rb +84 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +151 -0
  78. data/spec/lib/helpers.rb +52 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +76 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +24 -0
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  94. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  95. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  96. data/spec/unit/file_format/rails_format_spec.rb +164 -0
  97. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  98. data/spec/unit/filter/field_filter_spec.rb +66 -0
  99. data/spec/unit/filter/filter_spec.rb +17 -0
  100. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  101. data/spec/unit/mailer_spec.rb +30 -0
  102. data/spec/unit/request_spec.rb +111 -0
  103. data/spec/unit/source/log_parser_spec.rb +119 -0
  104. data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
  105. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  106. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  107. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  108. data/spec/unit/tracker/tracker_api_spec.rb +124 -0
  109. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  110. data/tasks/github-gem.rake +323 -0
  111. data/tasks/request_log_analyzer.rake +26 -0
  112. metadata +220 -0
@@ -0,0 +1,113 @@
1
+ # Module for generating output
2
+ module RequestLogAnalyzer::Output
3
+
4
+ # Load class files if needed
5
+ def self.const_missing(const)
6
+ RequestLogAnalyzer::load_default_class_file(self, const)
7
+ end
8
+
9
+ # Loads a Output::Base subclass instance.
10
+ def self.load(file_format, *args)
11
+
12
+ klass = nil
13
+ if file_format.kind_of?(RequestLogAnalyzer::Output::Base)
14
+ # this already is a file format! return itself
15
+ return file_format
16
+
17
+ elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::Output::Base)
18
+ # a usable class is provided. Use this format class.
19
+ klass = file_format
20
+
21
+ elsif file_format.kind_of?(String) && File.exist?(file_format)
22
+ # load a format from a ruby file
23
+ require file_format
24
+ const = RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb'))
25
+ if RequestLogAnalyzer::FileFormat.const_defined?(const)
26
+ klass = RequestLogAnalyzer::Output.const_get(const)
27
+ elsif Object.const_defined?(const)
28
+ klass = Object.const_get(const)
29
+ else
30
+ raise "Cannot load class #{const} from #{file_format}!"
31
+ end
32
+
33
+ else
34
+ # load a provided file format
35
+ klass = RequestLogAnalyzer::Output.const_get(RequestLogAnalyzer::to_camelcase(file_format))
36
+ end
37
+
38
+ # check the returned klass to see if it can be used
39
+ raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
40
+ raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::Output::Base)
41
+
42
+ klass.create(*args) # return an instance of the class
43
+ end
44
+
45
+ # Base Class used for generating output for reports.
46
+ # All output should inherit fromt this class.
47
+ class Base
48
+
49
+ attr_accessor :io, :options, :style
50
+
51
+ # Initialize a report
52
+ # <tt>io</tt> iO Object (file, STDOUT, etc.)
53
+ # <tt>options</tt> Specific style options
54
+ def initialize(io, options = {})
55
+ @io = io
56
+ @options = options
57
+ @style = options[:style] || { :cell_separator => true, :table_border => false }
58
+ end
59
+
60
+ # Apply a style block.. with style :)
61
+ def with_style(temp_style = {})
62
+ old_style = @style
63
+ @style = @style.merge(temp_style)
64
+ yield(self) if block_given?
65
+ @style = old_style
66
+ end
67
+
68
+ # Generate a header for a report
69
+ def header
70
+ end
71
+
72
+ # Generate the footer of a report
73
+ def footer
74
+ end
75
+
76
+ def slice_results(array)
77
+ return array if options[:amount] == :all
78
+ return array.slice(0, options[:amount]) # otherwise
79
+ end
80
+
81
+ # Generate a report table and push it into the output object.
82
+ # Yeilds a rows array into which the rows can be pushed
83
+ # <tt>*colums<tt> Array of Column hashes (see Column options).
84
+ # <tt>&block</tt>: A block yeilding the rows.
85
+ #
86
+ # === Column options
87
+ # Columns is an array of hashes containing the column definitions.
88
+ # * <tt>:align</tt> Alignment :left or :right
89
+ # * <tt>:treshold</tt> Width in characters or :rest
90
+ # * <tt>:type</tt> :ratio or nil
91
+ # * <tt>:width</tt> Width in characters or :rest
92
+ #
93
+ # === Example
94
+ # The output object should support table definitions:
95
+ #
96
+ # output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
97
+ # sorted_frequencies.each do |(cat, count)|
98
+ # rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
99
+ # end
100
+ # end
101
+ #
102
+ def table(*columns, &block)
103
+ end
104
+
105
+ protected
106
+ # Check if a given table defination hash includes a header (title)
107
+ # <tt>columns</tt> The columns hash
108
+ def table_has_header?(columns)
109
+ columns.any? { |column| !column[:title].nil? }
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,220 @@
1
+ # coding: utf-8
2
+ module RequestLogAnalyzer::Output
3
+
4
+ # Fixed Width output class.
5
+ # Outputs a fixed width ASCII or UF8 report.
6
+ class FixedWidth < Base
7
+
8
+ # Mixin module. Will disable any colorizing.
9
+ module Monochrome
10
+ def colorize(text, *options)
11
+ text
12
+ end
13
+ end
14
+
15
+ # Colorize module
16
+ module Color
17
+
18
+ STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
19
+ COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
20
+
21
+ # Colorize text
22
+ # <tt>text</tt> The text to colorize
23
+ # Options
24
+ # * <tt>:background</tt> The background color to paint. Defined in Color::COLORS
25
+ # * <tt>:color</tt> The foreground color to paint. Defined in Color::COLORS
26
+ # * <tt>:on</tt> Alias for :background
27
+ # * <tt>:style</tt> Font style, defined in Color::STYLES
28
+ #
29
+ # Returns ASCII colored string
30
+ def colorize(text, *options)
31
+
32
+ font_style = ''
33
+ foreground_color = '0'
34
+ background_color = ''
35
+
36
+ options.each do |option|
37
+ if option.kind_of?(Symbol)
38
+ foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
39
+ font_style = "#{STYLES[option]};" if STYLES.include?(option)
40
+ elsif option.kind_of?(Hash)
41
+ options.each do |key, value|
42
+ case key
43
+ when :color; foreground_color = "3#{COLORS[value]}" if COLORS.include?(value)
44
+ when :background; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
45
+ when :on; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
46
+ when :style; font_style = "#{STYLES[value]};" if STYLES.include?(value)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
52
+ end
53
+
54
+ end
55
+
56
+ attr_reader :characters
57
+
58
+ CHARACTERS = {
59
+ :ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
60
+ :utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
61
+ }
62
+
63
+ # Initialize a report
64
+ # <tt>io</tt> iO Object (file, STDOUT, etc.)
65
+ # <tt>options</tt>
66
+ # * <tt>:characters</tt> :utf for UTF8 or :ascii for ANSI compatible output. Defaults to :utf.
67
+ # * <tt>:color</tt> If true, ASCII colorization is used, else Monochrome. Defaults to Monochrome.
68
+ # * <tt>:width</tt> Output width in characters. Defaults to 80.
69
+ def initialize(io, options = {})
70
+ super(io, options)
71
+ @options[:width] ||= 80
72
+ @options[:characters] ||= :utf
73
+ @characters = CHARACTERS[@options[:characters]]
74
+
75
+ color_module = @options[:color] ? Color : Monochrome
76
+ (class << self; self; end).send(:include, color_module)
77
+ end
78
+
79
+ # Write a string to the output object.
80
+ # <tt>str</tt> The string to write.
81
+ def print(str)
82
+ @io << str
83
+ end
84
+
85
+ alias :<< :print
86
+
87
+ # Write a string to the output object with a newline at the end.
88
+ # <tt>str</tt> The string to write.
89
+ def puts(str = '')
90
+ @io << str << "\n"
91
+ end
92
+
93
+ # Write the title of a report
94
+ # <tt>title</tt> The title to write
95
+ def title(title)
96
+ puts
97
+ puts colorize(title, :bold, :white)
98
+ line(:green)
99
+ end
100
+
101
+ # Write a line
102
+ def line(*font)
103
+ puts colorize(characters[:horizontal_line] * @options[:width], *font)
104
+ end
105
+
106
+ # Write a link
107
+ # <tt>text</tt> The text in the link, or the URL itself if no text is given
108
+ # <tt>url</tt> The url to link to.
109
+ def link(text, url = nil)
110
+ if url.nil?
111
+ colorize(text, :blue, :bold)
112
+ else
113
+ "#{text} (#{colorize(url, :blue, :bold)})"
114
+ end
115
+ end
116
+
117
+ # Generate a header for a report
118
+ def header
119
+ if io.kind_of?(File)
120
+ puts colorize("Request-log-analyzer summary report", :white, :bold)
121
+ line(:green)
122
+ puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
123
+ puts "Website: #{link('http://github.com/wvanbergen/request-log-analyzer')}"
124
+ end
125
+ end
126
+
127
+ # Generate a footer for a report
128
+ def footer
129
+ puts
130
+ puts "Need an expert to analyze your application?"
131
+ puts "Mail to #{link('contact@railsdoctors.com')} or visit us at #{link('http://railsdoctors.com')}."
132
+ line(:green)
133
+ puts "Thanks for using #{colorize('request-log-analyzer', :white, :bold)}!"
134
+ end
135
+
136
+ # Generate a report table and push it into the output object.
137
+ # <tt>*colums<tt> Columns hash
138
+ # <tt>&block</tt>: A block yeilding the rows.
139
+ def table(*columns, &block)
140
+
141
+ rows = Array.new
142
+ yield(rows)
143
+
144
+ # determine maximum cell widths
145
+ max_cell_widths = rows.inject(Array.new(columns.length, 0)) do |result, row|
146
+ lengths = row.map { |column| column.to_s.length }
147
+ result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
148
+ end
149
+ columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
150
+
151
+ # determine actual column width
152
+ column_widths = columns.map do |column|
153
+ if column[:width] == :rest
154
+ nil
155
+ elsif column[:width]
156
+ column[:width]
157
+ elsif column[:min_width]
158
+ [column[:min_width], column[:actual_width]].max
159
+ elsif column[:max_width]
160
+ [column[:max_width], column[:actual_width]].min
161
+ else
162
+ column[:actual_width]
163
+ end
164
+ end
165
+
166
+ if column_widths.include?(nil)
167
+ fill_column = columns[column_widths.index(nil)]
168
+ width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
169
+ column_widths[column_widths.index(nil)] = width_left
170
+ end
171
+
172
+ line(:green) if @style[:top_line]
173
+
174
+ # Print table header
175
+ if table_has_header?(columns)
176
+ column_titles = []
177
+ columns.each_with_index do |column, index|
178
+ width = column_widths[index]
179
+ alignment = (column[:align] == :right ? '' : '-')
180
+ column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
181
+ end
182
+
183
+ puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
184
+ line(:green)
185
+ end
186
+
187
+ # Print the rows
188
+ rows.each do |row|
189
+ row_values = []
190
+ columns.each_with_index do |column, index|
191
+ width = column_widths[index]
192
+ case column[:type]
193
+ when :ratio
194
+ if width > 4
195
+ if column[:treshold] && column[:treshold] < row[index].to_f
196
+ bar = ''
197
+ bar << characters[:block] * (width.to_f * column[:treshold]).round
198
+ bar << colorize(characters[:block] * (width.to_f * (row[index].to_f - column[:treshold])).round, :red)
199
+ row_values.push(bar)
200
+ else
201
+ # Create a bar by combining block characters
202
+ row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
203
+ end
204
+ else
205
+ # Too few characters for a ratio bar. Display nothing
206
+ row_values.push('')
207
+ end
208
+ else
209
+ alignment = (columns[index][:align] == :right ? '' : '-')
210
+ cell_value = "%#{alignment}#{width}s" % row[index].to_s[0...width]
211
+ cell_value = colorize(cell_value, :bold, :brown) if columns[index][:highlight]
212
+ row_values.push(cell_value)
213
+ end
214
+ end
215
+ puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
216
+ end
217
+ end
218
+
219
+ end
220
+ end
@@ -0,0 +1,184 @@
1
+ module RequestLogAnalyzer::Output
2
+
3
+ # HTML Output class. Generated a HTML-formatted report, including CSS.
4
+ class HTML < Base
5
+
6
+ # def initialize(io, options = {})
7
+ # super(io, options)
8
+ # end
9
+
10
+ # Print a string to the io object.
11
+ def print(str)
12
+ @io << str
13
+ end
14
+
15
+ alias :<< :print
16
+
17
+ # Put a string with newline
18
+ def puts(str = '')
19
+ @io << str << "<br/>\n"
20
+ end
21
+
22
+ # Place a title
23
+ def title(title)
24
+ @io.puts(tag(:h2, title))
25
+ end
26
+
27
+ # Render a single line
28
+ # <tt>*font</tt> The font.
29
+ def line(*font)
30
+ @io.puts(tag(:hr))
31
+ end
32
+
33
+ # Write a link
34
+ # <tt>text</tt> The text in the link
35
+ # <tt>url</tt> The url to link to.
36
+ def link(text, url = nil)
37
+ url = text if url.nil?
38
+ tag(:a, text, :href => url)
39
+ end
40
+
41
+ # Generate a report table in HTML and push it into the output object.
42
+ # <tt>*colums<tt> Columns hash
43
+ # <tt>&block</tt>: A block yeilding the rows.
44
+ def table(*columns, &block)
45
+ rows = Array.new
46
+ yield(rows)
47
+
48
+ @io << tag(:table, {:id => 'mytable', :cellspacing => 0}) do |content|
49
+ if table_has_header?(columns)
50
+ content << tag(:tr) do
51
+ columns.map { |col| tag(:th, col[:title]) }.join("\n")
52
+ end
53
+ end
54
+
55
+ odd = false
56
+ rows.each do |row|
57
+ odd = !odd
58
+ content << tag(:tr) do
59
+ if odd
60
+ row.map { |cell| tag(:td, cell, :class => 'alt') }.join("\n")
61
+ else
62
+ row.map { |cell| tag(:td, cell) }.join("\n")
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ # Generate HTML content type
71
+ def content_type
72
+ 'text/html; charset="ISO-8859-1";'
73
+ end
74
+
75
+ # Genrate HTML header and associated stylesheet
76
+ def header
77
+ @io.content_type = content_type if @io.respond_to?(:content_type)
78
+
79
+ @io << "<html>"
80
+ @io << tag(:head) do |headers|
81
+ headers << tag(:title, 'Request-log-analyzer report')
82
+ headers << tag(:style, '
83
+ body {
84
+ font: normal 11px auto "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
85
+ color: #4f6b72;
86
+ background: #E6EAE9;
87
+ padding-left:20px;
88
+ padding-top:20px;
89
+ padding-bottom:20px;
90
+ }
91
+
92
+ a {
93
+ color: #c75f3e;
94
+ }
95
+
96
+ .color_bar {
97
+ border: 1px solid;
98
+ height:10px;
99
+ background: #CAE8EA;
100
+ }
101
+
102
+ #mytable {
103
+ width: 700px;
104
+ padding: 0;
105
+ margin: 0;
106
+ padding-bottom:10px;
107
+ }
108
+
109
+ caption {
110
+ padding: 0 0 5px 0;
111
+ width: 700px;
112
+ font: italic 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
113
+ text-align: right;
114
+ }
115
+
116
+ th {
117
+ font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
118
+ color: #4f6b72;
119
+ border-right: 1px solid #C1DAD7;
120
+ border-bottom: 1px solid #C1DAD7;
121
+ border-top: 1px solid #C1DAD7;
122
+ letter-spacing: 2px;
123
+ text-transform: uppercase;
124
+ text-align: left;
125
+ padding: 6px 6px 6px 12px;
126
+ background: #CAE8EA url(images/bg_header.jpg) no-repeat;
127
+ }
128
+
129
+ td {
130
+ font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
131
+ border-right: 1px solid #C1DAD7;
132
+ border-bottom: 1px solid #C1DAD7;
133
+ background: #fff;
134
+ padding: 6px 6px 6px 12px;
135
+ color: #4f6b72;
136
+ }
137
+
138
+ td.alt {
139
+ background: #F5FAFA;
140
+ color: #797268;
141
+ }
142
+ ', :type => "text/css")
143
+ end
144
+ @io << '<body>'
145
+ @io << tag(:h1, 'Request-log-analyzer summary report')
146
+ @io << tag(:p, "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke")
147
+ end
148
+
149
+ # Generate a footer for a report
150
+ def footer
151
+ @io << tag(:hr) << tag(:h2, 'Thanks for using request-log-analyzer')
152
+ @io << tag(:p, 'For more information please visit the ' + link('Request-log-analyzer website', 'http://github.com/wvanbergen/request-log-analyzer'))
153
+ @io << tag(:p, 'If you need an expert who can analyze your application, mail to ' + link('contact@railsdoctors.com', 'mailto:contact@railsdoctors.com') + ' or visit us at ' + link('http://railsdoctors.com', 'http://railsdoctors.com') + '.')
154
+ @io << "</body></html>\n"
155
+ end
156
+
157
+ protected
158
+
159
+ # HTML tag writer helper
160
+ # <tt>tag</tt> The tag to generate
161
+ # <tt>content</tt> The content inside the tag
162
+ # <tt>attributes</tt> Attributes to write in the tag
163
+ def tag(tag, content = nil, attributes = nil)
164
+ if block_given?
165
+ attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
166
+ content_string = ''
167
+ content = yield(content_string)
168
+ content = content_string unless content_string.empty?
169
+ "<#{tag}#{attributes}>#{content}</#{tag}>"
170
+ else
171
+ attributes = attributes.nil? ? '' : ' ' + attributes.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
172
+ if content.nil?
173
+ "<#{tag}#{attributes} />"
174
+ else
175
+ if content.class == Float
176
+ "<#{tag}#{attributes}><div class='color_bar' style=\"width:#{(content*200).floor}px;\"/></#{tag}>"
177
+ else
178
+ "<#{tag}#{attributes}>#{content}</#{tag}>"
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end