request-log-analyzer 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. data/LICENSE +3 -3
  2. data/README.rdoc +1 -1
  3. data/bin/request-log-analyzer +17 -14
  4. data/lib/cli/command_line_arguments.rb +51 -51
  5. data/lib/cli/database_console.rb +3 -3
  6. data/lib/cli/database_console_init.rb +4 -3
  7. data/lib/cli/progressbar.rb +10 -10
  8. data/lib/cli/tools.rb +3 -3
  9. data/lib/request_log_analyzer/aggregator/database_inserter.rb +13 -14
  10. data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
  11. data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
  12. data/lib/request_log_analyzer/aggregator.rb +11 -15
  13. data/lib/request_log_analyzer/controller.rb +162 -89
  14. data/lib/request_log_analyzer/database/base.rb +21 -21
  15. data/lib/request_log_analyzer/database/connection.rb +3 -3
  16. data/lib/request_log_analyzer/database/request.rb +22 -0
  17. data/lib/request_log_analyzer/database/source.rb +13 -0
  18. data/lib/request_log_analyzer/database/warning.rb +14 -0
  19. data/lib/request_log_analyzer/database.rb +28 -103
  20. data/lib/request_log_analyzer/file_format/amazon_s3.rb +17 -18
  21. data/lib/request_log_analyzer/file_format/apache.rb +26 -27
  22. data/lib/request_log_analyzer/file_format/merb.rb +30 -13
  23. data/lib/request_log_analyzer/file_format/rack.rb +4 -4
  24. data/lib/request_log_analyzer/file_format/rails.rb +143 -73
  25. data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
  26. data/lib/request_log_analyzer/file_format.rb +16 -28
  27. data/lib/request_log_analyzer/filter/anonymize.rb +8 -7
  28. data/lib/request_log_analyzer/filter/field.rb +9 -9
  29. data/lib/request_log_analyzer/filter/timespan.rb +12 -10
  30. data/lib/request_log_analyzer/filter.rb +12 -16
  31. data/lib/request_log_analyzer/line_definition.rb +15 -14
  32. data/lib/request_log_analyzer/log_processor.rb +27 -29
  33. data/lib/request_log_analyzer/mailer.rb +15 -9
  34. data/lib/request_log_analyzer/output/fixed_width.rb +40 -41
  35. data/lib/request_log_analyzer/output/html.rb +20 -20
  36. data/lib/request_log_analyzer/output.rb +53 -12
  37. data/lib/request_log_analyzer/request.rb +75 -68
  38. data/lib/request_log_analyzer/source/database_loader.rb +10 -14
  39. data/lib/request_log_analyzer/source/log_parser.rb +57 -50
  40. data/lib/request_log_analyzer/source.rb +10 -12
  41. data/lib/request_log_analyzer/tracker/duration.rb +39 -132
  42. data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
  43. data/lib/request_log_analyzer/tracker/hourly_spread.rb +21 -21
  44. data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
  45. data/lib/request_log_analyzer/tracker/traffic.rb +36 -116
  46. data/lib/request_log_analyzer/tracker.rb +139 -32
  47. data/lib/request_log_analyzer.rb +4 -4
  48. data/request-log-analyzer.gemspec +19 -15
  49. data/spec/database.yml +6 -0
  50. data/spec/fixtures/rails_22.log +1 -1
  51. data/spec/integration/command_line_usage_spec.rb +7 -1
  52. data/spec/lib/helpers.rb +7 -7
  53. data/spec/lib/macros.rb +3 -3
  54. data/spec/lib/matchers.rb +41 -27
  55. data/spec/lib/mocks.rb +15 -14
  56. data/spec/lib/testing_format.rb +9 -9
  57. data/spec/spec_helper.rb +6 -6
  58. data/spec/unit/aggregator/database_inserter_spec.rb +16 -16
  59. data/spec/unit/aggregator/summarizer_spec.rb +4 -4
  60. data/spec/unit/controller/controller_spec.rb +2 -2
  61. data/spec/unit/controller/log_processor_spec.rb +1 -1
  62. data/spec/unit/database/base_class_spec.rb +26 -33
  63. data/spec/unit/database/connection_spec.rb +3 -3
  64. data/spec/unit/database/database_spec.rb +33 -38
  65. data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
  66. data/spec/unit/file_format/apache_format_spec.rb +13 -13
  67. data/spec/unit/file_format/file_format_api_spec.rb +13 -13
  68. data/spec/unit/file_format/line_definition_spec.rb +24 -17
  69. data/spec/unit/file_format/merb_format_spec.rb +41 -45
  70. data/spec/unit/file_format/rails_format_spec.rb +157 -117
  71. data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
  72. data/spec/unit/filter/field_filter_spec.rb +13 -13
  73. data/spec/unit/filter/filter_spec.rb +1 -1
  74. data/spec/unit/filter/timespan_filter_spec.rb +15 -15
  75. data/spec/unit/mailer_spec.rb +30 -0
  76. data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
  77. data/spec/unit/source/log_parser_spec.rb +27 -27
  78. data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
  79. data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
  80. data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
  81. data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
  82. data/spec/unit/tracker/tracker_api_spec.rb +117 -42
  83. data/spec/unit/tracker/traffic_tracker_spec.rb +81 -79
  84. data/tasks/github-gem.rake +125 -75
  85. data/tasks/request_log_analyzer.rake +2 -2
  86. metadata +13 -8
