forked from kaspanet/kaspad
-
Notifications
You must be signed in to change notification settings - Fork 7
/
backend.go
191 lines (168 loc) · 5.71 KB
/
backend.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package logger
import (
"fmt"
"github.com/jrick/logrotate/rotator"
"github.com/pkg/errors"
"io"
"os"
"path/filepath"
"runtime/debug"
"strings"
"sync"
"sync/atomic"
)
const normalLogSize = 512
// defaultFlags specifies changes to the default logger behavior. It is set
// during package init and configured using the LOGFLAGS environment variable.
// New logger backends can override these default flags using WithFlags.
// We're using this instead of `init()` function because variables are initialized before init functions,
// and this variable is used inside other variable intializations, so runs before them
var defaultFlags = getDefaultFlags()
// Flags to modify Backend's behavior.
const (
// LogFlagLongFile modifies the logger output to include full path and line number
// of the logging callsite, e.g. /a/b/c/main.go:123.
LogFlagLongFile uint32 = 1 << iota
// LogFlagShortFile modifies the logger output to include filename and line number
// of the logging callsite, e.g. main.go:123. takes precedence over LogFlagLongFile.
LogFlagShortFile
)
// Read logger flags from the LOGFLAGS environment variable. Multiple flags can
// be set at once, separated by commas.
func getDefaultFlags() (flags uint32) {
for _, f := range strings.Split(os.Getenv("LOGFLAGS"), ",") {
switch f {
case "longfile":
flags |= LogFlagLongFile
case "shortfile":
flags |= LogFlagShortFile
}
}
return
}
const logsBuffer = 0
// Backend is a logging backend. Subsystems created from the backend write to
// the backend's Writer. Backend provides atomic writes to the Writer from all
// subsystems.
type Backend struct {
flag uint32
isRunning uint32
writers []logWriter
writeChan chan logEntry
syncClose sync.Mutex // used to sync that the logger finished writing everything
}
// NewBackendWithFlags configures a Backend to use the specified flags rather than using
// the package's defaults as determined through the LOGFLAGS environment
// variable.
func NewBackendWithFlags(flags uint32) *Backend {
return &Backend{flag: flags, writeChan: make(chan logEntry, logsBuffer)}
}
// NewBackend creates a new logger backend.
func NewBackend() *Backend {
return NewBackendWithFlags(defaultFlags)
}
const (
defaultThresholdKB = 100 * 1000 // 100 MB logs by default.
defaultMaxRolls = 8 // keep 8 last logs by default.
)
type logWriter interface {
io.WriteCloser
LogLevel() Level
}
type logWriterWrap struct {
io.WriteCloser
logLevel Level
}
func (lw logWriterWrap) LogLevel() Level {
return lw.logLevel
}
// AddLogFile adds a file which the log will write into on a certain
// log level with the default log rotation settings. It'll create the file if it doesn't exist.
func (b *Backend) AddLogFile(logFile string, logLevel Level) error {
return b.AddLogFileWithCustomRotator(logFile, logLevel, defaultThresholdKB, defaultMaxRolls)
}
// AddLogWriter adds a type implementing io.WriteCloser which the log will write into on a certain
// log level with the default log rotation settings. It'll create the file if it doesn't exist.
func (b *Backend) AddLogWriter(logWriter io.WriteCloser, logLevel Level) error {
if b.IsRunning() {
return errors.New("The logger is already running")
}
b.writers = append(b.writers, logWriterWrap{
WriteCloser: logWriter,
logLevel: logLevel,
})
return nil
}
// AddLogFileWithCustomRotator adds a file which the log will write into on a certain
// log level, with the specified log rotation settings.
// It'll create the file if it doesn't exist.
func (b *Backend) AddLogFileWithCustomRotator(logFile string, logLevel Level, thresholdKB int64, maxRolls int) error {
if b.IsRunning() {
return errors.New("The logger is already running")
}
logDir, _ := filepath.Split(logFile)
// if the logDir is empty then `logFile` is in the cwd and there's no need to create any directory.
if logDir != "" {
err := os.MkdirAll(logDir, 0700)
if err != nil {
return errors.Errorf("failed to create log directory: %+v", err)
}
}
r, err := rotator.New(logFile, thresholdKB, false, maxRolls)
if err != nil {
return errors.Errorf("failed to create file rotator: %s", err)
}
b.writers = append(b.writers, logWriterWrap{
WriteCloser: r,
logLevel: logLevel,
})
return nil
}
// Run launches the logger backend in a separate go-routine. should only be called once.
func (b *Backend) Run() error {
if !atomic.CompareAndSwapUint32(&b.isRunning, 0, 1) {
return errors.New("The logger is already running")
}
go func() {
defer func() {
if err := recover(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Fatal error in logger.Backend goroutine: %+v\n", err)
_, _ = fmt.Fprintf(os.Stderr, "Goroutine stacktrace: %s\n", debug.Stack())
}
}()
b.runBlocking()
}()
return nil
}
func (b *Backend) runBlocking() {
defer atomic.StoreUint32(&b.isRunning, 0)
b.syncClose.Lock()
defer b.syncClose.Unlock()
for log := range b.writeChan {
for _, writer := range b.writers {
if log.level >= writer.LogLevel() {
_, _ = writer.Write(log.log)
}
}
}
}
// IsRunning returns true if backend.Run() has been called and false if it hasn't.
func (b *Backend) IsRunning() bool {
return atomic.LoadUint32(&b.isRunning) != 0
}
// Close finalizes all log rotators for this backend
func (b *Backend) Close() {
close(b.writeChan)
// Wait for it to finish writing using the syncClose mutex.
b.syncClose.Lock()
defer b.syncClose.Unlock()
for _, writer := range b.writers {
_ = writer.Close()
}
}
// Logger returns a new logger for a particular subsystem that writes to the
// Backend b. A tag describes the subsystem and is included in all log
// messages. The logger uses the info verbosity level by default.
func (b *Backend) Logger(subsystemTag string) *Logger {
return &Logger{LevelOff, subsystemTag, b, b.writeChan}
}