request-log-analyzer 1.3.4 → 1.3.5

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.
@@ -57,11 +57,14 @@ module RequestLogAnalyzer::Aggregator
57
57
 
58
58
  # Records source changes in the sources table
59
59
  def source_change(change, filename)
60
- case change
61
- when :started
62
- @sources[filename] = database.source_class.create!(:filename => filename)
63
- when :finished
64
- @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
60
+ if File.exist?(filename)
61
+ case change
62
+ when :started
63
+ p database.source_class
64
+ @sources[filename] = database.source_class.create!(:filename => filename)
65
+ when :finished
66
+ @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
67
+ end
65
68
  end
66
69
  end
67
70
 
@@ -13,8 +13,9 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
13
13
  def line_type
14
14
  self.class.name.underscore.gsub(/_line$/, '').to_sym
15
15
  end
16
-
17
- cattr_accessor :database, :line_definition
16
+
17
+ class_inheritable_accessor :line_definition
18
+ cattr_accessor :database
18
19
 
19
20
  def self.subclass_from_line_definition(definition)
20
21
  klass = Class.new(RequestLogAnalyzer::Database::Base)
@@ -9,7 +9,7 @@ module RequestLogAnalyzer::FileFormat
9
9
  line_definition :access do |line|
10
10
  line.header = true
11
11
  line.footer = true
12
- line.regexp = /^([^\ ]+) ([^\ ]+) \[([^\]]{26})\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
12
+ line.regexp = /^([^\ ]+) ([^\ ]+) \[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
13
13
  line.captures << { :name => :bucket_owner, :type => :string } <<
14
14
  { :name => :bucket, :type => :string } <<
15
15
  { :name => :timestamp, :type => :timestamp } <<
@@ -21,8 +21,8 @@ module RequestLogAnalyzer::FileFormat
21
21
  { :name => :request_uri, :type => :string } <<
22
22
  { :name => :http_status, :type => :integer } <<
23
23
  { :name => :error_code, :type => :nillable_string } <<
24
- { :name => :bytes_sent, :type => :integer } <<
25
- { :name => :object_size, :type => :integer } <<
24
+ { :name => :bytes_sent, :type => :traffic, :unit => :byte } <<
25
+ { :name => :object_size, :type => :traffic, :unit => :byte } <<
26
26
  { :name => :total_time, :type => :duration, :unit => :msec } <<
27
27
  { :name => :turnaround_time, :type => :duration, :unit => :msec } <<
28
28
  { :name => :referer, :type => :referer } <<
@@ -34,7 +34,8 @@ module RequestLogAnalyzer::FileFormat
34
34
  analyze.hourly_spread
35
35
 
36
36
  analyze.frequency :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Most popular items"
37
- analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Duration"
37
+ analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Request duration"
38
+ analyze.traffic :traffic => :bytes_sent, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Traffic"
38
39
  analyze.frequency :category => :http_status, :title => 'HTTP status codes'
39
40
  analyze.frequency :category => :error_code, :title => 'Error codes'
40
41
  end
@@ -47,7 +48,7 @@ module RequestLogAnalyzer::FileFormat
47
48
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
48
49
  # to speed up parsing.
49
50
  def convert_timestamp(value, definition)
50
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
51
+ d = /^(\d{2})\/([A-Za-z]{3})\/(\d{4}).(\d{2}):(\d{2}):(\d{2})/.match(value).captures
51
52
  "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
52
53
  end
53
54
 
@@ -17,26 +17,30 @@ module RequestLogAnalyzer::FileFormat
17
17
  # A hash of predefined Apache log formats
18
18
  LOG_FORMAT_DEFAULTS = {
19
19
  :common => '%h %l %u %t "%r" %>s %b',
20
- :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
20
+ :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"',
21
+ :rack => '%h %l %u %t "%r" %>s %b %T',
22
+ :referer => '%{Referer}i -> %U',
23
+ :agent => '%{User-agent}i'
21
24
  }
22
-
25
+
23
26
  # A hash that defines how the log format directives should be parsed.
24
27
  LOG_DIRECTIVES = {
25
28
  '%' => { :regexp => '%', :captures => [] },
26
29
  'h' => { :regexp => '([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)', :captures => [{:name => :remote_host, :type => :string}] },
27
30
  'a' => { :regexp => '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', :captures => [{:name => :remote_ip, :type => :string}] },
28
- 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :integer}] },
31
+ 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] },
29
32
  'c' => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] },
