request-log-analyzer 1.3.7 → 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 (84) 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 +2 -2
  7. data/lib/cli/progressbar.rb +10 -10
  8. data/lib/cli/tools.rb +3 -3
  9. data/lib/request_log_analyzer.rb +4 -4
  10. data/lib/request_log_analyzer/aggregator.rb +10 -10
  11. data/lib/request_log_analyzer/aggregator/database_inserter.rb +9 -9
  12. data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
  13. data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
  14. data/lib/request_log_analyzer/controller.rb +153 -69
  15. data/lib/request_log_analyzer/database.rb +13 -13
  16. data/lib/request_log_analyzer/database/base.rb +17 -17
  17. data/lib/request_log_analyzer/database/connection.rb +3 -3
  18. data/lib/request_log_analyzer/database/request.rb +2 -2
  19. data/lib/request_log_analyzer/database/source.rb +1 -1
  20. data/lib/request_log_analyzer/file_format.rb +15 -15
  21. data/lib/request_log_analyzer/file_format/amazon_s3.rb +16 -16
  22. data/lib/request_log_analyzer/file_format/apache.rb +20 -19
  23. data/lib/request_log_analyzer/file_format/merb.rb +12 -12
  24. data/lib/request_log_analyzer/file_format/rack.rb +4 -4
  25. data/lib/request_log_analyzer/file_format/rails.rb +146 -70
  26. data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
  27. data/lib/request_log_analyzer/filter.rb +6 -6
  28. data/lib/request_log_analyzer/filter/anonymize.rb +6 -6
  29. data/lib/request_log_analyzer/filter/field.rb +9 -9
  30. data/lib/request_log_analyzer/filter/timespan.rb +12 -10
  31. data/lib/request_log_analyzer/line_definition.rb +15 -14
  32. data/lib/request_log_analyzer/log_processor.rb +22 -22
  33. data/lib/request_log_analyzer/mailer.rb +15 -9
  34. data/lib/request_log_analyzer/output.rb +53 -12
  35. data/lib/request_log_analyzer/output/fixed_width.rb +40 -41
  36. data/lib/request_log_analyzer/output/html.rb +20 -20
  37. data/lib/request_log_analyzer/request.rb +35 -36
  38. data/lib/request_log_analyzer/source.rb +7 -7
  39. data/lib/request_log_analyzer/source/database_loader.rb +7 -7
  40. data/lib/request_log_analyzer/source/log_parser.rb +48 -43
  41. data/lib/request_log_analyzer/tracker.rb +128 -14
  42. data/lib/request_log_analyzer/tracker/duration.rb +39 -132
  43. data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
  44. data/lib/request_log_analyzer/tracker/hourly_spread.rb +20 -19
  45. data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
  46. data/lib/request_log_analyzer/tracker/traffic.rb +36 -116
  47. data/request-log-analyzer.gemspec +19 -15
  48. data/spec/fixtures/rails_22.log +1 -1
  49. data/spec/integration/command_line_usage_spec.rb +1 -1
  50. data/spec/lib/helpers.rb +7 -7
  51. data/spec/lib/macros.rb +3 -3
  52. data/spec/lib/matchers.rb +41 -27
  53. data/spec/lib/mocks.rb +15 -14
  54. data/spec/lib/testing_format.rb +9 -9
  55. data/spec/spec_helper.rb +6 -6
  56. data/spec/unit/aggregator/database_inserter_spec.rb +13 -13
  57. data/spec/unit/aggregator/summarizer_spec.rb +4 -4
  58. data/spec/unit/controller/controller_spec.rb +2 -2
  59. data/spec/unit/controller/log_processor_spec.rb +1 -1
  60. data/spec/unit/database/base_class_spec.rb +19 -19
  61. data/spec/unit/database/connection_spec.rb +3 -3
  62. data/spec/unit/database/database_spec.rb +25 -25
  63. data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
  64. data/spec/unit/file_format/apache_format_spec.rb +13 -13
  65. data/spec/unit/file_format/file_format_api_spec.rb +13 -13
  66. data/spec/unit/file_format/line_definition_spec.rb +24 -17
  67. data/spec/unit/file_format/merb_format_spec.rb +41 -45
  68. data/spec/unit/file_format/rails_format_spec.rb +157 -117
  69. data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
  70. data/spec/unit/filter/field_filter_spec.rb +13 -13
  71. data/spec/unit/filter/filter_spec.rb +1 -1
  72. data/spec/unit/filter/timespan_filter_spec.rb +15 -15
  73. data/spec/unit/mailer_spec.rb +30 -0
  74. data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
  75. data/spec/unit/source/log_parser_spec.rb +27 -27
  76. data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
  77. data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
  78. data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
  79. data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
  80. data/spec/unit/tracker/tracker_api_spec.rb +13 -13
  81. data/spec/unit/tracker/traffic_tracker_spec.rb +81 -79
  82. data/tasks/github-gem.rake +125 -75
  83. data/tasks/request_log_analyzer.rake +2 -2
  84. metadata +8 -6
