wvanbergen-request-log-analyzer 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,14 +27,6 @@ begin
27
27
  strip.switch(:keep_junk_lines, :j)
28
28
  end
29
29
 
30
- command_line.command(:anonymize) do |anonymize|
31
- anonymize.minimum_parameters = 1
32
- anonymize.option(:format, :alias => :f, :default => 'rails')
33
- anonymize.option(:output, :alias => :o)
34
- anonymize.switch(:discard_teaser_lines, :t)
35
- anonymize.switch(:keep_junk_lines, :j)
36
- end
37
-
38
30
  command_line.option(:format, :alias => :f, :default => 'rails')
39
31
  command_line.option(:file, :alias => :e)
40
32
  command_line.switch(:assume_correct_order)
@@ -111,9 +103,6 @@ when :install
111
103
  when :strip
112
104
  require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
113
105
  RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
114
- when :anonymize
115
- require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
116
- RequestLogAnalyzer::LogProcessor.build(:anonymize, arguments).run!
117
106
  else
118
107
  puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
119
108
  puts "Website: http://github.com/wvanbergen/request-log-analyzer"
@@ -12,15 +12,19 @@ module RequestLogAnalyzer::Aggregator
12
12
  @trackers = []
13
13
  end
14
14
 
15
+ def reset!
16
+ @trackers = []
17
+ end
18
+
15
19
  def method_missing(tracker_method, *args)
16
20
  track(tracker_method, args.first)
17
21
  end
18
22
 
19
- def category(category_field, options = {})
23
+ def frequency(category_field, options = {})
20
24
  if category_field.kind_of?(Symbol)
21
- track(:category, options.merge(:category => category_field))
25
+ track(:frequency, options.merge(:category => category_field))
22
26
  elsif category_field.kind_of?(Hash)
23
- track(:category, category_field.merge(options))
27
+ track(:frequency, category_field.merge(options))
24
28
  end
25
29
  end
26
30
 
@@ -131,7 +131,7 @@ module RequestLogAnalyzer
131
131
  def handle_progress(message, value = nil)
132
132
  case message
133
133
  when :started
134
- @progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value))
134
+ @progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value), STDOUT)
135
135
  when :finished
136
136
  @progress_bar.finish
137
137
  @progress_bar = nil
@@ -160,6 +160,18 @@ module RequestLogAnalyzer
160
160
  @filters << filter.new(file_format, @options.merge(filter_options))
161
161
  end
162
162
 
163
+ def filter_request(request)
164
+ @filters.each do |filter|
165
+ request = filter.filter(request)
166
+ return nil if request.nil?
167
+ end
168
+ return request
169
+ end
170
+
171
+ def aggregate_request(request)
172
+ @aggregators.each { |agg| agg.aggregate(request) }
173
+ end
174
+
163
175
  # Runs RequestLogAnalyzer
164
176
  # 1. Call prepare on every aggregator
165
177
  # 2. Generate requests from source object
@@ -175,8 +187,8 @@ module RequestLogAnalyzer
175
187
 
176
188
  begin
177
189
  @source.each_request do |request|
178
- @filters.each { |filter| request = filter.filter(request) }
179
- @aggregators.each { |agg| agg.aggregate(request) } if request
190
+ request = filter_request(request)
191
+ aggregate_request(request) unless request.nil?
180
192
  end
181
193
  rescue Interrupt => e
182
194
  handle_progress(:interrupted)
@@ -7,7 +7,7 @@ module RequestLogAnalyzer::FileFormat
7
7
  line.header = true
8
8
  line.teaser = /Started/
9
9
  line.regexp = /Started request handling\:\ (.+)/
10
- line.captures << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly }
10
+ line.captures << { :name => :timestamp, :type => :timestamp }
11
11
  end
12
12
 
13
13
  # ~ Params: {"action"=>"create", "controller"=>"session"}
@@ -23,10 +23,10 @@ module RequestLogAnalyzer::FileFormat
23
23
  line.footer = true
24
24
  line.teaser = /\{:dispatch_time/
25
25
  line.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
- line.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 }
26
+ line.captures << { :name => :dispatch_time, :type => :duration } \
27
+ << { :name => :after_filters_time, :type => :duration } \
28
+ << { :name => :before_filters_time, :type => :duration } \
29
+ << { :name => :action_time, :type => :duration }
30
30
  end
31
31
 
32
32
 
@@ -9,9 +9,9 @@ module RequestLogAnalyzer::FileFormat
9
9
  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]+)\]/
10
10
  line.captures << { :name => :controller, :type => :string } \
11
11
  << { :name => :action, :type => :string } \
12
- << { :name => :format, :type => :string } \
13
- << { :name => :ip, :type => :string, :anonymize => :ip } \
14
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
12
+ << { :name => :format, :type => :format } \
13
+ << { :name => :ip, :type => :string } \
14
+ << { :name => :timestamp, :type => :timestamp } \
15
15
  << { :name => :method, :type => :string }
16
16
  end
17
17
 
@@ -28,7 +28,7 @@ module RequestLogAnalyzer::FileFormat
28
28
  << { :name => :message, :type => :string } \
29
29
  << { :name => :line, :type => :integer } \
30
30
  << { :name => :file, :type => :string } \
31
- << { :name => :stack_trace, :type => :string, :anonymize => true }
31
+ << { :name => :stack_trace, :type => :string }
32
32
  end
33
33
 
34
34
 
@@ -49,42 +49,56 @@ module RequestLogAnalyzer::FileFormat
49
49
  line.teaser = /Completed in /
50
50
  line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
51
51
 
52
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
53
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
54
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
52
+ line.captures << { :name => :duration, :type => :duration, :unit => :sec } \
53
+ << { :name => :view, :type => :duration, :unit => :sec } \
54
+ << { :name => :db, :type => :duration, :unit => :sec } \
55
55
  << { :name => :status, :type => :integer } \
56
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
56
+ << { :name => :url, :type => :string } # Old variant
57
57
 
58
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
59
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
60
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
61
- << { :name => :status, :type => :integer} \
62
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
58
+ line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
59
+ << { :name => :view, :type => :duration, :unit => :msec } \
60
+ << { :name => :db, :type => :duration, :unit => :msec } \
61
+ << { :name => :status, :type => :integer } \
62
+ << { :name => :url, :type => :string } # 2.2 variant
63
63
  end