30
- 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :msec}] },
33
+ 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :musec}] },
31
34
  'l' => { :regexp => '([\w-]+)', :captures => [{:name => :remote_logname, :type => :nillable_string}] },
32
- 'T' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
33
- 't' => { :regexp => '\[([^\]]{26})\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
35
+ 'T' => { :regexp => '((?:\d+(?:\.\d+))|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
36
+ 't' => { :regexp => '\[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
34
37
  's' => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] },
35
38
  'u' => { :regexp => '(\w+|-)', :captures => [{:name => :user, :type => :nillable_string}] },
36
- 'r' => { :regexp => '([A-Z]+) ([^\s]+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
39
+ 'U' => { :regexp => '(\/\S*)', :captures => [{:name => :path, :type => :string}] },
40
+ 'r' => { :regexp => '([A-Z]+) (\S+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
37
41
  {:name => :path, :type => :path}, {:name => :http_version, :type => :string}]},
38
- 'i' => { 'Referer' => { :regexp => '([^\s]+)', :captures => [{:name => :referer, :type => :nillable_string}] },
39
- 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
42
+ 'i' => { 'Referer' => { :regexp => '(\S+)', :captures => [{:name => :referer, :type => :nillable_string}] },
43
+ 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
40
44
  }
41
45
  }
42
46
 
@@ -97,6 +101,10 @@ module RequestLogAnalyzer::FileFormat
97
101
  analyze.duration :duration => :duration, :category => :path , :title => 'Request duration'
98
102
  end
99
103
 
104
+ if line_definition.captures?(:path) && line_definition.captures?(:bytes_sent)
105
+ analyze.traffic :traffic => :bytes_sent, :category => :path , :title => 'Traffic'
106
+ end
107
+
100
108
  return analyze.trackers
101
109
  end
102
110
 
@@ -109,7 +117,7 @@ module RequestLogAnalyzer::FileFormat
109
117
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
110
118
  # to speed up parsing.
111
119
  def convert_timestamp(value, definition)
112
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
120
+ d = /^(\d{2})\/([A-Za-z]{3})\/(\d{4}).(\d{2}):(\d{2}):(\d{2})/.match(value).captures
113
121
  "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
114
122
  end
115
123
 
@@ -0,0 +1,11 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ class Rack < Apache
4
+
5
+ def self.create(*args)
6
+ super(:rack, *args)
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -104,7 +104,7 @@ module RequestLogAnalyzer::Output
104
104
  end
105
105
 
106
106
  # Write a link
107
- # <tt>text</tt> The text in the link
107
+ # <tt>text</tt> The text in the link, or the URL itself if no text is given
108
108
  # <tt>url</tt> The url to link to.
109
109
  def link(text, url = nil)
110
110
  if url.nil?
@@ -114,13 +114,13 @@ module RequestLogAnalyzer::Output
114
114
  end
115
115
  end
116
116
 
117
- # Generate a header for a report
117
+ # Generate a header for a report
118
118
  def header
119
119
  if io.kind_of?(File)
120
- puts "Request-log-analyzer summary report"
121
- line
120
+ puts colorize("Request-log-analyzer summary report", :white, :bold)
121
+ line(:green)
122
122
  puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
123
- puts "Request-log-analyzer website: http://github.com/wvanbergen/request-log-analyzer"
123
+ puts "Website: #{link('http://github.com/wvanbergen/request-log-analyzer')}"
124
124
  end
125
125
  end
126
126
 
@@ -128,8 +128,9 @@ module RequestLogAnalyzer::Output
128
128
  def footer
129
129
  puts
130
130
  puts "Need an expert to analyze your application?"
131
- puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
132
- puts "Thanks for using request-log-analyzer!"
131
+ puts "Mail to #{link('contact@railsdoctors.com')} or visit us at #{link('http://railsdoctors.com')}."
132
+ line(:green)
133
+ puts "Thanks for using #{colorize('request-log-analyzer', :white, :bold)}!"
133
134
  end
134
135
 
135
136
  # Generate a report table and push it into the output object.
@@ -41,13 +41,25 @@ module RequestLogAnalyzer
41
41
  DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
42
42
  end
43
43
 
44
+ def convert_traffic(value, capture_definition)
45
+ return nil if value.nil?
46
+ case capture_definition[:unit]
47
+ when :GB, :G, :gigabyte then (value.to_f * 1000_000_000).round
48
+ when :GiB, :gibibyte then (value.to_f * (2 ** 30)).round
49
+ when :MB, :M, :megabyte then (value.to_f * 1000_000).round
50
+ when :MiB, :mebibyte then (value.to_f * (2 ** 20)).round
51
+ when :KB, :K, :kilobyte, :kB then (value.to_f * 1000).round
52
+ when :KiB, :kibibyte then (value.to_f * (2 ** 10)).round
53
+ else value.to_i
54
+ end
55
+ end
56
+
44
57
  def convert_duration(value, capture_definition)
45
- if value.nil?
46
- nil
47
- elsif capture_definition[:unit] == :msec
48
- value.to_f / 1000.0
49
- else
50
- value.to_f
58
+ return nil if value.nil?
59
+ case capture_definition[:unit]
60
+ when :microsec, :musec then value.to_f / 1000000.0
61
+ when :msec, :millisec then value.to_f / 1000.0
62
+ else value.to_f
51
63
  end
52
64
  end
53
65
  end
@@ -51,10 +51,10 @@ module RequestLogAnalyzer::Tracker
51
51
  raise "Capture mismatch for multiple values in a request"
52
52
  end
53
53
  else
54
- category = @categorizer.call(request) # options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
55
- duration = @durationizer.call(request) # options[:duration].respond_to?(:call) ? options[:duration].call(request) : request[options[:duration]]
54
+ category = @categorizer.call(request)
55
+ duration = @durationizer.call(request)
56
56
 
57
- if duration.kind_of?(Float) && category.kind_of?(String)
57
+ if duration.kind_of?(Numeric) && !category.nil?
58
58
  @categories[category] ||= {:hits => 0, :cumulative => 0.0, :min => duration, :max => duration }
59
59
  @categories[category][:hits] += 1
60
60
  @categories[category][:cumulative] += duration
@@ -0,0 +1,186 @@
1
+ module RequestLogAnalyzer::Tracker
2
+
3
+ # Analyze the average and total traffic of requests
4
+ #
5
+ # === Options
6
+ # * <tt>:amount</tt> The amount of lines in the report
7
+ # * <tt>:category</tt> Proc that handles request categorization for given fileformat (REQUEST_CATEGORIZER)
8
+ # * <tt>:traffic</tt> The field containing the duration in the request hash.
9
+ # * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
10
+ # * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
11
+ # * <tt>:title</tt> Title do be displayed above the report
12
+ # * <tt>:unless</tt> Handle request if this proc is false for the handled request.
13
+ class Traffic < Base
14
+
15
+ attr_reader :categories
16
+
17
+ # Check if duration and catagory option have been received,
18
+ def prepare
19
+ raise "No traffic field set up for category tracker #{self.inspect}" unless options[:traffic]
20
+ raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
21
+
22
+ @categorizer = options[:category].respond_to?(:call) ? options[:category] : lambda { |request| request[options[:category]] }
23
+ @trafficizer = options[:traffic].respond_to?(:call) ? options[:traffic] : lambda { |request| request[options[:traffic]] }
24
+ @categories = {}
25
+ end
26
+
27
+ # Get the duration information fron the request and store it in the different categories.
28
+ # <tt>request</tt> The request.
29
+ def update(request)
30
+ category = @categorizer.call(request)
31
+ traffic = @trafficizer.call(request)
32
+
33
+ if traffic.kind_of?(Numeric) && !category.nil?
34
+ @categories[category] ||= {:hits => 0, :cumulative => 0, :min => traffic, :max => traffic }
35
+ @categories[category][:hits] += 1
36
+ @categories[category][:cumulative] += traffic
37
+ @categories[category][:min] = traffic if traffic < @categories[category][:min]
38
+ @categories[category][:max] = traffic if traffic > @categories[category][:max]
39
+ end
40
+ end
41
+
42
+ # Get the number of hits of a specific category.
43
+ # <tt>cat</tt> The category
44
+ def hits(cat)
45
+ categories[cat][:hits]
46
+ end
47
+
48
+ # Get the total duration of a specific category.
49
+ # <tt>cat</tt> The category
50
+ def cumulative_traffic(cat)
51
+ categories[cat][:cumulative]
52
+ end
53
+
54
+ # Get the minimal duration of a specific category.
55
+ # <tt>cat</tt> The category
56
+ def min_traffic(cat)
57
+ categories[cat][:min]
58
+ end
59
+
60
+ # Get the maximum duration of a specific category.
61
+ # <tt>cat</tt> The category
62
+ def max_traffic(cat)
63
+ categories[cat][:max]
64
+ end
65
+
66
+ # Get the average duration of a specific category.
67
+ # <tt>cat</tt> The category
68
+ def average_traffic(cat)
69
+ categories[cat][:cumulative].to_f / categories[cat][:hits]
70
+ end
71
+
72
+ # Get the average duration of a all categories.
73
+ def overall_average_traffic
74
+ overall_cumulative_duration.to_f / overall_hits
75
+ end
76
+
77
+ # Get the cumlative duration of a all categories.
78
+ def overall_cumulative_traffic
79
+ categories.inject(0) { |sum, (name, cat)| sum + cat[:cumulative] }
80
+ end
81
+
82
+ # Get the total hits of a all categories.
83
+ def overall_hits
84
+ categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
85
+ end
86
+
87
+ # Return categories sorted by hits.
88
+ def sorted_by_hits
89
+ sorted_by(:hits)
90
+ end
91
+
92
+ # Return categories sorted by cumulative duration.
93
+ def sorted_by_cumulative
94
+ sorted_by(:cumulative)
95
+ end
96
+
97
+ # Return categories sorted by cumulative duration.
98
+ def sorted_by_average
99
+ sorted_by { |cat| cat[:cumulative].to_f / cat[:hits] }
100
+ end
101
+
102
+ # Return categories sorted by a given key.
103
+ # <tt>by</tt> The key.
104
+ def sorted_by(by = nil)
105
+ if block_given?
106
+ categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
107
+ else
108
+ categories.sort { |a, b| b[1][by] <=> a[1][by] }
109
+ end
110
+ end
111
+
112
+ # Block function to build a result table using a provided sorting function.
113
+ # <tt>output</tt> The output object.
114
+ # <tt>amount</tt> The number of rows in the report table (default 10).
115
+ # === Options
116
+ # * </tt>:title</tt> The title of the table
117
+ # * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
118
+ def report_table(output, amount = 10, options = {}, &block)
119
+
120
+ output.title(options[:title])
121
+
122
+ top_categories = @categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }.slice(0...amount)
123
+ output.table({:title => 'Category', :width => :rest},
124
+ {:title => 'Hits', :align => :right, :highlight => (options[:sort] == :hits), :min_width => 4},
125
+ {:title => 'Cumulative', :align => :right, :highlight => (options[:sort] == :cumulative), :min_width => 10},
126
+ {:title => 'Average', :align => :right, :highlight => (options[:sort] == :average), :min_width => 8},
127
+ {:title => 'Min', :align => :right, :highlight => (options[:sort] == :min)},
128
+ {:title => 'Max', :align => :right, :highlight => (options[:sort] == :max)}) do |rows|
129
+
130
+ top_categories.each do |(cat, info)|
131
+ rows << [cat, info[:hits], format_traffic(info[:cumulative]), format_traffic((info[:cumulative] / info[:hits]).round),
132
+ format_traffic(info[:min]), format_traffic(info[:max])]
133
+ end
134
+ end
135
+ end
136
+
137
+ # Formats the traffic number using x B/kB/MB/GB etc notation
138
+ def format_traffic(bytes)
139
+ return "0 B" if bytes.zero?
140
+ case Math.log10(bytes).floor
141
+ when 1...4 then '%d B' % bytes
142
+ when 4...7 then '%d kB' % (bytes / 1000)
143
+ when 7...10 then '%d MB' % (bytes / 1000_000)
144
+ when 10...13 then '%d GB' % (bytes / 1000_000_000)
145
+ else '%d TB' % (bytes / 1000_000_000_000)
146
+ end
147
+ end
148
+
149
+ # Generate a request duration report to the given output object
150
+ # By default colulative and average duration are generated.
151
+ # Any options for the report should have been set during initialize.
152
+ # <tt>output</tt> The output object
153
+ def report(output)
154
+
155
+ options[:report] ||= [:cumulative, :average]
156
+ options[:top] ||= 20
157
+
158
+ options[:report].each do |report|
159
+ case report
160
+ when :average
161
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by average", :sort => :average) { |cat| cat[:cumulative] / cat[:hits] }
162
+ when :cumulative
163
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by sum", :sort => :cumulative) { |cat| cat[:cumulative] }
164
+ when :hits
165
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by hits", :sort => :hits) { |cat| cat[:hits] }
166
+ else
167
+ raise "Unknown duration report specified: #{report}!"
168
+ end
169
+ end
170
+
171
+ output.puts
172
+ output.puts "#{output.colorize(title, :white, :bold)} - observed total: " + output.colorize(format_traffic(overall_cumulative_traffic), :brown, :bold)
173
+ end
174
+
175
+ # Returns the title of this tracker for reports
176
+ def title
177
+ options[:title] || 'Request traffic'
178
+ end
179
+
180
+ # Returns all the categories and the tracked duration as a hash than can be exported to YAML
181
+ def to_yaml_object
182
+ return nil if @categories.empty?
183
+ @categories
184
+ end
185
+ end
186
+ end
@@ -10,8 +10,8 @@ Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_t
10
10
  module RequestLogAnalyzer
