require 'rubygems' require 'sqlite3' module Base # Set of functions that can be used to easily log requests into a SQLite3 Database. class RecordInserter attr_reader :database attr_reader :current_request attr_reader :warning_count # Initializer # db_file The file which will be used for the SQLite3 Database storage. def initialize(db_file, options = {}) @database = SQLite3::Database.new(db_file) @insert_statements = nil @warning_count = 0 create_tables_if_needed! self.initialize_hook(options) if self.respond_to?(:initialize_hook) end # Calculate the database durations of the requests currenty in the database. # Used if a logfile does contain any database durations. def calculate_db_durations! @database.execute('UPDATE "completed_queries" SET "database" = "duration" - "rendering" WHERE "database" IS NULL OR "database" = 0.0') end # Insert a batch of loglines into the database. # Function prepares insert statements, yeilds and then closes and commits. def insert_batch(&block) @database.transaction prepare_statements! block.call(self) close_prepared_statements! @database.commit rescue Exception => e puts e.message @database.rollback end def insert_warning(line, warning) @database.execute("INSERT INTO parse_warnings (line, warning) VALUES (:line, :warning)", :line => line, :warning => warning) @warning_count += 1 end # Insert a request into the database. # def insert(request, close_statements = false) # raise 'No insert defined for this log file type' # end # Insert a batch of files into the database. # db_file The filename of the database file to use. # Returns the created database. def self.insert_batch_into(db_file, options = {}, &block) db = self.new(db_file) db.insert_batch(&block) return db end def count(type) @database.get_first_value("SELECT COUNT(*) FROM \"#{type}_requests\"").to_i end protected # Prepare insert statements. def prepare_statements! @insert_statements = { :started => @database.prepare(" INSERT INTO started_requests ( line, timestamp, ip, method, controller, action) VALUES (:line, :timestamp, :ip, :method, :controller, :action)"), :failed => @database.prepare(" INSERT INTO failed_requests ( line, exception_string, stack_trace, error) VALUES (:line, :exception_string, :stack_trace, :error)"), :completed => @database.prepare(" INSERT INTO completed_requests ( line, url, status, duration, rendering_time, database_time) VALUES (:line, :url, :status, :duration, :rendering, :db)") } end # Close all prepared statments def close_prepared_statements! @insert_statements.each { |key, stmt| stmt.close } end # Create the needed database tables if they don't exist. def create_tables_if_needed! @database.execute(" CREATE TABLE IF NOT EXISTS started_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, line INTEGER NOT NULL, timestamp DATETIME NOT NULL, controller VARCHAR(255) NOT NULL, action VARCHAR(255) NOT NULL, method VARCHAR(6) NOT NULL, ip VARCHAR(6) NOT NULL ) "); @database.execute(" CREATE TABLE IF NOT EXISTS failed_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, line INTEGER NOT NULL, started_request_id INTEGER, error VARCHAR(255), exception_string VARCHAR(255), stack_trace TEXT ) "); @database.execute(" CREATE TABLE IF NOT EXISTS completed_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, line INTEGER NOT NULL, started_request_id INTEGER, url VARCHAR(255) NOT NULL, hashed_url VARCHAR(255), status INTEGER NOT NULL, duration FLOAT, rendering_time FLOAT, database_time FLOAT ) "); @database.execute("CREATE TABLE IF NOT EXISTS parse_warnings ( id INTEGER PRIMARY KEY AUTOINCREMENT, line INTEGER NOT NULL, warning VARCHAR(255) NOT NULL ) "); end end end