require 'date' module RailsAnalyzer # Parse a rails log file class LogParser LOG_LINES = { # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET] :started => { :teaser => /Processing/, :regexp => /Processing (\w+)#(\w+) \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/, :params => { :controller => 1, :action => 2, :ip => 3, :timestamp => 4, :method => 5} }, # RuntimeError (Cannot destroy employee): /app/models/employee.rb:198:in `before_destroy' :failed => { :teaser => /Error/, :regexp => /(\w+)(Error|Invalid) \((.*)\)\:(.*)/, :params => { :error => 1, :exception_string => 3, :stack_trace => 4 } }, # Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees] :completed => { :teaser => /Completed/, :regexp => /Completed in (\d+\.\d{5}) \(\d+ reqs\/sec\) (\| Rendering: (\d+\.\d{5}) \(\d+\%\) )?(\| DB: (\d+\.\d{5}) \(\d+\%\) )?\| (\d\d\d).+\[(http.+)\]/, :params => { :url => 7, :status => [6, :to_i], :duration => [1, :to_f], :rendering => [3, :to_f], :db => [5, :to_f] } } } # LogParser initializer # file The fileobject this LogParser wil operate on. def initialize(file, options = {}) @file_name = file @options = options @file_size = File.size(@file_name) end def progress(&block) @progress_handler = block end # Output a warning # message The warning message (object) def warn(message) puts " -> " + message.to_s end # Finds a log line and then parses the information in the line. # Yields a hash containing the information found. # *line_types The log line types to look for (defaults to LOG_LINES.keys). # Yeilds a Hash when it encounters a chunk of information. def each(*line_types, &block) # parse everything by default line_types = LOG_LINES.keys if line_types.empty? File.open(@file_name) do |file| file.each_line do |line| @progress_handler.call(file.pos, @file_size) if @progress_handler line_types.each do |line_type| if LOG_LINES[line_type][:teaser] =~ line if LOG_LINES[line_type][:regexp] =~ line request = { :type => line_type, :line => file.lineno } LOG_LINES[line_type][:params].each do |key, value| request[key] = case value when Numeric; $~[value] when Array; $~[value.first].send(value.last) else; nil end end yield(request) if block_given? else warn("Unparsable #{line_type} line: " + line[0..79]) unless line_type == :failed end end end end @progress_handler.call(:finished, @file_size) if @progress_handler end end end end