data/lib/cli/tools.rb CHANGED
@@ -4,15 +4,15 @@
4
4
  def terminal_width(default_width = 81)
5
5
  tiocgwinsz = 0x5413
6
6
  data = [0, 0, 0, 0].pack("SSSS")
7
- if @out.ioctl(tiocgwinsz, data) >= 0
7
+ if @out.ioctl(tiocgwinsz, data) >= 0
8
8
  rows, cols, xpixels, ypixels = data.unpack("SSSS")
9
9
  raise unless cols > 0
10
10
  cols
11
11
  else
12
12
  raise
13
13
  end
14
- rescue
15
- begin
14
+ rescue
15
+ begin
16
16
  IO.popen('stty -a 2>&1') do |pipe|
17
17
  column_line = pipe.detect { |line| /(\d+) columns/ =~ line }
18
18
  raise unless column_line
@@ -8,10 +8,10 @@ Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_t
8
8
  # - This module itselfs contains some functions to help with class and source file loading.
9
9
  # - The actual application resides in the RequestLogAnalyzer::Controller class.
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.7"
13
+ # Do not change the value by hand; it will be updated automatically by the gem release script.
14
+ VERSION = "1.4.0"
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.
@@ -35,7 +35,7 @@ module RequestLogAnalyzer
35
35
  str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
36
36
  end
37
37
 
38
- # Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
38
+ # Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
39
39
  # (<tt>RequestLogAnalyzer::Controller</tt>). This can be used to find the class that is defined in a given filename.
40
40
  # <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
41
41
  def self.to_camelcase(str)
@@ -1,13 +1,13 @@
1
1
  module RequestLogAnalyzer::Aggregator
2
-
2
+
3
3
  def self.const_missing(const)
4
4
  RequestLogAnalyzer::load_default_class_file(self, const)
5
5
  end
6
-
6
+
7
7
  # The base class of an aggregator. This class provides the interface to which
8
8
  # every aggregator should comply (by simply subclassing this class).
9
9
  class Base
10
-
10
+
11
11
  attr_reader :options, :source
12
12
 
13
13
  # Intializes a new RequestLogAnalyzer::Aggregator::Base instance
@@ -17,30 +17,30 @@ module RequestLogAnalyzer::Aggregator
17
17
  @options = options
18
18
  end
19
19
 
20
- # The prepare function is called just before parsing starts. This function
20
+ # The prepare function is called just before parsing starts. This function
21
21
  # can be used to initialie variables, etc.
22
22
  def prepare
23
23
  end
24
-
24
+
25
25
  # The aggregate function is called for every request.
26
26
  # Implement the aggregating functionality in this method
27
27
  def aggregate(request)
28
28
  end
29
-
29
+
30
30
  # The finalize function is called after all sources are parsed and no more
31
31
  # requests will be passed to the aggregator
32
32
  def finalize
33
33
  end
34
-
34
+
35
35
  # The warning method is called if the parser eits a warning.
36
36
  def warning(type, message, lineno)
37
- end
38
-
37
+ end
38
+
39
39
  # The report function is called at the end. Implement any result reporting
40
40
  # in this function.
41
41
  def report(output)
42
42
  end
43
-
43
+
44
44
  # The source_change function gets called when handling a source is started or finished.
45
45
  def source_change(change, filename)
46
46
  end
@@ -4,7 +4,7 @@ module RequestLogAnalyzer::Aggregator
4
4
  # The database aggregator will create an SQLite3 database with all parsed request information.