64
64
 
65
65
 
66
66
 
67
67
  REQUEST_CATEGORIZER = Proc.new do |request|
68
- format = request[:format] || 'html'
69
- "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
68
+ "#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
70
69
  end
71
70
 
72
71
  report do |analyze|
73
72
  analyze.timespan :line_type => :processing
74
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
75
- analyze.category :method, :title => 'HTTP methods'
76
- analyze.category :status, :title => 'HTTP statuses returned'
77
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
73
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
74
+ analyze.frequency :method, :title => 'HTTP methods'
75
+ analyze.frequency :status, :title => 'HTTP statuses returned'
76
+ analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
78
77
 
79
78
  analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
80
79
  analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
81
80
  analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
82
81
 
83
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
82
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
84
83
  :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
85
84
 
86
85
  analyze.hourly_spread :line_type => :processing
87
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
86
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
87
+ end
88
+
89
+ # Define a custom Request class for the Rails file format to speed up timestamp handling
90
+ # and to ensure that a format is always set.
91
+ class Request < RequestLogAnalyzer::Request
92
+
93
+ # Do not use DateTime.parse
94
+ def convert_timestamp(value, definition)
95
+ value.gsub(/[^0-9]/, '')[0...14].to_i unless value.nil?
96
+ end
97
+
98
+ # Set 'html' as default format for a request
99
+ def convert_format(value, definition)
100
+ value || 'html'
101
+ end
88
102
  end
89
103
 
90
104
  end
@@ -10,8 +10,8 @@ module RequestLogAnalyzer::FileFormat
10
10
  line.captures << { :name => :controller, :type => :string } \
11
11
  << { :name => :action, :type => :string } \
12
12
  << { :name => :format, :type => :string } \
13
- << { :name => :ip, :type => :string, :anonymize => :ip } \
14
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
13
+ << { :name => :ip, :type => :string } \
14
+ << { :name => :timestamp, :type => :timestamp } \
15
15
  << { :name => :method, :type => :string }
16
16
  end
17
17
 
@@ -25,14 +25,14 @@ module RequestLogAnalyzer::FileFormat
25
25
  line.teaser = /Rendered /
26
26
  line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
27
27
  line.captures << { :name => :render_file, :type => :string } \
28
- << { :name => :render_duration, :type => :msec }
28
+ << { :name => :render_duration, :type => :duration, :unit => :msec }
29
29
  end
30
30
 
31
31
  # User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) 
32
32
  line_definition :query_executed do |line|
33
33
  line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?(.+) (?:\e\[0m)?/
34
34
  line.captures << { :name => :query_class, :type => :string } \
35
- << { :name => :query_duration, :type => :msec } \
35
+ << { :name => :query_duration, :type => :duration, :unit => :msec } \
36
36
  << { :name => :query_sql, :type => :string }
37
37
  end
38
38
 
@@ -40,7 +40,7 @@ module RequestLogAnalyzer::FileFormat
40
40
  line_definition :query_cached do |line|
41
41
  line.teaser = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
42
42
  line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
43
- line.captures << { :name => :cached_duration, :type => :msec } \
43
+ line.captures << { :name => :cached_duration, :type => :duration, :unit => :msec } \
44
44
  << { :name => :cached_sql, :type => :string }
45
45
  end
46
46
 
@@ -52,7 +52,7 @@ module RequestLogAnalyzer::FileFormat
52
52
  << { :name => :message, :type => :string } \
53
53
  << { :name => :line, :type => :integer } \
54
54
  << { :name => :file, :type => :string } \
55
- << { :name => :stack_trace, :type => :string, :anonymize => true }
55
+ << { :name => :stack_trace, :type => :string }
56
56
  end
57
57
 
58
58
 
@@ -73,17 +73,17 @@ module RequestLogAnalyzer::FileFormat
73
73
  line.teaser = /Completed in /
74
74
  line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
75
75
 
76
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
77
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
78
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
76
+ line.captures << { :name => :duration, :type => :duration } \
77
+ << { :name => :view, :type => :duration } \
78
+ << { :name => :db, :type => :duration } \
79
79
  << { :name => :status, :type => :integer } \
80
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
80
+ << { :name => :url, :type => :string } # Old variant
81
81
 
82
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
83
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
84
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
82
+ line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
83
+ << { :name => :view, :type => :duration, :unit => :msec } \
84
+ << { :name => :db, :type => :duration, :unit => :msec } \
85
85
  << { :name => :status, :type => :integer} \
86
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
86
+ << { :name => :url, :type => :string } # 2.2 variant
87
87
  end
88
88
 
89
89
  REQUEST_CATEGORIZER = Proc.new do |request|
@@ -93,20 +93,27 @@ module RequestLogAnalyzer::FileFormat
93
93
 
94
94
  report do |analyze|
95
95
  analyze.timespan :line_type => :processing
96
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
- analyze.category :method, :title => 'HTTP methods'
98
- analyze.category :status, :title => 'HTTP statuses returned'
99
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
96
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
+ analyze.frequency :method, :title => 'HTTP methods'
98
+ analyze.frequency :status, :title => 'HTTP statuses returned'
99
+ analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
100
100
 
101
101
  analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
102
102
  analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
103
103
  analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
104
104
 
105
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
105
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
106
106
  :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
107
107
 
108
108
  analyze.hourly_spread :line_type => :processing
109
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
109
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
110
110
  end
111
+
112
+ class Request < RequestLogAnalyzer::Request
113
+
114
+ def convert_timestamp(value, definition)
115
+ value.gsub(/[^0-9]/)[0...14].to_i unless value.nil?
116
+ end
117
+ end
111
118
  end
112
119
  end
@@ -1,6 +1,5 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
2
 
3
-
4
3
  def self.const_missing(const)
5
4
  RequestLogAnalyzer::load_default_class_file(self, const)
6
5
  end
@@ -24,14 +23,24 @@ module RequestLogAnalyzer::FileFormat
24
23
  elsif file_format.kind_of?(String) && File.exist?(file_format)
25
24
  # load a format from a ruby file
26
25
  require file_format
27
- klass = Object.const_get(RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb')))
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
28
34
 
29
35
  else
30
36
  # load a provided file format
31
37
  klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
32
38
  end
33
39
 
34
- raise if klass.nil?
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" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
43
+
35
44
  @current_file_format = klass.new # return an instance of the class
36
45
  end
37
46
 
@@ -56,13 +65,29 @@ module RequestLogAnalyzer::FileFormat
56
65
 
57
66
  # Registers the line definer instance for a subclass.
58
67
  def self.inherited(subclass)
59
- subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
60
- subclass.class_eval { class << self; attr_accessor :line_definer; end }
61
- subclass.class_eval { class << self; attr_accessor :report_definer; end }
62
-
63
- unless subclass.const_defined?('Request')
64
- subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request))
65
- end
68
+ if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
69
+
70
+ # Create aline and report definer for this class
71
+ subclass.class_eval do
72
+ instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
73
+ instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
74
+ class << self; attr_accessor :line_definer, :report_definer; end
75
+ end
76
+
77
+ # Create a custom Request class for this file format
78
+ subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
79
+ else
80
+
81
+ # Copy the line and report definer from the parent class.
82
+ subclass.class_eval do
83
+ instance_variable_set(:@line_definer, superclass.line_definer)
84
+ instance_variable_set(:@report_definer, superclass.report_definer)
85
+ class << self; attr_accessor :line_definer, :report_definer; end
86
+ end
87
+
88
+ # Create a custom Request class based on the superclass's Request class
89
+ subclass.const_set('Request', Class.new(subclass.superclass::Request)) unless subclass.const_defined?('Request')
90
+ end
66
91
  end
