lib/request_log_analyzer/controller.rb in request-log-analyzer-1.3.7 vs lib/request_log_analyzer/controller.rb in request-log-analyzer-1.4.0
- old
+ new
@@ -1,7 +1,7 @@
module RequestLogAnalyzer
-
+
# The RequestLogAnalyzer::Controller class creates a LogParser instance for the
# requested file format, and connect it with sources and aggregators.
#
# Sources are streams or files from which the requests will be parsed.
# Aggregators will handle every passed request to yield a meaningfull results.
@@ -20,41 +20,43 @@
attr_reader :source, :filters, :aggregators, :output, :options
# Builds a RequestLogAnalyzer::Controller given parsed command line arguments
# <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
# <rr>report_with</tt> Width of the report. Defaults to 80.
- def self.build(arguments)
- options = { }
-
- # Database command line options
- options[:database] = arguments[:database] if arguments[:database]
+ def self.build_from_arguments(arguments)
+
+ options = {}
+
+ # Copy fields
+ options[:database] = arguments[:database]
options[:reset_database] = arguments[:reset_database]
options[:debug] = arguments[:debug]
options[:dump] = arguments[:dump]
options[:parse_strategy] = arguments[:parse_strategy]
options[:no_progress] = arguments[:no_progress]
+ options[:format] = arguments[:format]
+ options[:output] = arguments[:output]
+ options[:file] = arguments[:file]
+ options[:format] = arguments[:format]
+ options[:after] = arguments[:after]
+ options[:before] = arguments[:before]
+ options[:reject] = arguments[:reject]
+ options[:select] = arguments[:select]
+ options[:boring] = arguments[:boring]
+ options[:aggregator] = arguments[:aggregator]
+ options[:report_width] = arguments[:report_width]
+ options[:report_sort] = arguments[:report_sort]
+ options[:report_amount] = arguments[:report_amount]
- output_class = RequestLogAnalyzer::Output::const_get(arguments[:output])
- if arguments[:file]
- output_file = File.new(arguments[:file], "w+")
- options[:output] = output_class.new(output_file, :width => 80, :color => false, :characters => :ascii)
- elsif arguments[:mail]
- output_mail = RequestLogAnalyzer::Mailer.new(arguments[:mail])
- options[:output] = output_class.new(output_mail, :width => 80, :color => false, :characters => :ascii)
- else
- options[:output] = output_class.new(STDOUT, :width => arguments[:report_width].to_i,
- :color => !arguments[:boring], :characters => (arguments[:boring] ? :ascii : :utf))
+ # Apache format workaround
+ if arguments[:rails_format]
+ options[:format] = {:rails => arguments[:rails_format]}
+ elsif arguments[:apache_format]
+ options[:format] = {:apache => arguments[:apache_format]}
end
-
- # Create the controller with the correct file format
- file_format = if arguments[:apache_format]
- RequestLogAnalyzer::FileFormat.load(:apache, arguments[:apache_format])
- else
- RequestLogAnalyzer::FileFormat.load(arguments[:format])
- end
-
- # register sources
+
+ # Register sources
if arguments.parameters.length == 1
file = arguments.parameters[0]
if file == '-' || file == 'STDIN'
options.store(:source_files, $stdin)
elsif File.exist?(file)
@@ -65,70 +67,150 @@
end
else
options.store(:source_files, arguments.parameters)
end
- controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
- #controller = Controller.new(RequestLogAnalyzer::Source::DatabaseLoader.new(file_format, options), options)
+ build(options)
+ end
+
+ # Build a new controller using parameters (Base for new API)
+ # <tt>source</tt> The source file
+ # Options are passd on to the LogParser.
+ #
+ # Options
+ # * <tt>:database</tt> Database file
+ # * <tt>:reset_database</tt>
+ # * <tt>:debug</tt> Enables echo aggregator.
+ # * <tt>:dump</tt>
+ # * <tt>:parse_strategy</tt>
+ # * <tt>:no_progress</tt>
+ # * <tt>:output</tt> :fixed_width, :html or Output class. Defaults to fixed width.
+ # * <tt>:file</tt> Filestring or File or StringIO
+ # * <tt>:format</tt> :rails, {:apache => 'FORMATSTRING'}, :merb, etcetera or Format Class. Defaults to :rails.
+ # * <tt>:source_files</tt> File or STDIN
+ # * <tt>:after</tt> Drop all requests after this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
+ # * <tt>:before</tt> Drop all requests before this date (Date, DateTime, Time, or a String in "YYYY-MM-DD hh:mm:ss" format)
+ # * <tt>:reject</tt> Reject specific {:field => :value} combination. Expects single hash.
+ # * <tt>:select</tt> Select specific {:field => :value} combination. Expects single hash.
+ # * <tt>:aggregator</tt> Array of aggregators (ATM: STRINGS OR SYMBOLS ONLY!). Defaults to [:summarizer
+ # * <tt>:boring</tt> Do not show color on STDOUT. Defaults to False.
+ # * <tt>:report_width</tt> Width or reports in characters. Defaults to 80.
+ #
+ # TODO:
+ # Check if defaults work (Aggregator defaults seem wrong).
+ # Refactor :database => options[:database], :dump => options[:dump] away from contoller intialization.
+ def self.build(options)
+ # Defaults
+ options[:output] ||= :fixed_width
+ options[:format] ||= :rails
+ options[:aggregator] ||= [:summarizer]
+ options[:report_width] ||= 80
+ options[:report_amount] ||= 20
+ options[:report_sort] ||= 'sum,mean'
+ options[:boring] ||= false
+ # Set the output class
+ output_args = {}
+ output_object = nil
+ if options[:output].is_a? Class
+ output_class = options[:output]
+ else
+ output_class = RequestLogAnalyzer::Output::const_get(options[:output])
+ end
+
+ output_sort = options[:report_sort].split(',').map { |s| s.to_sym }
+ output_amount = options[:report_amount] == 'all' ? :all : options[:report_amount].to_i
+
+ if options[:file]
+ output_object = %w[File StringIO].include?(options[:file].class.name) ? options[:file] : File.new(options[:file], "w+")
+ output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
+ elsif options[:mail]
+ output_object = RequestLogAnalyzer::Mailer.new(arguments[:mail])
+ output_args = {:width => 80, :color => false, :characters => :ascii, :sort => output_sort, :amount => output_amount }
+ else
+ output_object = STDOUT
+ output_args = {:width => options[:report_width].to_i, :color => !options[:boring],
+ :characters => (options[:boring] ? :ascii : :utf), :sort => output_sort, :amount => output_amount }
+ end
+
+ output_instance = output_class.new(output_object, output_args)
+
+ # Create the controller with the correct file format
+ if options[:format].kind_of?(Hash)
+ file_format = RequestLogAnalyzer::FileFormat.load(options[:format].keys[0], options[:format].values[0])
+ else
+ file_format = RequestLogAnalyzer::FileFormat.load(options[:format])
+ end
+
+ # Kickstart the controller
+ controller = Controller.new( RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => options[:source_files]),
+ { :output => output_instance,
+ :database => options[:database], # FUGLY!
+ :dump => options[:dump],
+ :reset_database => options[:reset_database]})
+
# register filters
- if arguments[:after] || arguments[:before]
+ if options[:after] || options[:before]
filter_options = {}
- filter_options[:after] = DateTime.parse(arguments[:after])
- filter_options[:before] = DateTime.parse(arguments[:before]) if arguments[:before]
+ [:after, :before].each do |filter|
+ case options[filter]
+ when Date, DateTime, Time
+ filter_options[filter] = options[filter]
+ when String
+ filter_options[filter] = DateTime.parse(options[filter])
+ end
+ end
controller.add_filter(:timespan, filter_options)
end
-
- arguments[:reject].each do |(field, value)|
- controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
+
+ if options[:reject]
+ options[:reject].each do |(field, value)|
+ controller.add_filter(:field, :mode => :reject, :field => field, :value => value)
+ end
end
-
- arguments[:select].each do |(field, value)|
- controller.add_filter(:field, :mode => :select, :field => field, :value => value)
+
+ if options[:reject]
+ options[:select].each do |(field, value)|
+ controller.add_filter(:field, :mode => :select, :field => field, :value => value)
+ end
end
# register aggregators
- arguments[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
+ options[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
+ controller.add_aggregator(:summarizer) if options[:aggregator].empty?
+ controller.add_aggregator(:echo) if options[:debug]
+ controller.add_aggregator(:database_inserter) if options[:database] && !options[:aggregator].include?('database')
- # register the database
- controller.add_aggregator(:summarizer) if arguments[:aggregator].empty?
- controller.add_aggregator(:database_inserter) if arguments[:database] && !arguments[:aggregator].include?('database')
-
- # register the echo aggregator in debug mode
- controller.add_aggregator(:echo) if arguments[:debug]
-
file_format.setup_environment(controller)
-
return controller
- end
+ end
# Builds a new Controller for the given log file format.
# <tt>format</tt> Logfile format. Defaults to :rails
# Options are passd on to the LogParser.
- # * <tt>:aggregator</tt> Aggregator array.
# * <tt>:database</tt> Database the controller should use.
- # * <tt>:echo</tt> Output debug information.
- # * <tt>:silent</tt> Do not output any warnings.
- # * <tt>:colorize</tt> Colorize output
+ # * <tt>:dump</tt> Yaml Dump the contrller should use.
# * <tt>:output</tt> All report outputs get << through this output.
+ # * <tt>:no_progress</tt> No progress bar
def initialize(source, options = {})
@source = source
@options = options
@aggregators = []
@filters = []
@output = options[:output]
+ @interrupted = false
# Register the request format for this session after checking its validity
raise "Invalid file format!" unless @source.file_format.valid?
-
+
# Install event handlers for wrnings, progress updates and source changes
@source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } }
@source.progress = lambda { |message, value| handle_progress(message, value) } unless options[:no_progress]
@source.source_changes = lambda { |change, filename| handle_source_change(change, filename) }
end
-
+
# Progress function.
# Expects :started with file, :progress with current line and :finished or :interrupted when done.
# <tt>message</tt> Current state (:started, :finished, :interupted or :progress).
# <tt>value</tt> File or current line.
def handle_progress(message, value = nil)
@@ -145,76 +227,78 @@
end
when :progress
@progress_bar.set(value)
end
end
-
+
# Source change handler
def handle_source_change(change, filename)
@aggregators.each { |agg| agg.source_change(change, File.expand_path(filename, Dir.pwd)) }
end
-
- # Adds an aggregator to the controller. The aggregator will be called for every request
+
+ # Adds an aggregator to the controller. The aggregator will be called for every request
# that is parsed from the provided sources (see add_source)
- def add_aggregator(agg)
+ def add_aggregator(agg)
agg = RequestLogAnalyzer::Aggregator.const_get(RequestLogAnalyzer::to_camelcase(agg)) if agg.kind_of?(Symbol)
@aggregators << agg.new(@source, @options)
end
-
+
alias :>> :add_aggregator
-
+
# Adds a request filter to the controller.
def add_filter(filter, filter_options = {})
filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
@filters << filter.new(source.file_format, @options.merge(filter_options))
end
-
+
# Push a request through the entire filterchain (@filters).
# <tt>request</tt> The request to filter.
# Returns the filtered request or nil.
def filter_request(request)
- @filters.each do |filter|
+ @filters.each do |filter|
request = filter.filter(request)
return nil if request.nil?
end
return request
end
-
+
# Push a request to all the aggregators (@aggregators).
- # <tt>request</tt> The request to push to the aggregators.
+ # <tt>request</tt> The request to push to the aggregators.
def aggregate_request(request)
return false unless request
@aggregators.each { |agg| agg.aggregate(request) }
return true
end
-
+
# Runs RequestLogAnalyzer
# 1. Call prepare on every aggregator
# 2. Generate requests from source object
# 3. Filter out unwanted requests
# 4. Call aggregate for remaning requests on every aggregator
# 4. Call finalize on every aggregator
# 5. Call report on every aggregator
# 6. Finalize Source
def run!
-
+
+ # @aggregators.each{|agg| p agg}
+
@aggregators.each { |agg| agg.prepare }
install_signal_handlers
-
+
@source.each_request do |request|
break if @interrupted
aggregate_request(filter_request(request))
end
@aggregators.each { |agg| agg.finalize }
@output.header
@aggregators.each { |agg| agg.report(@output) }
@output.footer
-
+
@source.finalize
-
+
if @output.io.kind_of?(File)
puts
puts "Report written to: " + File.expand_path(@output.io.path)
puts "Need an expert to analyze your application?"
puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
@@ -222,16 +306,16 @@
@output.io.close
elsif @output.io.kind_of?(RequestLogAnalyzer::Mailer)
@output.io.mail
end
end
-
+
def install_signal_handlers
Signal.trap("INT") do
handle_progress(:interrupted)
puts "Caught interrupt! Stopping parsing..."
@interrupted = true
end
end
-
+
end
end