@@ -5,13 +5,49 @@ module RequestLogAnalyzer::Output
5
5
  def self.const_missing(const)
6
6
  RequestLogAnalyzer::load_default_class_file(self, const)
7
7
  end
8
+
9
+ # Loads a Output::Base subclass instance.
10
+ def self.load(file_format, *args)
11
+
12
+ klass = nil
13
+ if file_format.kind_of?(RequestLogAnalyzer::Output::Base)
14
+ # this already is a file format! return itself
15
+ return file_format
16
+
17
+ elsif file_format.kind_of?(Class) && file_format.ancestors.include?(RequestLogAnalyzer::Output::Base)
18
+ # a usable class is provided. Use this format class.
19
+ klass = file_format
20
+
21
+ elsif file_format.kind_of?(String) && File.exist?(file_format)
22
+ # load a format from a ruby file
23
+ require file_format
24
+ const = RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb'))
25
+ if RequestLogAnalyzer::FileFormat.const_defined?(const)
26
+ klass = RequestLogAnalyzer::Output.const_get(const)
27
+ elsif Object.const_defined?(const)
28
+ klass = Object.const_get(const)
29
+ else
30
+ raise "Cannot load class #{const} from #{file_format}!"
31
+ end
32
+
33
+ else
34
+ # load a provided file format
35
+ klass = RequestLogAnalyzer::Output.const_get(RequestLogAnalyzer::to_camelcase(file_format))
36
+ end
37
+
38
+ # check the returned klass to see if it can be used
39
+ raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
40
+ raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::Output::Base)
41
+
42
+ klass.create(*args) # return an instance of the class
43
+ end
8
44
 
9
45
  # Base Class used for generating output for reports.
10
46
  # All output should inherit fromt this class.
11
47
  class Base
12
-
48
+
13
49
  attr_accessor :io, :options, :style
14
-
50
+
15
51
  # Initialize a report
16
52
  # <tt>io</tt> iO Object (file, STDOUT, etc.)
17
53
  # <tt>options</tt> Specific style options
@@ -27,32 +63,37 @@ module RequestLogAnalyzer::Output
27
63
  @style = @style.merge(temp_style)
28
64
  yield(self) if block_given?
29
65
  @style = old_style
30
- end
31
-
66
+ end
67
+
32
68
  # Generate a header for a report
33
69
  def header
34
70
  end
35
-
36
- # Generate the footer of a report
71
+
72
+ # Generate the footer of a report
37
73
  def footer
38
74
  end
39
75
 
76
+ def slice_results(array)
77
+ return array if options[:amount] == :all
78
+ return array.slice(0, options[:amount]) # otherwise
79
+ end
80
+
40
81
  # Generate a report table and push it into the output object.
41
82
  # Yeilds a rows array into which the rows can be pushed
42
83
  # <tt>*colums<tt> Array of Column hashes (see Column options).
43
84
  # <tt>&block</tt>: A block yeilding the rows.
