request-log-analyzer 1.3.4 → 1.4.0
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/LICENSE +3 -3
- data/README.rdoc +1 -1
- data/bin/request-log-analyzer +17 -14
- data/lib/cli/command_line_arguments.rb +51 -51
- data/lib/cli/database_console.rb +3 -3
- data/lib/cli/database_console_init.rb +4 -3
- data/lib/cli/progressbar.rb +10 -10
- data/lib/cli/tools.rb +3 -3
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +19 -17
- data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
- data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
- data/lib/request_log_analyzer/aggregator.rb +11 -15
- data/lib/request_log_analyzer/controller.rb +162 -89
- data/lib/request_log_analyzer/database/base.rb +22 -21
- data/lib/request_log_analyzer/database/connection.rb +3 -3
- 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/database.rb +28 -103
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +20 -20
- data/lib/request_log_analyzer/file_format/apache.rb +37 -30
- data/lib/request_log_analyzer/file_format/merb.rb +30 -13
- data/lib/request_log_analyzer/file_format/rack.rb +11 -0
- data/lib/request_log_analyzer/file_format/rails.rb +143 -73
- data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
- data/lib/request_log_analyzer/file_format.rb +16 -28
- data/lib/request_log_analyzer/filter/anonymize.rb +8 -7
- data/lib/request_log_analyzer/filter/field.rb +9 -9
- data/lib/request_log_analyzer/filter/timespan.rb +12 -10
- data/lib/request_log_analyzer/filter.rb +12 -16
- data/lib/request_log_analyzer/line_definition.rb +15 -14
- data/lib/request_log_analyzer/log_processor.rb +27 -29
- data/lib/request_log_analyzer/mailer.rb +15 -9
- data/lib/request_log_analyzer/output/fixed_width.rb +48 -48
- data/lib/request_log_analyzer/output/html.rb +20 -20
- data/lib/request_log_analyzer/output.rb +53 -12
- data/lib/request_log_analyzer/request.rb +88 -69
- data/lib/request_log_analyzer/source/database_loader.rb +10 -14
- data/lib/request_log_analyzer/source/log_parser.rb +57 -50
- data/lib/request_log_analyzer/source.rb +10 -12
- data/lib/request_log_analyzer/tracker/duration.rb +41 -134
- data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +21 -21
- data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
- data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
- data/lib/request_log_analyzer/tracker.rb +139 -32
- data/lib/request_log_analyzer.rb +5 -5
- data/request-log-analyzer.gemspec +19 -15
- data/spec/database.yml +6 -0
- data/spec/fixtures/rails_22.log +1 -1
- data/spec/integration/command_line_usage_spec.rb +7 -1
- data/spec/lib/helpers.rb +7 -7
- data/spec/lib/macros.rb +3 -3
- data/spec/lib/matchers.rb +41 -27
- data/spec/lib/mocks.rb +19 -15
- data/spec/lib/testing_format.rb +9 -9
- data/spec/spec_helper.rb +6 -6
- data/spec/unit/aggregator/database_inserter_spec.rb +16 -16
- data/spec/unit/aggregator/summarizer_spec.rb +4 -4
- data/spec/unit/controller/controller_spec.rb +2 -2
- data/spec/unit/controller/log_processor_spec.rb +1 -1
- data/spec/unit/database/base_class_spec.rb +26 -33
- data/spec/unit/database/connection_spec.rb +3 -3
- data/spec/unit/database/database_spec.rb +33 -38
- data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
- data/spec/unit/file_format/apache_format_spec.rb +48 -11
- data/spec/unit/file_format/file_format_api_spec.rb +13 -13
- data/spec/unit/file_format/line_definition_spec.rb +24 -17
- data/spec/unit/file_format/merb_format_spec.rb +41 -45
- data/spec/unit/file_format/rails_format_spec.rb +157 -117
- data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
- data/spec/unit/filter/field_filter_spec.rb +13 -13
- data/spec/unit/filter/filter_spec.rb +1 -1
- data/spec/unit/filter/timespan_filter_spec.rb +15 -15
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
- data/spec/unit/source/log_parser_spec.rb +27 -27
- data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
- data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
- data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
- data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
- data/spec/unit/tracker/tracker_api_spec.rb +117 -42
- data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
- data/tasks/github-gem.rake +125 -75
- data/tasks/request_log_analyzer.rake +2 -2
- metadata +19 -10
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Filter class loader using const_missing
|
|
4
4
|
# This function will automatically load the class file based on the name of the class
|
|
5
5
|
def self.const_missing(const)
|
|
6
6
|
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
7
7
|
end
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
# Base filter class used to filter input requests.
|
|
10
|
-
# All filters should interit from this base.
|
|
10
|
+
# All filters should interit from this base.
|
|
11
11
|
class Base
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
attr_reader :log_parser
|
|
16
|
-
attr_reader :options
|
|
17
|
-
|
|
12
|
+
|
|
13
|
+
attr_reader :file_format, :options
|
|
14
|
+
|
|
18
15
|
# Initializer
|
|
19
16
|
# <tt>format</tt> The file format
|
|
20
17
|
# <tt>options</tt> Are passed to the filters.
|
|
21
18
|
def initialize(format, options = {})
|
|
22
|
-
@
|
|
23
|
-
|
|
19
|
+
@file_format = format
|
|
20
|
+
@options = options
|
|
24
21
|
end
|
|
25
|
-
|
|
22
|
+
|
|
26
23
|
# Return the request if the request should be kept.
|
|
27
24
|
# Return nil otherwise.
|
|
28
25
|
def filter(request)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
26
|
+
request
|
|
27
|
+
end
|
|
32
28
|
end
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module RequestLogAnalyzer
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# The line definition class is used to specify what lines should be parsed from the log file.
|
|
4
4
|
# It contains functionality to match a line against the definition and parse the information
|
|
5
5
|
# from this line. This is used by the LogParser class when parsing a log file..
|
|
6
6
|
class LineDefinition
|
|
7
7
|
|
|
8
8
|
class Definer
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
attr_accessor :line_definitions
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
def initialize
|
|
13
13
|
@line_definitions = {}
|
|
14
14
|
end
|
|
@@ -16,7 +16,7 @@ module RequestLogAnalyzer
|
|
|
16
16
|
def initialize_copy(other)
|
|
17
17
|
@line_definitions = other.line_definitions.dup
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
def method_missing(name, *args, &block)
|
|
21
21
|
if block_given?
|
|
22
22
|
@line_definitions[name] = RequestLogAnalyzer::LineDefinition.define(name, &block)
|
|
@@ -29,25 +29,26 @@ module RequestLogAnalyzer
|
|
|
29
29
|
attr_reader :name
|
|
30
30
|
attr_accessor :teaser, :regexp, :captures
|
|
31
31
|
attr_accessor :header, :footer
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
alias_method :header?, :header
|
|
34
34
|
alias_method :footer?, :footer
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
# Initializes the LineDefinition instance with a hash containing the different elements of
|
|
37
37
|
# the definition.
|
|
38
38
|
def initialize(name, definition = {})
|
|
39
39
|
@name = name
|
|
40
40
|
@captures = []
|
|
41
|
+
@teaser = nil
|
|
41
42
|
definition.each { |key, value| self.send("#{key.to_s}=".to_sym, value) }
|
|
42
43
|
end
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
def self.define(name, &block)
|
|
45
46
|
definition = self.new(name)
|
|
46
47
|
yield(definition) if block_given?
|
|
47
48
|
return definition
|
|
48
49
|
end
|
|
49
|
-
|
|
50
|
-
# Checks whether a given line matches this definition.
|
|
50
|
+
|
|
51
|
+
# Checks whether a given line matches this definition.
|
|
51
52
|
# It will return false if a line does not match. If the line matches, a hash is returned
|
|
52
53
|
# with all the fields parsed from that line as content.
|
|
53
54
|
# If the line definition has a teaser-check, a :teaser_check_failed warning will be emitted
|
|
@@ -66,7 +67,7 @@ module RequestLogAnalyzer
|
|
|
66
67
|
return false
|
|
67
68
|
end
|
|
68
69
|
end
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
alias :=~ :matches
|
|
71
72
|
|
|
72
73
|
# matches the line and converts the captured values using the request's
|
|
@@ -84,17 +85,17 @@ module RequestLogAnalyzer
|
|
|
84
85
|
def convert_captured_values(values, request)
|
|
85
86
|
value_hash = {}
|
|
86
87
|
captures.each_with_index do |capture, index|
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
# convert the value using the request convert_value function
|
|
89
90
|
converted = request.convert_value(values[index], capture)
|
|
90
91
|
value_hash[capture[:name]] ||= converted
|
|
91
|
-
|
|
92
|
+
|
|
92
93
|
# Add items directly to the resulting hash from the converted value
|
|
93
94
|
# if it is a hash and they are set in the :provides hash for this line definition
|
|
94
95
|
if converted.kind_of?(Hash) && capture[:provides].kind_of?(Hash)
|
|
95
96
|
capture[:provides].each do |name, type|
|
|
96
97
|
value_hash[name] ||= request.convert_value(converted[name], { :type => type })
|
|
97
|
-
end
|
|
98
|
+
end
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
return value_hash
|
|
@@ -106,5 +107,5 @@ module RequestLogAnalyzer
|
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
end
|
|
109
|
-
|
|
110
|
+
|
|
110
111
|
end
|
|
@@ -1,55 +1,53 @@
|
|
|
1
1
|
module RequestLogAnalyzer
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# The Logprocessor class is used to perform simple processing actions over log files.
|
|
4
|
-
# It will go over the log file/stream line by line, pass the line to a processor and
|
|
5
|
-
# write the result back to the output file or stream. The processor can alter the
|
|
4
|
+
# It will go over the log file/stream line by line, pass the line to a processor and
|
|
5
|
+
# write the result back to the output file or stream. The processor can alter the
|
|
6
6
|
# contents of the line, remain it intact or remove it altogether, based on the current
|
|
7
7
|
# file format
|
|
8
8
|
#
|
|
9
9
|
# Currently, one processors is supported:
|
|
10
|
-
# * :strip will remove all irrelevent lines (according to the file format) from the
|
|
10
|
+
# * :strip will remove all irrelevent lines (according to the file format) from the
|
|
11
11
|
# sources. A compact, information packed log will remain/.
|
|
12
12
|
#
|
|
13
13
|
class LogProcessor
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
attr_reader :mode, :options, :sources
|
|
14
|
+
|
|
15
|
+
attr_reader :mode, :options, :sources, :file_format
|
|
18
16
|
attr_accessor :output_file
|
|
19
|
-
|
|
17
|
+
|
|
20
18
|
# Builds a logprocessor instance from the arguments given on the command line
|
|
21
|
-
# <tt>command</tt> The command hat was used to start the log processor. This will set the
|
|
19
|
+
# <tt>command</tt> The command hat was used to start the log processor. This will set the
|
|
22
20
|
# processing mode. Currently, only :strip is supported.
|
|
23
21
|
# <tt>arguments</tt> The parsed command line arguments (a CommandLine::Arguments instance)
|
|
24
22
|
def self.build(command, arguments)
|
|
25
|
-
|
|
26
|
-
options = {
|
|
27
|
-
:discard_teaser_lines => arguments[:discard_teaser_lines],
|
|
28
|
-
:keep_junk_lines => arguments[:keep_junk_lines],
|
|
23
|
+
|
|
24
|
+
options = {
|
|
25
|
+
:discard_teaser_lines => arguments[:discard_teaser_lines],
|
|
26
|
+
:keep_junk_lines => arguments[:keep_junk_lines],
|
|
29
27
|
}
|
|
30
|
-
|
|
28
|
+
|
|
31
29
|
log_processor = RequestLogAnalyzer::LogProcessor.new(arguments[:format].to_sym, command, options)
|
|
32
30
|
log_processor.output_file = arguments[:output] if arguments[:output]
|
|
33
31
|
|
|
34
32
|
arguments.parameters.each do |input|
|
|
35
33
|
log_processor.sources << input
|
|
36
34
|
end
|
|
37
|
-
|
|
35
|
+
|
|
38
36
|
return log_processor
|
|
39
37
|
end
|
|
40
|
-
|
|
38
|
+
|
|
41
39
|
# Initializes a new LogProcessor instance.
|
|
42
40
|
# <tt>format</tt> The file format to use (e.g. :rails).
|
|
43
41
|
# <tt>mode</tt> The processing mode
|
|
44
42
|
# <tt>options</tt> A hash with options to take into account
|
|
45
43
|
def initialize(format, mode, options = {})
|
|
46
|
-
@options
|
|
47
|
-
@mode
|
|
48
|
-
@sources
|
|
44
|
+
@options = options
|
|
45
|
+
@mode = mode
|
|
46
|
+
@sources = []
|
|
47
|
+
@file_format = format
|
|
49
48
|
$output_file = nil
|
|
50
|
-
self.register_file_format(format)
|
|
51
49
|
end
|
|
52
|
-
|
|
50
|
+
|
|
53
51
|
# Processes input files by opening it and sending the filestream to <code>process_io</code>,
|
|
54
52
|
# in which the actual processing is performed.
|
|
55
53
|
# <tt>file</tt> The file to process
|
|
@@ -65,7 +63,7 @@ module RequestLogAnalyzer
|
|
|
65
63
|
when :strip; io.each_line { |line| @output << strip_line(line) }
|
|
66
64
|
end
|
|
67
65
|
end
|
|
68
|
-
|
|
66
|
+
|
|
69
67
|
# Returns the line itself if the string matches any of the line definitions. If no match is
|
|
70
68
|
# found, an empty line is returned, which will strip the line from the output.
|
|
71
69
|
# <tt>line</tt> The line to strip
|
|
@@ -74,25 +72,25 @@ module RequestLogAnalyzer
|
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
# Runs the log processing by setting up the output stream and iterating over all the
|
|
77
|
-
# input sources. Input sources can either be filenames (String instances) or IO streams
|
|
75
|
+
# input sources. Input sources can either be filenames (String instances) or IO streams
|
|
78
76
|
# (IO instances). The strings "-" and "STDIN" will be substituted for the $stdin variable.
|
|
79
77
|
def run!
|
|
80
78
|
if @output_file.nil?
|
|
81
|
-
@output = $stdout
|
|
79
|
+
@output = $stdout
|
|
82
80
|
else
|
|
83
|
-
@output = File.new(@output_file, 'a')
|
|
81
|
+
@output = File.new(@output_file, 'a')
|
|
84
82
|
end
|
|
85
|
-
|
|
83
|
+
|
|
86
84
|
@sources.each do |source|
|
|
87
85
|
if source.kind_of?(String) && File.exist?(source)
|
|
88
86
|
process_file(source)
|
|
89
87
|
elsif source.kind_of?(IO)
|
|
90
88
|
process_io(source)
|
|
91
89
|
elsif ['-', 'STDIN'].include?(source)
|
|
92
|
-
process_io($stdin)
|
|
90
|
+
process_io($stdin)
|
|
93
91
|
end
|
|
94
92
|
end
|
|
95
|
-
|
|
93
|
+
|
|
96
94
|
ensure
|
|
97
95
|
@output.close if @output.kind_of?(File)
|
|
98
96
|
end
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
module RequestLogAnalyzer
|
|
2
2
|
|
|
3
3
|
class Mailer
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
attr_accessor :data, :to, :host
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
# Initialize a mailer
|
|
8
8
|
# <tt>to</tt> to address
|
|
9
9
|
# <tt>host</tt> the mailer host
|
|
10
10
|
# <tt>options</tt> Specific style options
|
|
11
|
+
# Options
|
|
12
|
+
# <tt>:debug</tt> Do not actually mail
|
|
11
13
|
def initialize(to, host = 'localhost', options = {})
|
|
12
|
-
require 'net/smtp'
|
|
14
|
+
require 'net/smtp'
|
|
13
15
|
@to = to
|
|
14
16
|
@host = host
|
|
15
17
|
@options = options
|
|
16
18
|
@data = []
|
|
17
19
|
end
|
|
18
|
-
|
|
20
|
+
|
|
19
21
|
def mail
|
|
20
22
|
from = @options[:from] || 'contact@railsdoctors.com'
|
|
21
23
|
from_alias = @options[:from_alias] || 'Request-log-analyzer reporter'
|
|
@@ -28,16 +30,20 @@ Subject: #{subject}
|
|
|
28
30
|
|
|
29
31
|
#{@data.to_s}
|
|
30
32
|
END_OF_MESSAGE
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
unless @options[:debug]
|
|
35
|
+
Net::SMTP.start(@host) do |smtp|
|
|
36
|
+
smtp.send_message msg, from, to
|
|
37
|
+
end
|
|
34
38
|
end
|
|
39
|
+
|
|
40
|
+
return [msg, from, to]
|
|
35
41
|
end
|
|
36
|
-
|
|
42
|
+
|
|
37
43
|
def << string
|
|
38
44
|
data << string
|
|
39
45
|
end
|
|
40
|
-
|
|
46
|
+
|
|
41
47
|
def puts string
|
|
42
48
|
data << string
|
|
43
49
|
end
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
module RequestLogAnalyzer::Output
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
# Fixed Width output class.
|
|
5
5
|
# Outputs a fixed width ASCII or UF8 report.
|
|
6
6
|
class FixedWidth < Base
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
# Mixin module. Will disable any colorizing.
|
|
9
9
|
module Monochrome
|
|
10
10
|
def colorize(text, *options)
|
|
11
11
|
text
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Colorize module
|
|
16
16
|
module Color
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
|
|
19
19
|
COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
# Colorize text
|
|
22
22
|
# <tt>text</tt> The text to colorize
|
|
23
23
|
# Options
|
|
@@ -25,18 +25,18 @@ module RequestLogAnalyzer::Output
|
|
|
25
25
|
# * <tt>:color</tt> The foreground color to paint. Defined in Color::COLORS
|
|
26
26
|
# * <tt>:on</tt> Alias for :background
|
|
27
27
|
# * <tt>:style</tt> Font style, defined in Color::STYLES
|
|
28
|
-
#
|
|
28
|
+
#
|
|
29
29
|
# Returns ASCII colored string
|
|
30
30
|
def colorize(text, *options)
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
font_style = ''
|
|
33
33
|
foreground_color = '0'
|
|
34
34
|
background_color = ''
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
options.each do |option|
|
|
37
37
|
if option.kind_of?(Symbol)
|
|
38
38
|
foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
|
|
39
|
-
font_style = "#{STYLES[option]};" if STYLES.include?(option)
|
|
39
|
+
font_style = "#{STYLES[option]};" if STYLES.include?(option)
|
|
40
40
|
elsif option.kind_of?(Hash)
|
|
41
41
|
options.each do |key, value|
|
|
42
42
|
case key
|
|
@@ -50,16 +50,16 @@ module RequestLogAnalyzer::Output
|
|
|
50
50
|
end
|
|
51
51
|
return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
|
|
52
52
|
end
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
end
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
attr_reader :characters
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
CHARACTERS = {
|
|
59
59
|
:ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
|
|
60
60
|
:utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
# Initialize a report
|
|
64
64
|
# <tt>io</tt> iO Object (file, STDOUT, etc.)
|
|
65
65
|
# <tt>options</tt>
|
|
@@ -71,25 +71,25 @@ module RequestLogAnalyzer::Output
|
|
|
71
71
|
@options[:width] ||= 80
|
|
72
72
|
@options[:characters] ||= :utf
|
|
73
73
|
@characters = CHARACTERS[@options[:characters]]
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
color_module = @options[:color] ? Color : Monochrome
|
|
76
|
-
(class << self; self; end).send(:include, color_module)
|
|
76
|
+
(class << self; self; end).send(:include, color_module)
|
|
77
77
|
end
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
# Write a string to the output object.
|
|
80
80
|
# <tt>str</tt> The string to write.
|
|
81
81
|
def print(str)
|
|
82
82
|
@io << str
|
|
83
83
|
end
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
alias :<< :print
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
# Write a string to the output object with a newline at the end.
|
|
88
88
|
# <tt>str</tt> The string to write.
|
|
89
89
|
def puts(str = '')
|
|
90
90
|
@io << str << "\n"
|
|
91
91
|
end
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
# Write the title of a report
|
|
94
94
|
# <tt>title</tt> The title to write
|
|
95
95
|
def title(title)
|
|
@@ -97,14 +97,14 @@ module RequestLogAnalyzer::Output
|
|
|
97
97
|
puts colorize(title, :bold, :white)
|
|
98
98
|
line(:green)
|
|
99
99
|
end
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
# Write a line
|
|
102
|
-
def line(*font)
|
|
102
|
+
def line(*font)
|
|
103
103
|
puts colorize(characters[:horizontal_line] * @options[:width], *font)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
# Write a link
|
|
107
|
-
# <tt>text</tt> The text in the link
|
|
107
|
+
# <tt>text</tt> The text in the link, or the URL itself if no text is given
|
|
108
108
|
# <tt>url</tt> The url to link to.
|
|
109
109
|
def link(text, url = nil)
|
|
110
110
|
if url.nil?
|
|
@@ -113,30 +113,31 @@ module RequestLogAnalyzer::Output
|
|
|
113
113
|
"#{text} (#{colorize(url, :blue, :bold)})"
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
|
-
|
|
117
|
-
# Generate a header for a report
|
|
116
|
+
|
|
117
|
+
# Generate a header for a report
|
|
118
118
|
def header
|
|
119
119
|
if io.kind_of?(File)
|
|
120
|
-
puts "Request-log-analyzer summary report"
|
|
121
|
-
line
|
|
120
|
+
puts colorize("Request-log-analyzer summary report", :white, :bold)
|
|
121
|
+
line(:green)
|
|
122
122
|
puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
|
|
123
|
-
puts "
|
|
123
|
+
puts "Website: #{link('http://github.com/wvanbergen/request-log-analyzer')}"
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
# Generate a footer for a report
|
|
128
128
|
def footer
|
|
129
129
|
puts
|
|
130
130
|
puts "Need an expert to analyze your application?"
|
|
131
|
-
puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
|
|
132
|
-
|
|
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)}!"
|
|
133
134
|
end
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
# Generate a report table and push it into the output object.
|
|
136
137
|
# <tt>*colums<tt> Columns hash
|
|
137
138
|
# <tt>&block</tt>: A block yeilding the rows.
|
|
138
139
|
def table(*columns, &block)
|
|
139
|
-
|
|
140
|
+
|
|
140
141
|
rows = Array.new
|
|
141
142
|
yield(rows)
|
|
142
143
|
|
|
@@ -146,7 +147,7 @@ module RequestLogAnalyzer::Output
|
|
|
146
147
|
result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
|
|
147
148
|
end
|
|
148
149
|
columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
# determine actual column width
|
|
151
152
|
column_widths = columns.map do |column|
|
|
152
153
|
if column[:width] == :rest
|
|
@@ -161,29 +162,28 @@ module RequestLogAnalyzer::Output
|
|
|
161
162
|
column[:actual_width]
|
|
162
163
|
end
|
|
163
164
|
end
|
|
164
|
-
|
|
165
|
+
|
|
165
166
|
if column_widths.include?(nil)
|
|
166
167
|
fill_column = columns[column_widths.index(nil)]
|
|
167
168
|
width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
|
|
168
|
-
column_widths[column_widths.index(nil)] =
|
|
169
|
-
when :ratio; width_left # max out
|
|
170
|
-
else; [width_left, fill_column[:actual_width]].min
|
|
171
|
-
end
|
|
169
|
+
column_widths[column_widths.index(nil)] = width_left
|
|
172
170
|
end
|
|
173
|
-
|
|
171
|
+
|
|
172
|
+
line(:green) if @style[:top_line]
|
|
173
|
+
|
|
174
174
|
# Print table header
|
|
175
175
|
if table_has_header?(columns)
|
|
176
176
|
column_titles = []
|
|
177
177
|
columns.each_with_index do |column, index|
|
|
178
|
-
width = column_widths[index]
|
|
178
|
+
width = column_widths[index]
|
|
179
179
|
alignment = (column[:align] == :right ? '' : '-')
|
|
180
180
|
column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
|
|
181
181
|
end
|
|
182
|
-
|
|
182
|
+
|
|
183
183
|
puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
|
184
184
|
line(:green)
|
|
185
185
|
end
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
# Print the rows
|
|
188
188
|
rows.each do |row|
|
|
189
189
|
row_values = []
|
|
@@ -194,12 +194,12 @@ module RequestLogAnalyzer::Output
|
|
|
194
194
|
if width > 4
|
|
195
195
|
if column[:treshold] && column[:treshold] < row[index].to_f
|
|
196
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)
|
|
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
200
|
else
|
|
201
201
|
# Create a bar by combining block characters
|
|
202
|
-
row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
|
|
202
|
+
row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
|
|
203
203
|
end
|
|
204
204
|
else
|
|
205
205
|
# Too few characters for a ratio bar. Display nothing
|
|
@@ -212,9 +212,9 @@ module RequestLogAnalyzer::Output
|
|
|
212
212
|
row_values.push(cell_value)
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
|
-
puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
|
215
|
+
puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
|
216
216
|
end
|
|
217
217
|
end
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
end
|
|
220
220
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Output
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# HTML Output class. Generated a HTML-formatted report, including CSS.
|
|
4
4
|
class HTML < Base
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
# def initialize(io, options = {})
|
|
7
7
|
# super(io, options)
|
|
8
8
|
# end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
# Print a string to the io object.
|
|
11
11
|
def print(str)
|
|
12
12
|
@io << str
|
|
@@ -26,10 +26,10 @@ module RequestLogAnalyzer::Output
|
|
|
26
26
|
|
|
27
27
|
# Render a single line
|
|
28
28
|
# <tt>*font</tt> The font.
|
|
29
|
-
def line(*font)
|
|
29
|
+
def line(*font)
|
|
30
30
|
@io.puts(tag(:hr))
|
|
31
31
|
end
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
# Write a link
|
|
34
34
|
# <tt>text</tt> The text in the link
|
|
35
35
|
# <tt>url</tt> The url to link to.
|
|
@@ -44,29 +44,29 @@ module RequestLogAnalyzer::Output
|
|
|
44
44
|
def table(*columns, &block)
|
|
45
45
|
rows = Array.new
|
|
46
46
|
yield(rows)
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
@io << tag(:table, {:id => 'mytable', :cellspacing => 0}) do |content|
|
|
49
49
|
if table_has_header?(columns)
|
|
50
50
|
content << tag(:tr) do
|
|
51
51
|
columns.map { |col| tag(:th, col[:title]) }.join("\n")
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
odd = false
|
|
56
56
|
rows.each do |row|
|
|
57
57
|
odd = !odd
|
|
58
58
|
content << tag(:tr) do
|
|
59
59
|
if odd
|
|
60
|
-
row.map { |cell| tag(:td, cell, :class => 'alt') }.join("\n")
|
|
60
|
+
row.map { |cell| tag(:td, cell, :class => 'alt') }.join("\n")
|
|
61
61
|
else
|
|
62
|
-
row.map { |cell| tag(:td, cell) }.join("\n")
|
|
62
|
+
row.map { |cell| tag(:td, cell) }.join("\n")
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
end
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
# Genrate HTML header and associated stylesheet
|
|
71
71
|
def header
|
|
72
72
|
@io << "<html>"
|
|
@@ -85,13 +85,13 @@ module RequestLogAnalyzer::Output
|
|
|
85
85
|
a {
|
|
86
86
|
color: #c75f3e;
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
.color_bar {
|
|
90
90
|
border: 1px solid;
|
|
91
91
|
height:10px;
|
|
92
92
|
background: #CAE8EA;
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
#mytable {
|
|
96
96
|
width: 700px;
|
|
97
97
|
padding: 0;
|
|
@@ -101,7 +101,7 @@ module RequestLogAnalyzer::Output
|
|
|
101
101
|
|
|
102
102
|
caption {
|
|
103
103
|
padding: 0 0 5px 0;
|
|
104
|
-
width: 700px;
|
|
104
|
+
width: 700px;
|
|
105
105
|
font: italic 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
|
|
106
106
|
text-align: right;
|
|
107
107
|
}
|
|
@@ -138,7 +138,7 @@ module RequestLogAnalyzer::Output
|
|
|
138
138
|
@io << tag(:h1, 'Request-log-analyzer summary report')
|
|
139
139
|
@io << tag(:p, "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke")
|
|
140
140
|
end
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
# Generate a footer for a report
|
|
143
143
|
def footer
|
|
144
144
|
@io << tag(:hr) << tag(:h2, 'Thanks for using request-log-analyzer')
|
|
@@ -146,9 +146,9 @@ module RequestLogAnalyzer::Output
|
|
|
146
146
|
@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') + '.')
|
|
147
147
|
@io << "</body></html>\n"
|
|
148
148
|
end
|
|
149
|
-
|
|
150
|
-
protected
|
|
151
|
-
|
|
149
|
+
|
|
150
|
+
protected
|
|
151
|
+
|
|
152
152
|
# HTML tag writer helper
|
|
153
153
|
# <tt>tag</tt> The tag to generate
|
|
154
154
|
# <tt>content</tt> The content inside the tag
|
|
@@ -158,7 +158,7 @@ module RequestLogAnalyzer::Output
|
|
|
158
158
|
attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
|
|
159
159
|
content_string = ''
|
|
160
160
|
content = yield(content_string)
|
|
161
|
-
content = content_string unless content_string.empty?
|
|
161
|
+
content = content_string unless content_string.empty?
|
|
162
162
|
"<#{tag}#{attributes}>#{content}</#{tag}>"
|
|
163
163
|
else
|
|
164
164
|
attributes = attributes.nil? ? '' : ' ' + attributes.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
|
|
@@ -172,6 +172,6 @@ module RequestLogAnalyzer::Output
|
|
|
172
172
|
end
|
|
173
173
|
end
|
|
174
174
|
end
|
|
175
|
-
end
|
|
175
|
+
end
|
|
176
176
|
end
|
|
177
177
|
end
|