11
11
 
12
12
  # The current version of request-log-analyzer.
13
- # This will be diplayed in output reports etc.
14
- VERSION = "1.3.4"
13
+ # This will be diplayed in output reports etc.
14
+ VERSION = "1.3.5"
15
15
 
16
16
  # Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
17
17
  # <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
@@ -25,7 +25,7 @@ module RequestLogAnalyzer
25
25
  # <tt>const</tt>:: The constant to load from the base constant as a string or symbol. This should be 'Bar' or :Bar when the constant Foo::Bar is being loaded.
26
26
  def self.load_default_class_file(base, const)
27
27
  require "#{to_underscore("#{base.name}::#{const}")}"
28
- base.const_get(const)
28
+ base.const_get(const) if base.const_defined?(const)
29
29
  end
30
30
 
31
31
  # Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "request-log-analyzer"
3
- s.version = "1.3.4"
4
- s.date = "2009-09-14"
3
+ s.version = "1.3.5"
4
+ s.date = "2009-09-16"
5
5
 
6
6
  s.rubyforge_project = 'r-l-a'
7
7
 
@@ -31,6 +31,6 @@ Gem::Specification.new do |s|
31
31
  s.email = ['willem@railsdoctors.com', 'bart@railsdoctors.com']
32
32
  s.homepage = 'http://railsdoctors.com'