5
5
  #
6
6
  # The prepare method will create a database schema according to the file format definitions.
7
- # It will also create ActiveRecord::Base subclasses to interact with the created tables.
7
+ # It will also create ActiveRecord::Base subclasses to interact with the created tables.
8
8
  # Then, the aggregate method will be called for every parsed request. The information of
9
9
  # these requests is inserted into the tables using the ActiveRecord classes.
10
10
  #
@@ -22,11 +22,11 @@ module RequestLogAnalyzer::Aggregator
22
22
  @sources = {}
23
23
  @database = RequestLogAnalyzer::Database.new(options[:database])
24
24
  @database.file_format = source.file_format
25
-
25
+
26
26
  database.drop_database_schema! if options[:reset_database]
27
27
  database.create_database_schema!
28
28
  end
29
-
29
+
30
30
  # Aggregates a request into the database
31
31
  # This will create a record in the requests table and create a record for every line that has been parsed,
32
32
  # in which the captured values will be stored.
@@ -42,19 +42,19 @@ module RequestLogAnalyzer::Aggregator
42
42
  rescue SQLite3::SQLException => e
43
43
  raise Interrupt, e.message
44
44
  end
45
-
45
+
46
46
  # Finalizes the aggregator by closing the connection to the database
47
47
  def finalize
48
48
  @request_count = RequestLogAnalyzer::Database::Request.count
49
49
  database.disconnect
50
50
  database.remove_orm_classes!
51
51
  end
52
-
52
+
53
53
  # Records w warining in the warnings table.
54
54
  def warning(type, message, lineno)
55
55
  RequestLogAnalyzer::Database::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
56
56
  end
57
-
57
+
58
58
  # Records source changes in the sources table
59
59
  def source_change(change, filename)
60
60
  if File.exist?(filename)
@@ -66,11 +66,11 @@ module RequestLogAnalyzer::Aggregator
66
66
  end
67
67
  end
68
68
  end
69
-
69
+
70
70
  # Prints a short report of what has been inserted into the database
71
71
  def report(output)
72
72
  output.title('Request database created')
73
-
73
+
74
74
  output << "A database file has been created with all parsed request information.\n"
75
75
  output << "#{@request_count} requests have been added to the database.\n"
76
76
  output << "\n"
@@ -78,6 +78,6 @@ module RequestLogAnalyzer::Aggregator
78
78
  output << output.colorize(" $ request-log-analyzer console -d #{options[:database]}\n", :bold)
79
79
  output << "\n"
80
80
  end
81
-
81
+
82
82
  end
83
83
  end
@@ -1,23 +1,28 @@
1
1
  module RequestLogAnalyzer::Aggregator
2
2
 
3
- # Echo Aggregator. Writes everything passed to it
3
+ # Echo Aggregator. Writes everything to the screen when it is passed to this aggregator
4
4
  class Echo < Base
5
-
5
+
6
+ attr_accessor :warnings
7
+
6
8
  def prepare
7
- @warnings = ""
9
+ @warnings = []
8
10
  end
9
-
11
+
12
+ # Display every parsed line immediately to the terminal
10
13
  def aggregate(request)
11
- puts "\nRequest: " + request.inspect
14
+ puts "\nRequest: " + request.lines.inspect
12
15
  end
13
-
16
+
17
+ # Capture all warnings during parsing
14
18
  def warning(type, message, lineno)
15
- @warnings << "WARNING #{type.inspect} on line #{lineno}: #{message}\n"
19
+ @warnings << "WARNING #{type.inspect} on line #{lineno}: #{message}"
16
20
  end
17
-
21
+
22
+ # Display every warning in the report when finished parsing
18
23
  def report(output)
19
24
  output.title("Warnings during parsing")
20
- output.puts @warnings
25
+ @warnings.each { |w| output.puts(w) }
21
26
  end
22
27
 
23
28
  end
@@ -1,9 +1,9 @@
1
1
  module RequestLogAnalyzer::Aggregator
2
2
 
3
3
  class Summarizer < Base
4
-
4
+
5
5
  class Definer
6
-
6
+
7
7
  attr_reader :trackers
8
8
 
9
9
  # Initialize tracker array
