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,81 +1,275 @@
|
|
|
1
|
-
module RequestLogAnalyzer
|
|
1
|
+
module RequestLogAnalyzer::FileFormat
|
|
2
|
+
|
|
3
|
+
def self.const_missing(const) # :nodoc:
|
|
4
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Loads a FileFormat::Base subclass instance.
|
|
8
|
+
# You can provide:
|
|
9
|
+
# * A FileFormat instance (which will return itself)
|
|
10
|
+
# * A FileFormat class (of which an imstance will be returned)
|
|
11
|
+
# * A filename (from which the FileFormat class is loaded)
|
|
12
|
+
# * A symbol of a built-in file format (e.g. :rails)
|
|
13
|
+
def self.load(file_format, *args)
|
|
14
|
+
klass = nil
|
|
15
|
+
if file_format.kind_of?(RequestLogAnalyzer::FileFormat::Base)
|
|
16
|
+
# this already is a file format! return itself
|
|
17
|
+
return @current_file_format = file_format
|
|
18
|
+
|
|
19
|
+
elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
|
20
|
+
# a usable class is provided. Use this format class.
|
|
21
|
+
klass = file_format
|
|
22
|
+
|
|
23
|
+
elsif file_format.kind_of?(String) && File.exist?(file_format)
|
|
24
|
+
# load a format from a ruby file
|
|
25
|
+
require file_format
|
|
26
|
+
const = RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb'))
|
|
27
|
+
if RequestLogAnalyzer::FileFormat.const_defined?(const)
|
|
28
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(const)
|
|
29
|
+
elsif Object.const_defined?(const)
|
|
30
|
+
klass = Object.const_get(const)
|
|
31
|
+
else
|
|
32
|
+
raise "Cannot load class #{const} from #{file_format}!"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
else
|
|
36
|
+
# load a provided file format
|
|
37
|
+
klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# check the returned klass to see if it can be used
|
|
41
|
+
raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
|
|
42
|
+
raise "Invalid FileFormat class from #{file_format.inspect}" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
|
|
43
|
+
|
|
44
|
+
@current_file_format = klass.create(*args) # return an instance of the class
|
|
45
|
+
end
|
|
2
46
|
|
|
3
|
-
|
|
47
|
+
# Returns an array of all FileFormat instances that are shipped with request-log-analyzer by default.
|
|
48
|
+
def self.all_formats
|
|
49
|
+
@all_formats ||= Dir[File.dirname(__FILE__) + '/file_format/*.rb'].map do |file|
|
|
50
|
+
self.load(File.basename(file, '.rb'))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Autodetects the filetype of a given file.
|
|
55
|
+
#
|
|
56
|
+
# Returns a FileFormat instance, by parsing the first couple of lines of the provided file
|
|
57
|
+
# with avery known file format and return the most promosing file format based on the parser
|
|
58
|
+
# statistics. The <tt>autodetect_score</tt> method is used to score the fitness of a format.
|
|
59
|
+
#
|
|
60
|
+
# <tt>file</tt>:: The file to detect the file format for.
|
|
61
|
+
# <tt>line_count</tt>:: The number of lines to take into consideration
|
|
62
|
+
def self.autodetect(file, line_count = 50)
|
|
63
|
+
|
|
64
|
+
parsers = all_formats.map { |f| RequestLogAnalyzer::Source::LogParser.new(f, :parse_strategy => 'cautious') }
|
|
65
|
+
|
|
66
|
+
File.open(file, 'rb') do |io|
|
|
67
|
+
while io.lineno < line_count && (line = io.gets)
|
|
68
|
+
parsers.each { |parser| parser.parse_line(line) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
4
71
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
72
|
+
parsers.select { |p| autodetect_score(p) > 0 }.max { |a, b| autodetect_score(a) <=> autodetect_score(b) }.file_format rescue nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Calculates a file format auto detection score based on the parser statistics.
|
|
76
|
+
#
|
|
77
|
+
# This method returns a score as an integer. Usually, the score will increase as more
|
|
78
|
+
# lines are parsed. Usually, a file_format with a score of zero or lower should not be
|
|
79
|
+
# considered.
|
|
80
|
+
#
|
|
81
|
+
# <tt>parser</tt>:: The parsed that was use to parse the initial lines of the log file.
|
|
82
|
+
def self.autodetect_score(parser)
|
|
83
|
+
score = 0
|
|
84
|
+
score -= parser.file_format.line_definitions.length
|
|
85
|
+
score -= parser.warnings * 3
|
|
86
|
+
score += parser.parsed_lines * 1
|
|
87
|
+
score += parser.parsed_requests * 10
|
|
88
|
+
|
|
89
|
+
# As Apache matches several simular formats, subtracting 1 will make a specific matcher have a higher score
|
|
90
|
+
score -= 1 if parser.file_format.class == RequestLogAnalyzer::FileFormat::Apache
|
|
91
|
+
|
|
92
|
+
score
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# This module contains some methods to construct regular expressions for log fragments
|
|
96
|
+
# that are commonly used, like IP addresses and timestamp.
|
|
97
|
+
#
|
|
98
|
+
# You need to extend (or include in an unlikely case) this module in your file format
|
|
99
|
+
# to use these regular expression constructors.
|
|
100
|
+
module CommonRegularExpressions
|
|
101
|
+
|
|
102
|
+
TIMESTAMP_PARTS = {
|
|
103
|
+
'a' => '(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)',
|
|
104
|
+
'b' => '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)',
|
|
105
|
+
'y' => '\d{2}', 'Y' => '\d{4}', 'm' => '\d{2}', 'd' => '\d{2}',
|
|
106
|
+
'H' => '\d{2}', 'M' => '\d{2}', 'S' => '\d{2}', 'k' => '(?:\d| )\d',
|
|
107
|
+
'z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
|
|
108
|
+
'Z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
|
|
109
|
+
'%' => '%'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Create a regular expression for a timestamp, generated by a strftime call.
|
|
113
|
+
# Provide the format string to construct a matching regular expression.
|
|
114
|
+
# Set blank to true to allow and empty string, or set blank to a string to set
|
|
115
|
+
# a substitute for the nil value.
|
|
116
|
+
def timestamp(format_string, blank = false)
|
|
117
|
+
regexp = ''
|
|
118
|
+
format_string.scan(/([^%]*)(?:%([A-Za-z%]))?/) do |literal, variable|
|
|
119
|
+
regexp << Regexp.quote(literal)
|
|
120
|
+
if variable
|
|
121
|
+
if TIMESTAMP_PARTS.has_key?(variable)
|
|
122
|
+
regexp << TIMESTAMP_PARTS[variable]
|
|
123
|
+
else
|
|
124
|
+
raise "Unknown variable: %#{variable}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
10
127
|
end
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
128
|
+
|
|
129
|
+
regexp = Regexp.new(regexp)
|
|
130
|
+
return case blank
|
|
131
|
+
when String then Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
|
|
132
|
+
when true then Regexp.union(regexp, //)
|
|
133
|
+
else regexp
|
|
14
134
|
end
|
|
15
135
|
end
|
|
16
|
-
|
|
136
|
+
|
|
137
|
+
# Construct a regular expression to parse IPv4 and IPv6 addresses.
|
|
138
|
+
#
|
|
139
|
+
# Allow nil values if the blank option is given. This can be true to
|
|
140
|
+
# allow an empty string or to a string substitute for the nil value.
|
|
141
|
+
def ip_address(blank = false)
|
|
142
|
+
|
|
143
|
+
# IP address regexp copied from Resolv::IPv4 and Resolv::IPv6,
|
|
144
|
+
# but adjusted to work for the purpose of request-log-analyzer.
|
|
145
|
+
ipv4_regexp = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
|
146
|
+
ipv6_regex_8_hex = /(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}/
|
|
147
|
+
ipv6_regex_compressed_hex = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)/
|
|
148
|
+
ipv6_regex_6_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}:){6})#{ipv4_regexp}/
|
|
149
|
+
ipv6_regex_compressed_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}:)*)#{ipv4_regexp}/
|
|
150
|
+
ipv6_regexp = Regexp.union(ipv6_regex_8_hex, ipv6_regex_compressed_hex, ipv6_regex_6_hex_4_dec, ipv6_regex_compressed_hex_4_dec)
|
|
151
|
+
|
|
152
|
+
# Allow the field to be blank if this option is given. This can be true to
|
|
153
|
+
# allow an empty string or a string alternative for the nil value.
|
|
154
|
+
ip_regexp = case blank
|
|
155
|
+
when String then Regexp.union(ipv4_regexp, ipv6_regexp, Regexp.new(Regexp.quote(blank)))
|
|
156
|
+
when true then Regexp.union(ipv4_regexp, ipv6_regexp, //)
|
|
157
|
+
else Regexp.union(ipv4_regexp, ipv6_regexp)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Base class for all log file format definitions. This class provides functions for subclasses to
|
|
163
|
+
# define their LineDefinitions and to define a summary report.
|
|
164
|
+
#
|
|
165
|
+
# A subclass of this class is instantiated when request-log-analyzer is started and this instance
|
|
166
|
+
# is shared with all components of the application so they can act on the specifics of the format
|
|
167
|
+
class Base
|
|
168
|
+
|
|
169
|
+
attr_reader :line_definitions, :report_trackers
|
|
170
|
+
|
|
171
|
+
####################################################################################
|
|
172
|
+
# CLASS METHODS for format definition
|
|
173
|
+
####################################################################################
|
|
174
|
+
|
|
175
|
+
# Registers the line definer instance for a subclass.
|
|
17
176
|
def self.inherited(subclass)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
177
|
+
if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
|
|
178
|
+
|
|
179
|
+
# Create aline and report definer for this class
|
|
180
|
+
subclass.class_eval do
|
|
181
|
+
instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
|
|
182
|
+
instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
|
|
183
|
+
class << self; attr_accessor :line_definer, :report_definer; end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Create a custom Request class for this file format
|
|
187
|
+
subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
|
|
188
|
+
else
|
|
189
|
+
|
|
190
|
+
# Copy the line and report definer from the parent class.
|
|
191
|
+
subclass.class_eval do
|
|
192
|
+
instance_variable_set(:@line_definer, superclass.line_definer.clone)
|
|
193
|
+
instance_variable_set(:@report_definer, superclass.report_definer.clone)
|
|
194
|
+
class << self; attr_accessor :line_definer, :report_definer; end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Create a custom Request class based on the superclass's Request class
|
|
198
|
+
subclass.const_set('Request', Class.new(subclass.superclass::Request)) unless subclass.const_defined?('Request')
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Specifies a single line defintions.
|
|
23
203
|
def self.line_definition(name, &block)
|
|
24
204
|
@line_definer.send(name, &block)
|
|
25
205
|
end
|
|
26
|
-
|
|
206
|
+
|
|
207
|
+
# Specifies multiple line definitions at once using a block
|
|
27
208
|
def self.format_definition(&block)
|
|
28
209
|
if block_given?
|
|
29
|
-
yield
|
|
210
|
+
yield self.line_definer
|
|
30
211
|
else
|
|
31
|
-
return
|
|
212
|
+
return self.line_definer
|
|
32
213
|
end
|
|
33
214
|
end
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def self.load(file_format)
|
|
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
|
|
215
|
+
|
|
216
|
+
# Specifies the summary report using a block.
|
|
217
|
+
def self.report(mode = :append, &block)
|
|
218
|
+
self.report_definer.reset! if mode == :overwrite
|
|
219
|
+
yield(self.report_definer)
|
|
63
220
|
end
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
221
|
+
|
|
222
|
+
####################################################################################
|
|
223
|
+
# Instantiation
|
|
224
|
+
####################################################################################
|
|
225
|
+
|
|
226
|
+
def self.create(*args)
|
|
227
|
+
# Ignore arguments
|
|
228
|
+
return self.new(line_definer.line_definitions, report_definer.trackers)
|
|
67
229
|
end
|
|
68
|
-
|
|
69
|
-
def report_trackers
|
|
70
|
-
|
|
230
|
+
|
|
231
|
+
def initialize(line_definitions = {}, report_trackers = [])
|
|
232
|
+
@line_definitions, @report_trackers = line_definitions, report_trackers
|
|
71
233
|
end
|
|
72
|
-
|
|
234
|
+
|
|
235
|
+
####################################################################################
|
|
236
|
+
# INSTANCE methods
|
|
237
|
+
####################################################################################
|
|
238
|
+
|
|
239
|
+
# Returns the Request class of this file format
|
|
240
|
+
def request_class
|
|
241
|
+
self.class::Request
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Returns a Request instance with the given parsed lines that should be provided as hashes.
|
|
245
|
+
def request(*hashes)
|
|
246
|
+
request_class.create(self, *hashes)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Checks whether the line definitions form a valid language.
|
|
250
|
+
# A file format should have at least a header and a footer line type
|
|
73
251
|
def valid?
|
|
74
|
-
line_definitions.
|
|
252
|
+
line_definitions.any? { |(name, ld)| ld.header } && line_definitions.any? { |(name, ld)| ld.footer }
|
|
75
253
|
end
|
|
76
|
-
|
|
254
|
+
|
|
255
|
+
# Returns true if this language captures the given symbol in one of its line definitions
|
|
256
|
+
def captures?(name)
|
|
257
|
+
line_definitions.any? { |(name, ld)| ld.captures?(name) }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Function that a file format con implement to monkey patch the environment.
|
|
261
|
+
# * <tt>controller</tt> The environment is provided as a controller instance
|
|
77
262
|
def setup_environment(controller)
|
|
78
|
-
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Parses a line by trying to parse it using every line definition in this file format
|
|
266
|
+
def parse_line(line, &warning_handler)
|
|
267
|
+
self.line_definitions.each do |lt, definition|
|
|
268
|
+
match = definition.matches(line, &warning_handler)
|
|
269
|
+
return match if match
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
return nil
|
|
79
273
|
end
|
|
80
274
|
end
|
|
81
275
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module RequestLogAnalyzer::Filter
|
|
2
|
+
|
|
3
|
+
# Filter to anonymize parsed values
|
|
4
|
+
# Options
|
|
5
|
+
# * <tt>:mode</tt> :reject or :accept.
|
|
6
|
+
# * <tt>:field</tt> Specific field to accept or reject.
|
|
7
|
+
# * <tt>:value</tt> Value that the field should match to be accepted or rejected.
|
|
8
|
+
class Anonymize < Base
|
|
9
|
+
|
|
10
|
+
def generate_random_ip
|
|
11
|
+
"#{rand(256)}.#{rand(256)}.#{rand(256)}.#{rand(256)}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def anonymize_url(value)
|
|
15
|
+
return value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fuzz(value)
|
|
19
|
+
value * ((75 + rand(50)) / 100.0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filter(request)
|
|
23
|
+
# TODO: request.attributes is bad practice
|
|
24
|
+
request.attributes.each do |key, value|
|
|
25
|
+
if key == :ip
|
|
26
|
+
request.attributes[key] = generate_random_ip
|
|
27
|
+
elsif key == :url
|
|
28
|
+
request.attributes[key] = anonymize_url(value)
|
|
29
|
+
elsif [ :duration, :view, :db, :type, :after_filters_time, :before_filters_time,
|
|
30
|
+
:action_time].include?(key)
|
|
31
|
+
request.attributes[key] = fuzz(value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
return request
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Filter to select or reject a specific field
|
|
4
4
|
# Options
|
|
5
5
|
# * <tt>:mode</tt> :reject or :accept.
|
|
6
6
|
# * <tt>:field</tt> Specific field to accept or reject.
|
|
7
7
|
# * <tt>:value</tt> Value that the field should match to be accepted or rejected.
|
|
8
8
|
class Field < Base
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
attr_reader :field, :value, :mode
|
|
11
|
-
|
|
12
|
-
def
|
|
11
|
+
|
|
12
|
+
def initialize(file_format, options = {})
|
|
13
|
+
super(file_format, options)
|
|
14
|
+
setup_filter
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Setup mode, field and value.
|
|
18
|
+
def setup_filter
|
|
13
19
|
@mode = (@options[:mode] || :accept).to_sym
|
|
14
20
|
@field = @options[:field].to_sym
|
|
15
|
-
|
|
21
|
+
|
|
16
22
|
# Convert the timestamp to the correct formats for quick timestamp comparisons
|
|
17
23
|
if @options[:value].kind_of?(String) && @options[:value][0, 1] == '/' && @options[:value][-1, 1] == '/'
|
|
18
24
|
@value = Regexp.new(@options[:value][1..-2])
|
|
@@ -20,17 +26,17 @@ module RequestLogAnalyzer::Filter
|
|
|
20
26
|
@value = @options[:value] # TODO: convert value?
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
|
-
|
|
29
|
+
|
|
30
|
+
# Keep request if @mode == :select and request has the field and value.
|
|
31
|
+
# Drop request if @mode == :reject and request has the field and value.
|
|
32
|
+
# Returns nil otherwise.
|
|
33
|
+
# <tt>request</tt> Request Object
|
|
24
34
|
def filter(request)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
found_field = request.every(@field).any? { |value| @value === value }
|
|
28
|
-
|
|
35
|
+
found_field = request.every(@field).any? { |value| @value === value.to_s }
|
|
29
36
|
return nil if !found_field && @mode == :select
|
|
30
37
|
return nil if found_field && @mode == :reject
|
|
31
|
-
|
|
32
38
|
return request
|
|
33
|
-
end
|
|
39
|
+
end
|
|
34
40
|
end
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
end
|
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Filter
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Reject all requests not in given timespan
|
|
4
4
|
# Options
|
|
5
5
|
# * <tt>:after</tt> Only keep requests after this DateTime.
|
|
6
6
|
# * <tt>:before</tt> Only keep requests before this DateTime.
|
|
7
7
|
class Timespan < Base
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
attr_reader :before, :after
|
|
10
|
-
|
|
11
|
-
def
|
|
12
|
-
|
|
13
|
-
@
|
|
10
|
+
|
|
11
|
+
def initialize(file_format, options = {})
|
|
12
|
+
@after = nil
|
|
13
|
+
@before = nil
|
|
14
|
+
super(file_format, options)
|
|
15
|
+
setup_filter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Convert the timestamp to the correct formats for quick timestamp comparisons.
|
|
20
|
+
# These are stored in the before and after attr_reader fields.
|
|
21
|
+
def setup_filter
|
|
22
|
+
@after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
|
|
14
23
|
@before = @options[:before].strftime('%Y%m%d%H%M%S').to_i if options[:before]
|
|
15
24
|
end
|
|
16
|
-
|
|
25
|
+
|
|
26
|
+
# Returns request if:
|
|
27
|
+
# * @after <= request.timestamp <= @before
|
|
28
|
+
# * @after <= request.timestamp
|
|
29
|
+
# * request.timestamp <= @before
|
|
30
|
+
# Returns nil otherwise
|
|
31
|
+
# <tt>request</tt> Request object.
|
|
17
32
|
def filter(request)
|
|
18
|
-
return nil unless request
|
|
19
|
-
|
|
20
33
|
if @after && @before && request.timestamp <= @before && @after <= request.timestamp
|
|
21
34
|
return request
|
|
22
35
|
elsif @after && @before.nil? && @after <= request.timestamp
|
|
23
36
|
return request
|
|
24
|
-
elsif @before && @after.nil? && request.timestamp <= @before
|
|
37
|
+
elsif @before && @after.nil? && request.timestamp <= @before
|
|
25
38
|
return request
|
|
26
39
|
end
|
|
27
40
|
|
|
28
41
|
return nil
|
|
29
|
-
end
|
|
42
|
+
end
|
|
30
43
|
end
|
|
31
|
-
|
|
44
|
+
|
|
32
45
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
attr_reader :file_format, :options
|
|
14
|
+
|
|
15
|
+
# Initializer
|
|
16
|
+
# <tt>format</tt> The file format
|
|
17
|
+
# <tt>options</tt> Are passed to the filters.
|
|
18
|
+
def initialize(format, options = {})
|
|
19
|
+
@file_format = format
|
|
20
|
+
@options = options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Return the request if the request should be kept.
|
|
24
|
+
# Return nil otherwise.
|
|
25
|
+
def filter(request)
|
|
26
|
+
request
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|