request-log-analyzer 1.2.9 → 1.3.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.
- data/bin/request-log-analyzer +33 -19
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +42 -0
- data/lib/cli/tools.rb +1 -1
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +81 -0
- data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -2
- data/lib/request_log_analyzer/aggregator.rb +4 -0
- data/lib/request_log_analyzer/controller.rb +23 -7
- data/lib/request_log_analyzer/database/base.rb +114 -0
- data/lib/request_log_analyzer/database/connection.rb +38 -0
- data/lib/request_log_analyzer/database.rb +177 -0
- data/lib/request_log_analyzer/file_format.rb +6 -3
- data/lib/request_log_analyzer/mailer.rb +46 -0
- data/lib/request_log_analyzer/request.rb +2 -1
- data/lib/request_log_analyzer/source/{database.rb → database_loader.rb} +1 -1
- data/lib/request_log_analyzer/source/log_parser.rb +28 -15
- data/lib/request_log_analyzer/source.rb +7 -2
- data/lib/request_log_analyzer.rb +5 -8
- data/request-log-analyzer.gemspec +8 -8
- data/spec/database.yml +17 -0
- data/spec/fixtures/rails.db +0 -0
- data/spec/integration/command_line_usage_spec.rb +14 -9
- data/spec/lib/macros.rb +16 -0
- data/spec/lib/mocks.rb +18 -6
- data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
- data/spec/unit/database/base_class_spec.rb +190 -0
- data/spec/unit/database/connection_spec.rb +34 -0
- data/spec/unit/database/database_spec.rb +138 -0
- data/spec/unit/source/log_parser_spec.rb +12 -0
- metadata +29 -16
- data/lib/request_log_analyzer/aggregator/database.rb +0 -220
- data/spec/spec.opts +0 -3
- data/spec/unit/aggregator/database_spec.rb +0 -245
    
        data/bin/request-log-analyzer
    CHANGED
    
    | @@ -1,8 +1,9 @@ | |
| 1 1 | 
             
            #!/usr/bin/ruby
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 2 | 
            +
            $:.unshift(File.dirname(__FILE__) + '/../lib')
         | 
| 3 | 
            +
            require 'request_log_analyzer'
         | 
| 4 | 
            +
            require 'cli/command_line_arguments'
         | 
| 5 | 
            +
            require 'lib/cli/progressbar'
         | 
| 6 | 
            +
            require 'lib/cli/tools'
         | 
| 6 7 |  | 
| 7 8 | 
             
            # Parse the arguments given via commandline
         | 
| 8 9 | 
             
            begin
         | 
| @@ -11,40 +12,49 @@ begin | |
| 11 12 | 
             
                command_line.command(:install) do |install|
         | 
| 12 13 | 
             
                  install.parameters = 1
         | 
| 13 14 | 
             
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            +
             | 
| 16 | 
            +
                command_line.command(:console) do |cons|
         | 
| 17 | 
            +
                  cons.option(:database, :alias => :d, :required => true)
         | 
| 18 | 
            +
                  cons.option(:format, :alias => :f, :default => 'rails')
         | 
| 19 | 
            +
                  cons.option(:apache_format)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 15 22 | 
             
                command_line.command(:strip) do |strip|
         | 
| 16 23 | 
             
                  strip.minimum_parameters = 1
         | 
| 17 | 
            -
                  strip.option(:format, :alias => :f, :default => 'rails') | 
| 24 | 
            +
                  strip.option(:format, :alias => :f, :default => 'rails')
         | 
| 18 25 | 
             
                  strip.option(:output, :alias => :o)
         | 
| 19 26 | 
             
                  strip.switch(:discard_teaser_lines, :t)
         | 
| 20 27 | 
             
                  strip.switch(:keep_junk_lines, :j) 
         | 
| 21 28 | 
             
                end
         | 
| 22 | 
            -
             | 
| 29 | 
            +
             | 
