ngmoco-request-log-analyzer 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
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