44
- #
85
+ #
45
86
  # === Column options
46
87
  # Columns is an array of hashes containing the column definitions.
47
88
  # * <tt>:align</tt> Alignment :left or :right
48
89
  # * <tt>:treshold</tt> Width in characters or :rest
49
90
  # * <tt>:type</tt> :ratio or nil
50
91
  # * <tt>:width</tt> Width in characters or :rest
51
- #
92
+ #
52
93
  # === Example
53
94
  # The output object should support table definitions:
54
95
  #
55
- # output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
96
+ # output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
56
97
  # sorted_frequencies.each do |(cat, count)|
57
98
  # rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
58
99
  # end
@@ -60,13 +101,13 @@ module RequestLogAnalyzer::Output
60
101
  #
61
102
  def table(*columns, &block)
62
103
  end
63
-
104
+
64
105
  protected
65
106
  # Check if a given table defination hash includes a header (title)
66
107
  # <tt>columns</tt> The columns hash
67
108
  def table_has_header?(columns)
68
- columns.any? { |column| !column[:title].nil? }
109
+ columns.any? { |column| !column[:title].nil? }
69
110
  end
70
-
111
+
71
112
  end
72
113
  end
@@ -1,83 +1,85 @@
1
1
  module RequestLogAnalyzer
2
-
3
- # The Request class represents a parsed request from the log file.
2
+
3
+ # The Request class represents a parsed request from the log file.
4
4
  # Instances are created by the LogParser and are passed to the different aggregators, so they
5
- # can do their aggregating work.
5
+ # can do their aggregating work.
6
6
  #
7
7
  # This class provides several methods to access the data that was parsed from the log files.
8
8
  # Request#first(field_name) returns the first (only) value corresponding to the given field
9
9
  # Request#every(field_name) returns all values corresponding to the given field name as array.
10
10
  class Request
11
-
11
+
12
12
  module Converters
13
-
13
+
14
+ # Default converter function, which converts the parsed strings to a native Ruby type
15
+ # using the type indication in the line definition. It will use a custom connverter
16
+ # method if one is available.
14
17
  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
18
+ return capture_definition[:default] if value.nil?
19
+ custom_converter_method = :"convert_#{capture_definition[:type]}"
20
+ send(custom_converter_method, value, capture_definition)
31
21
  end
32
-
22
+
23
+ def convert_string(value, capture_definition); value; end
24
+ def convert_float(value, capture_definition); value.to_f; end
25
+ def convert_decimal(value, capture_definition); value.to_f; end
26
+ def convert_int(value, capture_definition); value.to_i; end
27
+ def convert_integer(value, capture_definition); value.to_i; end
28
+ def convert_sym(value, capture_definition); value.to_sym; end
29
+ def convert_symbol(value, capture_definition); value.to_sym; end
30
+
31
+ # Converts :eval field, which should evaluate to a hash.
33
32
  def convert_eval(value, capture_definition)
34
- eval(value).inject({}) { |h, (k, v)| h[k.to_sym] = v; h}
33
+ eval(value).inject({}) { |h, (k, v)| h[k.to_sym] = v; h}
35
34
  rescue SyntaxError
36
35
  nil
37
36
  end
38
-
39
- # Slow default method to parse timestamps
37
+
38
+ # Slow default method to parse timestamps.
39
+ # Reimplement this function in a file format specific Request class
40
+ # to improve the timestamp parsing speed.
40
41
  def convert_timestamp(value, capture_definition)
41
- DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
42
+ DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i
42
43
  end
43
-
44
+
45
+ # Converts traffic fields to (whole) bytes based on the given unit.
44
46
  def convert_traffic(value, capture_definition)
45
- return nil if value.nil?
46
47
  case capture_definition[:unit]
48
+ when nil, :b, :B, :byte then value.to_i
47
49
  when :GB, :G, :gigabyte then (value.to_f * 1000_000_000).round
48
50
  when :GiB, :gibibyte then (value.to_f * (2 ** 30)).round
49
51
  when :MB, :M, :megabyte then (value.to_f * 1000_000).round
50
52
  when :MiB, :mebibyte then (value.to_f * (2 ** 20)).round