@@ -16,17 +16,17 @@ module RequestLogAnalyzer::Aggregator
16
16
  def initialize_copy(other)
17
17
  @trackers = other.trackers.dup
18
18
  end
19
-
19
+
20
20
  # Drop all trackers
21
21
  def reset!
22
22
  @trackers = []
23
23
  end
24
-
24
+
25
25
  # Include missing trackers through method missing.
26
26
  def method_missing(tracker_method, *args)
27
27
  track(tracker_method, *args)
28
28
  end
29
-
29
+
30
30
  # Track the frequency of a specific category
31
31
  # <tt>category_field</tt> Field to track
32
32
  # <tt>options</tt> options are passed to new frequency tracker
@@ -37,7 +37,7 @@ module RequestLogAnalyzer::Aggregator
37
37
  track(:frequency, category_field.merge(options))
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Track the duration of a specific category
42
42
  # <tt>duration_field</tt> Field to track
43
43
  # <tt>options</tt> options are passed to new frequency tracker
@@ -45,10 +45,10 @@ module RequestLogAnalyzer::Aggregator
45
45
  if duration_field.kind_of?(Symbol)
46
46
  track(:duration, options.merge(:duration => duration_field))
47
47
  elsif duration_field.kind_of?(Hash)
48
- track(:duration, duration_field.merge(options))
48
+ track(:duration, duration_field.merge(options))
49
49
  end
50
- end
51
-
50
+ end
51
+
52
52
  # Helper function to initialize a tracker and add it to the tracker array.
53
53
  # <tt>tracker_class</tt> The class to include
54
54
  # <tt>optiont</tt> The options to pass to the trackers.
@@ -57,10 +57,10 @@ module RequestLogAnalyzer::Aggregator
57
57
  @trackers << tracker_klass.new(options)
58
58
  end
59
59
  end
60
-
60
+
61
61
  attr_reader :trackers
62
62
  attr_reader :warnings_encountered
63
-
63
+
64
64
  # Initialize summarizer.
65
65
  # Generate trackers from speciefied source.file_format.report_trackers and set them up
66
66
  def initialize(source, options = {})
@@ -69,16 +69,16 @@ module RequestLogAnalyzer::Aggregator
69
69
  @trackers = source.file_format.report_trackers
70
70
  setup
71
71
  end
72
-
72
+
73
73
  def setup
74
74
  end
75
-
75
+
76
76
  # Call prepare on all trackers.
77
77
  def prepare
78
78
  raise "No trackers set up in Summarizer!" if @trackers.nil? || @trackers.empty?
79
79
  @trackers.each { |tracker| tracker.prepare }
80
80
  end
81
-
81
+
82
82
  # Pass all requests to trackers and let them update if necessary.
83
83
  # <tt>request</tt> The request to pass.
84
84
  def aggregate(request)
@@ -86,19 +86,19 @@ module RequestLogAnalyzer::Aggregator
86
86
  tracker.update(request) if tracker.should_update?(request)
87
87
  end
88
88
  end
89
-
89
+
90
90
  # Call finalize on all trackers. Saves a YAML dump if this is set in the options.
91
91
  def finalize
92
92
  @trackers.each { |tracker| tracker.finalize }
93
93
  save_results_dump(options[:dump]) if options[:dump]
94
94
  end
95
-
95
+
96
96
  # Saves the results of all the trackers in YAML format to a file.
97
97
  # <tt>filename</tt> The file to store the YAML dump in.
98
98
  def save_results_dump(filename)
99
99
  File.open(filename, 'w') { |file| file.write(to_yaml) }
100
100
  end
101
-
101
+
102
102
  # Exports all the tracker results to YAML. It will call the to_yaml_object method
103
103
  # for every tracker and combines these into a single YAML export.
104
104
  def to_yaml
@@ -108,7 +108,7 @@ module RequestLogAnalyzer::Aggregator
108
108
  end
109
109
  YAML::dump(trackers_export)
110
110
  end
111
-
111
+
112
112
  # Call report on all trackers.
113
113
  # <tt>output</tt> RequestLogAnalyzer::Output object to output to
114
114
  def report(output)
@@ -121,13 +121,13 @@ module RequestLogAnalyzer::Aggregator
121
121
  end
122
122
  report_footer(output)