| 23 30 | 
             
                command_line.option(:format, :alias => :f, :default => 'rails')
         | 
| 24 31 | 
             
                command_line.option(:apache_format)
         | 
| 25 | 
            -
             | 
| 32 | 
            +
             | 
| 26 33 | 
             
                command_line.option(:file, :alias => :e)
         | 
| 34 | 
            +
                command_line.option(:mail, :alias => :m)
         | 
| 27 35 | 
             
                command_line.option(:parse_strategy, :default => 'assume-correct')
         | 
| 28 36 | 
             
                command_line.option(:dump)
         | 
| 29 | 
            -
             | 
| 37 | 
            +
             | 
| 30 38 | 
             
                command_line.option(:aggregator, :alias => :a, :multiple => true)
         | 
| 31 | 
            -
             | 
| 39 | 
            +
             | 
| 40 | 
            +
                command_line.option(:database, :alias => :d)
         | 
| 41 | 
            +
                command_line.switch(:reset_database)
         | 
| 32 42 |  | 
| 33 43 | 
             
                # filtering options
         | 
| 34 44 | 
             
                command_line.option(:select, :multiple => true, :parameters => 2)
         | 
| 35 45 | 
             
                command_line.option(:reject, :multiple => true, :parameters => 2)
         | 
| 36 46 | 
             
                command_line.option(:after)
         | 
| 37 | 
            -
                command_line.option(:before) | 
| 38 | 
            -
             | 
| 47 | 
            +
                command_line.option(:before)
         | 
| 48 | 
            +
             | 
| 39 49 | 
             
                command_line.switch(:boring, :b)
         | 
| 40 | 
            -
                command_line.option(:output, :alias => :o, :default => 'FixedWidth') | 
| 50 | 
            +
                command_line.option(:output, :alias => :o, :default => 'FixedWidth')
         | 
| 41 51 | 
             
                command_line.option(:report_width, :default => terminal_width - 1)
         | 
| 42 | 
            -
             | 
| 52 | 
            +
             | 
| 43 53 | 
             
                command_line.switch(:debug)
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                command_line.minimum_parameters = 1 | 
| 54 | 
            +
             | 
| 55 | 
            +
                command_line.minimum_parameters = 1
         | 
| 46 56 | 
             
              end
         | 
| 47 | 
            -
             | 
| 57 | 
            +
             | 
| 48 58 | 
             
            rescue CommandLine::Error => e
         | 
| 49 59 | 
             
              puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"  
         | 
| 50 60 | 
             
              puts "Website: http://railsdoctors.com"
         | 
| @@ -65,6 +75,7 @@ rescue CommandLine::Error => e | |
| 65 75 | 
             
              puts "  --database <filename>, -d: Creates an SQLite3 database of all the parsed request information."
         | 
| 66 76 | 
             
              puts "  --debug                    Print debug information while parsing."
         | 
| 67 77 | 
             
              puts "  --file <filename>          Output to file."
         | 
| 78 | 
            +
              puts "  --mail <emailaddress>      Send report to an email address."
         | 
| 68 79 | 
             
              puts "  --output <format>          Output format. Supports 'HTML' and 'FixedWidth' (default)"  
         | 
| 69 80 | 
             
              puts "  --dump <filename>          Dump the YAML formatted results in the given file"
         | 
| 70 81 | 
             
              puts 
         | 
| @@ -83,11 +94,14 @@ end | |
| 83 94 | 
             
            case arguments.command
         | 
| 84 95 | 
             
            when :install
         | 
| 85 96 | 
             
              install_rake_tasks(arguments.parameters[0])
         | 
| 97 | 
            +
            when :console
         | 
| 98 | 
            +
              require 'cli/database_console'
         | 
| 99 | 
            +
              DatabaseConsole.new(arguments).run!
         | 
| 86 100 | 
             
            when :strip
         | 
| 87 | 
            -
              require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor' | 