51
53
  when :KB, :K, :kilobyte, :kB then (value.to_f * 1000).round
52
54
  when :KiB, :kibibyte then (value.to_f * (2 ** 10)).round
53
- else value.to_i
55
+ else raise "Unknown traffic unit"
54
56
  end
55
57
  end
56
-
58
+
59
+ # Convert duration fields to float, and make sure the values are in seconds.
57
60
  def convert_duration(value, capture_definition)
58
- return nil if value.nil?
59
61
  case capture_definition[:unit]
62
+ when nil, :sec, :s then value.to_f
60
63
  when :microsec, :musec then value.to_f / 1000000.0
61
64
  when :msec, :millisec then value.to_f / 1000.0
62
- else value.to_f
65
+ else raise "Unknown duration unit"
63
66
  end
64
67
  end
65
68
  end
66
-
67
- include RequestLogAnalyzer::FileFormat::Awareness
69
+
70
+ # Install the default converter methods
68
71
  include Converters
69
-
70
- attr_reader :lines
71
- attr_reader :attributes
72
-
73
- # Initializes a new Request object.
72
+
73
+ attr_reader :lines, :attributes, :file_format
74
+
75
+ # Initializes a new Request object.
74
76
  # It will apply the the provided FileFormat module to this instance.
75
77
  def initialize(file_format, attributes = {})
76
- @lines = []
77
- @attributes = attributes
78
- register_file_format(file_format)
78
+ @lines = []
79
+ @attributes = attributes
80
+ @file_format = file_format
79
81
  end
80
-
82
+
81
83
  # Creates a new request that was parsed from the log with the given FileFormat. The hashes
82
84
  # that are passed to this function are added as lines to this request.
83
85
  def self.create(file_format, *hashes)
@@ -85,9 +87,11 @@ module RequestLogAnalyzer
85
87
  hashes.flatten.each { |hash| request << hash }
86
88
  return request
87
89
  end
88
-
89
- # Adds another line to the request.
90
- # The line should be provides as a hash of the fields parsed from the line.
90
+
91
+ # Adds another line to the request when it is parsed in the LogParser.
92
+ #
93
+ # The line should be provided as a hash with the attributes line_definition, :captures,
94
+ # :lineno and :source set. This function is called from LogParser.
91
95
  def add_parsed_line (parsed_line)
92
96
  value_hash = parsed_line[:line_definition].convert_captured_values(parsed_line[:captures], self)
93
97
  value_hash[:line_type] = parsed_line[:line_definition].name
@@ -95,72 +99,75 @@ module RequestLogAnalyzer
95
99
  value_hash[:source] = parsed_line[:source]
96
100
  add_line_hash(value_hash)
97
101
  end
98
-
102
+
103
+ # Adds another line to the request using a plain hash.
104
+ #
105
+ # The line should be provides as a hash of the fields parsed from the line.
99
106
  def add_line_hash(value_hash)
100
107
  @lines << value_hash
101
108
  @attributes = value_hash.merge(@attributes)
102
109
  end
103
-
104
-
110
+
111
+ # Adds another line to the request. This method switches automatically between
112
+ # the add_line_hash and add_parsed_line based on the keys of the provided hash.
105
113
  def <<(hash)
106
114
  hash[:line_definition] ? add_parsed_line(hash) : add_line_hash(hash)
107
115
  end
108
-
116
+
109
117
  # Checks whether the given line type was parsed from the log file for this request
110
118
  def has_line_type?(line_type)
111
119
  return true if @lines.length == 1 && @lines[0][:line_type] == line_type.to_sym
112
-
113
120
  @lines.detect { |l| l[:line_type] == line_type.to_sym }
114
121
  end
115
-
122
+
116
123
  alias :=~ :has_line_type?
117
-
124
+
118
125
  # Returns the value that was captured for the "field" of this request.
119
126
  # This function will return the first value that was captured if the field
120
127
  # was captured in multiple lines
121
128
  def first(field)
122
129
  @attributes[field]
123
130
  end
124
-
131
+
125
132
  alias :[] :first
126
-
133
+
127
134
  # Returns an array of all the "field" values that were captured for this request
128
135
  def every(field)
