module RequestLogAnalyzer::Aggregator # The database aggregator will create an SQLite3 database with all parsed request information. # # The prepare method will create a database schema according to the file format definitions. # It will also create ActiveRecord::Base subclasses to interact with the created tables. # Then, the aggregate method will be called for every parsed request. The information of # these requests is inserted into the tables using the ActiveRecord classes. # # A requests table will be created, in which a record is inserted for every parsed request. # For every line type, a separate table will be created with a request_id field to point to # the request record, and a field for every parsed value. Finally, a warnings table will be # created to log all parse warnings. class DatabaseInserter < Base attr_reader :request_count, :sources, :database # Establishes a connection to the database and creates the necessary database schema for the # current file format def prepare @sources = {} @database = RequestLogAnalyzer::Database.new(options[:database]) @database.file_format = source.file_format database.drop_database_schema! if options[:reset_database] database.create_database_schema! end # Aggregates a request into the database # This will create a record in the requests table and create a record for every line that has been parsed, # in which the captured values will be stored. def aggregate(request) @request_object = RequestLogAnalyzer::Database::Request.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno) request.lines.each do |line| class_columns = database.get_class(line[:line_type]).column_names.reject { |column| ['id', 'source_id', 'request_id'].include?(column) } attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten] attributes[:source_id] = @sources[line[:source]].id if @sources[line[:source]] @request_object.send("#{line[:line_type]}_lines").build(attributes) end @request_object.save! rescue SQLite3::SQLException => e raise Interrupt, e.message end # Finalizes the aggregator by closing the connection to the database def finalize @request_count = RequestLogAnalyzer::Database::Request.count database.disconnect database.remove_orm_classes! end # Records w warining in the warnings table. def warning(type, message, lineno) RequestLogAnalyzer::Database::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno) end # Records source changes in the sources table def source_change(change, filename) if File.exist?(filename) case change when :started @sources[filename] = RequestLogAnalyzer::Database::Source.create!(:filename => filename) when :finished @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename)) end end end # Prints a short report of what has been inserted into the database def report(output) output.title('Request database created') output << "A database file has been created with all parsed request information.\n" output << "#{@request_count} requests have been added to the database.\n" output << "\n" output << "To open a Ruby console to inspect the database, run the following command.\n" output << output.colorize(" $ request-log-analyzer console -d #{options[:database]}\n", :bold) output << "\n" end end end