request-log-analyzer 1.0.2 → 1.6.3
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 +4 -4
- data/README.rdoc +38 -0
- data/Rakefile +6 -3
- data/bin/request-log-analyzer +70 -72
- data/lib/cli/command_line_arguments.rb +53 -53
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +43 -0
- data/lib/cli/progressbar.rb +166 -189
- data/lib/cli/tools.rb +49 -0
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
- data/lib/request_log_analyzer/aggregator/echo.rb +17 -12
- data/lib/request_log_analyzer/aggregator/summarizer.rb +101 -63
- data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +17 -13
- data/lib/request_log_analyzer/controller.rb +251 -98
- data/lib/request_log_analyzer/database/base.rb +114 -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/database.rb +102 -0
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +74 -0
- data/lib/request_log_analyzer/file_format/apache.rb +147 -0
- data/lib/request_log_analyzer/file_format/delayed_job.rb +55 -0
- data/lib/request_log_analyzer/file_format/merb.rb +65 -29
- data/lib/request_log_analyzer/file_format/mysql.rb +101 -0
- data/lib/request_log_analyzer/file_format/postgresql.rb +68 -0
- data/lib/request_log_analyzer/file_format/rack.rb +9 -0
- data/lib/request_log_analyzer/file_format/rails.rb +164 -78
- data/lib/request_log_analyzer/file_format/rails3.rb +86 -0
- data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
- data/lib/request_log_analyzer/file_format.rb +252 -58
- data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
- data/lib/request_log_analyzer/filter/field.rb +19 -13
- data/lib/request_log_analyzer/filter/timespan.rb +25 -12
- data/lib/request_log_analyzer/filter.rb +30 -0
- data/lib/request_log_analyzer/line_definition.rb +69 -96
- data/lib/request_log_analyzer/log_processor.rb +31 -53
- data/lib/request_log_analyzer/mailer.rb +65 -0
- data/lib/request_log_analyzer/output/fancy_html.rb +49 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
- data/lib/request_log_analyzer/output/html.rb +187 -0
- data/lib/request_log_analyzer/output.rb +117 -0
- data/lib/request_log_analyzer/request.rb +125 -40
- data/lib/request_log_analyzer/source/database_loader.rb +87 -0
- data/lib/request_log_analyzer/source/log_parser.rb +297 -0
- data/lib/request_log_analyzer/source.rb +72 -0
- data/lib/request_log_analyzer/tracker/duration.rb +28 -64
- data/lib/request_log_analyzer/tracker/frequency.rb +108 -0
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +76 -49
- data/lib/request_log_analyzer/tracker/numeric_value.rb +223 -0
- data/lib/request_log_analyzer/tracker/timespan.rb +54 -27
- data/lib/request_log_analyzer/tracker/traffic.rb +40 -0
- data/lib/request_log_analyzer/tracker.rb +101 -0
- data/lib/request_log_analyzer.rb +43 -13
- data/request-log-analyzer.gemspec +41 -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_prefixed.log +9 -0
- data/spec/fixtures/mysql_slow_query.log +110 -0
- data/spec/fixtures/postgresql.log +2980 -0
- data/spec/fixtures/rails.db +0 -0
- data/spec/fixtures/rails_22.log +1 -1
- data/spec/fixtures/sinatra.log +99 -0
- data/spec/integration/command_line_usage_spec.rb +84 -0
- data/spec/integration/mailer_spec.rb +179 -0
- data/spec/integration/munin_plugins_rails_spec.rb +58 -0
- data/spec/integration/scout_spec.rb +152 -0
- data/spec/lib/helpers.rb +72 -0
- data/spec/lib/macros.rb +18 -0
- data/spec/lib/matchers.rb +77 -0
- data/spec/lib/mocks.rb +77 -0
- data/spec/lib/testing_format.rb +46 -0
- data/spec/spec_helper.rb +16 -59
- 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 +67 -0
- data/spec/unit/file_format/apache_format_spec.rb +203 -0
- data/spec/unit/file_format/common_regular_expressions_spec.rb +53 -0
- data/spec/unit/file_format/delayed_job_format_spec.rb +83 -0
- data/spec/unit/file_format/file_format_api_spec.rb +69 -0
- data/spec/unit/file_format/format_autodetection_spec.rb +40 -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/mysql_format_spec.rb +154 -0
- data/spec/unit/file_format/postgresql_format_spec.rb +65 -0
- data/spec/unit/file_format/rack_format_spec.rb +50 -0
- data/spec/unit/file_format/rails3_format_spec.rb +120 -0
- data/spec/unit/file_format/rails_format_spec.rb +180 -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 +42 -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 +49 -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/numeric_value_tracker_spec.rb +166 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
- data/spec/unit/tracker/tracker_api_spec.rb +125 -0
- data/spec/unit/tracker/traffic_tracker_spec.rb +28 -0
- data/tasks/github-gem.rake +290 -139
- data/tasks/request_log_analyzer.rake +23 -7
- metadata +221 -94
- data/DESIGN +0 -14
- data/HACKING +0 -7
- data/README.textile +0 -36
- data/lib/cli/bashcolorizer.rb +0 -60
- data/lib/request_log_analyzer/aggregator/database.rb +0 -148
- data/lib/request_log_analyzer/filter/base.rb +0 -29
- data/lib/request_log_analyzer/log_parser.rb +0 -173
- data/lib/request_log_analyzer/source/base.rb +0 -42
- data/lib/request_log_analyzer/source/log_file.rb +0 -170
- data/lib/request_log_analyzer/tracker/base.rb +0 -54
- data/lib/request_log_analyzer/tracker/category.rb +0 -71
- data/spec/controller_spec.rb +0 -40
- data/spec/database_inserter_spec.rb +0 -101
- data/spec/file_format_spec.rb +0 -78
- data/spec/file_formats/spec_format.rb +0 -26
- data/spec/filter_spec.rb +0 -137
- data/spec/line_definition_spec.rb +0 -124
- data/spec/log_parser_spec.rb +0 -68
- data/spec/log_processor_spec.rb +0 -57
- data/spec/merb_format_spec.rb +0 -38
- data/spec/rails_format_spec.rb +0 -76
- data/spec/request_spec.rb +0 -72
- data/spec/summarizer_spec.rb +0 -9
- data/tasks/rspec.rake +0 -6
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
module RequestLogAnalyzer
|
|
2
|
-
|
|
3
|
-
# The LogParser class reads log data from a given source and uses a file format definition
|
|
4
|
-
# to parse all relevent information about requests from the file. A FileFormat module should
|
|
5
|
-
# be provided that contains the definitions of the lines that occur in the log data.
|
|
6
|
-
#
|
|
7
|
-
# De order in which lines occur is used to combine lines to a single request. If these lines
|
|
8
|
-
# are mixed, requests cannot be combined properly. This can be the case if data is written to
|
|
9
|
-
# the log file simultaneously by different mongrel processes. This problem is detected by the
|
|
10
|
-
# parser, but the requests that are mixed up cannot be parsed. It will emit warnings when this
|
|
11
|
-
# occurs.
|
|
12
|
-
class LogParser
|
|
13
|
-
|
|
14
|
-
include RequestLogAnalyzer::FileFormat::Awareness
|
|
15
|
-
|
|
16
|
-
# A hash of options
|
|
17
|
-
attr_reader :options
|
|
18
|
-
|
|
19
|
-
# The current Request object that is being parsed
|
|
20
|
-
attr_reader :current_request
|
|
21
|
-
|
|
22
|
-
# The total number of parsed lines
|
|
23
|
-
attr_reader :parsed_lines
|
|
24
|
-
|
|
25
|
-
# The total number of parsed requests.
|
|
26
|
-
attr_reader :parsed_requests
|
|
27
|
-
|
|
28
|
-
# The number of skipped requests because of date constraints
|
|
29
|
-
attr_reader :skipped_requests
|
|
30
|
-
|
|
31
|
-
# Initializes the parser instance.
|
|
32
|
-
# It will apply the language specific FileFormat module to this instance. It will use the line
|
|
33
|
-
# definitions in this module to parse any input.
|
|
34
|
-
def initialize(format, options = {})
|
|
35
|
-
@line_definitions = {}
|
|
36
|
-
@options = options
|
|
37
|
-
@parsed_lines = 0
|
|
38
|
-
@parsed_requests = 0
|
|
39
|
-
@skipped_requests = 0
|
|
40
|
-
|
|
41
|
-
@current_io = nil
|
|
42
|
-
|
|
43
|
-
# install the file format module (see RequestLogAnalyzer::FileFormat)
|
|
44
|
-
# and register all the line definitions to the parser
|
|
45
|
-
self.register_file_format(format)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Parses a list of consequent files of the same format
|
|
49
|
-
def parse_files(files, options = {}, &block)
|
|
50
|
-
files.each { |file| parse_file(file, options, &block) }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Parses a file.
|
|
54
|
-
# Creates an IO stream for the provided file, and sends it to parse_io for further handling
|
|
55
|
-
def parse_file(file, options = {}, &block)
|
|
56
|
-
@progress_handler.call(:started, file) if @progress_handler
|
|
57
|
-
File.open(file, 'r') { |f| parse_io(f, options, &block) }
|
|
58
|
-
@progress_handler.call(:finished, file) if @progress_handler
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def parse_stream(stream, options = {}, &block)
|
|
62
|
-
parse_io(stream, options, &block)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Finds a log line and then parses the information in the line.
|
|
66
|
-
# Yields a hash containing the information found.
|
|
67
|
-
# <tt>*line_types</tt> The log line types to look for (defaults to LOG_LINES.keys).
|
|
68
|
-
# Yeilds a Hash when it encounters a chunk of information.
|
|
69
|
-
def parse_io(io, options = {}, &block)
|
|
70
|
-
|
|
71
|
-
# parse every line type by default
|
|
72
|
-
line_types = options[:line_types] || file_format.line_definitions.keys
|
|
73
|
-
|
|
74
|
-
# check whether all provided line types are valid
|
|
75
|
-
unknown = line_types.reject { |line_type| file_format.line_definitions.has_key?(line_type) }
|
|
76
|
-
raise "Unknown line types: #{unknown.join(', ')}" unless unknown.empty?
|
|
77
|
-
|
|
78
|
-
@current_io = io
|
|
79
|
-
@current_io.each_line do |line|
|
|
80
|
-
|
|
81
|
-
@progress_handler.call(:progress, @current_io.pos) if @progress_handler && @current_io.kind_of?(File)
|
|
82
|
-
|
|
83
|
-
request_data = nil
|
|
84
|
-
line_types.each do |line_type|
|
|
85
|
-
line_type_definition = file_format.line_definitions[line_type]
|
|
86
|
-
break if request_data = line_type_definition.matches(line, @current_io.lineno, self)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
if request_data
|
|
90
|
-
@parsed_lines += 1
|
|
91
|
-
update_current_request(request_data, &block)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
|
|
96
|
-
|
|
97
|
-
@current_io = nil
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Add a block to this method to install a progress handler while parsing
|
|
101
|
-
def progress=(proc)
|
|
102
|
-
@progress_handler = proc
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Add a block to this method to install a warning handler while parsing
|
|
106
|
-
def warning=(proc)
|
|
107
|
-
@warning_handler = proc
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# This method is called by the parser if it encounteres any problems.
|
|
111
|
-
# It will call the warning handler. The default controller will pass all warnings to every
|
|
112
|
-
# aggregator that is registered and running
|
|
113
|
-
def warn(type, message)
|
|
114
|
-
@warning_handler.call(type, message, @current_io.lineno) if @warning_handler
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
protected
|
|
118
|
-
|
|
119
|
-
# Combines the different lines of a request into a single Request object. It will start a
|
|
120
|
-
# new request when a header line is encountered en will emit the request when a footer line
|
|
121
|
-
# is encountered.
|
|
122
|
-
#
|
|
123
|
-
# - Every line that is parsed before a header line is ignored as it cannot be included in
|
|
124
|
-
# any request. It will emit a :no_current_request warning.
|
|
125
|
-
# - A header line that is parsed before a request is closed by a footer line, is a sign of
|
|
126
|
-
# an unprpertly ordered file. All data that is gathered for the request until then is
|
|
127
|
-
# discarded, the next request is ignored as well and a :unclosed_request warning is
|
|
128
|
-
# emitted.
|
|
129
|
-
def update_current_request(request_data, &block)
|
|
130
|
-
if header_line?(request_data)
|
|
131
|
-
unless @current_request.nil?
|
|
132
|
-
if options[:assume_correct_order]
|
|
133
|
-
handle_request(@current_request, &block)
|
|
134
|
-
@current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
|
|
135
|
-
else
|
|
136
|
-
warn(:unclosed_request, "Encountered header line, but previous request was not closed!")
|
|
137
|
-
@current_request = nil # remove all data that was parsed, skip next request as well.
|
|
138
|
-
end
|
|
139
|
-
else
|
|
140
|
-
@current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
|
|
141
|
-
end
|
|
142
|
-
else
|
|
143
|
-
unless @current_request.nil?
|
|
144
|
-
@current_request << request_data
|
|
145
|
-
if footer_line?(request_data)
|
|
146
|
-
handle_request(@current_request, &block)
|
|
147
|
-
@current_request = nil
|
|
148
|
-
end
|
|
149
|
-
else
|
|
150
|
-
warn(:no_current_request, "Parsebale line found outside of a request!")
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Handles the parsed request by calling the request handler.
|
|
156
|
-
# The default controller will send the request to every running aggegator.
|
|
157
|
-
def handle_request(request, &block)
|
|
158
|
-
@parsed_requests += 1
|
|
159
|
-
accepted = block_given? ? yield(request) : true
|
|
160
|
-
@skipped_requests += 1 if !accepted
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Checks whether a given line hash is a header line.
|
|
164
|
-
def header_line?(hash)
|
|
165
|
-
file_format.line_definitions[hash[:line_type]].header
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Checks whether a given line hash is a footer line.
|
|
169
|
-
def footer_line?(hash)
|
|
170
|
-
file_format.line_definitions[hash[:line_type]].footer
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
module RequestLogAnalyzer::Source
|
|
2
|
-
class Base
|
|
3
|
-
|
|
4
|
-
include RequestLogAnalyzer::FileFormat::Awareness
|
|
5
|
-
|
|
6
|
-
# A hash of options
|
|
7
|
-
attr_reader :options
|
|
8
|
-
|
|
9
|
-
# The current Request object that is being parsed
|
|
10
|
-
attr_reader :current_request
|
|
11
|
-
|
|
12
|
-
# The total number of parsed lines
|
|
13
|
-
attr_reader :parsed_lines
|
|
14
|
-
|
|
15
|
-
# The total number of parsed requests.
|
|
16
|
-
attr_reader :parsed_requests
|
|
17
|
-
|
|
18
|
-
# The number of skipped lines because of warnings
|
|
19
|
-
attr_reader :skipped_lines
|
|
20
|
-
|
|
21
|
-
# Base source class used to filter input requests.
|
|
22
|
-
|
|
23
|
-
# Initializer
|
|
24
|
-
# <tt>format</tt> The file format
|
|
25
|
-
# <tt>options</tt> Are passed to the filters.
|
|
26
|
-
def initialize(format, options = {})
|
|
27
|
-
@options = options
|
|
28
|
-
register_file_format(format)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def prepare
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def requests(&block)
|
|
35
|
-
return true
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def finalize
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
module RequestLogAnalyzer::Source
|
|
2
|
-
|
|
3
|
-
# The LogParser class reads log data from a given source and uses a file format definition
|
|
4
|
-
# to parse all relevent information about requests from the file. A FileFormat module should
|
|
5
|
-
# be provided that contains the definitions of the lines that occur in the log data.
|
|
6
|
-
#
|
|
7
|
-
# De order in which lines occur is used to combine lines to a single request. If these lines
|
|
8
|
-
# are mixed, requests cannot be combined properly. This can be the case if data is written to
|
|
9
|
-
# the log file simultaneously by different mongrel processes. This problem is detected by the
|
|
10
|
-
# parser, but the requests that are mixed up cannot be parsed. It will emit warnings when this
|
|
11
|
-
# occurs.
|
|
12
|
-
class LogFile < RequestLogAnalyzer::Source::Base
|
|
13
|
-
|
|
14
|
-
attr_reader :source_files
|
|
15
|
-
|
|
16
|
-
# Initializes the parser instance.
|
|
17
|
-
# It will apply the language specific FileFormat module to this instance. It will use the line
|
|
18
|
-
# definitions in this module to parse any input.
|
|
19
|
-
def initialize(format, options = {})
|
|
20
|
-
@line_definitions = {}
|
|
21
|
-
@options = options
|
|
22
|
-
@parsed_lines = 0
|
|
23
|
-
@parsed_requests = 0
|
|
24
|
-
@skipped_lines = 0
|
|
25
|
-
@current_io = nil
|
|
26
|
-
@source_files = options[:source_files]
|
|
27
|
-
|
|
28
|
-
# install the file format module (see RequestLogAnalyzer::FileFormat)
|
|
29
|
-
# and register all the line definitions to the parser
|
|
30
|
-
self.register_file_format(format)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def requests(options = {}, &block)
|
|
34
|
-
|
|
35
|
-
case @source_files
|
|
36
|
-
when IO;
|
|
37
|
-
puts "Parsing from the standard input. Press CTRL+C to finish."
|
|
38
|
-
parse_stream(@source_files, options, &block)
|
|
39
|
-
when String
|
|
40
|
-
parse_file(@source_files, options, &block)
|
|
41
|
-
when Array
|
|
42
|
-
parse_files(@source_files, options, &block)
|
|
43
|
-
else
|
|
44
|
-
raise "Unknown source provided"
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Parses a list of consequent files of the same format
|
|
49
|
-
def parse_files(files, options = {}, &block)
|
|
50
|
-
files.each { |file| parse_file(file, options, &block) }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Parses a file.
|
|
54
|
-
# Creates an IO stream for the provided file, and sends it to parse_io for further handling
|
|
55
|
-
def parse_file(file, options = {}, &block)
|
|
56
|
-
@progress_handler.call(:started, file) if @progress_handler
|
|
57
|
-
File.open(file, 'r') { |f| parse_io(f, options, &block) }
|
|
58
|
-
@progress_handler.call(:finished, file) if @progress_handler
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def parse_stream(stream, options = {}, &block)
|
|
62
|
-
parse_io(stream, options, &block)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Finds a log line and then parses the information in the line.
|
|
66
|
-
# Yields a hash containing the information found.
|
|
67
|
-
# <tt>*line_types</tt> The log line types to look for (defaults to LOG_LINES.keys).
|
|
68
|
-
# Yeilds a Hash when it encounters a chunk of information.
|
|
69
|
-
def parse_io(io, options = {}, &block)
|
|
70
|
-
|
|
71
|
-
# parse every line type by default
|
|
72
|
-
line_types = options[:line_types] || file_format.line_definitions.keys
|
|
73
|
-
|
|
74
|
-
# check whether all provided line types are valid
|
|
75
|
-
unknown = line_types.reject { |line_type| file_format.line_definitions.has_key?(line_type) }
|
|
76
|
-
raise "Unknown line types: #{unknown.join(', ')}" unless unknown.empty?
|
|
77
|
-
|
|
78
|
-
@current_io = io
|
|
79
|
-
@current_io.each_line do |line|
|
|
80
|
-
|
|
81
|
-
@progress_handler.call(:progress, @current_io.pos) if @progress_handler && @current_io.kind_of?(File)
|
|
82
|
-
|
|
83
|
-
request_data = nil
|
|
84
|
-
line_types.each do |line_type|
|
|
85
|
-
line_type_definition = file_format.line_definitions[line_type]
|
|
86
|
-
break if request_data = line_type_definition.matches(line, @current_io.lineno, self)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
if request_data
|
|
90
|
-
@parsed_lines += 1
|
|
91
|
-
update_current_request(request_data, &block)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
|
|
96
|
-
|
|
97
|
-
@current_io = nil
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Add a block to this method to install a progress handler while parsing
|
|
101
|
-
def progress=(proc)
|
|
102
|
-
@progress_handler = proc
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Add a block to this method to install a warning handler while parsing
|
|
106
|
-
def warning=(proc)
|
|
107
|
-
@warning_handler = proc
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# This method is called by the parser if it encounteres any problems.
|
|
111
|
-
# It will call the warning handler. The default controller will pass all warnings to every
|
|
112
|
-
# aggregator that is registered and running
|
|
113
|
-
def warn(type, message)
|
|
114
|
-
@warning_handler.call(type, message, @current_io.lineno) if @warning_handler
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
protected
|
|
118
|
-
|
|
119
|
-
# Combines the different lines of a request into a single Request object. It will start a
|
|
120
|
-
# new request when a header line is encountered en will emit the request when a footer line
|
|
121
|
-
# is encountered.
|
|
122
|
-
#
|
|
123
|
-
# - Every line that is parsed before a header line is ignored as it cannot be included in
|
|
124
|
-
# any request. It will emit a :no_current_request warning.
|
|
125
|
-
# - A header line that is parsed before a request is closed by a footer line, is a sign of
|
|
126
|
-
# an unprpertly ordered file. All data that is gathered for the request until then is
|
|
127
|
-
# discarded, the next request is ignored as well and a :unclosed_request warning is
|
|
128
|
-
# emitted.
|
|
129
|
-
def update_current_request(request_data, &block)
|
|
130
|
-
if header_line?(request_data)
|
|
131
|
-
unless @current_request.nil?
|
|
132
|
-
if options[:assume_correct_order]
|
|
133
|
-
@parsed_requests += 1
|
|
134
|
-
yield @current_request
|
|
135
|
-
@current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
|
|
136
|
-
else
|
|
137
|
-
@skipped_lines += 1
|
|
138
|
-
warn(:unclosed_request, "Encountered header line, but previous request was not closed!")
|
|
139
|
-
@current_request = nil # remove all data that was parsed, skip next request as well.
|
|
140
|
-
end
|
|
141
|
-
else
|
|
142
|
-
@current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
|
|
143
|
-
end
|
|
144
|
-
else
|
|
145
|
-
unless @current_request.nil?
|
|
146
|
-
@current_request << request_data
|
|
147
|
-
if footer_line?(request_data)
|
|
148
|
-
@parsed_requests += 1
|
|
149
|
-
yield @current_request
|
|
150
|
-
@current_request = nil
|
|
151
|
-
end
|
|
152
|
-
else
|
|
153
|
-
@skipped_lines += 1
|
|
154
|
-
warn(:no_current_request, "Parsebale line found outside of a request!")
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Checks whether a given line hash is a header line.
|
|
160
|
-
def header_line?(hash)
|
|
161
|
-
file_format.line_definitions[hash[:line_type]].header
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Checks whether a given line hash is a footer line.
|
|
165
|
-
def footer_line?(hash)
|
|
166
|
-
file_format.line_definitions[hash[:line_type]].footer
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module RequestLogAnalyzer
|
|
2
|
-
module Tracker
|
|
3
|
-
|
|
4
|
-
# Base tracker. All other trackers inherit from this class
|
|
5
|
-
#
|
|
6
|
-
# Accepts the following options:
|
|
7
|
-
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
|
8
|
-
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
|
9
|
-
# * <tt>:output</tt> Direct output here (defaults to STDOUT)
|
|
10
|
-
#
|
|
11
|
-
# For example :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
|
|
12
|
-
class Base
|
|
13
|
-
|
|
14
|
-
attr_reader :options
|
|
15
|
-
|
|
16
|
-
def initialize(options ={})
|
|
17
|
-
@options = options
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def prepare
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def update(request)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def finalize
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def should_update?(request)
|
|
30
|
-
return false if options[:line_type] && !request.has_line_type?(options[:line_type])
|
|
31
|
-
|
|
32
|
-
if options[:if].kind_of?(Symbol)
|
|
33
|
-
return false unless request[options[:if]]
|
|
34
|
-
elsif options[:if].respond_to?(:call)
|
|
35
|
-
return false unless options[:if].call(request)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
if options[:unless].kind_of?(Symbol)
|
|
39
|
-
return false if request[options[:unless]]
|
|
40
|
-
elsif options[:unless].respond_to?(:call)
|
|
41
|
-
return false if options[:unless].call(request)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
return true
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def report(output=STDOUT, report_width = 80, color = false)
|
|
48
|
-
output << self.inspect
|
|
49
|
-
output << "\n"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
module RequestLogAnalyzer::Tracker
|
|
2
|
-
|
|
3
|
-
# Catagorize requests.
|
|
4
|
-
# Count and analyze requests for a specific attribute
|
|
5
|
-
#
|
|
6
|
-
# Accepts the following options:
|
|
7
|
-
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
|
8
|
-
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
|
9
|
-
# * <tt>:title</tt> Title do be displayed above the report.
|
|
10
|
-
# * <tt>:category</tt> Proc that handles the request categorization.
|
|
11
|
-
# * <tt>:amount</tt> The amount of lines in the report
|
|
12
|
-
#
|
|
13
|
-
# The items in the update request hash are set during the creation of the Duration tracker.
|
|
14
|
-
#
|
|
15
|
-
# Example output:
|
|
16
|
-
# HTTP methods
|
|
17
|
-
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
-
# GET ┃ 22248 hits (46.2%) ┃░░░░░░░░░░░░░░░░░
|
|
19
|
-
# PUT ┃ 13685 hits (28.4%) ┃░░░░░░░░░░░
|
|
20
|
-
# POST ┃ 11662 hits (24.2%) ┃░░░░░░░░░
|
|
21
|
-
# DELETE ┃ 512 hits (1.1%) ┃
|
|
22
|
-
class Category < RequestLogAnalyzer::Tracker::Base
|
|
23
|
-
|
|
24
|
-
attr_reader :categories
|
|
25
|
-
|
|
26
|
-
def prepare
|
|
27
|
-
raise "No categorizer set up for category tracker #{self.inspect}" unless options[:category]
|
|
28
|
-
@categories = {}
|
|
29
|
-
if options[:all_categories].kind_of?(Enumerable)
|
|
30
|
-
options[:all_categories].each { |cat| @categories[cat] = 0 }
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def update(request)
|
|
35
|
-
cat = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
|
|
36
|
-
if !cat.nil? || options[:nils]
|
|
37
|
-
@categories[cat] ||= 0
|
|
38
|
-
@categories[cat] += 1
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def report(output = STDOUT, report_width = 80, color = false)
|
|
43
|
-
if options[:title]
|
|
44
|
-
output << "\n#{options[:title]}\n"
|
|
45
|
-
output << green(('━' * report_width), color) + "\n"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
if @categories.empty?
|
|
49
|
-
output << "None found.\n"
|
|
50
|
-
else
|
|
51
|
-
sorted_categories = @categories.sort { |a, b| b[1] <=> a[1] }
|
|
52
|
-
total_hits = sorted_categories.inject(0) { |carry, item| carry + item[1] }
|
|
53
|
-
sorted_categories = sorted_categories.slice(0...options[:amount]) if options[:amount]
|
|
54
|
-
|
|
55
|
-
adjuster = color ? 33 : 24 # justifcation calcultaion is slight different when color codes are inserterted
|
|
56
|
-
max_cat_length = [sorted_categories.map { |c| c[0].to_s.length }.max, report_width - adjuster].min
|
|
57
|
-
sorted_categories.each do |(cat, count)|
|
|
58
|
-
text = "%-#{max_cat_length+1}s┃%7d hits %s" % [cat.to_s[0..max_cat_length], count, (green("(%0.01f%%)", color) % [(count.to_f / total_hits) * 100])]
|
|
59
|
-
space_left = report_width - (max_cat_length + adjuster + 3)
|
|
60
|
-
if space_left > 3
|
|
61
|
-
bar_chars = (space_left * (count.to_f / total_hits)).round
|
|
62
|
-
output << "%-#{max_cat_length + adjuster}s %s%s" % [text, '┃', '░' * bar_chars] + "\n"
|
|
63
|
-
else
|
|
64
|
-
output << text + "\n"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
end
|
|
71
|
-
end
|
data/spec/controller_spec.rb
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper'
|
|
2
|
-
|
|
3
|
-
describe RequestLogAnalyzer::Controller do
|
|
4
|
-
|
|
5
|
-
include RequestLogAnalyzerSpecHelper
|
|
6
|
-
|
|
7
|
-
# it "should include the file format module" do
|
|
8
|
-
# controller = RequestLogAnalyzer::Controller.new(:rails)
|
|
9
|
-
# (class << controller; self; end).ancestors.include?(RequestLogAnalyzer::FileFormat::Rails)
|
|
10
|
-
# end
|
|
11
|
-
|
|
12
|
-
it "should call the aggregators when run" do
|
|
13
|
-
file_format = RequestLogAnalyzer::FileFormat.load(:rails)
|
|
14
|
-
source = RequestLogAnalyzer::Source::LogFile.new(file_format, :source_files => log_fixture(:rails_1x))
|
|
15
|
-
controller = RequestLogAnalyzer::Controller.new(source)
|
|
16
|
-
|
|
17
|
-
mock_aggregator = mock('aggregator')
|
|
18
|
-
mock_aggregator.should_receive(:prepare).once.ordered
|
|
19
|
-
mock_aggregator.should_receive(:aggregate).with(an_instance_of(RequestLogAnalyzer::Request)).at_least(:twice).ordered
|
|
20
|
-
mock_aggregator.should_receive(:finalize).once.ordered
|
|
21
|
-
mock_aggregator.should_receive(:report).once.ordered
|
|
22
|
-
|
|
23
|
-
another_mock_aggregator = mock('another aggregator')
|
|
24
|
-
another_mock_aggregator.should_receive(:prepare).once.ordered
|
|
25
|
-
another_mock_aggregator.should_receive(:aggregate).with(an_instance_of(RequestLogAnalyzer::Request)).at_least(:twice).ordered
|
|
26
|
-
another_mock_aggregator.should_receive(:finalize).once.ordered
|
|
27
|
-
another_mock_aggregator.should_receive(:report).once.ordered
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
controller.aggregators << mock_aggregator << another_mock_aggregator
|
|
31
|
-
controller.run!
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it "should run well from the command line" do
|
|
35
|
-
temp_file = "#{File.dirname(__FILE__)}/fixtures/temp.txt"
|
|
36
|
-
system("#{File.dirname(__FILE__)}/../bin/request-log-analyzer #{log_fixture(:rails_1x)} > #{temp_file}").should be_true
|
|
37
|
-
File.unlink(temp_file)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper'
|
|
2
|
-
require 'request_log_analyzer/aggregator/database'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
describe RequestLogAnalyzer::Aggregator::Database, "schema creation" do
|
|
6
|
-
|
|
7
|
-
TEST_DATABASE_FILE = File.dirname(__FILE__) + "/fixtures/requests.db"
|
|
8
|
-
include RequestLogAnalyzerSpecHelper
|
|
9
|
-
|
|
10
|
-
before(:each) do
|
|
11
|
-
log_parser = RequestLogAnalyzer::LogParser.new(spec_format)
|
|
12
|
-
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database => TEST_DATABASE_FILE)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
after(:each) do
|
|
16
|
-
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "should create the correct tables" do
|
|
20
|
-
ActiveRecord::Migration.should_receive(:create_table).with("warnings")
|
|
21
|
-
ActiveRecord::Migration.should_receive(:create_table).with("requests")
|
|
22
|
-
ActiveRecord::Migration.should_receive(:create_table).with("first_lines")
|
|
23
|
-
ActiveRecord::Migration.should_receive(:create_table).with("test_lines")
|
|
24
|
-
ActiveRecord::Migration.should_receive(:create_table).with("last_lines")
|
|
25
|
-
@database_inserter.prepare
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it "should create a default Request class" do
|
|
29
|
-
@database_inserter.prepare
|
|
30
|
-
SpecFormat::Database::Request.ancestors.should include(ActiveRecord::Base)
|
|
31
|
-
SpecFormat::Database::Request.column_names.should include('first_lineno')
|
|
32
|
-
SpecFormat::Database::Request.column_names.should include('last_lineno')
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "should create associations for the default Request class" do
|
|
36
|
-
@database_inserter.prepare
|
|
37
|
-
@request = SpecFormat::Database::Request.new
|
|
38
|
-
@request.should respond_to(:test_lines)
|
|
39
|
-
@request.test_lines.should
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
it "should create the default table names" do
|
|
43
|
-
@database_inserter.prepare
|
|
44
|
-
@database_inserter.file_format.line_definitions.each do |name, definition|
|
|
45
|
-
klass = SpecFormat::Database.const_get("#{name}_line".camelize)
|
|
46
|
-
klass.column_names.should include('id')
|
|
47
|
-
klass.column_names.should include('lineno')
|
|
48
|
-
klass.column_names.should include('request_id')
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
it "should create the correct fields in the table" do
|
|
53
|
-
@database_inserter.prepare
|
|
54
|
-
|
|
55
|
-
SpecFormat::Database::FirstLine.column_names.should include('request_no')
|
|
56
|
-
SpecFormat::Database::LastLine.column_names.should include('request_no')
|
|
57
|
-
SpecFormat::Database::TestLine.column_names.should include('test_capture')
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
describe RequestLogAnalyzer::Aggregator::Database, "record insertion" do
|
|
63
|
-
include RequestLogAnalyzerSpecHelper
|
|
64
|
-
|
|
65
|
-
before(:each) do
|
|
66
|
-
log_parser = RequestLogAnalyzer::LogParser.new(spec_format)
|
|
67
|
-
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database => TEST_DATABASE_FILE)
|
|
68
|
-
@database_inserter.prepare
|
|
69
|
-
|
|
70
|
-
@incomplete_request = RequestLogAnalyzer::Request.create(spec_format, {:line_type => :first, :request_no => 564})
|
|
71
|
-
@completed_request = RequestLogAnalyzer::Request.create(spec_format,
|
|
72
|
-
{:line_type => :first, :request_no => 564},
|
|
73
|
-
{:line_type => :test, :test_capture => "awesome"},
|
|
74
|
-
{:line_type => :test, :test_capture => "indeed"},
|
|
75
|
-
{:line_type => :last, :request_no => 564})
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
after(:each) do
|
|
79
|
-
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "should insert a record in the request table" do
|
|
83
|
-
SpecFormat::Database::Request.count.should == 0
|
|
84
|
-
@database_inserter.aggregate(@incomplete_request)
|
|
85
|
-
SpecFormat::Database::Request.count.should == 1
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
it "should insert records in all relevant line tables" do
|
|
89
|
-
@database_inserter.aggregate(@completed_request)
|
|
90
|
-
request = SpecFormat::Database::Request.first
|
|
91
|
-
request.should have(2).test_lines
|
|
92
|
-
request.should have(1).first_lines
|
|
93
|
-
request.should have(1).last_lines
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "should log a warning in the warnings table" do
|
|
97
|
-
SpecFormat::Database::Warning.should_receive(:create!).with(hash_including(:warning_type => 'test_warning'))
|
|
98
|
-
@database_inserter.warning(:test_warning, "Testing the warning system", 12)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
end
|