129
136
  @lines.inject([]) { |result, fields| result << fields[field] if fields.has_key?(field); result }
130
- end
131
-
132
- # Returns true if this request does not yet contain any parsed lines. This should only occur
137
+ end
138
+
139
+ # Returns true if this request does not yet contain any parsed lines. This should only occur
133
140
  # during parsing. An empty request should never be sent to the aggregators
134
141
  def empty?
135
142
  @lines.length == 0
136
143
  end
137
-
144
+
138
145
  # Checks whether this request is completed. A completed request contains both a parsed header
139
- # line and a parsed footer line. Not that calling this function in single line mode will always
146
+ # line and a parsed footer line. Not that calling this function in single line mode will always
140
147
  # return false.
141
148
  def completed?
142
149
  header_found, footer_found = false, false
143
- @lines.each do |line|
150
+ @lines.each do |line|
144
151
  line_def = file_format.line_definitions[line[:line_type]]
145
152
  header_found = true if line_def.header
146
- footer_found = true if line_def.footer
153
+ footer_found = true if line_def.footer
147
154
  end
148
- header_found && footer_found
155
+ header_found && footer_found
149
156
  end
150
-
157
+
151
158
  # This function is called before a Requests is yielded.
152
159
  def validate
153
160
  end
154
-
155
- # Returns the first timestamp encountered in a request.
161
+
162
+ # Returns the first timestamp encountered in a request.
156
163
  def timestamp
157
164
  first(:timestamp)
158
165
  end
159
-
166
+
160
167
  def first_lineno
161
168
  @lines.map { |line| line[:lineno] }.reject { |v| v.nil? }.min
162
169
  end
163
-
170
+
164
171
  def last_lineno
165
172
  @lines.map { |line| line[:lineno] }.reject { |v| v.nil? }.max
166
173
  end
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'activerecord'
3
3
 
4
4
  module RequestLogAnalyzer::Source
5
-
5
+
6
6
  # Active Resource hook
7
7
  class Request < ActiveRecord::Base
8
8
  has_many :completed_lines
@@ -26,8 +26,7 @@ module RequestLogAnalyzer::Source
26
26
  # The Database class gets log data from the database.
27
27
  class DatabaseLoader < Base
28
28
 
29
- attr_reader :source_files
30
- attr_reader :requests
29
+ attr_reader :source_files, :file_format, :requests
31
30
 
32
31
  # Initializes the log file parser instance.
33
32
  # It will apply the language specific FileFormat module to this instance. It will use the line
@@ -35,16 +34,13 @@ module RequestLogAnalyzer::Source
35
34
  #
36
35
  # <tt>format</tt>:: The current file format instance
37
36
  # <tt>options</tt>:: A hash of options that are used by the parser
38
- def initialize(format, options = {})
39
- @line_definitions = {}
40
- @options = options
37
+ def initialize(format, options = {})
38
+ super(format, options)
41
39
  @source_files = options[:source_files]
42
40
  @parsed_requests = 0
43
41
  @requests = []
44
-
45
- self.register_file_format(format)
46
42
  end
47
-
43
+
48
44
  # Reads the input, which can either be a file, sequence of files or STDIN to parse
49
45
  # lines specified in the FileFormat. This lines will be combined into Request instances,
50
46
  # that will be yielded. The actual parsing occurs in the parse_io method.
@@ -56,10 +52,10 @@ module RequestLogAnalyzer::Source
56
52
  RequestLogAnalyzer::Source::Request.find(:all).each do |request|
57
53
  @parsed_requests += 1
58
54
  @progress_handler.call(:progress, @parsed_requests) if @progress_handler
59
-
55
+
60
56
  yield request.convert(self.file_format)
61
57
  end
62
-
58
+
63
59
  @progress_handler.call(:finished, @source_files) if @progress_handler
64
60
  end
65
61
 
@@ -70,15 +66,15 @@ module RequestLogAnalyzer::Source
70
66
  end
71
67
 
72
68
  # Add a block to this method to install a warning handler while parsing,
73
- # <tt>proc</tt>:: The proc that will be called to handle parse warning messages
69
+ # <tt>proc</tt>:: The proc that will be called to handle parse warning messages
74
70
  def warning=(proc)