| 101 | 
            +
              require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
         | 
| 88 102 | 
             
              RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
         | 
| 89 103 | 
             
            else 
         | 
| 90 | 
            -
              puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}" | 
| 104 | 
            +
              puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
         | 
| 91 105 | 
             
              puts "Website: http://railsdoctors.com"
         | 
| 92 106 | 
             
              puts
         | 
| 93 107 |  | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            class DatabaseConsole
         | 
| 3 | 
            +
              
         | 
| 4 | 
            +
              IRB = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
         | 
| 5 | 
            +
              
         | 
| 6 | 
            +
              def initialize(arguments)
         | 
| 7 | 
            +
                @arguments = arguments
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
              
         | 
| 10 | 
            +
              def run!
         | 
| 11 | 
            +
                libraries = ['irb/completion', 'rubygems', './lib/request_log_analyzer', './lib/cli/database_console_init']
         | 
| 12 | 
            +
                libaries_string = libraries.map { |l| "-r #{l}" }.join(' ')
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                ENV['RLA_DBCONSOLE_DATABASE'] = @arguments[:database]
         | 
| 15 | 
            +
                if @arguments[:apache_format]
         | 
| 16 | 
            +
                  ENV['RLA_DBCONSOLE_FORMAT'] = 'apache'
         | 
| 17 | 
            +
                  ENV['RLA_DBCONSOLE_FORMAT_ARGUMENT'] = @arguments[:apache_format]
         | 
| 18 | 
            +
                else
         | 
| 19 | 
            +
                  ENV['RLA_DBCONSOLE_FORMAT'] = @arguments[:format]
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                # ENV['RLA_DBCONSOLE_FORMAT_ARGS'] = arguments['database']
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                exec("#{IRB} #{libaries_string} --simple-prompt")
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
             | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # Setup the include path
         | 
| 2 | 
            +
            $:.unshift(File.dirname(__FILE__) + '/..')
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            $database = RequestLogAnalyzer::Database.new(ENV['RLA_DBCONSOLE_DATABASE'])
         | 
| 5 | 
            +
            $database.load_database_schema!
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require 'cli/tools'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            def wordwrap(string, max = 80, indent = "")
         | 
| 10 | 
            +
              strings = [""]
         | 
| 11 | 
            +
              string.split(", ").each do |item|
         | 
| 12 | 
            +
                if strings.last.length == 0 || strings.last.length + item.length <= max
         | 
| 13 | 
            +
                  strings.last << item << ', '
         | 
| 14 | 
            +
                else
         | 
| 15 | 
            +
                  strings << (item + ', ')
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
              strings.map(&:strip).join("\n#{indent}").slice(0..-2)
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            class Request
         | 
| 22 | 
            +
              def inspect
         | 
| 23 | 
            +
                request_inspect = "Request[id: #{id}]"
         | 
| 24 | 
            +
                request_inspect << " <#{lines.first.source.filename}>" if lines.first.source
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                inspected_lines = lines.map do |line|
         | 
| 27 | 
            +
                  inspect_line = "   - #{line.line_type} (line #{line.lineno})"
         | 
| 28 | 
            +
                  if (inspect_attributes = line.attributes.reject { |(k, v)| [:id, :source_id, :request_id, :lineno].include?(k.to_sym) }).any?
         | 
| 29 | 
            +
                    inspect_attributes = inspect_attributes.map { |(k,v)| "#{k} = #{v.inspect}" }.join(', ')
         | 
| 30 | 
            +
                    inspect_line << "\n      " + wordwrap(inspect_attributes, terminal_width - 6, "      ")
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  inspect_line
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                request_inspect << "\n" << inspected_lines.join("\n") << "\n\n"
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            puts "request-log-analyzer database console"
         | 
| 40 | 
            +
            puts "-------------------------------------"
         | 
| 41 | 
            +
            puts "The following ActiveRecord classes are available:"
         | 
