ngmoco-request-log-analyzer 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/DESIGN.rdoc +41 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +8 -0
- data/bin/request-log-analyzer +114 -0
- data/lib/cli/command_line_arguments.rb +301 -0
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +43 -0
- data/lib/cli/progressbar.rb +213 -0
- data/lib/cli/tools.rb +46 -0
- data/lib/request_log_analyzer.rb +44 -0
- data/lib/request_log_analyzer/aggregator.rb +49 -0
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
- data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
- data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
- data/lib/request_log_analyzer/controller.rb +332 -0
- data/lib/request_log_analyzer/database.rb +102 -0
- data/lib/request_log_analyzer/database/base.rb +115 -0
- data/lib/request_log_analyzer/database/connection.rb +38 -0
- data/lib/request_log_analyzer/database/request.rb +22 -0
- data/lib/request_log_analyzer/database/source.rb +13 -0
- data/lib/request_log_analyzer/database/warning.rb +14 -0
- data/lib/request_log_analyzer/file_format.rb +160 -0
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
- data/lib/request_log_analyzer/file_format/apache.rb +141 -0
- data/lib/request_log_analyzer/file_format/merb.rb +67 -0
- data/lib/request_log_analyzer/file_format/rack.rb +11 -0
- data/lib/request_log_analyzer/file_format/rails.rb +176 -0
- data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
- data/lib/request_log_analyzer/filter.rb +30 -0
- data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
- data/lib/request_log_analyzer/filter/field.rb +42 -0
- data/lib/request_log_analyzer/filter/timespan.rb +45 -0
- data/lib/request_log_analyzer/line_definition.rb +111 -0
- data/lib/request_log_analyzer/log_processor.rb +99 -0
- data/lib/request_log_analyzer/mailer.rb +62 -0
- data/lib/request_log_analyzer/output.rb +113 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
- data/lib/request_log_analyzer/output/html.rb +184 -0
- data/lib/request_log_analyzer/request.rb +175 -0
- data/lib/request_log_analyzer/source.rb +72 -0
- data/lib/request_log_analyzer/source/database_loader.rb +87 -0
- data/lib/request_log_analyzer/source/log_parser.rb +274 -0
- data/lib/request_log_analyzer/tracker.rb +206 -0
- data/lib/request_log_analyzer/tracker/duration.rb +104 -0
- data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
- data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
- data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
- data/request-log-analyzer.gemspec +40 -0
- data/spec/database.yml +23 -0
- data/spec/fixtures/apache_combined.log +5 -0
- data/spec/fixtures/apache_common.log +10 -0
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/fixtures/header_and_footer.log +6 -0
- data/spec/fixtures/merb.log +84 -0
- data/spec/fixtures/merb_prefixed.log +9 -0
- data/spec/fixtures/multiple_files_1.log +5 -0
- data/spec/fixtures/multiple_files_2.log +2 -0
- data/spec/fixtures/rails.db +0 -0
- data/spec/fixtures/rails_1x.log +59 -0
- data/spec/fixtures/rails_22.log +12 -0
- data/spec/fixtures/rails_22_cached.log +10 -0
- data/spec/fixtures/rails_unordered.log +24 -0
- data/spec/fixtures/syslog_1x.log +5 -0
- data/spec/fixtures/test_file_format.log +13 -0
- data/spec/fixtures/test_language_combined.log +14 -0
- data/spec/fixtures/test_order.log +16 -0
- data/spec/integration/command_line_usage_spec.rb +84 -0
- data/spec/integration/munin_plugins_rails_spec.rb +58 -0
- data/spec/integration/scout_spec.rb +151 -0
- data/spec/lib/helpers.rb +52 -0
- data/spec/lib/macros.rb +18 -0
- data/spec/lib/matchers.rb +77 -0
- data/spec/lib/mocks.rb +76 -0
- data/spec/lib/testing_format.rb +46 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
- data/spec/unit/aggregator/summarizer_spec.rb +26 -0
- data/spec/unit/controller/controller_spec.rb +41 -0
- data/spec/unit/controller/log_processor_spec.rb +18 -0
- data/spec/unit/database/base_class_spec.rb +183 -0
- data/spec/unit/database/connection_spec.rb +34 -0
- data/spec/unit/database/database_spec.rb +133 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
- data/spec/unit/file_format/apache_format_spec.rb +203 -0
- data/spec/unit/file_format/file_format_api_spec.rb +69 -0
- data/spec/unit/file_format/line_definition_spec.rb +75 -0
- data/spec/unit/file_format/merb_format_spec.rb +52 -0
- data/spec/unit/file_format/rails_format_spec.rb +164 -0
- data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
- data/spec/unit/filter/field_filter_spec.rb +66 -0
- data/spec/unit/filter/filter_spec.rb +17 -0
- data/spec/unit/filter/timespan_filter_spec.rb +58 -0
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/request_spec.rb +111 -0
- data/spec/unit/source/log_parser_spec.rb +119 -0
- data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
- data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
- data/spec/unit/tracker/tracker_api_spec.rb +124 -0
- data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
- data/tasks/github-gem.rake +323 -0
- data/tasks/request_log_analyzer.rake +26 -0
- 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
|