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