wvanbergen-request-log-analyzer 1.1.0 → 1.1.1
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/README.rdoc +4 -3
- data/bin/request-log-analyzer +4 -5
- data/lib/cli/command_line_arguments.rb +2 -2
- data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -3
- data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +5 -1
- data/lib/request_log_analyzer/controller.rb +11 -16
- data/lib/request_log_analyzer/file_format/merb.rb +32 -26
- data/lib/request_log_analyzer/file_format/rails.rb +73 -71
- data/lib/request_log_analyzer/file_format/rails_development.rb +93 -95
- data/lib/request_log_analyzer/file_format.rb +71 -38
- data/lib/request_log_analyzer/filter/anonimize.rb +1 -1
- data/lib/request_log_analyzer/filter.rb +38 -0
- data/lib/request_log_analyzer/line_definition.rb +1 -1
- data/lib/request_log_analyzer/output/fixed_width.rb +133 -117
- data/lib/request_log_analyzer/output/html.rb +138 -60
- data/lib/request_log_analyzer/output.rb +6 -8
- data/lib/request_log_analyzer/request.rb +3 -1
- data/lib/request_log_analyzer/source/{log_file.rb → log_parser.rb} +15 -6
- data/lib/request_log_analyzer/{source/base.rb → source.rb} +5 -0
- data/lib/request_log_analyzer/tracker/category.rb +7 -8
- data/lib/request_log_analyzer/tracker/duration.rb +15 -12
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +8 -8
- data/lib/request_log_analyzer/tracker/timespan.rb +10 -10
- data/lib/request_log_analyzer/tracker.rb +58 -0
- data/lib/request_log_analyzer.rb +28 -6
- data/spec/controller_spec.rb +5 -4
- data/spec/database_inserter_spec.rb +5 -8
- data/spec/file_format_spec.rb +2 -2
- data/spec/file_formats/spec_format.rb +2 -1
- data/spec/filter_spec.rb +0 -3
- data/spec/log_parser_spec.rb +6 -6
- data/spec/merb_format_spec.rb +38 -38
- data/spec/rails_format_spec.rb +2 -2
- data/spec/request_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -37
- data/tasks/github-gem.rake +2 -1
- metadata +7 -8
- data/lib/request_log_analyzer/filter/base.rb +0 -32
- data/lib/request_log_analyzer/log_parser.rb +0 -173
- data/lib/request_log_analyzer/tracker/base.rb +0 -54
@@ -1,29 +1,80 @@
|
|
1
|
-
module RequestLogAnalyzer
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
2
2
|
|
3
|
-
|
3
|
+
|
4
|
+
def self.const_missing(const)
|
5
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Loads a FileFormat::Base subclass instance.
|
9
|
+
# You can provide:
|
10
|
+
# * A FileFormat instance (which will return itself)
|
11
|
+
# * A FileFormat class (of which an imstance will be returned)
|
12
|
+
# * A filename (from which the FileFormat class is loaded)
|
13
|
+
# * A symbol of a built-in file format (e.g. :rails)
|
14
|
+
def self.load(file_format)
|
15
|
+
klass = nil
|
16
|
+
if file_format.kind_of?(RequestLogAnalyzer::FileFormat::Base)
|
17
|
+
# this already is a file format! return itself
|
18
|
+
return @current_file_format = file_format
|
19
|
+
|
20
|
+
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
21
|
+
# a usable class is provided. Use this format class.
|
22
|
+
klass = file_format
|
23
|
+
|
24
|
+
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
25
|
+
# load a format from a ruby file
|
26
|
+
require file_format
|
27
|
+
klass = Object.const_get(RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb')))
|
28
|
+
|
29
|
+
else
|
30
|
+
# load a provided file format
|
31
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
32
|
+
end
|
4
33
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
34
|
+
raise if klass.nil?
|
35
|
+
@current_file_format = klass.new # return an instance of the class
|
36
|
+
end
|
37
|
+
|
38
|
+
# Makes classes aware of a file format by registering the file_format variable
|
39
|
+
module Awareness
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.send(:attr_reader, :file_format)
|
15
43
|
end
|
44
|
+
|
45
|
+
def register_file_format(format)
|
46
|
+
@file_format = format
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Base class for all log file format definitions. This class provides functions for subclasses to
|
51
|
+
# define their LineDefinitions and to define a summary report.
|
52
|
+
#
|
53
|
+
# A subclass of this class is instantiated when request-log-analyzer is started and this instance
|
54
|
+
# is shared with all components of the application so they can act on the specifics of the format
|
55
|
+
class Base
|
16
56
|
|
57
|
+
# Registers the line definer instance for a subclass.
|
17
58
|
def self.inherited(subclass)
|
18
59
|
subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
|
19
60
|
subclass.class_eval { class << self; attr_accessor :line_definer; end }
|
20
|
-
subclass.class_eval { class << self; attr_accessor :report_definer; end }
|
61
|
+
subclass.class_eval { class << self; attr_accessor :report_definer; end }
|
62
|
+
|
63
|
+
unless subclass.const_defined?('Request')
|
64
|
+
subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request))
|
65
|
+
end
|
21
66
|
end
|
22
67
|
|
68
|
+
# Specifies a single line defintions.
|
23
69
|
def self.line_definition(name, &block)
|
24
70
|
@line_definer.send(name, &block)
|
25
71
|
end
|
26
72
|
|
73
|
+
def create_request(*hashes)
|
74
|
+
self.class::Request.create(self, *hashes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Specifies multiple line definitions at once using a block
|
27
78
|
def self.format_definition(&block)
|
28
79
|
if block_given?
|
29
80
|
yield(@line_definer)
|
@@ -32,48 +83,30 @@ module RequestLogAnalyzer
|
|
32
83
|
end
|
33
84
|
end
|
34
85
|
|
86
|
+
# Specifies the summary report using a block.
|
35
87
|
def self.report(&block)
|
36
88
|
@report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
|
37
89
|
yield(@report_definer)
|
38
90
|
end
|
39
91
|
|
40
|
-
|
41
|
-
if file_format.kind_of?(RequestLogAnalyzer::FileFormat)
|
42
|
-
# this already is a file format! return itself
|
43
|
-
return file_format
|
44
|
-
|
45
|
-
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat)
|
46
|
-
klass = file_format
|
47
|
-
|
48
|
-
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
49
|
-
# load a format from a ruby file
|
50
|
-
require file_format
|
51
|
-
klass_name = File.basename(file_format, '.rb').split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join('')
|
52
|
-
klass = Object.const_get(klass_name)
|
53
|
-
|
54
|
-
elsif File.exist?("#{File.dirname(__FILE__)}/file_format/#{file_format}.rb")
|
55
|
-
# load a provided file format
|
56
|
-
require "#{File.dirname(__FILE__)}/file_format/#{file_format}"
|
57
|
-
klass_name = file_format.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join('')
|
58
|
-
klass = RequestLogAnalyzer::FileFormat.const_get(klass_name)
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
klass.new # return an instance of the class
|
63
|
-
end
|
64
|
-
|
92
|
+
# Returns all line definitions
|
65
93
|
def line_definitions
|
66
94
|
@line_definitions ||= self.class.line_definer.line_definitions
|
67
95
|
end
|
68
96
|
|
97
|
+
# Returns all the defined trackers for the summary report.
|
69
98
|
def report_trackers
|
70
99
|
self.class.instance_variable_get(:@report_definer).trackers rescue []
|
71
100
|
end
|
72
101
|
|
102
|
+
# Checks whether the line definitions form a valid language.
|
103
|
+
# A file format should have at least a header and a footer line type
|
73
104
|
def valid?
|
74
105
|
line_definitions.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
|
75
106
|
end
|
76
107
|
|
108
|
+
# Function that a file format con implement to monkey patch the environment.
|
109
|
+
# * <tt>controller</tt> The environment is provided as a controller instance
|
77
110
|
def setup_environment(controller)
|
78
111
|
|
79
112
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RequestLogAnalyzer::Filter
|
2
|
+
|
3
|
+
# Filter class loader using const_missing
|
4
|
+
# This function will automatically load the class file based on the name of the class
|
5
|
+
def self.const_missing(const)
|
6
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Base filter class used to filter input requests.
|
10
|
+
# All filters should interit from this base.
|
11
|
+
class Base
|
12
|
+
|
13
|
+
include RequestLogAnalyzer::FileFormat::Awareness
|
14
|
+
|
15
|
+
attr_reader :log_parser
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
# Initializer
|
19
|
+
# <tt>format</tt> The file format
|
20
|
+
# <tt>options</tt> Are passed to the filters.
|
21
|
+
def initialize(format, options = {})
|
22
|
+
@options = options
|
23
|
+
register_file_format(format)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initialize the filter
|
27
|
+
def prepare
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the request if the request should be kept.
|
31
|
+
# Return nil otherwise.
|
32
|
+
def filter(request)
|
33
|
+
return nil unless request
|
34
|
+
return request
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -1,158 +1,174 @@
|
|
1
|
-
|
1
|
+
module RequestLogAnalyzer::Output
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class FixedWidth < Base
|
4
|
+
|
5
|
+
module Monochrome
|
6
|
+
def colorize(text, *options)
|
7
|
+
text
|
8
|
+
end
|
6
9
|
end
|
7
|
-
end
|
8
10
|
|
9
|
-
|
11
|
+
module Color
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
|
14
|
+
COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
|
13
15
|
|
14
|
-
|
16
|
+
def colorize(text, *options)
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
font_style = ''
|
19
|
+
foreground_color = '0'
|
20
|
+
background_color = ''
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
options.each do |option|
|
23
|
+
if option.kind_of?(Symbol)
|
24
|
+
foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
|
25
|
+
font_style = "#{STYLES[option]};" if STYLES.include?(option)
|
26
|
+
elsif option.kind_of?(Hash)
|
27
|
+
options.each do |key, value|
|
28
|
+
case key
|
29
|
+
when :color; foreground_color = "3#{COLORS[value]}" if COLORS.include?(value)
|
30
|
+
when :background; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
|
31
|
+
when :on; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
|
32
|
+
when :style; font_style = "#{STYLES[value]};" if STYLES.include?(value)
|
33
|
+
end
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|
37
|
+
return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
|
34
38
|
end
|
35
|
-
return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
|
36
|
-
end
|
37
39
|
|
38
|
-
|
40
|
+
end
|
39
41
|
|
40
|
-
|
42
|
+
attr_reader :characters
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
CHARACTERS = {
|
45
|
+
:ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
|
46
|
+
:utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
|
47
|
+
}
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def initialize(io, options = {})
|
50
|
+
super(io, options)
|
51
|
+
@options[:width] ||= 80
|
52
|
+
@options[:characters] ||= :utf
|
53
|
+
@characters = CHARACTERS[@options[:characters]]
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
color_module = @options[:color] ? Color : Monochrome
|
56
|
+
(class << self; self; end).send(:include, color_module)
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
def print(str)
|
60
|
+
@io << str
|
61
|
+
end
|
60
62
|
|
61
|
-
|
63
|
+
alias :<< :print
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
def puts(str = '')
|
66
|
+
@io << str << "\n"
|
67
|
+
end
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
def title(title)
|
70
|
+
puts
|
71
|
+
puts colorize(title, :bold, :white)
|
72
|
+
line(:green)
|
73
|
+
end
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
def line(*font)
|
76
|
+
puts colorize(characters[:horizontal_line] * @options[:width], *font)
|
77
|
+
end
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
def link(text, url = nil)
|
80
|
+
if url.nil?
|
81
|
+
colorize(text, :blue, :bold)
|
82
|
+
else
|
83
|
+
"#{text} (#{colorize(url, :blue, :bold)})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def header
|
88
|
+
if io.kind_of?(File)
|
89
|
+
puts "Request-log-analyzer summary report"
|
90
|
+
line
|
91
|
+
puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
|
92
|
+
puts "Request-log-analyzer website: http://github.com/wvanbergen/request-log-analyzer"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def footer
|
97
|
+
puts
|
98
|
+
puts "Thanks for using request-log-analyzer!"
|
82
99
|
end
|
83
|
-
end
|
84
100
|
|
85
|
-
|
101
|
+
def table(*columns, &block)
|
86
102
|
|
87
|
-
|
88
|
-
|
103
|
+
rows = Array.new
|
104
|
+
yield(rows)
|
89
105
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
106
|
+
# determine maximum cell widths
|
107
|
+
max_cell_widths = rows.inject(Array.new(columns.length, 0)) do |result, row|
|
108
|
+
lengths = row.map { |column| column.to_s.length }
|
109
|
+
result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
|
110
|
+
end
|
111
|
+
columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
|
96
112
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
113
|
+
# determine actual column width
|
114
|
+
column_widths = columns.map do |column|
|
115
|
+
if column[:width] == :rest
|
116
|
+
nil
|
117
|
+
elsif column[:width]
|
118
|
+
column[:width]
|
119
|
+
elsif column[:min_width]
|
120
|
+
[column[:min_width], column[:actual_width]].max
|
121
|
+
elsif column[:max_width]
|
122
|
+
[column[:max_width], column[:actual_width]].min
|
123
|
+
else
|
124
|
+
column[:actual_width]
|
125
|
+
end
|
109
126
|
end
|
110
|
-
end
|
111
127
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
# Print table header
|
118
|
-
if table_has_header?(columns)
|
119
|
-
column_titles = []
|
120
|
-
columns.each_with_index do |column, index|
|
121
|
-
width = column_widths[index]
|
122
|
-
alignment = (column[:align] == :right ? '' : '-')
|
123
|
-
column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
|
128
|
+
if column_widths.include?(nil)
|
129
|
+
width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
|
130
|
+
column_widths[column_widths.index(nil)] = width_left
|
124
131
|
end
|
132
|
+
|
133
|
+
# Print table header
|
134
|
+
if table_has_header?(columns)
|
135
|
+
column_titles = []
|
136
|
+
columns.each_with_index do |column, index|
|
137
|
+
width = column_widths[index]
|
138
|
+
alignment = (column[:align] == :right ? '' : '-')
|
139
|
+
column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
|
140
|
+
end
|
125
141
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
142
|
+
puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
143
|
+
line(:green)
|
144
|
+
end
|
130
145
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
146
|
+
rows.each do |row|
|
147
|
+
row_values = []
|
148
|
+
columns.each_with_index do |column, index|
|
149
|
+
width = column_widths[index]
|
150
|
+
case column[:type]
|
151
|
+
when :ratio
|
152
|
+
if width > 4
|
153
|
+
if column[:treshold] && column[:treshold] < row[index].to_f
|
154
|
+
bar = ''
|
155
|
+
bar << characters[:block] * (width.to_f * column[:treshold]).round
|
156
|
+
bar << colorize(characters[:block] * (width.to_f * (row[index].to_f - column[:treshold])).round, :red)
|
157
|
+
row_values.push(bar)
|
158
|
+
else
|
159
|
+
row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
|
160
|
+
end
|
143
161
|
else
|
144
|
-
row_values.push(
|
162
|
+
row_values.push('')
|
145
163
|
end
|
146
164
|
else
|
147
|
-
|
165
|
+
alignment = (columns[index][:align] == :right ? '' : '-')
|
166
|
+
row_values.push("%#{alignment}#{width}s" % row[index].to_s[0...width])
|
148
167
|
end
|
149
|
-
else
|
150
|
-
alignment = (columns[index][:align] == :right ? '' : '-')
|
151
|
-
row_values.push("%#{alignment}#{width}s" % row[index].to_s[0...width])
|
152
168
|
end
|
169
|
+
puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
153
170
|
end
|
154
|
-
puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
155
171
|
end
|
156
|
-
end
|
157
172
|
|
173
|
+
end
|
158
174
|
end
|