75
71
  @warning_handler = proc
76
72
  end
77
73
 
78
74
  # This method is called by the parser if it encounteres any parsing problems.
79
- # It will call the installed warning handler if any.
75
+ # It will call the installed warning handler if any.
80
76
  #
81
- # By default, RequestLogAnalyzer::Controller will install a warning handler
77
+ # By default, RequestLogAnalyzer::Controller will install a warning handler
82
78
  # that will pass the warnings to each aggregator so they can do something useful
83
79
  # with it.
84
80
  #
@@ -1,13 +1,13 @@
1
1
  module RequestLogAnalyzer::Source
2
-
2
+
3
3
  # The LogParser class reads log data from a given source and uses a file format definition
4
- # to parse all relevent information about requests from the file. A FileFormat module should
4
+ # to parse all relevent information about requests from the file. A FileFormat module should
5
5
  # be provided that contains the definitions of the lines that occur in the log data.
6
6
  #
7
- # De order in which lines occur is used to combine lines to a single request. If these lines
8
- # are mixed, requests cannot be combined properly. This can be the case if data is written to
9
- # the log file simultaneously by different mongrel processes. This problem is detected by the
10
- # parser. It will emit warnings when this occurs. LogParser supports multiple parse strategies
7
+ # De order in which lines occur is used to combine lines to a single request. If these lines
8
+ # are mixed, requests cannot be combined properly. This can be the case if data is written to
9
+ # the log file simultaneously by different mongrel processes. This problem is detected by the
10
+ # parser. It will emit warnings when this occurs. LogParser supports multiple parse strategies
11
11
  # that deal differently with this problem.
12
12
  class LogParser < Base
13
13
 
@@ -15,7 +15,7 @@ module RequestLogAnalyzer::Source
15
15
 
16
16
  # The default parse strategy that will be used to parse the input.
17
17
  DEFAULT_PARSE_STRATEGY = 'assume-correct'
18
-
18
+
19
19
  # All available parse strategies.
20
20
  PARSE_STRATEGIES = ['cautious', 'assume-correct']
21
21
 
@@ -27,42 +27,44 @@ module RequestLogAnalyzer::Source
27
27
  #
28
28
  # <tt>format</tt>:: The current file format instance
29
29
  # <tt>options</tt>:: A hash of options that are used by the parser
30
- def initialize(format, options = {})
31
- @line_definitions = {}
32
- @options = options
30
+ def initialize(format, options = {})
31
+ super(format, options)
33
32
  @parsed_lines = 0
34
33
  @parsed_requests = 0
35
34
  @skipped_lines = 0
36
35
  @skipped_requests = 0
36
+ @current_request = nil
37
+ @current_source = nil
37
38
  @current_file = nil
38
39
  @current_lineno = nil
39
40
  @source_files = options[:source_files]
40
-
41
+ @progress_handler = nil
42
+
41
43
  @options[:parse_strategy] ||= DEFAULT_PARSE_STRATEGY
42
44
  raise "Unknown parse strategy" unless PARSE_STRATEGIES.include?(@options[:parse_strategy])
43
-
44
- self.register_file_format(format)
45
45
  end
46
-
46
+
47
47
  # Reads the input, which can either be a file, sequence of files or STDIN to parse
48
48
  # lines specified in the FileFormat. This lines will be combined into Request instances,
49
49
  # that will be yielded. The actual parsing occurs in the parse_io method.
50
50
  # <tt>options</tt>:: A Hash of options that will be pased to parse_io.
51
51
  def each_request(options = {}, &block) # :yields: :request, request
52
-
52
+
53
53
  case @source_files
54
54
  when IO
55
- puts "Parsing from the standard input. Press CTRL+C to finish." # FIXME: not here
56
- parse_stream(@source_files, options, &block)
55
+ if @source_files == $stdin
56
+ puts "Parsing from the standard input. Press CTRL+C to finish." # FIXME: not here
57
+ end
58
+ parse_stream(@source_files, options, &block)
57
59
  when String
58
- parse_file(@source_files, options, &block)
60
+ parse_file(@source_files, options, &block)
59
61
  when Array