67
92
 
68
93
  # Specifies a single line defintions.
@@ -77,16 +102,19 @@ module RequestLogAnalyzer::FileFormat
77
102
  # Specifies multiple line definitions at once using a block
78
103
  def self.format_definition(&block)
79
104
  if block_given?
80
- yield(@line_definer)
105
+ yield self.line_definer
81
106
  else
82
- return @line_definer
107
+ return self.line_definer
83
108
  end
84
109
  end
85
110
 
86
111
  # Specifies the summary report using a block.
87
- def self.report(&block)
88
- @report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
89
- yield(@report_definer)
112
+ def self.report(mode = :append, &block)
113
+ if mode == :overwrite
114
+ self.report_definer.reset!
115
+ end
116
+
117
+ yield(self.report_definer)
90
118
  end
91
119
 
92
120
  # Returns all line definitions
@@ -96,7 +124,7 @@ module RequestLogAnalyzer::FileFormat
96
124
 
97
125
  # Returns all the defined trackers for the summary report.
98
126
  def report_trackers
99
- self.class.instance_variable_get(:@report_definer).trackers rescue []
127
+ self.class.report_definer.trackers# => rescue []
100
128
  end
101
129
 
102
130
  # Checks whether the line definitions form a valid language.
@@ -1,11 +1,11 @@
1
1
  module RequestLogAnalyzer::Filter
2
2
 
3
- # Filter to select or reject a specific field
3
+ # Filter to anonymize parsed values
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
- class Anonimize < Base
8
+ class Anonymize < Base
9
9
 
10
10
  def prepare
11
11
  end
@@ -22,9 +22,7 @@ module RequestLogAnalyzer::Filter
22
22
  value * ((75 + rand(50)) / 100.0)
23
23
  end
24
24
 
25
- def filter(request)
26
- return nil unless request
27
-
25
+ def filter(request)
28
26
  request.attributes.each do |key, value|
29
27
  if key == :ip
30
28
  request.attributes[key] = generate_random_ip
@@ -27,13 +27,9 @@ module RequestLogAnalyzer::Filter
27
27
  # Returns nil otherwise.
28
28
  # <tt>request</tt> Request Object
29
29
  def filter(request)
30
- return nil unless request
31
-
32
- found_field = request.every(@field).any? { |value| @value === value }
33
-
30
+ found_field = request.every(@field).any? { |value| @value === value.to_s }
34
31
  return nil if !found_field && @mode == :select
35
32
  return nil if found_field && @mode == :reject
36
-
37
33
  return request
38
34
  end
39
35
  end
@@ -22,8 +22,6 @@ module RequestLogAnalyzer::Filter
22
22
  # Returns nil otherwise
23
23
  # <tt>request</tt> Request object.
24
24
  def filter(request)
25
- return nil unless request
26
-
27
25
  if @after && @before && request.timestamp <= @before && @after <= request.timestamp
28
26
  return request
29
27
  elsif @after && @before.nil? && @after <= request.timestamp
@@ -1,22 +1,10 @@
1
1
  module RequestLogAnalyzer
2
2
 
3
- module Anonymizers
4
- def anonymizer_for_ip(value, capture_definition)
5
- '127.0.0.1'
6
- end
7
-
8
- def anonymizer_for_url(value, capture_definition)
9
- value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
10
- end
11
- end
12
-
13
3
  # The line definition class is used to specify what lines should be parsed from the log file.
14
4
  # It contains functionality to match a line against the definition and parse the information
15
5
  # from this line. This is used by the LogParser class when parsing a log file..
16
6
  class LineDefinition
17
7
 
18
- include RequestLogAnalyzer::Anonymizers
19
-
20
8
  class Definer
21
9
 
22
10
  attr_accessor :line_definitions
@@ -52,20 +40,6 @@ module RequestLogAnalyzer
52
40
  return definition
53
41
  end
54
42
 
55
- # Converts a parsed value (String) to the desired value using some heuristics.
56
- def convert_value(value, type)
57
- case type
58
- when :integer; value.to_i
59
- when :float; value.to_f
60
- when :decimal; value.to_f
61
- when :symbol; value.to_sym
62
- when :sec; value.to_f
63
- when :msec; value.to_f / 1000
64
- when :timestamp; value.gsub(/[^0-9]/,'')[0..13].to_i # Retrieve with: DateTime.parse(value, '%Y%m%d%H%M%S')
65
- else value
66
- end
67
- end
68
-
69
43
  # Checks whether a given line matches this definition.
70
44
  # It will return false if a line does not match. If the line matches, a hash is returned
71
45
  # with all the fields parsed from that line as content.
@@ -74,17 +48,7 @@ module RequestLogAnalyzer
74
48
  def matches(line, lineno = nil, parser = nil)
75
49
  if @teaser.nil? || @teaser =~ line
76
50
  if match_data = line.match(@regexp)