123
123
  end
124
-
124
+
125
125
  # Generate report header.
126
126
  # <tt>output</tt> RequestLogAnalyzer::Output object to output to
127
127
  def report_header(output)
128
128
  output.title("Request summary")
129
-
130
- output.with_style(:cell_separator => false) do
129
+
130
+ output.with_style(:cell_separator => false) do
131
131
  output.table({:width => 20}, {:font => :bold}) do |rows|
132
132
  rows << ['Parsed lines:', source.parsed_lines]
133
133
  rows << ['Skipped lines:', source.skipped_lines]
@@ -138,13 +138,13 @@ module RequestLogAnalyzer::Aggregator
138
138
  end
139
139
  output << "\n"
140
140
  end
141
-
141
+
142
142
  # Generate report footer.
143
143
  # <tt>output</tt> RequestLogAnalyzer::Output object to output to
144
144
  def report_footer(output)
145
145
  if has_log_ordering_warnings?
146
146
  output.title("Parse warnings")
147
-
147
+
148
148
  output.puts "Parseable lines were ancountered without a header line before it. It"
149
149
  output.puts "could be that logging is not setup correctly for your application."
150
150
  output.puts "Visit this website for logging configuration tips:"
@@ -152,17 +152,17 @@ module RequestLogAnalyzer::Aggregator
152
152
  output.puts
153
153
  end
154
154
  end
155
-
155
+
156
156
  # Returns true if there were any warnings generated by the trackers
157
157
  def has_warnings?
158
158
  @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 0
159
159
  end
160
-
160
+
161
161
  # Returns true if there were any log ordering warnings
162
162
  def has_log_ordering_warnings?
163
163
  @warnings_encountered[:no_current_request] && @warnings_encountered[:no_current_request] > 0
164
164
  end
165
-
165
+
166
166
  # Store an encountered warning
167
167
  # <tt>type</tt> Type of warning
168
168
  # <tt>message</tt> Warning message
@@ -1,5 +1,5 @@
1
1
  module RequestLogAnalyzer
2
-
2
+
3
3
  # The RequestLogAnalyzer::Controller class creates a LogParser instance for the
4
4
  # requested file format, and connect it with sources and aggregators.
5
5
  #
@@ -22,37 +22,39 @@ module RequestLogAnalyzer
22
22
  # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
23
23
  # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
24
24
  # <rr>report_with</tt> Width of the report. Defaults to 80.
25
- def self.build(arguments)
26
- options = { }
27
-
28
- # Database command line options
29
- options[:database] = arguments[:database] if arguments[:database]
25
+ def self.build_from_arguments(arguments)
26
+
27
+ options = {}
28
+
29
+ # Copy fields
30
+ options[:database] = arguments[:database]
30
31
  options[:reset_database] = arguments[:reset_database]
31
32
  options[:debug] = arguments[:debug]
32
33
  options[:dump] = arguments[:dump]
33
34
  options[:parse_strategy] = arguments[:parse_strategy]
34
35
  options[:no_progress] = arguments[:no_progress]
36
+ options[:format] = arguments[:format]
37
+ options[:output] = arguments[:output]
38
+ options[:file] = arguments[:file]
39
+ options[:format] = arguments[:format]
40
+ options[:after] = arguments[:after]
41
+ options[:before] = arguments[:before]
42
+ options[:reject] = arguments[:reject]
43
+ options[:select] = arguments[:select]
44
+ options[:boring] = arguments[:boring]
45
+ options[:aggregator] = arguments[:aggregator]
46
+ options[:report_width] = arguments[:report_width]
47
+ options[:report_sort] = arguments[:report_sort]
48
+ options[:report_amount] = arguments[:report_amount]
35
49
 
36
- output_class = RequestLogAnalyzer::Output::const_get(arguments[:output])
37
- if arguments[:file]
38
- output_file = File.new(arguments[:file], "w+")
39
- options[:output] = output_class.new(output_file, :width => 80, :color => false, :characters => :ascii)
40
- elsif arguments[:mail]
41
- output_mail = RequestLogAnalyzer::Mailer.new(arguments[:mail])
42
- options[:output] = output_class.new(output_mail, :width => 80, :color => false, :characters => :ascii)
43
- else
44
- options[:output] = output_class.new(STDOUT, :width => arguments[:report_width].to_i,
45
- :color => !arguments[:boring], :characters => (arguments[:boring] ? :ascii : :utf))
50
+ # Apache format workaround
51
+ if arguments[:rails_format]
52
+ options[:format] = {:rails => arguments[:rails_format]}
53
+ elsif arguments[:apache_format]
54
+ options[:format] = {:apache => arguments[:apache_format]}
46
55
  end