60
- parse_files(@source_files, options, &block)
62
+ parse_files(@source_files, options, &block)
61
63
  else
62
64
  raise "Unknown source provided"
63
65
  end
64
66
  end
65
-
67
+
66
68
  # Make sure the Enumerable methods work as expected
67
69
  alias_method :each, :each_request
68
70
 
@@ -73,12 +75,12 @@ module RequestLogAnalyzer::Source
73
75
  def parse_files(files, options = {}, &block) # :yields: request
74
76
  files.each { |file| parse_file(file, options, &block) }
75
77
  end
76
-
78
+
77
79
  # Check if a file has a compressed extention in the filename.
78
80
  # If recognized, return the command string used to decompress the file
79
81
  def decompress_file?(filename)
80
82
  nice_command = "nice -n 5"
81
-
83
+
82
84
  return "#{nice_command} gunzip -c -d #{filename}" if filename.match(/\.tar.gz$/) || filename.match(/\.tgz$/) || filename.match(/\.gz$/)
83
85
  return "#{nice_command} bunzip2 -c -d #{filename}" if filename.match(/\.bz2$/)
84
86
  return "#{nice_command} unzip -p #{filename}" if filename.match(/\.zip$/)
@@ -97,27 +99,32 @@ module RequestLogAnalyzer::Source
97
99
  # <tt>options</tt>:: A Hash of options that will be pased to parse_io.
98
100
  def parse_file(file, options = {}, &block)
99
101
 
100
- @progress_handler = @dormant_progress_handler
101
102
  @current_source = File.expand_path(file)
102
- @progress_handler.call(:started, file) if @progress_handler
103
103
  @source_changes_handler.call(:started, @current_source) if @source_changes_handler
104
-
104
+
105
105
  if decompress_file?(file).empty?
106
+
107
+ @progress_handler = @dormant_progress_handler
108
+ @progress_handler.call(:started, file) if @progress_handler
109
+
106
110
  File.open(file, 'r') { |f| parse_io(f, options, &block) }
111
+
112
+ @progress_handler.call(:finished, file) if @progress_handler
113
+ @progress_handler = nil
107
114
  else
108
115
  IO.popen(decompress_file?(file), 'r') { |f| parse_io(f, options, &block) }
109
116
  end
110
-
117
+
111
118
  @source_changes_handler.call(:finished, @current_source) if @source_changes_handler
112
- @progress_handler.call(:finished, file) if @progress_handler
119
+
113
120
  @current_source = nil
114
- @progress_handler = nil
121
+
115
122
  end
116
123
 
117
124
  # Parses an IO stream. It will simply call parse_io. This function does not support progress updates
118
125
  # because the length of a stream is not known.
119
126
  # <tt>stream</tt>:: The IO stream that should be parsed.
120
- # <tt>options</tt>:: A Hash of options that will be pased to parse_io.
127
+ # <tt>options</tt>:: A Hash of options that will be pased to parse_io.
121
128
  def parse_stream(stream, options = {}, &block)
122
129
  parse_io(stream, options, &block)
123
130
  end
@@ -137,16 +144,16 @@ module RequestLogAnalyzer::Source
137
144
  def parse_io(io, options = {}, &block) # :yields: request
138
145
  @current_lineno = 1
139
146
  while line = io.gets
140
- @progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 127 == 0
141
-
147
+ @progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 255 == 0
148
+
142
149
  if request_data = file_format.parse_line(line) { |wt, message| warn(wt, message) }
143
150
  @parsed_lines += 1
144
151
  update_current_request(request_data.merge(:source => @current_source, :lineno => @current_lineno), &block)
145
152
  end
146
-
153
+
147
154
  @current_lineno += 1
148
155
  end
149
-
156
+
150
157
  warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
151
158
  @current_lineno = nil
152
159
  end
@@ -162,7 +169,7 @@ module RequestLogAnalyzer::Source
162
169
  def warning=(proc)
163
170
  @warning_handler = proc
164
171
  end
165
-
172
+
166
173
  # Add a block to this method to install a source change handler while parsing,
167
174
  # <tt>proc</tt>:: The proc that will be called to handle source changes
168
175
  def source_changes=(proc)