77
- request_info = { :line_type => name, :lineno => lineno }
78
-
79
- captures.each_with_index do |capture, index|
80
- next if capture == :ignore
81
-
82
- if match_data.captures[index]
83
- request_info[capture[:name]] = convert_value(match_data.captures[index], capture[:type])
84
- end
85
-
86
- end
87
- return request_info
51
+ return { :line_definition => self, :lineno => lineno, :captures => match_data.captures}
88
52
  else
89
53
  if @teaser && parser
90
54
  parser.warn(:teaser_check_failed, "Teaser matched for #{name.inspect}, but full line did not:\n#{line.inspect}")
@@ -97,63 +61,23 @@ module RequestLogAnalyzer
97
61
  end
98
62
 
99
63
  alias :=~ :matches
100
-
101
- def anonymize_value(value, capture_definition)
102
- if capture_definition[:anonymize].respond_to?(:call)
103
- capture_definition[:anonymize].call(value, capture_definition)
64
+
65
+ def match_for(line, request, lineno = nil, parser = nil)
66
+ if match_info = matches(line, lineno, parser)
67
+ convert_captured_values(match_info[:captures], request)
104
68
  else
105
- case capture_definition[:anonymize]
106
- when nil; value
107
- when false; value
108
- when true; '***'
109
- when :slightly; anonymize_slightly(value, capture_definition)
110
- else
111
- method_name = "anonymizer_for_#{capture_definition[:anonymize]}".to_sym
112
- self.respond_to?(method_name) ? self.send(method_name, value, capture_definition) : '***'
113
- end
69
+ false
114
70
  end
115
71
  end
116
-
117
- def anonymize_slightly(value, capture_definition)
118
- case capture_definition[:type]
119
- when :integer
120
- (value.to_i * (0.8 + rand * 0.4)).to_i
121
- when :double
122
- (value.to_f * (0.8 + rand * 0.4)).to_f
123
- when :msec
124
- (value.to_i * (0.8 + rand * 0.4)).to_i
125
- when :sec
126
- (value.to_f * (0.8 + rand * 0.4)).to_f
127
- when :timestamp
128
- (DateTime.parse(value) + (rand(100) - 50)).to_s
129
- else
130
- puts "Cannot anonymize #{capture_definition[:type].inspect} slightly, using ***"
131
- '***'
132
- end
133
- end
134
72
 
135
- # Anonymize a log line
136
- def anonymize(line, options = {})
137
- if self.teaser.nil? || self.teaser =~ line
138
- if self.regexp =~ line
139
- pos_adjustment = 0
140
- captures.each_with_index do |capture, index|
141
- unless $~[index + 1].nil?
142
- anonymized_value = anonymize_value($~[index + 1], capture).to_s
143
- line[($~.begin(index + 1) + pos_adjustment)...($~.end(index + 1) + pos_adjustment)] = anonymized_value
144
- pos_adjustment += anonymized_value.length - $~[index + 1].length
145
- end
146
- end
147
- line
148
- elsif self.teaser.nil?
149
- nil
150
- else
151
- options[:discard_teaser_lines] ? "" : line
152
- end
153
- else
154
- nil
73
+ def convert_captured_values(values, request)
74
+ value_hash = {}
75
+ captures.each_with_index do |capture, index|
76
+ value_hash[capture[:name]] ||= request.convert_value(values[index], capture)
155
77
  end
78
+ return value_hash
156
79
  end
80
+
157
81
  end
158
82
 
159
83
  end
@@ -6,12 +6,9 @@ module RequestLogAnalyzer
6
6
  # contents of the line, remain it intact or remove it altogether, based on the current
7
7
  # file format
8
8
  #
9
- # Currently, two processors are supported, :strip and :anonymize.
9
+ # Currently, one processors is supported:
10
10
  # * :strip will remove all irrelevent lines (according to the file format) from the
11
11
  # sources. A compact, information packed log will remain/.
12
- # * :anonymize will anonymize sensitive information from the lines according to the
13
- # anonymization rules in the file format. The result can be passed to third parties
14
- # without privacy concerns.
15
12
  #
16
13
  class LogProcessor
17
14
 
@@ -21,8 +18,8 @@ module RequestLogAnalyzer
21
18
  attr_accessor :output_file
22
19
 
23
20
  # Builds a logprocessor instance from the arguments given on the command line
24
- # <tt>command</tt> The command hat was used to start the log processor. This can either be
25
- # :strip or :anonymize. This will set the processing mode.
21
+ # <tt>command</tt> The command hat was used to start the log processor. This will set the
22
+ # processing mode. Currently, only :strip is supported.
26
23
  # <tt>arguments</tt> The parsed command line arguments (a CommandLine::Arguments instance)
27
24
  def self.build(command, arguments)
28
25
 
@@ -43,7 +40,7 @@ module RequestLogAnalyzer
43
40
 
44
41
  # Initializes a new LogProcessor instance.
45
42
  # <tt>format</tt> The file format to use (e.g. :rails).
46
- # <tt>mode</tt> The processing mode (:anonymize or :strip)
43
+ # <tt>mode</tt> The processing mode
47
44
  # <tt>options</tt> A hash with options to take into account
48
45
  def initialize(format, mode, options = {})
49
46
  @options = options
@@ -61,12 +58,11 @@ module RequestLogAnalyzer
61
58
  end
62
59
 
63
60
  # Processes an input stream by iteration over each line and processing it according to
64
- # the current operation mode (:strip, :anonymize)
61
+ # the current operation mode
65
62
  # <tt>io</tt> The IO instance to process.
66
63
  def process_io(io)
67
64
  case mode
68
65
  when :strip; io.each_line { |line| @output << strip_line(line) }
69
- when :anonymize; io.each_line { |line| @output << anonymize_line(line) }
70
66
  end
71
67
  end
72
68
 
@@ -77,22 +73,6 @@ module RequestLogAnalyzer
77
73
  file_format.line_definitions.any? { |name, definition| definition =~ line } ? line : ""
78
74
  end
79
75
 
80
- # Returns an anonymized version of the provided line. This can be a copy of the line it self,
81
- # an empty string or a string in which some substrings are substituted for anonymized values.
82
- # <tt>line</tt> The line to anonymize
83
- def anonymize_line(line)
84
- anonymized_line = nil
85
- file_format.line_definitions.detect { |name, definition| anonymized_line = definition.anonymize(line, options) }
86
-
87
- if anonymized_line
88
- return anonymized_line
89
- elsif options[:keep_junk_lines]
90
- return line
91
- else
92
- return ""
93
- end
94
- end
95
-
96
76
  # Runs the log processing by setting up the output stream and iterating over all the
