diff --git a/command/execute_command.go b/command/execute_command.go index b667fb1c..f5055f39 100644 --- a/command/execute_command.go +++ b/command/execute_command.go @@ -4,7 +4,7 @@ import ( "github.com/goraft/raft" "github.com/otoolep/rqlite/db" - log "code.google.com/p/log4go" + "github.com/otoolep/rqlite/log" ) // This command encapsulates a sqlite statement. @@ -26,7 +26,7 @@ func (c *ExecuteCommand) CommandName() string { // Apply executes an sqlite statement. func (c *ExecuteCommand) Apply(server raft.Server) (interface{}, error) { - log.Trace("Applying ExecuteCommand: '%s'", c.Stmt) + log.Tracef("Applying ExecuteCommand: '%s'", c.Stmt) db := server.Context().(*db.DB) return nil, db.Execute(c.Stmt) } @@ -53,7 +53,7 @@ func (c *TransactionExecuteCommandSet) CommandName() string { // Apply executes a set of sqlite statements, within a transaction. All statements // will take effect, or none. func (c *TransactionExecuteCommandSet) Apply(server raft.Server) (interface{}, error) { - log.Trace("Applying TransactionExecuteCommandSet of size %d", len(c.Stmts)) + log.Tracef("Applying TransactionExecuteCommandSet of size %d", len(c.Stmts)) commitSuccess := false db := server.Context().(*db.DB) @@ -61,26 +61,26 @@ func (c *TransactionExecuteCommandSet) Apply(server raft.Server) (interface{}, e if !commitSuccess { err := db.RollbackTransaction() if err != nil { - log.Error("Failed to rollback transaction: %s", err.Error) + log.Errorf("Failed to rollback transaction: %s", err.Error()) } } }() err := db.StartTransaction() if err != nil { - log.Error("Failed to start transaction:", err.Error()) + log.Errorf("Failed to start transaction: %s", err.Error()) return nil, err } for i := range c.Stmts { err = db.Execute(c.Stmts[i]) if err != nil { - log.Error("Failed to execute statement within transaction", err.Error()) + log.Errorf("Failed to execute statement within transaction: %s", err.Error()) return nil, err } } err = db.CommitTransaction() if err != nil { - log.Error("Failed to commit transaction:", err.Error()) + log.Errorf("Failed to commit transaction: %s", err.Error()) return nil, err } else { commitSuccess = true diff --git a/db/db.go b/db/db.go index 49019668..8662ae6c 100644 --- a/db/db.go +++ b/db/db.go @@ -8,7 +8,7 @@ import ( _ "github.com/mattn/go-sqlite3" - log "code.google.com/p/log4go" + "github.com/otoolep/rqlite/log" ) const ( @@ -26,17 +26,17 @@ type RowResults []map[string]string // New creates a new database. Deletes any existing database. func New(dbPath string) *DB { - log.Trace("Removing any existing SQLite database at %s", dbPath) + log.Tracef("Removing any existing SQLite database at %s", dbPath) os.Remove(dbPath) return Open(dbPath) } // Open an existing database, creating it if it does not exist. func Open(dbPath string) *DB { - log.Trace("Opening SQLite database path at %s", dbPath) + log.Tracef("Opening SQLite database path at %s", dbPath) dbc, err := sql.Open("sqlite3", dbPath) if err != nil { - log.Error(err) + log.Error(err.Error()) return nil } return &DB{ @@ -53,11 +53,11 @@ func (db *DB) Close() error { // RowResults. func (db *DB) Query(query string) (RowResults, error) { if !strings.HasPrefix(strings.ToUpper(query), "SELECT ") { - log.Warn("Query \"%s\" may modify the database", query) + log.Warnf("Query \"%s\" may modify the database", query) } rows, err := db.dbConn.Query(query) if err != nil { - log.Error("failed to execute SQLite query", err.Error()) + log.Errorf("failed to execute SQLite query: %s", err.Error()) return nil, err } defer rows.Close() @@ -74,7 +74,7 @@ func (db *DB) Query(query string) (RowResults, error) { for rows.Next() { err = rows.Scan(dest...) if err != nil { - log.Error("failed to scan SQLite row", err.Error()) + log.Errorf("failed to scan SQLite row: %s", err.Error()) return nil, err } @@ -88,7 +88,7 @@ func (db *DB) Query(query string) (RowResults, error) { } results = append(results, r) } - log.Debug(func() string { return "Executed query successfully: " + query }) + log.Debugf("Executed query successfully: %s", query) return results, nil } @@ -100,7 +100,8 @@ func (db *DB) Execute(stmt string) error { return fmt.Sprintf("Error executing \"%s\", error: %s", stmt, err.Error()) } return fmt.Sprintf("Successfully executed \"%s\"", stmt) - }) + }()) + return err } @@ -112,7 +113,7 @@ func (db *DB) StartTransaction() error { return "Error starting transaction" } return "Successfully started transaction" - }) + }()) return err } @@ -124,7 +125,7 @@ func (db *DB) CommitTransaction() error { return "Error ending transaction" } return "Successfully ended transaction" - }) + }()) return err } @@ -137,6 +138,6 @@ func (db *DB) RollbackTransaction() error { return "Error rolling back transaction" } return "Successfully rolled back transaction" - }) + }()) return err } diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..2067ee37 --- /dev/null +++ b/log/log.go @@ -0,0 +1,103 @@ +package log + +import ( + "io" + "log" +) + +// Log levels +const ( + TRACE = iota // 0 + DEBUG = iota // 1 + INFO = iota // 2 + WARN = iota // 3 + ERROR = iota // 4 +) + +// Level set for logs +var Level = TRACE + +// SetLevel sets the log level +// given a string +func SetLevel(level string) { + switch level { + case "TRACE": + Level = TRACE + case "DEBUG": + Level = DEBUG + case "INFO": + Level = INFO + case "WARN": + Level = WARN + case "ERROR": + Level = ERROR + default: + Level = TRACE + } +} + +// SetOutput set the output destination +// of logs +func SetOutput(w io.Writer) { + log.SetOutput(w) +} + +// Tracef writes a formatted log on TRACE level +func Tracef(format string, v ...interface{}) { + if Level <= TRACE { + log.Printf("[TRACE] "+format, v...) + } +} + +// Trace writes a log on TRACE level +func Trace(s string) { + Tracef(s) +} + +// Debugf writes a formatted log on DEBUG level +func Debugf(format string, v ...interface{}) { + if Level <= DEBUG { + log.Printf("[DEBUG] "+format, v...) + } +} + +// Debug writes a log on DEBUG level +func Debug(s string) { + Debugf(s) +} + +// Infof writes a formatted log on INFO level +func Infof(format string, v ...interface{}) { + if Level <= INFO { + log.Printf("[INFO ] "+format, v...) + } +} + +// Info write a log on INFO level +func Info(s string) { + Infof(s) +} + +// Warnf writes a formatted log on WARN level +func Warnf(format string, v ...interface{}) { + if Level <= WARN { + log.Printf("[WARN ] "+format, v...) + } +} + +// Warn write a log on WARN level +func Warn(s string) { + Warnf(s) +} + +// Errorf writes a formatted log on ERROR level +func Errorf(format string, v ...interface{}) { + if Level <= ERROR { + log.Printf("[ERROR] "+format, v...) + } +} + +// Error write a log on ERROR level +func Error(s string) { + Errorf(s) +} diff --git a/log/log_test.go b/log/log_test.go new file mode 100644 index 00000000..a9587432 --- /dev/null +++ b/log/log_test.go @@ -0,0 +1,131 @@ +package log + +import ( + "bytes" + "io" + "os" + "testing" + + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { + TestingT(t) +} + +type LogSuite struct{} + +var _ = Suite(&LogSuite{}) + +func (s *LogSuite) TestSetLevel(c *C) { + SetLevel("DEBUG") + c.Assert(Level, Equals, DEBUG) + + SetLevel("INFO") + c.Assert(Level, Equals, INFO) + + SetLevel("WARN") + c.Assert(Level, Equals, WARN) + + SetLevel("ERROR") + c.Assert(Level, Equals, ERROR) + + SetLevel("TRACE") + c.Assert(Level, Equals, TRACE) +} + +func (s *LogSuite) TestLog(c *C) { + logFile, err := os.OpenFile("tmptestfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + c.Check(err, IsNil) + defer logFile.Close() + defer os.Remove("tmptestfile.txt") + + SetOutput(logFile) + + num, err := lineCounter(logFile) + c.Check(num, Equals, 0) + c.Check(err, IsNil) + + tmpFile, _ := os.Open("tmptestfile.txt") + Level = ERROR + Trace("a") + Debug("a") + Info("a") + Warn("a") + Error("a") + num, err = lineCounter(tmpFile) + c.Check(num, Equals, 1) + c.Check(err, IsNil) + tmpFile.Close() + + tmpFile, _ = os.Open("tmptestfile.txt") + Level = WARN + Trace("a") + Debug("a") + Info("a") + Warn("a") + Error("a") + num, err = lineCounter(tmpFile) + c.Check(num, Equals, 3) + c.Check(err, IsNil) + tmpFile.Close() + + tmpFile, _ = os.Open("tmptestfile.txt") + Level = INFO + Trace("a") + Debug("a") + Info("a") + Warn("a") + Error("a") + num, err = lineCounter(tmpFile) + c.Check(num, Equals, 6) + c.Check(err, IsNil) + tmpFile.Close() + + tmpFile, _ = os.Open("tmptestfile.txt") + Level = DEBUG + Trace("a") + Debug("a") + Info("a") + Warn("a") + Error("a") + num, err = lineCounter(tmpFile) + c.Check(num, Equals, 10) + c.Check(err, IsNil) + tmpFile.Close() + + tmpFile, _ = os.Open("tmptestfile.txt") + Level = TRACE + Trace("a") + Debug("a") + Info("a") + Warn("a") + Error("a") + num, err = lineCounter(tmpFile) + c.Check(num, Equals, 15) + c.Check(err, IsNil) + tmpFile.Close() +} + +// Taken from http://stackoverflow.com/a/24563853/1187471 +func lineCounter(r io.Reader) (int, error) { + buf := make([]byte, 8196) + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := r.Read(buf) + if err != nil && err != io.EOF { + return count, err + } + + count += bytes.Count(buf[:c], lineSep) + + if err == io.EOF { + break + } + } + + return count, nil +} diff --git a/main.go b/main.go index aca8d4b2..8dd9b453 100644 --- a/main.go +++ b/main.go @@ -16,14 +16,15 @@ import ( "net/http" "os" "os/signal" + "os/user" "path/filepath" "runtime" "runtime/pprof" + "strings" "time" + "github.com/otoolep/rqlite/log" "github.com/otoolep/rqlite/server" - - log "code.google.com/p/log4go" ) var host string @@ -52,44 +53,6 @@ func init() { } } -func setupLogging(loggingLevel, logFile string) { - level := log.DEBUG - switch loggingLevel { - case "TRACE": - level = log.TRACE - case "DEBUG": - level = log.DEBUG - case "INFO": - level = log.INFO - case "WARN": - level = log.WARNING - case "ERROR": - level = log.ERROR - } - - log.Global = make(map[string]*log.Filter) - - if logFile == "stdout" { - flw := log.NewConsoleLogWriter() - log.AddFilter("stdout", level, flw) - - } else { - logFileDir := filepath.Dir(logFile) - os.MkdirAll(logFileDir, 0744) - - flw := log.NewFileLogWriter(logFile, false) - log.AddFilter("file", level, flw) - - flw.SetFormat("[%D %T] [%L] (%S) %M") - flw.SetRotate(true) - flw.SetRotateSize(0) - flw.SetRotateLines(0) - flw.SetRotateDaily(true) - } - - log.Info("Redirectoring logging to %s", logFile) -} - func main() { flag.Parse() @@ -98,14 +61,23 @@ func main() { log.Info("Profiling enabled") f, err := os.Create(cpuprofile) if err != nil { - log.Error("Unable to create path: %s", err.Error()) + log.Errorf("Unable to create path: %s", err.Error()) } - defer f.Close() + defer closeFile(f) pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } - setupLogging(logLevel, logFile) + + // Set logging + log.SetLevel(logLevel) + if logFile != "stdout" { + f := createFile(logFile) + defer closeFile(f) + + log.Infof("Redirecting logging to %s", logFile) + log.SetOutput(f) + } // Set the data directory. if flag.NArg() == 0 { @@ -116,12 +88,12 @@ func main() { } path := flag.Arg(0) if err := os.MkdirAll(path, 0744); err != nil { - log.Error("Unable to create path: %s", err.Error()) + log.Errorf("Unable to create path: %s", err.Error()) } s := server.NewServer(path, dbfile, snapAfter, host, port) go func() { - log.Error(s.ListenAndServe(join)) + log.Error(s.ListenAndServe(join).Error()) }() if !disableReporting { @@ -134,6 +106,36 @@ func main() { log.Info("rqlite server stopped") } +func closeFile(f *os.File) { + if err := f.Close(); err != nil { + log.Errorf("Unable to close file: %s", err.Error()) + os.Exit(1) + } +} + +func createFile(path string) *os.File { + usr, _ := user.Current() + dir := usr.HomeDir + + // Check in case of paths like "/something/~/something/" + if path[:2] == "~/" { + path = strings.Replace(path, "~/", dir+"/", 1) + } + + if err := os.MkdirAll(filepath.Dir(path), 0744); err != nil { + log.Errorf("Unable to create path: %s", err.Error()) + os.Exit(1) + } + + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Errorf("Unable to open file: %s", err.Error()) + os.Exit(1) + } + + return f +} + func reportLaunch() { json := fmt.Sprintf(`{"os": "%s", "arch": "%s", "app": "rqlite"}`, runtime.GOOS, runtime.GOARCH) data := bytes.NewBufferString(json) diff --git a/server/server.go b/server/server.go index 4e4c23a8..9543e1e5 100644 --- a/server/server.go +++ b/server/server.go @@ -20,9 +20,8 @@ import ( "github.com/otoolep/rqlite/command" "github.com/otoolep/rqlite/db" "github.com/otoolep/rqlite/interfaces" + "github.com/otoolep/rqlite/log" "github.com/rcrowley/go-metrics" - - log "code.google.com/p/log4go" ) type FailedSqlStmt struct { @@ -213,9 +212,9 @@ func (s *Server) connectionString() string { func (s *Server) logSnapshot(err error, currentIndex, count uint64) { info := fmt.Sprintf("%s: snapshot of %d events at index %d", s.connectionString, count, currentIndex) if err != nil { - log.Info("%s attempted and failed: %v", info, err) + log.Infof("%s attempted and failed: %v", info, err) } else { - log.Info("%s completed", info) + log.Infof("%s completed", info) } } @@ -223,21 +222,21 @@ func (s *Server) logSnapshot(err error, currentIndex, count uint64) { func (s *Server) ListenAndServe(leader string) error { var err error - log.Info("Initializing Raft Server: %s", s.path) + log.Infof("Initializing Raft Server: %s", s.path) // Initialize and start Raft server. transporter := raft.NewHTTPTransporter("/raft", 200*time.Millisecond) stateMachine := NewDbStateMachine(s.dbPath) s.raftServer, err = raft.NewServer(s.name, s.path, transporter, stateMachine, s.db, "") if err != nil { - log.Error("Failed to create new Raft server", err.Error()) + log.Errorf("Failed to create new Raft server: %s", err.Error()) return err } log.Info("Loading latest snapshot, if any, from disk") err = s.raftServer.LoadSnapshot() if err != nil { - log.Error("Error loading snapshot: %s", err.Error()) + log.Errorf("Error loading snapshot: %s", err.Error()) } transporter.Install(s.raftServer, s) @@ -246,14 +245,14 @@ func (s *Server) ListenAndServe(leader string) error { if leader != "" { // Join to leader if specified. - log.Info("Attempting to join leader at %s", leader) + log.Infof("Attempting to join leader at %s", leader) if !s.raftServer.IsLogEmpty() { log.Error("Cannot join with an existing log") return errors.New("Cannot join with an existing log") } if err := s.Join(leader); err != nil { - log.Error("Failed to join leader", err.Error()) + log.Errorf("Failed to join leader: %s", err.Error()) return err } @@ -267,7 +266,7 @@ func (s *Server) ListenAndServe(leader string) error { ConnectionString: s.connectionString(), }) if err != nil { - log.Error("Failed to join to self", err.Error()) + log.Errorf("Failed to join to self: %", err.Error()) } } else { @@ -289,7 +288,7 @@ func (s *Server) ListenAndServe(leader string) error { s.router.HandleFunc("/db", s.writeHandler).Methods("POST") s.router.HandleFunc("/join", s.joinHandler).Methods("POST") - log.Info("Listening at %s", s.connectionString()) + log.Infof("Listening at %s", s.connectionString()) return s.httpServer.ListenAndServe() } @@ -325,7 +324,7 @@ func (s *Server) Join(leader string) error { if err != nil { return errors.New("Failed to parse redirect location") } - log.Info("Redirecting to leader at %s", u.Host) + log.Infof("Redirecting to leader at %s", u.Host) return s.Join(u.Host) } @@ -357,14 +356,14 @@ func (s *Server) joinHandler(w http.ResponseWriter, req *http.Request) { } func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) { - log.Trace("readHandler for URL: %s", req.URL) + log.Tracef("readHandler for URL: %s", req.URL) s.metrics.queryReceived.Inc(1) var failures = make([]FailedSqlStmt, 0) b, err := ioutil.ReadAll(req.Body) if err != nil { - log.Trace("Bad HTTP request", err.Error()) + log.Tracef("Bad HTTP request: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) s.metrics.queryFail.Inc(1) return @@ -374,7 +373,7 @@ func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) { startTime := time.Now() r, err := s.db.Query(stmt) if err != nil { - log.Trace("Bad SQL statement", err.Error()) + log.Tracef("Bad SQL statement: %s", err.Error()) s.metrics.queryFail.Inc(1) failures = append(failures, FailedSqlStmt{stmt, err.Error()}) } else { @@ -390,7 +389,7 @@ func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) { b, err = json.Marshal(rr) } if err != nil { - log.Trace("Failed to marshal JSON data", err.Error()) + log.Tracef("Failed to marshal JSON data: %s", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) // Internal error actually } else { w.Write([]byte(b)) @@ -406,7 +405,7 @@ func (s *Server) execute(tx bool, stmts []string) ([]FailedSqlStmt, error) { _, err := s.raftServer.Do(command.NewTransactionExecuteCommandSet(stmts)) if err != nil { - log.Trace("Transaction failed: %s", err.Error()) + log.Tracef("Transaction failed: %s", err.Error()) s.metrics.executeFail.Inc(1) failures = append(failures, FailedSqlStmt{stmts[0], err.Error()}) } else { @@ -417,7 +416,7 @@ func (s *Server) execute(tx bool, stmts []string) ([]FailedSqlStmt, error) { for i := range stmts { _, err := s.raftServer.Do(command.NewExecuteCommand(stmts[i])) if err != nil { - log.Trace("Execute statement %s failed: %s", stmts[i], err.Error()) + log.Tracef("Execute statement %s failed: %s", stmts[i], err.Error()) s.metrics.executeFail.Inc(1) failures = append(failures, FailedSqlStmt{stmts[i], err.Error()}) } else { @@ -439,7 +438,7 @@ func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) { return } - log.Trace("writeHandler for URL: %s", req.URL) + log.Tracef("writeHandler for URL: %s", req.URL) s.metrics.executeReceived.Inc(1) currentIndex := s.raftServer.CommitIndex() @@ -457,7 +456,7 @@ func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) { // Read the value from the POST body. b, err := ioutil.ReadAll(req.Body) if err != nil { - log.Trace("Bad HTTP request", err.Error()) + log.Tracef("Bad HTTP request: %s", err.Error()) s.metrics.executeFail.Inc(1) w.WriteHeader(http.StatusBadRequest) return @@ -467,7 +466,7 @@ func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) { stmts = stmts[:len(stmts)-1] } - log.Trace("Execute statement contains %d commands", len(stmts)) + log.Tracef("Execute statement contains %d commands", len(stmts)) if len(stmts) == 0 { log.Trace("No database execute commands supplied") s.metrics.executeFail.Inc(1) @@ -479,7 +478,7 @@ func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) { startTime = time.Now() failures, err := s.execute(transaction, stmts) if err != nil { - log.Error("Database mutation failed: %s", err.Error()) + log.Errorf("Database mutation failed: %s", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/server/statemachine.go b/server/statemachine.go index 32716a1a..bded3a61 100644 --- a/server/statemachine.go +++ b/server/statemachine.go @@ -4,7 +4,7 @@ import ( "io/ioutil" "os" - log "code.google.com/p/log4go" + "github.com/otoolep/rqlite/log" ) type DbStateMachine struct { @@ -17,7 +17,7 @@ func NewDbStateMachine(path string) *DbStateMachine { d := &DbStateMachine{ dbpath: path, } - log.Trace("New DB state machine created with path: %s", path) + log.Tracef("New DB state machine created with path: %s", path) return d } @@ -27,24 +27,24 @@ func NewDbStateMachine(path string) *DbStateMachine { // http://sqlite.org/howtocorrupt.html states it is safe to do this // as long as no transaction is in progress. func (d *DbStateMachine) Save() ([]byte, error) { - log.Trace("Capturing database state from path: %s", d.dbpath) + log.Tracef("Capturing database state from path: %s", d.dbpath) b, err := ioutil.ReadFile(d.dbpath) if err != nil { - log.Error("Failed to save state: ", err.Error()) + log.Errorf("Failed to save state: %s", err.Error()) return nil, err } - log.Trace("Database state successfully saved to %s", d.dbpath) + log.Tracef("Database state successfully saved to %s", d.dbpath) return b, nil } // Recovery restores the state of the database using the given data. func (d *DbStateMachine) Recovery(b []byte) error { - log.Trace("Restoring database state to path: %s", d.dbpath) + log.Tracef("Restoring database state to path: %s", d.dbpath) err := ioutil.WriteFile(d.dbpath, b, os.ModePerm) if err != nil { - log.Error("Failed to recover state: ", err.Error()) + log.Errorf("Failed to recover state: %s", err.Error()) return err } - log.Trace("Database restored successfully to %s", d.dbpath) + log.Tracef("Database restored successfully to %s", d.dbpath) return nil }