33
33
 
34
- s.files = %w(spec/unit/filter/anonymize_filter_spec.rb lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/fixtures/rails_22_cached.log spec/lib/macros.rb lib/request_log_analyzer/file_format/rails_development.rb spec/fixtures/apache_combined.log spec/fixtures/apache_common.log spec/fixtures/merb_prefixed.log lib/request_log_analyzer/file_format/amazon_s3.rb tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb lib/request_log_analyzer/database.rb spec/fixtures/decompression.log.bz2 lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb spec/fixtures/rails_unordered.log bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/lib/matchers.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/testing_format.rb spec/fixtures/test_order.log spec/fixtures/rails.db lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/database/base.rb lib/request_log_analyzer/aggregator.rb lib/cli/progressbar.rb lib/request_log_analyzer/mailer.rb README.rdoc spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/controller/log_processor_spec.rb spec/spec_helper.rb lib/request_log_analyzer.rb spec/database.yml Rakefile lib/request_log_analyzer/database/connection.rb spec/unit/filter/filter_spec.rb spec/fixtures/test_language_combined.log lib/request_log_analyzer/aggregator/database_inserter.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/decompression.tar.gz spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb lib/cli/database_console.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/lib/mocks.rb spec/lib/helpers.rb lib/cli/database_console_init.rb lib/request_log_analyzer/output.rb lib/request_log_analyzer/file_format/apache.rb spec/fixtures/rails_1x.log spec/fixtures/decompression.log.zip spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log tasks/github-gem.rake spec/unit/database/database_spec.rb lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/rails_22.log spec/fixtures/multiple_files_2.log spec/fixtures/syslog_1x.log LICENSE lib/request_log_analyzer/source/database_loader.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
35
- s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/database/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
34
+ s.files = %w(spec/unit/filter/anonymize_filter_spec.rb lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/fixtures/rails_22_cached.log spec/lib/macros.rb lib/request_log_analyzer/file_format/rails_development.rb spec/fixtures/apache_combined.log spec/fixtures/apache_common.log spec/fixtures/merb_prefixed.log lib/request_log_analyzer/file_format/amazon_s3.rb tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb lib/request_log_analyzer/database.rb spec/fixtures/decompression.log.bz2 spec/fixtures/rails_unordered.log lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/lib/matchers.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/testing_format.rb spec/fixtures/test_order.log spec/fixtures/rails.db lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/database/base.rb lib/request_log_analyzer/aggregator.rb lib/cli/progressbar.rb lib/request_log_analyzer/mailer.rb README.rdoc spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/controller/log_processor_spec.rb spec/spec_helper.rb lib/request_log_analyzer.rb spec/database.yml Rakefile lib/request_log_analyzer/database/connection.rb spec/unit/filter/filter_spec.rb spec/fixtures/test_language_combined.log lib/request_log_analyzer/aggregator/database_inserter.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rack.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/decompression.tar.gz spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb lib/cli/database_console.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/lib/mocks.rb spec/lib/helpers.rb lib/cli/database_console_init.rb lib/request_log_analyzer/output.rb lib/request_log_analyzer/file_format/apache.rb spec/fixtures/rails_1x.log spec/fixtures/decompression.log.zip spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log tasks/github-gem.rake spec/unit/database/database_spec.rb lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/tracker/traffic.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/syslog_1x.log spec/fixtures/rails_22.log spec/fixtures/multiple_files_2.log LICENSE lib/request_log_analyzer/source/database_loader.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
35
+ s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/database/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
36
36
  end
data/spec/lib/mocks.rb CHANGED
@@ -25,7 +25,7 @@ module RequestLogAnalyzer::Spec::Mocks
25
25
  def mock_io
26
26
  mio = mock('IO')
27
27
  mio.stub!(:print)
28
- mio.stub!(:puts)
28
+ mio.stub!(:puts)
29
29
  mio.stub!(:write)
30
30
  return mio
31
31
  end
@@ -35,12 +35,15 @@ module RequestLogAnalyzer::Spec::Mocks
35
35
  output.stub!(:header)
36
36
  output.stub!(:footer)
37
37
  output.stub!(:puts)
38
- output.stub!(:<<)
38
+ output.stub!(:<<)
39
+ output.stub!(:colorize).and_return("Fancy text")
40
+ output.stub!(:link)
39
41
  output.stub!(:title)
40
42
  output.stub!(:line)
41
43
  output.stub!(:with_style)
42
44
  output.stub!(:table).and_yield([])
43
45
  output.stub!(:io).and_return(mock_io)
46
+
44
47
  return output
45
48
  end
46
49
 
@@ -99,12 +99,49 @@ describe RequestLogAnalyzer::FileFormat::Apache do
99
99
  @log_parser.parse_file(log_fixture(:apache_common)) { counter.hit! }
100
100
  end
101
101
  end
102
+
103
+ context '"Rack" access log parser' do
104
+ before(:each) do
105
+ @file_format = RequestLogAnalyzer::FileFormat.load(:rack)
106
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
107
+ @sample_1 = '127.0.0.1 - - [16/Sep/2009 06:40:08] "GET /favicon.ico HTTP/1.1" 500 63183 0.0453'
108
+ end
109
+
110
+ it "should create a kind of an Apache file format" do
111
+ @file_format.should be_kind_of(RequestLogAnalyzer::FileFormat::Apache)
112
+ end
113
+
114
+ it "should have a valid language definitions" do
115
+ @file_format.should be_valid
116
+ end
117
+
118
+ it "should parse a valid access log line" do
119
+ @file_format.line_definitions[:access].matches(@sample_1).should be_kind_of(Hash)
120
+ end
121
+
122
+ it "should not parse a valid access log line" do
123
+ @file_format.line_definitions[:access].matches('addasdsasadadssadasd').should be_false
124
+ end
125
+
126
+ it "should read the correct values from a valid 404 access log line" do
127
+ @log_parser.parse_io(StringIO.new(@sample_1)) do |request|
128
+ request[:remote_host].should == '127.0.0.1'
129
+ request[:timestamp].should == 20090916064008
130
+ request[:http_status].should == 500
131
+ request[:http_method].should == 'GET'
132
+ request[:http_version].should == '1.1'
133
+ request[:bytes_sent].should == 63183
134
+ request[:user].should == nil
135
+ request[:duration].should == 0.0453
136
+ end
137
+ end
138
+ end
102
139
 
103
140
  context '"Combined" access log parsing' do
104
141
 
105
142
  before(:all) do
106
143
  @file_format = RequestLogAnalyzer::FileFormat.load(:apache, :combined)
107
- @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
144
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
108
145
  @sample_1 = '69.41.0.45 - - [02/Sep/2009:12:02:40 +0200] "GET //phpMyAdmin/ HTTP/1.1" 404 209 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"'
109
146
  @sample_2 = '10.0.1.1 - - [02/Sep/2009:05:08:33 +0200] "GET / HTTP/1.1" 200 30 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9"'
110
147
  end
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Traffic do
4
+
5
+ describe '#update' do
6
+
7
+ context 'using a field-based category' do
8
+ before(:each) do
9
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => :category)
10
+ @tracker.prepare
11
+ end
12
+
13
+ it "should register a request in the right category" do
14
+ @tracker.update(request(:category => 'a', :traffic => 200))
15
+ @tracker.categories.should include('a')
16
+ end
17
+
18
+ it "should register a hit in the right category" do
19
+ @tracker.update(request(:category => 'a', :traffic => 1))
20
+ @tracker.update(request(:category => 'b', :traffic => 2))
21
+ @tracker.update(request(:category => 'b', :traffic => 3))
22
+
23
+ @tracker.hits('a').should == 1
24
+ @tracker.hits('b').should == 2
25
+ end
26
+
27
+ it "should sum the traffics of the same category as cumulative traffic" do
28
+ @tracker.update(request(:category => 'a', :traffic => 1))
29
+ @tracker.update(request(:category => 'b', :traffic => 2))
30
+ @tracker.update(request(:category => 'b', :traffic => 3))
31
+
32
+ @tracker.cumulative_traffic('a').should == 1
33
+ @tracker.cumulative_traffic('b').should == 5
34
+ end
35
+
36
+ it "should calculate the average traffic correctly" do
37
+ @tracker.update(request(:category => 'a', :traffic => 1))
38
+ @tracker.update(request(:category => 'b', :traffic => 2))
39
+ @tracker.update(request(:category => 'b', :traffic => 3))
40
+
41
+ @tracker.average_traffic('a').should == 1.0
42
+ @tracker.average_traffic('b').should == 2.5
43
+ end
44
+
45
+ it "should set min and max traffic correctly" do
46
+ @tracker.update(request(:category => 'a', :traffic => 1))
47
+ @tracker.update(request(:category => 'b', :traffic => 2))
48
+ @tracker.update(request(:category => 'b', :traffic => 3))
49
+
50
+ @tracker.min_traffic('b').should == 2
51
+ @tracker.max_traffic('b').should == 3
52
+ end
53
+
54
+ end
55
+
56
+ context 'using a dynamic category' do
57
+ before(:each) do
58
+ @categorizer = Proc.new { |request| request[:traffic] < 2 ? 'few' : 'lots' }
59
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => @categorizer)
60
+ @tracker.prepare
61
+ end
62
+
63
+ it "should use the categorizer to determine the right category" do
64
+ @tracker.update(request(:category => 'a', :traffic => 1))
65
+ @tracker.update(request(:category => 'b', :traffic => 2))
66
+ @tracker.update(request(:category => 'b', :traffic => 3))
67
+
68
+ @tracker.categories.should include('few', 'lots')
69
+ end
70
+
71
+ it "should use the categorizer to aggregate the values correctly" do
72
+ @tracker.update(request(:category => 'a', :traffic => 1))
73
+ @tracker.update(request(:category => 'b', :traffic => 2))
74
+ @tracker.update(request(:category => 'b', :traffic => 3))
75
+
76
+ @tracker.max_traffic('few').should == 1
77
+ @tracker.min_traffic('lots').should == 2
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#report' do
83
+ before(:each) do
84
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:category => :category, :traffic => :traffic)
85
+ @tracker.prepare
86
+ end
87
+
88
+ it "should generate a report without errors when one category is present" do
89
+ @tracker.update(request(:category => 'a', :traffic => 2))
90
+ @tracker.report(mock_output)
91
+ lambda { @tracker.report(mock_output) }.should_not raise_error
92
+ end
93
+
94
+ it "should generate a report without errors when no category is present" do
95
+ lambda { @tracker.report(mock_output) }.should_not raise_error
96
+ end
97
+
98
+ it "should generate a report without errors when multiple categories are present" do
99
+ @tracker.update(request(:category => 'a', :traffic => 2))
100
+ @tracker.update(request(:category => 'b', :traffic => 2))
101
+ lambda { @tracker.report(mock_output) }.should_not raise_error
102
+ end
103
+
104
+ end
105
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request-log-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.4
4
+ version: 1.3.5
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-09-14 00:00:00 +02:00
13
+ date: 2009-09-16 00:00:00 +02:00
14
14
  default_executable: request-log-analyzer
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -61,10 +61,10 @@ files:
61
61
  - spec/integration/command_line_usage_spec.rb