97
77
  # input sources. Input sources can either be filenames (String instances) or IO streams
98
78
  # (IO instances). The strings "-" and "STDIN" will be substituted for the $stdin variable.
@@ -9,7 +9,45 @@ module RequestLogAnalyzer
9
9
  # Request#every(field_name) returns all values corresponding to the given field name as array.
10
10
  class Request
11
11
 
12
+ module Converters
13
+
14
+ def convert_value(value, capture_definition)
15
+ custom_converter_method = "convert_#{capture_definition[:type]}".to_sym
16
+ if respond_to?(custom_converter_method)
17
+ send(custom_converter_method, value, capture_definition)
18
+ elsif !value.nil?
19
+ case capture_definition[:type]
20
+ when :decimal; value.to_f
21
+ when :float; value.to_f
22
+ when :double; value.to_f
23
+ when :integer; value.to_i
24
+ when :int; value.to_i
25
+ when :symbol; value.to_sym
26
+ else; value.to_s
27
+ end
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ # Slow default method to parse timestamps
34
+ def convert_timestamp(value, capture_definition)
35
+ DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
36
+ end
37
+
38
+ def convert_duration(value, capture_definition)
39
+ if value.nil?
40
+ nil
41
+ elsif capture_definition[:unit] == :msec
42
+ value.to_f / 1000.0
43
+ else
44
+ value.to_f
45
+ end
46
+ end
47
+ end
48
+
12
49
  include RequestLogAnalyzer::FileFormat::Awareness
50
+ include Converters
13
51
 
14
52
  attr_reader :lines
15
53
  attr_reader :attributes
@@ -32,12 +70,22 @@ module RequestLogAnalyzer
32
70
 
33
71
  # Adds another line to the request.
34
72
  # The line should be provides as a hash of the fields parsed from the line.
35
- def add_parsed_line (request_info_hash)
36
- @lines << request_info_hash
37
- @attributes = request_info_hash.merge(@attributes)
73
+ def add_parsed_line (parsed_line)
74
+ value_hash = parsed_line[:line_definition].convert_captured_values(parsed_line[:captures], self)
75
+ value_hash[:line_type] = parsed_line[:line_definition].name
76
+ value_hash[:lineno] = parsed_line[:lineno]
77
+ add_line_hash(value_hash)
78
+ end
79
+
80
+ def add_line_hash(value_hash)
81
+ @lines << value_hash
82
+ @attributes = value_hash.merge(@attributes)
38
83
  end
39
84
 
40
- alias :<< :add_parsed_line
85
+
86
+ def <<(hash)
87
+ hash[:line_definition] ? add_parsed_line(hash) : add_line_hash(hash)
88
+ end
41
89
 
42
90
  # Checks whether the given line type was parsed from the log file for this request
43
91
  def has_line_type?(line_type)
@@ -81,6 +129,10 @@ module RequestLogAnalyzer
81
129
  header_found && footer_found
82
130
  end
83
131
 
132
+ # This function is called before a Requests is yielded.
133
+ def validate
134
+ end
135
+
84
136
  # Returns the first timestamp encountered in a request.
85
137
  def timestamp
86
138
  first(:timestamp)
@@ -131,7 +131,6 @@ module RequestLogAnalyzer::Source
131
131
  if header_line?(request_data)
132
132
  unless @current_request.nil?
133
133
  if options[:assume_correct_order]
134
- @parsed_requests += 1
135
134
  handle_request(@current_request, &block) #yield @current_request
136
135
  @current_request = @file_format.create_request(request_data)
137
136
  else
@@ -146,7 +145,6 @@ module RequestLogAnalyzer::Source
146
145
  unless @current_request.nil?
147
146
  @current_request << request_data
148
147
  if footer_line?(request_data)
149
- @parsed_requests += 1
150
148
  handle_request(@current_request, &block) # yield @current_request
151
149
  @current_request = nil
152
150
  end
@@ -161,18 +159,19 @@ module RequestLogAnalyzer::Source
161
159
  # The default controller will send the request to every running aggegator.
162
160
  def handle_request(request, &block)
163
161
  @parsed_requests += 1
162
+ request.validate
164
163
  accepted = block_given? ? yield(request) : true
165
164
  @skipped_requests += 1 if not accepted
166
165
  end
167
166
 
168
167
  # Checks whether a given line hash is a header line.
169
168
  def header_line?(hash)
170
- file_format.line_definitions[hash[:line_type]].header
169
+ hash[:line_definition].header
171
170
  end
172
171
 
173
172
  # Checks whether a given line hash is a footer line.
174
173
  def footer_line?(hash)
175
- file_format.line_definitions[hash[:line_type]].footer
174
+ hash[:line_definition].footer
176
175
  end
177
176
  end
178
177
 
@@ -1,6 +1,6 @@
1
1
  module RequestLogAnalyzer::Tracker
2
2
 
3
- # Catagorize requests.
3
+ # Catagorize requests by frequency.
4
4
  # Count and analyze requests for a specific attribute
5
5
  #
6
6
  # Accepts the following options:
@@ -19,7 +19,7 @@ module RequestLogAnalyzer::Tracker
19
19
  # PUT | 13685 hits (28.4%) |░░░░░░░░░░░
20
20
  # POST | 11662 hits (24.2%) |░░░░░░░░░
21
21
  # DELETE | 512 hits (1.1%) |
22
- class Category < Base
22
+ class Frequency < Base
23
23
 
24
24
  attr_reader :categories
25
25
 
@@ -4,42 +4,61 @@ describe RequestLogAnalyzer::Controller do
4
4
 
5
5
  include RequestLogAnalyzerSpecHelper
6
6
 
7
- # it "should include the file format module" do
8
- # controller = RequestLogAnalyzer::Controller.new(:rails)
9
- # (class << controller; self; end).ancestors.include?(RequestLogAnalyzer::FileFormat::Rails)
10
- # end
11
-
12
- it "should call the aggregators when run" do
7
+ it "should use a custom output generator correctly" do
13
8
 
14
- mock_output = mock('output')
15
- mock_output.stub!(:io).and_return($stdout)
9
+ mock_output = mock('RequestLogAnalyzer::Output::Base')
10
+ mock_output.stub!(:io).and_return(mock_io)
16
11
  mock_output.should_receive(:header)