47
-
48
- # Create the controller with the correct file format
49
- file_format = if arguments[:apache_format]
50
- RequestLogAnalyzer::FileFormat.load(:apache, arguments[:apache_format])
51
- else
52
- RequestLogAnalyzer::FileFormat.load(arguments[:format])
53
- end
54
-
55
- # register sources
56
+
57
+ # Register sources
56
58
  if arguments.parameters.length == 1
57
59
  file = arguments.parameters[0]
58
60
  if file == '-' || file == 'STDIN'
@@ -67,49 +69,128 @@ module RequestLogAnalyzer
67
69
  options.store(:source_files, arguments.parameters)
68
70
  end
69
71
 
70
- controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
71
- #controller = Controller.new(RequestLogAnalyzer::Source::DatabaseLoader.new(file_format, options), options)
72
+ build(options)
73
+ end
74
+
75
+ # Build a new controller using parameters (Base for new API)
76
+ # <tt>source</tt> The source file
77
+ # Options are passd on to the LogParser.
78
+ #
79
+ # Options
80
+ # * <tt>:database</tt> Database file
81
+ # * <tt>:reset_database</tt>
82
+ # * <tt>:debug</tt> Enables echo aggregator.
83
+ # * <tt>:dump</tt>
84
+ # * <tt>:parse_strategy</tt>
85
+ # * <tt>:no_progress</tt>
86
+ # * <tt>:output</tt> :fixed_width, :html or Output class. Defaults to fixed width.
87
+ # * <tt>:file</tt> Filestring or File or StringIO
88
+ # * <tt>:format</tt> :rails, {:apache => 'FORMATSTRING'}, :merb, etcetera or Format Class. Defaults to :rails.
89
+ # * <tt>:source_files</tt> File or STDIN
90
+ # * <tt>:after</tt> Drop all requests after this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
91
+ # * <tt>:before</tt> Drop all requests before this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
92
+ # * <tt>:reject</tt> Reject specific {:field => :value} combination. Expects single hash.
93
+ # * <tt>:select</tt> Select specific {:field => :value} combination. Expects single hash.
94
+ # * <tt>:aggregator</tt> Array of aggregators (ATM: STRINGS OR SYMBOLS ONLY!). Defaults to [:summarizer
95
+ # * <tt>:boring</tt> Do not show color on STDOUT. Defaults to False.
96
+ # * <tt>:report_width</tt> Width or reports in characters. Defaults to 80.
97
+ #
98
+ # TODO:
99
+ # Check if defaults work (Aggregator defaults seem wrong).
100
+ # Refactor :database => options[:database], :dump => options[:dump] away from contoller intialization.
101
+ def self.build(options)
102
+ # Defaults
103
+ options[:output] ||= :fixed_width
104
+ options[:format] ||= :rails
105
+ options[:aggregator] ||= [:summarizer]
106
+ options[:report_width] ||= 80
107
+ options[:report_amount] ||= 20
108
+ options[:report_sort] ||= 'sum,mean'
109
+ options[:boring] ||= false
110
+
111
+ # Set the output class
112
+ output_args = {}
113
+ output_object = nil
114
+ if options[:output].is_a? Class
115
+ output_class = options[:output]
116
+ else
117
+ output_class = RequestLogAnalyzer::Output::const_get(options[:output])
118
+ end
119
+
120
+ output_sort = options[:report_sort].split(',').map { |s| s.to_sym }
121
+ output_amount = options[:report_amount] == 'all' ? :all : options[:report_amount].to_i
122
+
123
+ if options[:file]
124
+ output_object = %w[File StringIO].include?(options[:file].class.name) ? options[:file] : File.new(options[:file], "w+")
125
+ output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
126
+ elsif options[:mail]
127
+ output_object = RequestLogAnalyzer::Mailer.new(arguments[:mail])
128
+ output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
129
+ else
130
+ output_object = STDOUT
131
+ output_args = {:width => options[:report_width].to_i, :color => !options[:boring],
132
+ :characters => (options[:boring] ? :ascii : :utf), :sort => output_sort, :amount => output_amount }
133
+ end
134
+
135
+ output_instance = output_class.new(output_object, output_args)
136
+
137
+ # Create the controller with the correct file format
138
+ if options[:format].kind_of?(Hash)
139
+ file_format = RequestLogAnalyzer::FileFormat.load(options[:format].keys[0], options[:format].values[0])
140
+ else
141
+ file_format = RequestLogAnalyzer::FileFormat.load(options[:format])
142
+ end
72
143
 