| 42 | 
            +
            puts $database.orm_classes.join(", ")
         | 
    
        data/lib/cli/tools.rb
    CHANGED
    
    
| @@ -0,0 +1,81 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module RequestLogAnalyzer::Aggregator
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # The database aggregator will create an SQLite3 database with all parsed request information.
         | 
| 5 | 
            +
              #
         | 
| 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. 
         | 
| 8 | 
            +
              # Then, the aggregate method will be called for every parsed request. The information of
         | 
| 9 | 
            +
              # these requests is inserted into the tables using the ActiveRecord classes.
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              # A requests table will be created, in which a record is inserted for every parsed request.
         | 
| 12 | 
            +
              # For every line type, a separate table will be created with a request_id field to point to
         | 
| 13 | 
            +
              # the request record, and a field for every parsed value. Finally, a warnings table will be
         | 
| 14 | 
            +
              # created to log all parse warnings.
         | 
| 15 | 
            +
              class DatabaseInserter < Base
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                attr_reader :request_count, :sources, :database
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # Establishes a connection to the database and creates the necessary database schema for the
         | 
| 20 | 
            +
                # current file format
         | 
| 21 | 
            +
                def prepare
         | 
| 22 | 
            +
                  @sources = {}
         | 
| 23 | 
            +
                  @database = RequestLogAnalyzer::Database.new(options[:database])
         | 
| 24 | 
            +
                  @database.file_format = source.file_format
         | 
| 25 | 
            +
                  
         | 
| 26 | 
            +
                  database.drop_database_schema! if options[:reset_database]
         | 
| 27 | 
            +
                  database.create_database_schema!
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                # Aggregates a request into the database
         | 
| 31 | 
            +
                # This will create a record in the requests table and create a record for every line that has been parsed,
         | 
| 32 | 
            +
                # in which the captured values will be stored.
         | 
| 33 | 
            +
                def aggregate(request)
         | 
| 34 | 
            +
                  @request_object = database.request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
         | 
| 35 | 
            +
                  request.lines.each do |line|
         | 
| 36 | 
            +
                    class_columns = database.get_class(line[:line_type]).column_names.reject { |column| ['id', 'source_id', 'request_id'].include?(column) }
         | 
| 37 | 
            +
                    attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
         | 
| 38 | 
            +
                    attributes[:source_id] = @sources[line[:source]].id if @sources[line[:source]]
         | 
| 39 | 
            +
                    @request_object.send("#{line[:line_type]}_lines").build(attributes)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                  @request_object.save!
         | 
| 42 | 
            +
                rescue SQLite3::SQLException => e
         | 
| 43 | 
            +
                  raise Interrupt, e.message
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                # Finalizes the aggregator by closing the connection to the database
         | 
| 47 | 
            +
                def finalize
         | 
| 48 | 
            +
                  @request_count = database.request_class.count
         | 
| 49 | 
            +
                  database.disconnect
         | 
| 50 | 
            +
                  database.remove_orm_classes!
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
                
         | 
| 53 | 
            +
                # Records w warining in the warnings table.
         | 
| 54 | 
            +
                def warning(type, message, lineno)
         | 
| 55 | 
            +
                  database.warning_class.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                
         | 
| 58 | 
            +
                # Records source changes in the sources table
         | 
| 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))
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                # Prints a short report of what has been inserted into the database
         | 
| 69 | 
            +
                def report(output)
         | 
| 70 | 
            +
                  output.title('Request database created')
         | 
| 71 | 
            +
                  
         | 
| 72 | 
            +
                  output <<  "A database file has been created with all parsed request information.\n"
         | 
| 73 | 
            +
                  output <<  "#{@request_count} requests have been added to the database.\n"
         | 
| 74 | 
            +
                  output << "\n"
         | 
| 75 | 
            +
                  output <<  "To open a Ruby console to inspect the database, run the following command.\n"
         | 