62
62
  - lib/request_log_analyzer/database.rb
63
63
  - spec/fixtures/decompression.log.bz2
64
+ - spec/fixtures/rails_unordered.log
64
65
  - lib/request_log_analyzer/log_processor.rb
65
66
  - lib/request_log_analyzer/tracker.rb
66
67
  - lib/request_log_analyzer/filter.rb
67
- - spec/fixtures/rails_unordered.log
68
68
  - bin/request-log-analyzer
69
69
  - request-log-analyzer.gemspec
70
70
  - DESIGN.rdoc
@@ -103,8 +103,10 @@ files:
103
103
  - spec/fixtures/test_language_combined.log
104
104
  - lib/request_log_analyzer/aggregator/database_inserter.rb
105
105
  - lib/request_log_analyzer/aggregator/summarizer.rb
106
+ - lib/request_log_analyzer/file_format/rack.rb
106
107
  - lib/request_log_analyzer/file_format/rails.rb
107
108
  - spec/fixtures/decompression.tar.gz
109
+ - spec/unit/tracker/traffic_tracker_spec.rb
108
110
  - spec/unit/filter/field_filter_spec.rb
109
111
  - spec/unit/database/base_class_spec.rb
110
112
  - lib/request_log_analyzer/filter/timespan.rb
@@ -136,11 +138,12 @@ files:
136
138
  - tasks/github-gem.rake
137
139
  - spec/unit/database/database_spec.rb
138
140
  - lib/request_log_analyzer/tracker/duration.rb
141
+ - lib/request_log_analyzer/tracker/traffic.rb
139
142
  - lib/request_log_analyzer/file_format.rb
140
143
  - spec/unit/aggregator/summarizer_spec.rb
144
+ - spec/fixtures/syslog_1x.log
141
145
  - spec/fixtures/rails_22.log
142
146
  - spec/fixtures/multiple_files_2.log
143
- - spec/fixtures/syslog_1x.log
144
147
  - LICENSE
145
148
  - lib/request_log_analyzer/source/database_loader.rb
146
149
  - spec/unit/tracker/frequency_tracker_spec.rb
@@ -191,6 +194,7 @@ test_files:
191
194
  - spec/unit/file_format/amazon_s3_format_spec.rb
192
195
  - spec/unit/controller/log_processor_spec.rb
193
196
  - spec/unit/filter/filter_spec.rb
197
+ - spec/unit/tracker/traffic_tracker_spec.rb
194
198
  - spec/unit/filter/field_filter_spec.rb
195
199
  - spec/unit/database/base_class_spec.rb
196
200
  - spec/unit/tracker/timespan_tracker_spec.rb