17
12
  mock_output.should_receive(:footer)
13
+
14
+ file_format = RequestLogAnalyzer::FileFormat.load(:rails)
15
+ source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
16
+ controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
17
+
18
+ controller.run!
19
+ end
20
+
21
+ it "should call aggregators correctly when run" do
18
22
 
19
23
  file_format = RequestLogAnalyzer::FileFormat.load(:rails)
20
24
  source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
21
25
  controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
22
26
 
23
- mock_aggregator = mock('aggregator')
27
+ mock_aggregator = mock('RequestLogAnalyzer::Aggregator::Base')
24
28
  mock_aggregator.should_receive(:prepare).once.ordered
25
29
  mock_aggregator.should_receive(:aggregate).with(an_instance_of(file_format.class::Request)).at_least(:twice).ordered
26
30
  mock_aggregator.should_receive(:finalize).once.ordered
27
31
  mock_aggregator.should_receive(:report).once.ordered
32
+
33
+ controller.aggregators << mock_aggregator
34
+ controller.run!
35
+ end
36
+
37
+ it "should call filters when run" do
38
+ file_format = RequestLogAnalyzer::FileFormat.load(:rails)
39
+ source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
40
+ controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
28
41
 
29
- another_mock_aggregator = mock('another aggregator')
30
- another_mock_aggregator.should_receive(:prepare).once.ordered
31
- another_mock_aggregator.should_receive(:aggregate).with(an_instance_of(file_format.class::Request)).at_least(:twice).ordered
32
- another_mock_aggregator.should_receive(:finalize).once.ordered
33
- another_mock_aggregator.should_receive(:report).once.ordered
34
-
35
- controller.aggregators << mock_aggregator << another_mock_aggregator
42
+ mock_filter = mock('RequestLogAnalyzer::Filter::Base')
43
+ mock_filter.should_receive(:prepare).once.ordered
44
+ mock_filter.should_receive(:filter).at_least(:twice)
45
+
46
+ controller.should_not_receive(:aggregate_request)
47
+
48
+ controller.filters << mock_filter
36
49
  controller.run!
37
50
  end
38
51
 
39
- it "should run well from the command line" do
40
- temp_file = "#{File.dirname(__FILE__)}/fixtures/temp.txt"
41
- system("#{File.dirname(__FILE__)}/../bin/request-log-analyzer #{log_fixture(:rails_1x)} > #{temp_file}").should be_true
52
+ it "should run well from the command line with the most important features" do
53
+
54
+ temp_file = "#{File.dirname(__FILE__)}/fixtures/report.txt"
55
+ temp_db = "#{File.dirname(__FILE__)}/fixtures/output.db"
56
+ binary = "#{File.dirname(__FILE__)}/../bin/request-log-analyzer"
57
+
58
+ system("#{binary} #{log_fixture(:rails_1x)} --database #{temp_db} --select Controller PeopleController --file #{temp_file} > /dev/null").should be_true
59
+
42
60
  File.unlink(temp_file)
61
+ File.unlink(temp_db)
43
62
  end
44
63
 
45
64
  end
@@ -50,13 +50,13 @@ describe RequestLogAnalyzer::FileFormat, :format_definition do
50
50
  line.first_test :regexp => /test/, :captures => []
51
51
  end
52
52
 
53
+
53
54
  @second_file_format.format_definition do |line|
54
55
  line.second_test :regexp => /test/, :captures => []
55
56
  end
56
57
 
58
+ @first_file_format.line_definer.should_not eql(@second_file_format.line_definer)
57
59
  @first_file_format.new.should have(1).line_definitions
58
- @first_file_format.new.line_definitions[:first_test].should_not be_nil
59
- @second_file_format.new.should have(1).line_definitions
60
60
  @second_file_format.new.line_definitions[:second_test].should_not be_nil
61
61
  end
62
62
  end
@@ -4,24 +4,31 @@ class SpecFormat < RequestLogAnalyzer::FileFormat::Base
4
4
  line.header = true
5
5
  line.teaser = /processing /
6
6
  line.regexp = /processing request (\d+)/
7
- line.captures = [{ :name => :request_no, :type => :integer, :anonymize => :slightly }]
7
+ line.captures = [{ :name => :request_no, :type => :integer }]
8
8
  end
9
9
 
10
10
  format_definition.test do |line|
11
11
  line.teaser = /testing /
12
- line.regexp = /testing is (\w+)/
13
- line.captures = [{ :name => :test_capture, :type => :string, :anonymize => true}]
12
+ line.regexp = /testing is (\w+)(?: in (\d+\.\d+)ms)?/
13
+ line.captures = [{ :name => :test_capture, :type => :test_type },
14
+ { :name => :duration, :type => :duration, :unit => :msec }]
14
15
  end
15
16
 
16
17
  format_definition.last do |line|
17
18
  line.footer = true
18
19
  line.teaser = /finishing /
19
20
  line.regexp = /finishing request (\d+)/
20
- line.captures = [{ :name => :request_no, :type => :integer}]
21
+ line.captures = [{ :name => :request_no, :type => :integer }]
21
22
  end
22
23
 
23
24
  report do |analyze|
24
- analyze.category :test_capture, :title => 'What is testing exactly?'
25
+ analyze.frequency :test_capture, :title => 'What is testing exactly?'
26
+ end
27
+
28
+ class Request < RequestLogAnalyzer::Request
29
+ def convert_test_type(value, definition)
30
+ "Testing is #{value}"
31
+ end
25
32
  end
26
33
 
27
34
  end
data/spec/filter_spec.rb CHANGED
@@ -134,11 +134,11 @@ describe RequestLogAnalyzer::Filter::Field, 'regexp in accept mode' do
134
134
  end
135
135
  end
136
136
 
137
- describe RequestLogAnalyzer::Filter::Anonimize, 'anonimize request' do
137
+ describe RequestLogAnalyzer::Filter::Anonymize, 'anonymize request' do
138
138
  include RequestLogAnalyzerSpecHelper
139
139
 
140
140
  before(:each) do
141
- @filter = RequestLogAnalyzer::Filter::Anonimize.new(spec_format)
141
+ @filter = RequestLogAnalyzer::Filter::Anonymize.new(spec_format)
142
142
  @filter.prepare
143
143
  end
144
144
 
