/
rotatewatcher.go
148 lines (126 loc) · 3.76 KB
/
rotatewatcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package rotatablesink
import (
"code.cloudfoundry.org/lager"
"io"
"os"
"time"
"sync"
"fmt"
"syscall"
)
type RotatableSink struct {
fileToWatch string
fileToWatchInode uint64
minLogLevel lager.LogLevel
WriterFactory FileWriterFactory
writerSink lager.Sink
writeL *sync.Mutex
DestinationFileInfo DestinationFileInfo
}
func (rs *RotatableSink) Log(logFmt lager.LogFormat) {
rs.writeL.Lock()
defer rs.writeL.Unlock()
rs.writerSink.Log(logFmt)
}
func NewRotatableSink(fileToWatch string, logLevel lager.LogLevel, fileWriterFactory FileWriterFactory, destinationFileInfo DestinationFileInfo, componentLogger lager.Logger) (*RotatableSink, error) {
var err error
rotatableSink := &RotatableSink{
fileToWatch: fileToWatch,
minLogLevel: logLevel,
WriterFactory: fileWriterFactory,
DestinationFileInfo: destinationFileInfo,
writeL: new(sync.Mutex),
}
err = rotatableSink.registerFileSink(fileToWatch)
if err != nil {
return nil, fmt.Errorf("register file sink: %s", err)
}
go func() {
for {
select {
case <-time.After(1 * time.Second):
fileExists, err := destinationFileInfo.FileExists(fileToWatch)
if err != nil {
componentLogger.Error("stat-file", fmt.Errorf("stat file: %s", err))
continue
}
if !fileExists {
err = rotatableSink.registerFileSink(fileToWatch)
if err != nil {
componentLogger.Error("register-moved-file-sink", err)
}
} else {
fileToWatchStatInode, err := destinationFileInfo.FileInode(fileToWatch)
if err != nil {
componentLogger.Error("register-rotated-file-sink", err)
continue
}
if fileToWatchStatInode != rotatableSink.fileToWatchInode {
err = rotatableSink.registerFileSink(fileToWatch)
if err != nil {
componentLogger.Error("register-rotated-file-sink", err)
}
}
}
}
}
}()
return rotatableSink, nil
}
func (rs *RotatableSink) registerFileSink(fileToWatch string) error {
var err error
err = rs.rotateFileSink()
if err != nil {
return fmt.Errorf("rotate file sink: %s", err)
}
rs.fileToWatchInode, err = rs.DestinationFileInfo.FileInode(fileToWatch)
if err != nil {
return fmt.Errorf("get file inode: %s", err)
}
return nil
}
func (rs *RotatableSink) rotateFileSink() error {
rs.writeL.Lock()
defer rs.writeL.Unlock()
outputLogFile, err := rs.WriterFactory.NewWriter(rs.fileToWatch)
if err != nil {
return fmt.Errorf("create file writer: %s", err)
}
rs.writerSink = lager.NewWriterSink(outputLogFile, rs.minLogLevel)
return nil
}
type FileWriterFactory interface {
NewWriter(fileName string) (io.Writer, error)
}
type DefaultFileWriterFunc func(string) (io.Writer, error)
func (dfwf DefaultFileWriterFunc) NewWriter(fileName string) (io.Writer, error) {
return dfwf(fileName)
}
func DefaultFileWriter(fileName string) (io.Writer, error) {
return os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
}
//go:generate counterfeiter -o ../fakes/destinationfileinfo.go --fake-name DestinationFileInfo . DestinationFileInfo
type DestinationFileInfo interface {
FileExists(string) (bool, error)
FileInode(string) (uint64, error)
}
type DefaultDestinationFileInfo struct{}
func (DefaultDestinationFileInfo) FileExists(filename string) (bool, error) {
_, err := os.Stat(filename)
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, fmt.Errorf("stat file: %s", err)
}
return true, nil
}
func (DefaultDestinationFileInfo) FileInode(filename string) (uint64, error) {
fileInfo, err := os.Stat(filename)
if err != nil {
return 0, fmt.Errorf("stat file: %s", err)
}
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
return stat.Ino, err
}
return 0, fmt.Errorf("unable to stat file: %s", err)
}