wvanbergen-request-log-analyzer 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/DESIGN +14 -0
  2. data/HACKING +7 -0
  3. data/README.textile +9 -98
  4. data/Rakefile +2 -2
  5. data/bin/request-log-analyzer +1 -1
  6. data/lib/cli/bashcolorizer.rb +60 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/progressbar.rb +236 -0
  9. data/lib/request_log_analyzer/aggregator/base.rb +51 -0
  10. data/lib/request_log_analyzer/aggregator/database.rb +97 -0
  11. data/lib/request_log_analyzer/aggregator/echo.rb +25 -0
  12. data/lib/request_log_analyzer/aggregator/summarizer.rb +116 -0
  13. data/lib/request_log_analyzer/controller.rb +206 -0
  14. data/lib/request_log_analyzer/file_format/merb.rb +33 -0
  15. data/lib/request_log_analyzer/file_format/rails.rb +119 -0
  16. data/lib/request_log_analyzer/file_format.rb +77 -0
  17. data/lib/request_log_analyzer/filter/base.rb +29 -0
  18. data/lib/request_log_analyzer/filter/field.rb +36 -0
  19. data/lib/request_log_analyzer/filter/timespan.rb +32 -0
  20. data/lib/request_log_analyzer/line_definition.rb +159 -0
  21. data/lib/request_log_analyzer/log_parser.rb +183 -0
  22. data/lib/request_log_analyzer/log_processor.rb +121 -0
  23. data/lib/request_log_analyzer/request.rb +115 -0
  24. data/lib/request_log_analyzer/source/base.rb +42 -0
  25. data/lib/request_log_analyzer/source/log_file.rb +180 -0
  26. data/lib/request_log_analyzer/tracker/base.rb +54 -0
  27. data/lib/request_log_analyzer/tracker/category.rb +71 -0
  28. data/lib/request_log_analyzer/tracker/duration.rb +81 -0
  29. data/lib/request_log_analyzer/tracker/hourly_spread.rb +80 -0
  30. data/lib/request_log_analyzer/tracker/timespan.rb +54 -0
  31. data/spec/file_format_spec.rb +78 -0
  32. data/spec/file_formats/spec_format.rb +26 -0
  33. data/spec/filter_spec.rb +137 -0
  34. data/spec/log_processor_spec.rb +57 -0
  35. data/tasks/rspec.rake +6 -0
  36. metadata +53 -55
  37. data/TODO +0 -58
  38. data/bin/request-log-database +0 -81
  39. data/lib/base/log_parser.rb +0 -78
  40. data/lib/base/record_inserter.rb +0 -139
  41. data/lib/command_line/arguments.rb +0 -129
  42. data/lib/command_line/flag.rb +0 -51
  43. data/lib/merb_analyzer/log_parser.rb +0 -26
  44. data/lib/rails_analyzer/log_parser.rb +0 -35
  45. data/lib/rails_analyzer/record_inserter.rb +0 -39
  46. data/tasks/test.rake +0 -8
  47. data/test/log_fragments/fragment_1.log +0 -59
  48. data/test/log_fragments/fragment_2.log +0 -5
  49. data/test/log_fragments/fragment_3.log +0 -12
  50. data/test/log_fragments/fragment_4.log +0 -10
  51. data/test/log_fragments/fragment_5.log +0 -24
  52. data/test/log_fragments/merb_1.log +0 -84
  53. data/test/merb_log_parser_test.rb +0 -39
  54. data/test/rails_log_parser_test.rb +0 -94
  55. data/test/record_inserter_test.rb +0 -45
