package reopen import ( "bufio" "io" "os" "sync" "time" ) // Reopener interface defines something that can be reopened. type Reopener interface { Reopen() error } // Writer is a writer that also can be reopened. type Writer interface { Reopener io.Writer } // WriteCloser is a io.WriteCloser that can also be reopened. type WriteCloser interface { Reopener io.WriteCloser } // FileWriter that can also be reopened. type FileWriter struct { // Ensures close/reopen/write are not called at the same time, protects f mu sync.Mutex f *os.File mode os.FileMode name string } // Close calls the under lying File.Close(). func (f *FileWriter) Close() error { f.mu.Lock() err := f.f.Close() f.mu.Unlock() return err } // Reopen the file. func (f *FileWriter) Reopen() error { f.mu.Lock() err := f.reopen() f.mu.Unlock() return err } // Write implements the stander io.Writer interface. func (f *FileWriter) Write(p []byte) (int, error) { f.mu.Lock() n, err := f.f.Write(p) f.mu.Unlock() return n, err } // reopen with mutex free. func (f *FileWriter) reopen() error { if f.f != nil { f.f.Close() f.f = nil } ff, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode) if err != nil { f.f = nil return err } f.f = ff return nil } // NewFileWriter opens a file for appending and writing and can be reopened. // It is a ReopenWriteCloser... func NewFileWriter(name string) (*FileWriter, error) { // Standard default mode return NewFileWriterMode(name, 0644) } // NewFileWriterMode opens a Reopener file with a specific permission. func NewFileWriterMode(name string, mode os.FileMode) (*FileWriter, error) { writer := FileWriter{ f: nil, name: name, mode: mode, } err := writer.reopen() if err != nil { return nil, err } return &writer, nil } // BufferedFileWriter is buffer writer than can be reopened. type BufferedFileWriter struct { mu sync.Mutex OrigWriter *FileWriter BufWriter *bufio.Writer } // Reopen implement Reopener. func (bw *BufferedFileWriter) Reopen() error { bw.mu.Lock() bw.BufWriter.Flush() // Use non-mutex version since we are using this one. err := bw.OrigWriter.reopen() bw.BufWriter.Reset(io.Writer(bw.OrigWriter)) bw.mu.Unlock() return err } // Close flushes the internal buffer and closes the destination file. func (bw *BufferedFileWriter) Close() error { bw.mu.Lock() bw.BufWriter.Flush() bw.OrigWriter.f.Close() bw.mu.Unlock() return nil } // Write implements io.Writer (and reopen.Writer). func (bw *BufferedFileWriter) Write(p []byte) (int, error) { bw.mu.Lock() n, err := bw.BufWriter.Write(p) // Special Case... if the used space in the buffer is LESS than // the input, then we did a flush in the middle of the line // and the full log line was not sent on its way. if bw.BufWriter.Buffered() < len(p) { bw.BufWriter.Flush() } bw.mu.Unlock() return n, err } // Flush flushes the buffer. func (bw *BufferedFileWriter) Flush() (err error) { bw.mu.Lock() defer bw.mu.Unlock() if err = bw.BufWriter.Flush(); err != nil { return err } if err = bw.OrigWriter.f.Sync(); err != nil { return err } return } // flushDaemon periodically flushes the log file buffers. func (bw *BufferedFileWriter) flushDaemon(interval time.Duration) { for range time.NewTicker(interval).C { bw.Flush() } } // NewBufferedFileWriter opens a buffered file that is periodically flushed. func NewBufferedFileWriter(w *FileWriter) *BufferedFileWriter { return NewBufferedFileWriterSize(w, bufferSize, flushInterval) } // NewBufferedFileWriterSize opens a buffered file with the given size that is periodically // flushed on the given interval. func NewBufferedFileWriterSize(w *FileWriter, size int, flush time.Duration) *BufferedFileWriter { bw := BufferedFileWriter{ OrigWriter: w, BufWriter: bufio.NewWriterSize(w, size), } go bw.flushDaemon(flush) return &bw } const bufferSize = 256 * 1024 const flushInterval = 30 * time.Second