wvanbergen-request-log-analyzer 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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