@@ -0,0 +1,206 @@
1
+ module RequestLogAnalyzer
2
+
3
+ # The RequestLogAnalyzer::Controller class creates a LogParser instance for the
4
+ # requested file format, and connect it with sources and aggregators.
5
+ #
6
+ # Sources are streams or files from which the requests will be parsed.
7
+ # Aggregators will handle every passed request to yield a meaningfull results.
8
+ #
9
+ # - Use the build-function to build a controller instance using command line arguments.
10
+ # - Use add_aggregator to register a new aggregator
11
+ # - Use add_source to register a new aggregator
12
+ # - Use the run! method to start the parser and send the requests to the aggregators.
13
+ #
14
+ # Note that the order of sources can be imported if you have log files than succeed
15
+ # eachother. Requests that span over succeeding files will be parsed correctly if the
16
+ # sources are registered in the correct order. This can be helpful to parse requests
17
+ # from several logrotated log files.
18
+ class Controller
19
+
20
+ include RequestLogAnalyzer::FileFormat::Awareness
21
+
22
+ attr_reader :aggregators
23
+ attr_reader :filters
24
+ attr_reader :log_parser
25
+ attr_reader :source
26
+ attr_reader :output
27
+ attr_reader :options
28
+
29
+ # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
30
+ # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
31
+ # <rr>report_with</tt> Width of the report. Defaults to 80.
32
+ def self.build(arguments, report_width = 80)
33
+
34
+ options = { :report_width => arguments[:report_width].to_i, :output => STDOUT}
35
+
36
+ options[:combined_requests] = !arguments[:single_lines]
37
+ options[:database] = arguments[:database] if arguments[:database]
38
+ options[:debug] = arguments[:debug]
39
+ options[:colorize] = !arguments[:boring]
40
+
41
+ if arguments[:file]
42
+ options[:output] = File.new(arguments[:file], "w+")
43
+ options[:colorize] = false
44
+ end
45
+
46
+ # Create the controller with the correct file format
47
+ file_format = RequestLogAnalyzer::FileFormat.load(arguments[:format])
48
+ source_files = nil
49
+ # register sources
50
+ if arguments.parameters.length == 1
51
+ file = arguments.parameters[0]
52
+ if file == '-' || file == 'STDIN'
53
+ options.store(:source_files, $stdin)
54
+ elsif File.exist?(file)
55
+ options.store(:source_files, file)
56
+ else
57
+ puts "File not found: #{file}"
58
+ exit(0)
59
+ end
60
+ else
61
+ options.store(:source_files, arguments.parameters)
62
+ end
63
+
64
+ controller = Controller.new(RequestLogAnalyzer::Source::LogFile.new(file_format, options), options)
65
+
66
+ # register filters
67
+ # filters are only supported in combined requests mode
68
+ if options[:combined_requests]
69
+
70
+ options[:assume_correct_order] = arguments[:assume_correct_order]
71
+
72
+ if arguments[:after] || arguments[:before]
73
+ filter_options = {}
74
+ filter_options[:after] = DateTime.parse(arguments[:after])
75
+ filter_options[:before] = DateTime.parse(arguments[:before]) if arguments[:before]
76
+ controller.add_filter(:timespan, filter_options)
77
+ end
78
+
79
+ arguments[:reject].each do |(field, value)|
80
+ controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
81
+ end
82
+
83
+ arguments[:select].each do |(field, value)|
84
+ controller.add_filter(:field, :mode => :select, :field => field, :value => value)
85
+ end
86
+
87
+ end
88
+
89
+ # register aggregators
90
+ arguments[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
91
+
92
+ # register the database
93
+ controller.add_aggregator(:database) if arguments[:database] && !arguments[:aggregator].include?('database')
94
+ controller.add_aggregator(:summarizer) if arguments[:aggregator].empty?
95
+
96
+ # register the echo aggregator in debug mode
97
+ controller.add_aggregator(:echo) if arguments[:debug]
98
+
99
+ return controller
100
+ end
101
+
102
+ # Builds a new Controller for the given log file format.
103
+ # <tt>format</tt> Logfile format. Defaults to :rails
104
+ # Options are passd on to the LogParser.
105
+ # * <tt>:aggregator</tt> Aggregator array.
106
+ # * <tt>:combined_requests</tt> Combine multiline requests into a single request.
107
+ # * <tt>:database</tt> Database the controller should use.
108
+ # * <tt>:echo</tt> Output debug information.
109
+ # * <tt>:silent</tt> Do not output any warnings.
110
+ # * <tt>:colorize</tt> Colorize output
111
+ # * <tt>:output</tt> All report outputs get << through this output.
112
+ def initialize(source, options = {})
113
+
114
+ @source = source
115
+ @options = options
116
+ @aggregators = []
117
+ @filters = []
118
+ @output = options[:output]
119
+
120
+ # Requester format through RequestLogAnalyzer::FileFormat and construct the parser
121
+ register_file_format(@source.file_format)
122
+
123
+ # Pass all warnings to every aggregator so they can do something useful with them.
124
+ @source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } } if @source
125
+
126
+ # Handle progress messagess
127
+ @source.progress = lambda { |message, value| handle_progress(message, value) } if @source
128
+ end
129
+
130
+ # Progress function.
131
+ # Expects :started with file, :progress with current line and :finished or :interrupted when done.
132
+ # <tt>message</tt> Current state (:started, :finished, :interupted or :progress).
133
+ # <tt>value</tt> File or current line.
134
+ def handle_progress(message, value = nil)
135
+ case message
136
+ when :started
137
+ @progress_bar = ProgressBar.new(green(File.basename(value), options[:colorize]), File.size(value))
138
+ when :finished
139
+ @progress_bar.finish
140
+ @progress_bar = nil
141
+ when :interrupted
142
+ if @progress_bar
143
+ @progress_bar.halt
144
+ @progress_bar = nil
145
+ end
146
+ when :progress
147
+ @progress_bar.set(value)
148
+ end
149
+ end
150
+
151
+ # Adds an aggregator to the controller. The aggregator will be called for every request
152
+ # that is parsed from the provided sources (see add_source)
153
+ def add_aggregator(agg)
154
+ if agg.kind_of?(Symbol)
155
+ require File.dirname(__FILE__) + "/aggregator/#{agg}"
156
+ agg = RequestLogAnalyzer::Aggregator.const_get(agg.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
157
+ end
158
+
159
+ @aggregators << agg.new(@source, @options)
160
+ end
161
+
162
+ alias :>> :add_aggregator
163
+
164
+ # Adds a request filter to the controller.
165
+ def add_filter(filter, filter_options = {})
166
+ if filter.kind_of?(Symbol)
167
+ require File.dirname(__FILE__) + "/filter/#{filter}"
168
+ filter = RequestLogAnalyzer::Filter.const_get(filter.to_s.split(/[^a-z0-9]/i).map{ |w| w.capitalize }.join(''))
169
+ end
170
+
171
+ @filters << filter.new(file_format, @options.merge(filter_options))
172
+ end
173
+
174
+ # Runs RequestLogAnalyzer
175
+ # 1. Call prepare on every aggregator
176
+ # 2. Generate requests from source object
177
+ # 3. Filter out unwanted requests
178
+ # 4. Call aggregate for remaning requests on every aggregator
179
+ # 4. Call finalize on every aggregator
180
+ # 5. Call report on every aggregator
181
+ # 6. Finalize Source
182
+ def run!
183
+
184
+ @filters.each { |filter| filter.prepare }
185
+ @aggregators.each { |agg| agg.prepare }
186
+
187
+ begin
188
+ @source.requests do |request|
189
+ @filters.each { |filter| request = filter.filter(request) }
190
+ @aggregators.each { |agg| agg.aggregate(request) } if request
191
+ end
192
+ rescue Interrupt => e
193
+ handle_progress(:interrupted)
194
+ puts "Caught interrupt! Stopped parsing."
195
+ end
196
+
197
+ puts "\n"
198
+
199
+ @aggregators.each { |agg| agg.finalize }
200
+ @aggregators.each { |agg| agg.report(@output, options[:report_width], options[:colorize]) }
201
+
202
+ @source.finalize
203
+ end
204
+
205
+ end
206
+ end
@@ -0,0 +1,33 @@
1
+ module RequestLogAnalyzer::FileFormat::Merb
2
+
3
+ LINE_DEFINITIONS = {
4
+
5
+ # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
6
+ :started => {
7
+ :header => true,
8
+ :teaser => /Started/,
9
+ :regexp => /Started request handling\:\ (.+)/,
10
+ :captures => [{ :name => :timestamp, :type => :timestamp, :anonymize => :slightly }]
11
+ },
12
+
13
+ # ~ Params: {"action"=>"create", "controller"=>"session"}
14
+ # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
15
+ :params => {
16
+ :teaser => /Params/,
17
+ :regexp => /Params\:\ \{(.+)\}/,
18
+ :captures => [{ :name => :raw_hash, :type => :string}]
19
+ },
20
+
21
+ # ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
22
+ :completed => {
23
+ :footer => true,
24
+ :teaser => /\{:dispatch_time/,
25
+ :regexp => /\{\:dispatch_time=>(\d+\.\d+(?:e-?\d+)?), (?:\:after_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?(?:\:before_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?\:action_time=>(\d+\.\d+(?:e-?\d+)?)\}/,
26
+ :captures => [{ :name => :dispatch_time, :type => :sec, :anonymize => :slightly },
27
+ { :name => :after_filters_time, :type => :sec, :anonymize => :slightly },
28
+ { :name => :before_filters_time, :type => :sec, :anonymize => :slightly },
29
+ { :name => :action_time, :type => :sec, :anonymize => :slightly }]
30
+ }
31
+ }
32
+
33
+ end
@@ -0,0 +1,119 @@
1
+ class RequestLogAnalyzer::FileFormat::Rails < RequestLogAnalyzer::FileFormat
2
+
3
+ # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
4
+ line_definition :processing do |line|
5
+ line.header = true # this line is the first log line for a request
6
+ line.teaser = /Processing /
7
+ line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
8
+ line.captures << { :name => :controller, :type => :string } \
9
+ << { :name => :action, :type => :string } \
10
+ << { :name => :format, :type => :string } \
11
+ << { :name => :ip, :type => :string, :anonymize => :ip } \
12
+ << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
13
+ << { :name => :method, :type => :string }
14
+ end
15
+
16
+ # Filter chain halted as [#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>] rendered_or_redirected.
17
+ line_definition :cache_hit do |line|
18
+ line.regexp = /Filter chain halted as \[\#<ActionController::Caching::Actions::ActionCacheFilter:.+>\] rendered_or_redirected/
19
+ end
20
+
21
+ # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy'
22
+ line_definition :failed do |line|
23
+ line.footer = true
24
+ line.regexp = /((?:[A-Z]\w+\:\:)*[A-Z]\w+) \((.*)\)(?: on line #(\d+) of .+)?\:(.*)/
25
+ line.captures << { :name => :error, :type => :string } \
26
+ << { :name => :exception_string, :type => :string } \
27
+ << { :name => :line, :type => :integer } \
28
+ << { :name => :file, :type => :string } \
29
+ << { :name => :stack_trace, :type => :string, :anonymize => true }
30
+ end
31
+
32
+
33
+ # Rails < 2.1 completed line example
34
+ # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]
35
+ RAILS_21_COMPLETED = /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (?:\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(?:\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/
36
+
37
+ # Rails > 2.1 completed line example
38
+ # Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]
39
+ RAILS_22_COMPLETED = /Completed in (\d+)ms \((?:View: (\d+), )?DB: (\d+)\) \| (\d\d\d).+\[(http.+)\]/
40
+
41
+ # The completed line uses a kind of hack to ensure that both old style logs and new style logs
42
+ # are both parsed by the same regular expression. The format in Rails 2.2 was slightly changed,
43
+ # but the line contains exactly the same information.
44
+ line_definition :completed do |line|
45
+
46
+ line.footer = true
47
+ line.teaser = /Completed in /
48
+ line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
49
+
50
+ line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
51
+ << { :name => :view, :type => :sec, :anonymize => :slightly } \
52
+ << { :name => :db, :type => :sec, :anonymize => :slightly } \
53
+ << { :name => :status, :type => :integer } \
54
+ << { :name => :url, :type => :string, :anonymize => :url } # Old variant
55
+
56
+ line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
57
+ << { :name => :view, :type => :msec, :anonymize => :slightly } \
58
+ << { :name => :db, :type => :msec, :anonymize => :slightly } \
59
+ << { :name => :status, :type => :integer} \
60
+ << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
61
+ end
62
+
63
+
64
+
65
+ REQUEST_CATEGORIZER = Proc.new do |request|
66
+ if request.combined?
67
+
68
+ if request =~ :failed
69
+ format = request[:format] || 'html'
70
+ "#{request[:error]} in #{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
71
+ else
72
+ format = request[:format] || 'html'
73
+ "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
74
+ end
75
+
76
+ else
77
+ case request.line_type
78
+ when :processing
79
+ format = request[:format] || 'html'
80
+ "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
81
+
82
+ when :completed
83
+ url = request[:url].downcase.split(/^http[s]?:\/\/[A-z0-9\.-]+/).last.split('?').first # only the relevant URL part
84
+ url << '/' if url[-1] != '/'[0] && url.length > 1 # pad a trailing slash for consistency
85
+
86
+ url.gsub!(/\/\d+-\d+-\d+(\/|$)/, '/:date/') # Combine all (year-month-day) queries
87
+ url.gsub!(/\/\d+-\d+(\/|$)/, '/:month/') # Combine all date (year-month) queries
88
+ url.gsub!(/\/\d+[\w-]*/, '/:id') # replace identifiers in URLs request[:url] # TODO: improve me
89
+ url
90
+
91
+ when :failed
92
+ request[:error]
93
+ else
94
+ raise "Cannot group this request: #{request.inspect}"
95
+ end
96
+ end
97
+ end
98
+
99
+ report do |analyze|
100
+ analyze.timespan :line_type => :processing
101
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
102
+ analyze.category :method, :title => 'HTTP methods'
103
+ analyze.category :status, :title => 'HTTP statuses returned'
104
+ analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
105
+
106
+ analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
107
+ analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
108
+ analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
109
+
110
+ analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
111
+ :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
112
+
113
+ analyze.hourly_spread :line_type => :processing
114
+ analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
115
+ end
116
+
117
+
118
+
119
+ end
@@ -0,0 +1,77 @@
1
+ module RequestLogAnalyzer
2
+
3
+ class FileFormat
4
+
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)
10
+ end
11
+
12
+ def register_file_format(format)
13
+ @file_format = format
14
+ end
15
+ end
16
+
17
+ 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
+
23
+ def self.line_definition(name, &block)
24
+ @line_definer.send(name, &block)
25
+ end
26
+
27
+ def self.format_definition(&block)
28
+ if block_given?
29
+ yield(@line_definer)
30
+ else
31
+ return @line_definer
32
+ end
33
+ 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
63
+ end
64
+
65
+ def line_definitions
66
+ @line_definitions ||= self.class.line_definer.line_definitions
67
+ end
68
+
69
+ def report_trackers
70
+ self.class.instance_variable_get(:@report_definer).trackers rescue []
71
+ end
72
+
73
+ def valid?
74
+ line_definitions.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ module RequestLogAnalyzer
2
+ module Filter
3
+ # Base filter class used to filter input requests.
4
+ # All filters should interit from this base.
5
+ class Base
6
+
7
+ include RequestLogAnalyzer::FileFormat::Awareness
8
+
9
+ attr_reader :log_parser
10
+ attr_reader :options
11
+
12
+ # Initializer
13
+ # <tt>format</tt> The file format
14
+ # <tt>options</tt> Are passed to the filters.
15
+ def initialize(format, options = {})
16
+ @options = options
17
+ register_file_format(format)
18
+ end
19
+
20
+ def prepare
21
+ end
22
+
23
+ def filter(request)
24
+ return nil unless request
25
+ return request
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module RequestLogAnalyzer::Filter
2
+
3
+ # Filter to select or reject a specific field
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 Field < Base
9
+
10
+ attr_reader :field, :value, :mode
11
+
12
+ def prepare
13
+ @mode = (@options[:mode] || :accept).to_sym
14
+ @field = @options[:field].to_sym
15
+
16
+ # Convert the timestamp to the correct formats for quick timestamp comparisons
17
+ if @options[:value].kind_of?(String) && @options[:value][0, 1] == '/' && @options[:value][-1, 1] == '/'
18
+ @value = Regexp.new(@options[:value][1..-2])
19
+ else
20
+ @value = @options[:value] # TODO: convert value?
21
+ end
22
+ end
23
+
24
+ def filter(request)
25
+ return nil unless request
26
+
27
+ found_field = request.every(@field).any? { |value| @value === value }
28
+
29
+ return nil if !found_field && @mode == :select
30
+ return nil if found_field && @mode == :reject
31
+
32
+ return request
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,32 @@
1
+ module RequestLogAnalyzer::Filter
2
+
3
+ # Reject all requests not in given timespan
4
+ # Options
5
+ # * <tt>:after</tt> Only keep requests after this DateTime.
6
+ # * <tt>:before</tt> Only keep requests before this DateTime.
7
+ class Timespan < Base
8
+
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]
14
+ @before = @options[:before].strftime('%Y%m%d%H%M%S').to_i if options[:before]
15
+ end
16
+
17
+ def filter(request)
18
+ return nil unless request
19
+
20
+ if @after && @before && request.timestamp <= @before && @after <= request.timestamp
21
+ return request
22
+ elsif @after && @before.nil? && @after <= request.timestamp
23
+ return request
24
+ elsif @before && @after.nil? && request.timestamp <= @before
25
+ return request
26
+ end
27
+
28
+ return nil
29
+ end
30
+ end
31
+
32
+ end