@@ -170,9 +177,9 @@ module RequestLogAnalyzer::Source
170
177
  end
171
178
 
172
179
  # This method is called by the parser if it encounteres any parsing problems.
173
- # It will call the installed warning handler if any.
180
+ # It will call the installed warning handler if any.
174
181
  #
175
- # By default, RequestLogAnalyzer::Controller will install a warning handler
182
+ # By default, RequestLogAnalyzer::Controller will install a warning handler
176
183
  # that will pass the warnings to each aggregator so they can do something useful
177
184
  # with it.
178
185
  #
@@ -184,24 +191,24 @@ module RequestLogAnalyzer::Source
184
191
 
185
192
  protected
186
193
 
187
- # Combines the different lines of a request into a single Request object. It will start a
188
- # new request when a header line is encountered en will emit the request when a footer line
194
+ # Combines the different lines of a request into a single Request object. It will start a
195
+ # new request when a header line is encountered en will emit the request when a footer line
189
196
  # is encountered.
190
197
  #
191
198
  # Combining the lines is done using heuristics. Problems can occur in this process. The
192
199
  # current parse strategy defines how these cases are handled.
193
200
  #
194
201
  # When using the 'assume-correct' parse strategy (default):
195
- # - Every line that is parsed before a header line is ignored as it cannot be included in
202
+ # - Every line that is parsed before a header line is ignored as it cannot be included in
196
203
  # any request. It will emit a :no_current_request warning.
197
- # - If a header line is found before the previous requests was closed, the previous request
204
+ # - If a header line is found before the previous requests was closed, the previous request
198
205
  # will be yielded and a new request will be started.
199
206
  #
200
207
  # When using the 'cautious' parse strategy:
201
- # - Every line that is parsed before a header line is ignored as it cannot be included in
208
+ # - Every line that is parsed before a header line is ignored as it cannot be included in
202
209
  # any request. It will emit a :no_current_request warning.
203
210
  # - A header line that is parsed before a request is closed by a footer line, is a sign of
204
- # an unproperly ordered file. All data that is gathered for the request until then is
211
+ # an unproperly ordered file. All data that is gathered for the request until then is
205
212
  # discarded and the next request is ignored as well. An :unclosed_request warning is
206
213
  # emitted.
207
214
  #
@@ -228,7 +235,7 @@ module RequestLogAnalyzer::Source
228
235
  @current_request << request_data
229
236
  if footer_line?(request_data)
230
237
  handle_request(@current_request, &block) # yield @current_request
231
- @current_request = nil
238
+ @current_request = nil
232
239
  end
233
240
  else
234
241
  @skipped_lines += 1
@@ -236,8 +243,8 @@ module RequestLogAnalyzer::Source
236
243
  end
237
244
  end
238
245
  end
239
-
240
- # Handles the parsed request by sending it into the pipeline.
246
+
247
+ # Handles the parsed request by sending it into the pipeline.
241
248
  #
242
249
  # - It will call RequestLogAnalyzer::Request#validate on the request instance
243
250
  # - It will send the request into the pipeline, checking whether it was accepted by all the filters.
@@ -249,19 +256,19 @@ module RequestLogAnalyzer::Source
249
256
  request.validate
250
257
  accepted = block_given? ? yield(request) : true
251
258
  @skipped_requests += 1 unless accepted
252
- end
259
+ end
253
260
 
254
261
  # Checks whether a given line hash is a header line according to the current file format.
255
- # <tt>hash</tt>:: A hash of data that was parsed from the line.
262
+ # <tt>hash</tt>:: A hash of data that was parsed from the line.
256
263
  def header_line?(hash)
257
264
  hash[:line_definition].header
258
265
  end
259
266
 
260
- # Checks whether a given line hash is a footer line according to the current file format.
261
- # <tt>hash</tt>:: A hash of data that was parsed from the line.
267
+ # Checks whether a given line hash is a footer line according to the current file format.
268
+ # <tt>hash</tt>:: A hash of data that was parsed from the line.
262
269
  def footer_line?(hash)
263
270
  hash[:line_definition].footer
264
- end
271
+ end
265
272
  end
266
273
 
267
274
  end