| 76 | 
            +
                  output <<  output.colorize("  $ request-log-analyzer console -d #{options[:database]}\n", :bold)
         | 
| 77 | 
            +
                  output << "\n"
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
                
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
            end
         | 
| @@ -130,9 +130,9 @@ module RequestLogAnalyzer::Aggregator | |
| 130 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 | 
            -
                      rows << ['Parsed requests:', source.parsed_requests]
         | 
| 134 133 | 
             
                      rows << ['Skipped lines:',   source.skipped_lines]
         | 
| 135 | 
            -
             | 
| 134 | 
            +
                      rows << ['Parsed requests:', source.parsed_requests]
         | 
| 135 | 
            +
                      rows << ['Skipped requests:', source.skipped_requests]
         | 
| 136 136 | 
             
                      rows << ["Warnings:", @warnings_encountered.map { |(key, value)| "#{key}: #{value}" }.join(', ')] if has_warnings?
         | 
| 137 137 | 
             
                    end
         | 
| 138 138 | 
             
                  end
         | 
| @@ -32,7 +32,10 @@ module RequestLogAnalyzer | |
| 32 32 | 
             
                def self.build(arguments)
         | 
| 33 33 | 
             
                  options = { }
         | 
| 34 34 |  | 
| 35 | 
            -
                   | 
| 35 | 
            +
                  # Database command line options
         | 
| 36 | 
            +
                  options[:database]       = arguments[:database] if arguments[:database]
         | 
| 37 | 
            +
                  options[:reset_database] = arguments[:reset_database]
         | 
| 38 | 
            +
                  
         | 
| 36 39 | 
             
                  options[:debug]    = arguments[:debug]
         | 
| 37 40 | 
             
                  options[:dump]     = arguments[:dump]
         | 
| 38 41 |  | 
| @@ -40,6 +43,9 @@ module RequestLogAnalyzer | |
| 40 43 | 
             
                  if arguments[:file]
         | 
| 41 44 | 
             
                    output_file = File.new(arguments[:file], "w+")
         | 
| 42 45 | 
             
                    options[:output] = output_class.new(output_file, :width => 80, :color => false, :characters => :ascii)
         | 
| 46 | 
            +
                  elsif arguments[:mail]
         | 
| 47 | 
            +
                    output_mail = RequestLogAnalyzer::Mailer.new(arguments[:mail])
         | 
| 48 | 
            +
                    options[:output] = output_class.new(output_mail, :width => 80, :color => false, :characters => :ascii)        
         | 
| 43 49 | 
             
                  else
         | 
| 44 50 | 
             
                    options[:output] = output_class.new(STDOUT, :width => arguments[:report_width].to_i, 
         | 
| 45 51 | 
             
                        :color => !arguments[:boring], :characters => (arguments[:boring] ? :ascii : :utf))
         | 
| @@ -68,7 +74,7 @@ module RequestLogAnalyzer | |
| 68 74 | 
             
                  end
         | 
| 69 75 |  | 
| 70 76 | 
             
                  controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
         | 
| 71 | 
            -
                  #controller = Controller.new(RequestLogAnalyzer::Source:: | 
| 77 | 
            +
                  #controller = Controller.new(RequestLogAnalyzer::Source::DatabaseLoader.new(file_format, options), options)
         | 
| 72 78 |  | 
| 73 79 | 
             
                  options[:parse_strategy] = arguments[:parse_strategy]
         | 
| 74 80 |  | 
| @@ -92,9 +98,9 @@ module RequestLogAnalyzer | |
| 92 98 | 
             
                  arguments[:aggregator].each { |agg| controller.add_aggregator(agg.to_sym) }
         | 
| 93 99 |  | 
| 94 100 | 
             
                  # register the database 
         | 
