1package reopen 2 3import ( 4 "bufio" 5 "io" 6 "io/ioutil" 7 "os" 8 "sync" 9 "time" 10) 11 12// Reopener interface defines something that can be reopened 13type Reopener interface { 14 Reopen() error 15} 16 17// Writer is a writer that also can be reopened 18type Writer interface { 19 Reopener 20 io.Writer 21} 22 23// WriteCloser is a io.WriteCloser that can also be reopened 24type WriteCloser interface { 25 Reopener 26 io.WriteCloser 27} 28 29// FileWriter that can also be reopened 30type FileWriter struct { 31 mu sync.Mutex // ensures close / reopen / write are not called at the same time, protects f 32 f *os.File 33 mode os.FileMode 34 name string 35} 36 37// Close calls the underlyding File.Close() 38func (f *FileWriter) Close() error { 39 f.mu.Lock() 40 err := f.f.Close() 41 f.mu.Unlock() 42 return err 43} 44 45// mutex free version 46func (f *FileWriter) reopen() error { 47 if f.f != nil { 48 f.f.Close() 49 f.f = nil 50 } 51 newf, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode) 52 if err != nil { 53 f.f = nil 54 return err 55 } 56 f.f = newf 57 58 return nil 59} 60 61// Reopen the file 62func (f *FileWriter) Reopen() error { 63 f.mu.Lock() 64 err := f.reopen() 65 f.mu.Unlock() 66 return err 67} 68 69// Write implements the stander io.Writer interface 70func (f *FileWriter) Write(p []byte) (int, error) { 71 f.mu.Lock() 72 n, err := f.f.Write(p) 73 f.mu.Unlock() 74 return n, err 75} 76 77// NewFileWriter opens a file for appending and writing and can be reopened. 78// it is a ReopenWriteCloser... 79func NewFileWriter(name string) (*FileWriter, error) { 80 // Standard default mode 81 return NewFileWriterMode(name, 0666) 82} 83 84// NewFileWriterMode opens a Reopener file with a specific permission 85func NewFileWriterMode(name string, mode os.FileMode) (*FileWriter, error) { 86 writer := FileWriter{ 87 f: nil, 88 name: name, 89 mode: mode, 90 } 91 err := writer.reopen() 92 if err != nil { 93 return nil, err 94 } 95 return &writer, nil 96} 97 98// BufferedFileWriter is buffer writer than can be reopned 99type BufferedFileWriter struct { 100 mu sync.Mutex 101 quitChan chan bool 102 done bool 103 origWriter *FileWriter 104 bufWriter *bufio.Writer 105} 106 107// Reopen implement Reopener 108func (bw *BufferedFileWriter) Reopen() error { 109 bw.mu.Lock() 110 bw.bufWriter.Flush() 111 112 // use non-mutex version since we are using this one 113 err := bw.origWriter.reopen() 114 115 bw.bufWriter.Reset(io.Writer(bw.origWriter)) 116 bw.mu.Unlock() 117 118 return err 119} 120 121// Close flushes the internal buffer and closes the destination file 122func (bw *BufferedFileWriter) Close() error { 123 bw.quitChan <- true 124 bw.mu.Lock() 125 bw.done = true 126 bw.bufWriter.Flush() 127 bw.origWriter.f.Close() 128 bw.mu.Unlock() 129 return nil 130} 131 132// Write implements io.Writer (and reopen.Writer) 133func (bw *BufferedFileWriter) Write(p []byte) (int, error) { 134 bw.mu.Lock() 135 n, err := bw.bufWriter.Write(p) 136 137 // Special Case... if the used space in the buffer is LESS than 138 // the input, then we did a flush in the middle of the line 139 // and the full log line was not sent on its way. 140 if bw.bufWriter.Buffered() < len(p) { 141 bw.bufWriter.Flush() 142 } 143 144 bw.mu.Unlock() 145 return n, err 146} 147 148// Flush flushes the buffer. 149func (bw *BufferedFileWriter) Flush() { 150 bw.mu.Lock() 151 // could add check if bw.done already 152 // should never happen 153 bw.bufWriter.Flush() 154 bw.origWriter.f.Sync() 155 bw.mu.Unlock() 156} 157 158// flushDaemon periodically flushes the log file buffers. 159func (bw *BufferedFileWriter) flushDaemon(interval time.Duration) { 160 ticker := time.NewTicker(interval) 161 for { 162 select { 163 case <-bw.quitChan: 164 ticker.Stop() 165 return 166 case <-ticker.C: 167 bw.Flush() 168 } 169 } 170} 171 172const bufferSize = 256 * 1024 173const flushInterval = 30 * time.Second 174 175// NewBufferedFileWriter opens a buffered file that is periodically 176// flushed. 177func NewBufferedFileWriter(w *FileWriter) *BufferedFileWriter { 178 return NewBufferedFileWriterSize(w, bufferSize, flushInterval) 179} 180 181// NewBufferedFileWriterSize opens a buffered file with the given size that is periodically 182// flushed on the given interval. 183func NewBufferedFileWriterSize(w *FileWriter, size int, flush time.Duration) *BufferedFileWriter { 184 bw := BufferedFileWriter{ 185 quitChan: make(chan bool, 1), 186 origWriter: w, 187 bufWriter: bufio.NewWriterSize(w, size), 188 } 189 go bw.flushDaemon(flush) 190 return &bw 191} 192 193type multiReopenWriter struct { 194 writers []Writer 195} 196 197// Reopen reopens all child Reopeners 198func (t *multiReopenWriter) Reopen() error { 199 for _, w := range t.writers { 200 err := w.Reopen() 201 if err != nil { 202 return err 203 } 204 } 205 return nil 206} 207 208// Write implements standard io.Write and reopen.Write 209func (t *multiReopenWriter) Write(p []byte) (int, error) { 210 for _, w := range t.writers { 211 n, err := w.Write(p) 212 if err != nil { 213 return n, err 214 } 215 if n != len(p) { 216 return n, io.ErrShortWrite 217 } 218 } 219 return len(p), nil 220} 221 222// MultiWriter creates a writer that duplicates its writes to all the 223// provided writers, similar to the Unix tee(1) command. 224// Also allow reopen 225func MultiWriter(writers ...Writer) Writer { 226 w := make([]Writer, len(writers)) 227 copy(w, writers) 228 return &multiReopenWriter{w} 229} 230 231type nopReopenWriteCloser struct { 232 io.Writer 233} 234 235func (nopReopenWriteCloser) Reopen() error { 236 return nil 237} 238 239func (nopReopenWriteCloser) Close() error { 240 return nil 241} 242 243// NopWriter turns a normal writer into a ReopenWriter 244// by doing a NOP on Reopen. See https://en.wikipedia.org/wiki/NOP 245func NopWriter(w io.Writer) WriteCloser { 246 return nopReopenWriteCloser{w} 247} 248 249// Reopenable versions of os.Stdout, os.Stderr, /dev/null (reopen does nothing) 250var ( 251 Stdout = NopWriter(os.Stdout) 252 Stderr = NopWriter(os.Stderr) 253 Discard = NopWriter(ioutil.Discard) 254) 255