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.
Files changed (144) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +4 -4
  4. data/README.rdoc +38 -0
  5. data/Rakefile +6 -3
  6. data/bin/request-log-analyzer +70 -72
  7. data/lib/cli/command_line_arguments.rb +53 -53
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +166 -189
  11. data/lib/cli/tools.rb +49 -0
  12. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  13. data/lib/request_log_analyzer/aggregator/echo.rb +17 -12
  14. data/lib/request_log_analyzer/aggregator/summarizer.rb +101 -63
  15. data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +17 -13
  16. data/lib/request_log_analyzer/controller.rb +251 -98
  17. data/lib/request_log_analyzer/database/base.rb +114 -0
  18. data/lib/request_log_analyzer/database/connection.rb +38 -0
  19. data/lib/request_log_analyzer/database/request.rb +22 -0
  20. data/lib/request_log_analyzer/database/source.rb +13 -0
  21. data/lib/request_log_analyzer/database/warning.rb +14 -0
  22. data/lib/request_log_analyzer/database.rb +102 -0
  23. data/lib/request_log_analyzer/file_format/amazon_s3.rb +74 -0
  24. data/lib/request_log_analyzer/file_format/apache.rb +147 -0
  25. data/lib/request_log_analyzer/file_format/delayed_job.rb +55 -0
  26. data/lib/request_log_analyzer/file_format/merb.rb +65 -29
  27. data/lib/request_log_analyzer/file_format/mysql.rb +101 -0
  28. data/lib/request_log_analyzer/file_format/postgresql.rb +68 -0
  29. data/lib/request_log_analyzer/file_format/rack.rb +9 -0
  30. data/lib/request_log_analyzer/file_format/rails.rb +164 -78
  31. data/lib/request_log_analyzer/file_format/rails3.rb +86 -0
  32. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  33. data/lib/request_log_analyzer/file_format.rb +252 -58
  34. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  35. data/lib/request_log_analyzer/filter/field.rb +19 -13
  36. data/lib/request_log_analyzer/filter/timespan.rb +25 -12
  37. data/lib/request_log_analyzer/filter.rb +30 -0
  38. data/lib/request_log_analyzer/line_definition.rb +69 -96
  39. data/lib/request_log_analyzer/log_processor.rb +31 -53
  40. data/lib/request_log_analyzer/mailer.rb +65 -0
  41. data/lib/request_log_analyzer/output/fancy_html.rb +49 -0
  42. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  43. data/lib/request_log_analyzer/output/html.rb +187 -0
  44. data/lib/request_log_analyzer/output.rb +117 -0
  45. data/lib/request_log_analyzer/request.rb +125 -40
  46. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  47. data/lib/request_log_analyzer/source/log_parser.rb +297 -0
  48. data/lib/request_log_analyzer/source.rb +72 -0
  49. data/lib/request_log_analyzer/tracker/duration.rb +28 -64
  50. data/lib/request_log_analyzer/tracker/frequency.rb +108 -0
  51. data/lib/request_log_analyzer/tracker/hourly_spread.rb +76 -49
  52. data/lib/request_log_analyzer/tracker/numeric_value.rb +223 -0
  53. data/lib/request_log_analyzer/tracker/timespan.rb +54 -27
  54. data/lib/request_log_analyzer/tracker/traffic.rb +40 -0
  55. data/lib/request_log_analyzer/tracker.rb +101 -0
  56. data/lib/request_log_analyzer.rb +43 -13
  57. data/request-log-analyzer.gemspec +41 -0
  58. data/spec/database.yml +23 -0
  59. data/spec/fixtures/apache_combined.log +5 -0
  60. data/spec/fixtures/apache_common.log +10 -0
  61. data/spec/fixtures/decompression.log +12 -0
  62. data/spec/fixtures/decompression.log.bz2 +0 -0
  63. data/spec/fixtures/decompression.log.gz +0 -0
  64. data/spec/fixtures/decompression.log.zip +0 -0
  65. data/spec/fixtures/decompression.tar.gz +0 -0
  66. data/spec/fixtures/decompression.tgz +0 -0
  67. data/spec/fixtures/header_and_footer.log +6 -0
  68. data/spec/fixtures/merb_prefixed.log +9 -0
  69. data/spec/fixtures/mysql_slow_query.log +110 -0
  70. data/spec/fixtures/postgresql.log +2980 -0
  71. data/spec/fixtures/rails.db +0 -0
  72. data/spec/fixtures/rails_22.log +1 -1
  73. data/spec/fixtures/sinatra.log +99 -0
  74. data/spec/integration/command_line_usage_spec.rb +84 -0
  75. data/spec/integration/mailer_spec.rb +179 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +152 -0
  78. data/spec/lib/helpers.rb +72 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +77 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +16 -59
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +67 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/common_regular_expressions_spec.rb +53 -0
  94. data/spec/unit/file_format/delayed_job_format_spec.rb +83 -0
  95. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  96. data/spec/unit/file_format/format_autodetection_spec.rb +40 -0
  97. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  98. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  99. data/spec/unit/file_format/mysql_format_spec.rb +154 -0
  100. data/spec/unit/file_format/postgresql_format_spec.rb +65 -0
  101. data/spec/unit/file_format/rack_format_spec.rb +50 -0
  102. data/spec/unit/file_format/rails3_format_spec.rb +120 -0
  103. data/spec/unit/file_format/rails_format_spec.rb +180 -0
  104. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  105. data/spec/unit/filter/field_filter_spec.rb +66 -0
  106. data/spec/unit/filter/filter_spec.rb +17 -0
  107. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  108. data/spec/unit/mailer_spec.rb +42 -0
  109. data/spec/unit/request_spec.rb +111 -0
  110. data/spec/unit/source/log_parser_spec.rb +119 -0
  111. data/spec/unit/tracker/duration_tracker_spec.rb +49 -0
  112. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  113. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  114. data/spec/unit/tracker/numeric_value_tracker_spec.rb +166 -0
  115. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  116. data/spec/unit/tracker/tracker_api_spec.rb +125 -0
  117. data/spec/unit/tracker/traffic_tracker_spec.rb +28 -0
  118. data/tasks/github-gem.rake +290 -139
  119. data/tasks/request_log_analyzer.rake +23 -7
  120. metadata +221 -94
  121. data/DESIGN +0 -14
  122. data/HACKING +0 -7
  123. data/README.textile +0 -36
  124. data/lib/cli/bashcolorizer.rb +0 -60
  125. data/lib/request_log_analyzer/aggregator/database.rb +0 -148
  126. data/lib/request_log_analyzer/filter/base.rb +0 -29
  127. data/lib/request_log_analyzer/log_parser.rb +0 -173
  128. data/lib/request_log_analyzer/source/base.rb +0 -42
  129. data/lib/request_log_analyzer/source/log_file.rb +0 -170
  130. data/lib/request_log_analyzer/tracker/base.rb +0 -54
  131. data/lib/request_log_analyzer/tracker/category.rb +0 -71
  132. data/spec/controller_spec.rb +0 -40
  133. data/spec/database_inserter_spec.rb +0 -101
  134. data/spec/file_format_spec.rb +0 -78
  135. data/spec/file_formats/spec_format.rb +0 -26
  136. data/spec/filter_spec.rb +0 -137
  137. data/spec/line_definition_spec.rb +0 -124
  138. data/spec/log_parser_spec.rb +0 -68
  139. data/spec/log_processor_spec.rb +0 -57
  140. data/spec/merb_format_spec.rb +0 -38
  141. data/spec/rails_format_spec.rb +0 -76
  142. data/spec/request_spec.rb +0 -72
  143. data/spec/summarizer_spec.rb +0 -9
  144. 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
- class FileFormat
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
- # Makes classes aware of a file format by registering the file_format variable
6
- module Awareness
7
-
8
- def self.included(base)
9
- base.send(:attr_reader, :file_format)
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
- def register_file_format(format)
13
- @file_format = format
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
- subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
19
- subclass.class_eval { class << self; attr_accessor :line_definer; end }
20
- subclass.class_eval { class << self; attr_accessor :report_definer; end }
21
- end
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(@line_definer)
210
+ yield self.line_definer
30
211
  else
31
- return @line_definer
212
+ return self.line_definer
32
213
  end
33
214
  end
34
-
35
- def self.report(&block)
36
- @report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
37
- yield(@report_definer)
38
- end
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
- def line_definitions
66
- @line_definitions ||= self.class.line_definer.line_definitions
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
- self.class.instance_variable_get(:@report_definer).trackers rescue []
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.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
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 prepare
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
- return nil unless request
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 prepare
12
- # Convert the timestamp to the correct formats for quick timestamp comparisons
13
- @after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
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