182 lines
3.9 KiB
Go
182 lines
3.9 KiB
Go
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
|