| 95 | 
            -
                  controller.add_aggregator(: | 
| 96 | 
            -
                  controller.add_aggregator(: | 
| 97 | 
            -
             | 
| 101 | 
            +
                  controller.add_aggregator(:summarizer)          if arguments[:aggregator].empty?
         | 
| 102 | 
            +
                  controller.add_aggregator(:database_inserter)   if arguments[:database] && !arguments[:aggregator].include?('database')
         | 
| 103 | 
            +
             | 
| 98 104 | 
             
                  # register the echo aggregator in debug mode
         | 
| 99 105 | 
             
                  controller.add_aggregator(:echo) if arguments[:debug]
         | 
| 100 106 |  | 
| @@ -128,6 +134,8 @@ module RequestLogAnalyzer | |
| 128 134 |  | 
| 129 135 | 
             
                  # Handle progress messagess
         | 
| 130 136 | 
             
                  @source.progress = lambda { |message, value| handle_progress(message, value) } if @source
         | 
| 137 | 
            +
                  
         | 
| 138 | 
            +
                  @source.source_changes = lambda { |change, filename| handle_source_change(change, filename) } if @source
         | 
| 131 139 | 
             
                end
         | 
| 132 140 |  | 
| 133 141 | 
             
                # Progress function.
         | 
| @@ -151,6 +159,11 @@ module RequestLogAnalyzer | |
| 151 159 | 
             
                  end
         | 
| 152 160 | 
             
                end
         | 
| 153 161 |  | 
| 162 | 
            +
                # Source change handler
         | 
| 163 | 
            +
                def handle_source_change(change, filename)
         | 
| 164 | 
            +
                  @aggregators.each { |agg| agg.source_change(change, File.expand_path(filename, Dir.pwd)) }
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
                
         | 
| 154 167 | 
             
                # Adds an aggregator to the controller. The aggregator will be called for every request 
         | 
| 155 168 | 
             
                # that is parsed from the provided sources (see add_source)
         | 
| 156 169 | 
             
                def add_aggregator(agg)      
         | 
| @@ -180,8 +193,9 @@ module RequestLogAnalyzer | |
| 180 193 | 
             
                # Push a request to all the aggregators (@aggregators).
         | 
| 181 194 | 
             
                # <tt>request</tt> The request to push to the aggregators.    
         | 
| 182 195 | 
             
                def aggregate_request(request)
         | 
| 183 | 
            -
                  return unless request
         | 
| 196 | 
            +
                  return false unless request
         | 
| 184 197 | 
             
                  @aggregators.each { |agg| agg.aggregate(request) }
         | 
| 198 | 
            +
                  return true
         | 
| 185 199 | 
             
                end
         | 
| 186 200 |  | 
| 187 201 | 
             
                # Runs RequestLogAnalyzer
         | 
| @@ -198,8 +212,8 @@ module RequestLogAnalyzer | |
| 198 212 | 
             
                  install_signal_handlers
         | 
| 199 213 |  | 
| 200 214 | 
             
                  @source.each_request do |request|
         | 
| 201 | 
            -
                    aggregate_request(filter_request(request))
         | 
| 202 215 | 
             
                    break if @interrupted
         | 
| 216 | 
            +
                    aggregate_request(filter_request(request))
         | 
| 203 217 | 
             
                  end
         | 
| 204 218 |  | 
| 205 219 | 
             
                  @aggregators.each { |agg| agg.finalize }
         | 
| @@ -217,6 +231,8 @@ module RequestLogAnalyzer | |
| 217 231 | 
             
                    puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
         | 
| 218 232 | 
             
                    puts "Thanks for using request-log-analyzer!"
         | 
| 219 233 | 
             
                    @output.io.close
         | 
| 234 | 
            +
                  elsif @output.io.kind_of?(RequestLogAnalyzer::Mailer)
         | 
| 235 | 
            +
                    @output.io.mail
         | 
| 220 236 | 
             
                  end
         | 
| 221 237 | 
             
                end
         | 
| 222 238 |  | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              self.abstract_class = true
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def <=>(other)
         | 
| 6 | 
            +
                if (source_id.nil? && other.source_id.nil?) || (source_comparison = source_id <=> other.source_id) == 0
         | 
| 7 | 
            +
                  lineno <=> other.lineno
         | 
| 8 | 
            +
                else
         | 
| 9 | 
            +
                  source_comparison
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def line_type
         | 
| 14 | 
            +
                self.class.name.underscore.gsub(/_line$/, '').to_sym
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              cattr_accessor :database, :line_definition
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def self.subclass_from_line_definition(definition)
         | 
| 20 | 
            +
                klass = Class.new(RequestLogAnalyzer::Database::Base)
         | 
| 21 | 
            +
                klass.set_table_name("#{definition.name}_lines")
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                klass.line_definition = definition
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                # Set relations with requests and sources table
         | 
| 26 | 
            +
                klass.belongs_to :request
         | 
| 27 | 
            +
                klass.belongs_to :source
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                # Serialize complex fields into the database
         | 
| 30 | 
            +
                definition.captures.select { |c| c.has_key?(:provides) }.each do |capture|
         | 
| 31 | 
            +
                  klass.send(:serialize, capture[:name], Hash)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                database.request_class.has_many  "#{definition.name}_lines".to_sym
         | 
| 35 | 
            +
                database.source_class.has_many   "#{definition.name}_lines".to_sym
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                return klass
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def self.subclass_from_table(table)
         | 
| 41 | 
            +
                raise "Table #{table} not found!" unless database.connection.table_exists?(table)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                klass = Class.new(RequestLogAnalyzer::Database::Base)
         | 
| 44 | 
            +
                klass.set_table_name(table)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                if klass.column_names.include?('request_id')
         | 
| 47 | 
            +
                  klass.belongs_to :request
         | 
| 48 | 
            +
                  database.request_class.has_many table.to_sym
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                if klass.column_names.include?('source_id')
         | 
| 52 | 
            +
                  klass.belongs_to :source
         | 
| 53 | 
            +
                  database.source_class.has_many table.to_sym
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                return klass
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
              
         | 
| 59 | 
            +
              def self.drop_table!
         | 
| 60 | 
            +
                database.connection.remove_index(self.table_name, [:source_id])  rescue nil
         | 
| 61 | 
            +
                database.connection.remove_index(self.table_name, [:request_id]) rescue nil
         | 
| 62 | 
            +
                database.connection.drop_table(self.table_name) if database.connection.table_exists?(self.table_name)
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
              
         | 
| 65 | 
            +
              def self.create_table!
         | 
| 66 | 
            +
                raise "No line_definition available to base table schema on!" unless self.line_definition
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                unless table_exists?
         | 
| 69 | 
            +
                  database.connection.create_table(table_name.to_sym) do |t|
         | 
| 70 | 
            +
                  
         | 
| 71 | 
            +
                    # Default fields
         | 
| 72 | 
            +
                    t.column :request_id, :integer
         | 
| 73 | 
            +
                    t.column :source_id,  :integer
         | 
| 74 | 
            +
                    t.column :lineno,     :integer
         | 
| 75 | 
            +
                  
         | 
| 76 | 
            +
                    line_definition.captures.each do |capture|
         | 
| 77 | 
            +
                      # Add a field for every capture
         | 
| 78 | 
            +
                      t.column(capture[:name], column_type(capture[:type]))
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      # If the capture provides other field as well, create columns for them, too
         | 
| 81 | 
            +
                      capture[:provides].each { |field, field_type| t.column(field, column_type(field_type)) } if capture[:provides].kind_of?(Hash)
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
                
         | 
| 86 | 
            +
                # Add indices to table for more speedy querying
         | 
| 87 | 
            +
                database.connection.add_index(self.table_name.to_sym, [:request_id]) # rescue
         | 
| 88 | 
            +
                database.connection.add_index(self.table_name.to_sym, [:source_id])  # rescue
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
              
         | 
| 91 | 
            +
              
         | 
| 92 | 
            +
              # Function to determine the column type for a field
         | 
| 93 | 
            +
              # TODO: make more robust / include in file-format definition
         | 
| 94 | 
            +
              def self.column_type(type_indicator)
         | 
| 95 | 
            +
                case type_indicator
         | 
| 96 | 
            +
                when :eval;      :text
         | 
| 97 | 
            +
                when :hash;      :text
         | 
| 98 | 
            +
                when :text;      :text
         | 
| 99 | 
            +
                when :string;    :string
         | 
| 100 | 
            +
                when :sec;       :double
         | 
| 101 | 
            +
                when :msec;      :double
         | 
| 102 | 
            +
                when :duration;  :double
         | 
| 103 | 
            +
                when :float;     :double
         | 
| 104 | 
            +
                when :double;    :double
         | 
| 105 | 
            +
                when :integer;   :integer
         | 
| 106 | 
            +
                when :int;       :int
         | 
| 107 | 
            +
                when :timestamp; :datetime
         | 
| 108 | 
            +
                when :datetime;  :datetime
         | 
| 109 | 
            +
                when :date;      :date
         | 
| 110 | 
            +
                else             :string
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end  
         | 
| 113 | 
            +
              
         | 
| 114 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module RequestLogAnalyzer::Database::Connection
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              def self.from_string(string)
         | 
| 4 | 
            +
                hash = {}
         | 
| 5 | 
            +
                if string =~ /^(?:\w+=(?:[^;])*;)*\w+=(?:[^;])*$/
         | 
| 6 | 
            +
                  string.scan(/(\w+)=([^;]*);?/) { |variable, value| hash[variable.to_sym] = value }
         | 
| 7 | 
            +
                elsif string =~ /^(\w+)\:\/\/(?:(?:([^:]+)(?:\:([^:]+))?\@)?([\w\.-]+)\/)?([\w\:\-\.\/]+)$/
         | 
| 8 | 
            +
                  hash[:adapter], hash[:username], hash[:password], hash[:host], hash[:database] = $1, $2, $3, $4, $5
         | 
| 9 | 
            +
                  hash.delete_if { |k, v| v.nil? }
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                return hash.empty? ? nil : hash
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def connect(connection_identifier)
         | 
| 15 | 
            +
                if connection_identifier.kind_of?(Hash)
         | 
| 16 | 
            +
                  RequestLogAnalyzer::Database::Base.establish_connection(connection_identifier)
         | 
| 17 | 
            +
                elsif connection_identifier == ':memory:'
         | 
| 18 | 
            +
                  RequestLogAnalyzer::Database::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
         | 
| 19 | 
            +
                elsif connection_hash = RequestLogAnalyzer::Database::Connection.from_string(connection_identifier)
         | 
| 20 | 
            +
                  RequestLogAnalyzer::Database::Base.establish_connection(connection_hash)
         | 
| 21 | 
            +
                elsif connection_identifier.kind_of?(String) # Normal SQLite 3 database file
         | 
| 22 | 
            +
                  RequestLogAnalyzer::Database::Base.establish_connection(:adapter => 'sqlite3', :database => connection_identifier)
         | 
| 23 | 
            +
                elsif connection_identifier.nil?
         | 
| 24 | 
            +
                  nil
         | 
| 25 | 
            +
                else
         | 
| 26 | 
            +
                  raise "Cannot connect with this connection_identifier: #{connection_identifier.inspect}"
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
              
         | 
| 30 | 
            +
              def disconnect
         | 
| 31 | 
            +
                RequestLogAnalyzer::Database::Base.remove_connection
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
              
         | 
| 34 | 
            +
              def connection
         | 
| 35 | 
            +
                RequestLogAnalyzer::Database::Base.connection
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
              
         | 
| 38 | 
            +
            end
         |