@@ -150,7 +150,7 @@ describe RequestLogAnalyzer::Filter::Anonimize, 'anonimize request' do
150
150
  @filter.filter(request(:url => 'https://test.mysite.com/employees'))[:url].should eql('http://example.com/employees')
151
151
  end
152
152
 
153
- it "should anonimize url" do
153
+ it "should fuzz durations" do
154
154
  @filter.filter(request(:duration => 100))[:duration].should_not eql(100)
155
155
  end
156
156
 
@@ -22,103 +22,36 @@ describe RequestLogAnalyzer::LineDefinition, :parsing do
22
22
  (@line_definition =~ "Testing LineDefinition, tries: 123").should be_kind_of(Hash)
23
23
  end
24
24
 
25
- it "should return a hash with all captures set" do
25
+ it "should return a hash with :captures set to an array" do
26
26
  hash = @line_definition.matches("Testing LineDefinition, tries: 123")
27
- hash[:what].should == "LineDefinition"
28
- hash[:tries].should == 123
27
+ hash[:captures][0].should == "LineDefinition"
28
+ hash[:captures][1].should == "123"
29
29
  end
30
30
 
31
- it "should return a hash with :line_type set" do
32
- @line_definition.matches("Testing LineDefinition, tries: 123")[:line_type].should == :test
33
- end
34
- end
35
-
36
- describe RequestLogAnalyzer::LineDefinition, :anonymizing_basics do
37
- before(:each) do
38
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
39
- :teaser => /Anonymize /,
40
- :regexp => /Anonymize (\w+)!/,
41
- :captures => [{ :name => :what, :type => :string }]
42
- })
43
- end
44
-
45
- it "should return nil if the teaser does not match" do
46
- @line_definition.anonymize("Nonsense").should be_nil
47
- end
48
-
49
- it "should return nil if no teaser exists and the regexp doesn't match" do
50
- line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
51
- :regexp => /Anonymize!/, :captures => []})
52
-
53
- line_definition.anonymize('nonsense').should be_nil
54
- end
55
-
56
- it "should return itself if only the teaser matches" do
57
- @line_definition.anonymize("Anonymize 456").should == "Anonymize 456"
58
- end
59
-
60
- it "should return an empty string if the teaser matches and discard_teaser_lines is set" do
61
- @line_definition.anonymize("Anonymize 456", :discard_teaser_lines => true).should == ""
31
+ it "should return a hash with :line_definition set" do
32
+ @line_definition.matches("Testing LineDefinition, tries: 123")[:line_definition].should == @line_definition
62
33
  end
63
-
64
- it "should return a string if the line matches" do
65
- @line_definition.anonymize("Anonymize anonymizing!").should be_kind_of(String)
66
- end
67
-
68
- it "should not anonymize :what" do
69
- @line_definition.anonymize("Anonymize anonymizing!").should == "Anonymize anonymizing!"
70
- end
71
34
  end
72
35
 
73
- describe RequestLogAnalyzer::LineDefinition, :anonymizing_specifics do
74
-
75
- it "should anonymize completely if anonymize is true" do
76
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
77
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => true }]})
36
+ describe RequestLogAnalyzer::LineDefinition, :converting do
78
37
 
79
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize ***!"
80
- end
81
-
82
- it "should anonymize a URL" do
83
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
84
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :url }]})
85
-
86
- @line_definition.anonymize("Anonymize https://www.not-anonymous.com/path/to/file.html!").should == "Anonymize http://example.com/path/to/file.html!"
87
- end
88
-
89
- it "should anonymize an IP address" do
90
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
91
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :ip }]})
92
-
93
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize 127.0.0.1!"
94
- end
38
+ include RequestLogAnalyzerSpecHelper
95
39
 
96
- it "should anonymize completely if the anonymizer is unknown" do
97
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
98
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :unknown }]})
99
-
100
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize ***!"
40
+ before(:each) do
41
+ @file_format = spec_format
42
+ @request = @file_format.create_request
101
43
  end
102
44
 
103
- it "should anonymize an integer slightly" do
104
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
105
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :integer, :anonymize => :slightly }]})
106
-
107
- @line_definition.anonymize("Anonymize 1234!").should =~ /Anonymize \d{3,4}\!/
45
+ it "should convert captures to a hash of converted values" do
46
+ hash = @file_format.line_definitions[:first].convert_captured_values(["456"], @request)
47
+ hash[:request_no].should == 456
108
48
  end
109
-
110
- it "should anonymize an integer slightly" do
111
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
112
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :integer, :anonymize => :slightly }]})
113
49
 
114
- @line_definition.anonymize("Anonymize 1234!").should =~ /Anonymize \d{3,4}\!/
50
+ it "should convert captures to a hash" do
51
+ hash = @file_format.line_definitions[:test].convert_captured_values(["willem", nil], @request)
52
+ hash[:test_capture].should == 'Testing is willem'
53
+ hash[:duration].should be_nil
115
54
  end
116
-
117
- it "should anonymize an double slightly" do
118
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
119
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :double, :anonymize => :slightly }]})
120
55
 
121
- @line_definition.anonymize("Anonymize 1.3!").should =~ /Anonymize 1\.\d+\!/
122
- end
123
-
56
+
124
57
  end
@@ -28,10 +28,9 @@ describe RequestLogAnalyzer::Source::LogParser, :requests do
28
28
 
29
29
  it "should parse all request values when spanned over multiple files" do
30
30
  @log_parser.parse_files([log_fixture(:multiple_files_1), log_fixture(:multiple_files_2)]) do |request|
31
- request.lines.should have(4).items
32
-
31
+ request.lines.should have(4).items
33
32
  request[:request_no].should == 1
34
- request[:test_capture].should == "amazing"
33
+ request[:test_capture].should == "Testing is amazing" # Note the custom converter
35
34
  end
36
35
  end
37
36
 
@@ -1,44 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
  require 'request_log_analyzer/log_processor'
3
3
 
