| | |
| | | logFile *os.File |
| | | ) |
| | | |
| | | const ( |
| | | maxLogBytes = 10 * 1024 * 1024 |
| | | maxLogBackups = 3 |
| | | ) |
| | | |
| | | // Init initializes the file logger in the persistent app data directory. |
| | | func Init() { |
| | | mu.Lock() |
| | | if logFile != nil { |
| | | logFile.Close() |
| | | logFile = nil |
| | | } |
| | | mu.Unlock() |
| | | |
| | | dir := paths.AppSupportDir() |
| | | if err := os.MkdirAll(dir, 0755); err != nil { |
| | | fmt.Fprintf(os.Stderr, "failed to create log dir: %v\n", err) |
| | | return |
| | | } |
| | | path := filepath.Join(dir, "app.log") |
| | | if err := rotateIfNeeded(path); err != nil { |
| | | fmt.Fprintf(os.Stderr, "failed to rotate log file: %v\n", err) |
| | | } |
| | | |
| | | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) |
| | | if err != nil { |
| | | fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err) |
| | | return |
| | | } |
| | | mu.Lock() |
| | | defer mu.Unlock() |
| | | logFile = f |
| | | } |
| | | |
| | | func rotateIfNeeded(path string) error { |
| | | info, err := os.Stat(path) |
| | | if err != nil { |
| | | if os.IsNotExist(err) { |
| | | return nil |
| | | } |
| | | return err |
| | | } |
| | | if info.Size() < maxLogBytes { |
| | | return nil |
| | | } |
| | | |
| | | oldest := fmt.Sprintf("%s.%d", path, maxLogBackups) |
| | | if err := os.Remove(oldest); err != nil && !os.IsNotExist(err) { |
| | | return err |
| | | } |
| | | for i := maxLogBackups - 1; i >= 1; i-- { |
| | | from := fmt.Sprintf("%s.%d", path, i) |
| | | to := fmt.Sprintf("%s.%d", path, i+1) |
| | | if err := os.Rename(from, to); err != nil && !os.IsNotExist(err) { |
| | | return err |
| | | } |
| | | } |
| | | return os.Rename(path, path+".1") |
| | | } |
| | | |
| | | func write(level, format string, args ...interface{}) { |
| | | msg := fmt.Sprintf(format, args...) |
| | | ts := time.Now().Format("2006-01-02 15:04:05") |