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.
- 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
|