4
- describe RequestLogAnalyzer::LogProcessor, 'anonymization' do
5
-
6
- include RequestLogAnalyzerSpecHelper
7
-
8
- before(:each) do
9
- @log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {})
10
- @alternate_log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {:keep_junk_lines => true, :discard_teaser_lines => true})
11
- end
12
-
13
- it "should keep a junk line if :keep_junk_lines is true" do
14
- @alternate_log_anonymizer.anonymize_line("junk line\n").should == "junk line\n"
15
- end
16
-
17
- it "should remove a junk line" do
18
- @log_anonymizer.anonymize_line("junk line\n").should be_empty
19
- end
20
-
21
- it "should keep a teaser line intact" do
22
- @log_anonymizer.anonymize_line("processing 1234\n").should == "processing 1234\n"
23
- end
24
-
25
- it "should discard a teaser line if discard_teaser_line is true" do
26
- @alternate_log_anonymizer.anonymize_line("processing 1234\n").should be_empty
27
- end
28
-
29
- it "should keep a matching line intact if no anonymizing is declared" do
30
- @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should == "finishing request 130\n"
31
- end
32
-
33
- it "should anonymize values completely if requested" do
34
- @alternate_log_anonymizer.anonymize_line("testing is great\n").should == "testing is ***\n"
35
- end
36
-
37
- it "should anonymize values slightly if requested" do
38
- @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should =~ /^finishing request 1\d\d\n$/
39
- end
40
- end
41
-
42
4
  describe RequestLogAnalyzer::LogProcessor, 'stripping log files' do
43
5
 
44
6
  include RequestLogAnalyzerSpecHelper
@@ -29,7 +29,7 @@ describe RequestLogAnalyzer::Source::LogParser, :merb do
29
29
  @log_parser.parse_file(log_fixture(:merb)) { |found_request| request ||= found_request }
30
30
 
31
31
  request.should be_completed
32
- #request[:timestamp].should == DateTime.parse('Fri Aug 29 11:10:23 +0200 2008') # FIX ME
32
+ request[:timestamp].should == 20080829111023 # 'Fri Aug 29 11:10:23 +0200 2008'
33
33
  request[:dispatch_time].should == 0.243424
34
34
  request[:after_filters_time].should == 6.9e-05
35
35
  request[:before_filters_time].should == 0.213213
@@ -80,6 +80,7 @@ describe "RequestLogAnalyzer::FileFormat::RailsDevelopment - Rails with developm
80
80
 
81
81
  before(:each) do
82
82
  @file_format = RequestLogAnalyzer::FileFormat.load(:rails_development)
83
+ @request = @file_format.create_request
83
84
  end
84
85
 
85
86
  it "should have a valid language definitions" do
@@ -87,33 +88,33 @@ describe "RequestLogAnalyzer::FileFormat::RailsDevelopment - Rails with developm
87
88
  end
88
89
 
89
90
  it "should parse a rendered line" do
90
- info = @file_format.line_definitions[:rendered].matches("Rendered layouts/_footer (2.9ms)")
91
+ info = @file_format.line_definitions[:rendered].match_for("Rendered layouts/_footer (2.9ms)", @request)
91
92
  info[:render_file].should == 'layouts/_footer'
92
93
  info[:render_duration].should == 0.0029
93
94
  end
94
95
 
95
96
  it "should parse a query executed line with colors" do
96
- info = @file_format.line_definitions[:query_executed].matches(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ")
97
+ info = @file_format.line_definitions[:query_executed].match_for(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ", @request)
97
98
  info[:query_class].should == 'User'
98
99
  info[:query_duration].should == 0.0004
99
100
  info[:query_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 18205844)'
100
101
  end
101
102
 
102
103
  it "should parse a query executed line without colors" do
103
- info = @file_format.line_definitions[:query_executed].matches(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ")
104
+ info = @file_format.line_definitions[:query_executed].match_for(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ", @request)
104
105
  info[:query_class].should == 'User'
105
106
  info[:query_duration].should == 0.0004
106
107
  info[:query_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 18205844)'
107
108
  end
108
109
 
109
110
  it "should parse a cached query line with colors" do
110
- info = @file_format.line_definitions[:query_cached].matches(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ')
111
+ info = @file_format.line_definitions[:query_cached].match_for(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ', @request)
111
112
  info[:cached_duration].should == 0.0
112
113
  info[:cached_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 0)'
113
114
  end
114
115
 
115
116
  it "should parse a cached query line without colors" do
116
- info = @file_format.line_definitions[:query_cached].matches(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ')
117
+ info = @file_format.line_definitions[:query_cached].match_for(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ', @request)
117
118
  info[:cached_duration].should == 0.0
118
119
  info[:cached_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 0)'
119
120
  end
data/spec/spec_helper.rb CHANGED
@@ -28,6 +28,22 @@ module RequestLogAnalyzerSpecHelper
28
28
  format.create_request(fields)
29
29
  end
30
30
  end
31
+
32
+ def mock_io
33
+ mio = mock('IO')
34
+ mio.stub!(:print)
35
+ mio.stub!(:puts)
36
+ mio.stub!(:write)
37
+ return mio
38
+ end
39
+
40
+ def mock_output
41
+ output = mock('RequestLogAnalyzer::Output')
42
+ output.stub!(:header)
43
+ output.stub!(:footer)
44
+ output.stub!(:io).and_return(mock_io)
45
+ return output
46
+ end
31
47
 
32
48
  end
33
49
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wvanbergen-request-log-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-01-21 00:00:00 -08:00
13
+ date: 2009-01-24 00:00:00 -08:00
14
14
  default_executable: request-log-analyzer
15
15
  dependencies: []
16
16
 
@@ -49,7 +49,7 @@ files:
49
49
  - lib/request_log_analyzer/file_format/rails_development.rb
50
50
  - lib/request_log_analyzer/filter
51
51
  - lib/request_log_analyzer/filter.rb
52
- - lib/request_log_analyzer/filter/anonimize.rb
52
+ - lib/request_log_analyzer/filter/anonymize.rb
53
53
  - lib/request_log_analyzer/filter/field.rb
54
54
  - lib/request_log_analyzer/filter/timespan.rb
55
55
  - lib/request_log_analyzer/line_definition.rb
@@ -64,8 +64,8 @@ files:
64
64
  - lib/request_log_analyzer/source/log_parser.rb
65
65
  - lib/request_log_analyzer/tracker
66
66
  - lib/request_log_analyzer/tracker.rb
67
- - lib/request_log_analyzer/tracker/category.rb
68
67
  - lib/request_log_analyzer/tracker/duration.rb
68
+ - lib/request_log_analyzer/tracker/frequency.rb
69
69
  - lib/request_log_analyzer/tracker/hourly_spread.rb
70
70
  - lib/request_log_analyzer/tracker/timespan.rb
71
71
  - spec