144
+ # Kickstart the controller
145
+ controller = Controller.new( RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => options[:source_files]),
146
+ { :output => output_instance,
147
+ :database => options[:database], # FUGLY!
148
+ :dump => options[:dump],
149
+ :reset_database => options[:reset_database]})
150
+
73
151
  # register filters
74
- if arguments[:after] || arguments[:before]
152
+ if options[:after] || options[:before]
75
153
  filter_options = {}
76
- filter_options[:after] = DateTime.parse(arguments[:after])
77
- filter_options[:before] = DateTime.parse(arguments[:before]) if arguments[:before]
154
+ [:after, :before].each do |filter|
155
+ case options[filter]
156
+ when Date, DateTime, Time
157
+ filter_options[filter] = options[filter]
158
+ when String
159
+ filter_options[filter] = DateTime.parse(options[filter])
160
+ end
161
+ end
78
162
  controller.add_filter(:timespan, filter_options)
79
163
  end
80
-
81
- arguments[:reject].each do |(field, value)|
82
- controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
164
+
165
+ if options[:reject]
166
+ options[:reject].each do |(field, value)|
167
+ controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
168
+ end
83
169
  end
84
-
85
- arguments[:select].each do |(field, value)|
86
- controller.add_filter(:field, :mode => :select, :field => field, :value => value)
170
+
171
+ if options[:reject]
172
+ options[:select].each do |(field, value)|
173
+ controller.add_filter(:field, :mode => :select, :field => field, :value => value)
174
+ end
87
175
  end
88
176
 
89
177
  # register aggregators
90
- arguments[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
178
+ options[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
179
+ controller.add_aggregator(:summarizer) if options[:aggregator].empty?
180
+ controller.add_aggregator(:echo) if options[:debug]
181
+ controller.add_aggregator(:database_inserter) if options[:database] && !options[:aggregator].include?('database')
91
182
 
92
- # register the database
93
- controller.add_aggregator(:summarizer) if arguments[:aggregator].empty?
94
- controller.add_aggregator(:database_inserter) if arguments[:database] && !arguments[:aggregator].include?('database')
95
-
96
- # register the echo aggregator in debug mode
97
- controller.add_aggregator(:echo) if arguments[:debug]
98
-
99
183
  file_format.setup_environment(controller)
100
-
101
184
  return controller
102
- end
185
+ end
103
186
 
104
187
  # Builds a new Controller for the given log file format.
105
188
  # <tt>format</tt> Logfile format. Defaults to :rails
106
189
  # Options are passd on to the LogParser.
107
- # * <tt>:aggregator</tt> Aggregator array.
108
190
  # * <tt>:database</tt> Database the controller should use.
109
- # * <tt>:echo</tt> Output debug information.
110
- # * <tt>:silent</tt> Do not output any warnings.
111
- # * <tt>:colorize</tt> Colorize output
191
+ # * <tt>:dump</tt> Yaml Dump the contrller should use.
112
192
  # * <tt>:output</tt> All report outputs get << through this output.
193
+ # * <tt>:no_progress</tt> No progress bar
113
194
  def initialize(source, options = {})
114
195
 
115
196
  @source = source
@@ -117,16 +198,17 @@ module RequestLogAnalyzer
117
198
  @aggregators = []
118
199
  @filters = []
119
200
  @output = options[:output]
201
+ @interrupted = false
120
202
 
121
203
  # Register the request format for this session after checking its validity
122
204
  raise "Invalid file format!" unless @source.file_format.valid?
123
-
205
+
124
206
  # Install event handlers for wrnings, progress updates and source changes
125
207
  @source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } }
126
208
  @source.progress = lambda { |message, value| handle_progress(message, value) } unless options[:no_progress]
127
209
  @source.source_changes = lambda { |change, filename| handle_source_change(change, filename) }
128
210
  end
129
-
211
+
130
212
  # Progress function.
131
213
  # Expects :started with file, :progress with current line and :finished or :interrupted when done.
132
214
  # <tt>message</tt> Current state (:started, :finished, :interupted or :progress).
@@ -147,46 +229,46 @@ module RequestLogAnalyzer
147
229
  @progress_bar.set(value)
148
230
  end
149
231
  end
150
-
232
+
151
233
  # Source change handler
152
234
  def handle_source_change(change, filename)
153
235
  @aggregators.each { |agg| agg.source_change(change, File.expand_path(filename, Dir.pwd)) }
154
236
  end
155
-
156
- # Adds an aggregator to the controller. The aggregator will be called for every request
237
+
238
+ # Adds an aggregator to the controller. The aggregator will be called for every request
157
239
  # that is parsed from the provided sources (see add_source)
158
- def add_aggregator(agg)
240
+ def add_aggregator(agg)
159
241
  agg = RequestLogAnalyzer::Aggregator.const_get(RequestLogAnalyzer::to_camelcase(agg)) if agg.kind_of?(Symbol)
160
242
  @aggregators << agg.new(@source, @options)
161
243
  end
162
-
244
+
163
245
  alias :>> :add_aggregator
164
-
246
+
165
247
  # Adds a request filter to the controller.
166
248
  def add_filter(filter, filter_options = {})
167
249
  filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
168
250
  @filters << filter.new(source.file_format, @options.merge(filter_options))
169
251
  end
170
-
252
+
171
253
  # Push a request through the entire filterchain (@filters).
172
254
  # <tt>request</tt> The request to filter.
173
255
  # Returns the filtered request or nil.
174
256
  def filter_request(request)
175
- @filters.each do |filter|
257
+ @filters.each do |filter|
176
258
  request = filter.filter(request)
177
259
  return nil if request.nil?
178
260
  end
179
261
  return request
180
262
  end
181
-
263
+
182
264
  # Push a request to all the aggregators (@aggregators).
183
- # <tt>request</tt> The request to push to the aggregators.
265
+ # <tt>request</tt> The request to push to the aggregators.
184
266
  def aggregate_request(request)
185
267
  return false unless request
186
268
  @aggregators.each { |agg| agg.aggregate(request) }
187
269
  return true
188
270
  end
189
-
271
+
190
272
  # Runs RequestLogAnalyzer
191
273
  # 1. Call prepare on every aggregator
192
274
  # 2. Generate requests from source object
@@ -196,10 +278,12 @@ module RequestLogAnalyzer
196
278
  # 5. Call report on every aggregator
197
279
  # 6. Finalize Source
198
280
  def run!
199
-
281
+
282
+ # @aggregators.each{|agg| p agg}
283
+
200
284
  @aggregators.each { |agg| agg.prepare }
201
285
  install_signal_handlers
202
-
286
+
203
287
  @source.each_request do |request|
204
288
  break if @interrupted
205
289
  aggregate_request(filter_request(request))
@@ -210,9 +294,9 @@ module RequestLogAnalyzer
210
294
  @output.header
211
295
  @aggregators.each { |agg| agg.report(@output) }
212
296
  @output.footer
213
-
297
+
214
298
  @source.finalize
215
-
299
+
216
300
  if @output.io.kind_of?(File)
217
301
  puts
218
302
  puts "Report written to: " + File.expand_path(@output.io.path)
@@ -224,7 +308,7 @@ module RequestLogAnalyzer
224
308
  @output.io.mail
225
309
  end
226
310
  end
227
-
311
+
228
312
  def install_signal_handlers
229
313
  Signal.trap("INT") do
230
314
  handle_progress(:interrupted)
@@ -232,6 +316,6 @@ module RequestLogAnalyzer
232
316
  @interrupted = true
233
317
  end
234
318
  end
235
-
319
+
236
320
  end
237
321
  end