/
logger_console.go
226 lines (188 loc) · 6.25 KB
/
logger_console.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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
Copyright 2018-Present Couchbase, Inc.
Use of this software is governed by the Business Source License included in
the file licenses/BSL-Couchbase.txt. As of the Change Date specified in that
file, in accordance with the Business Source License, use of this software will
be governed by the Apache License, Version 2.0, included in the file
licenses/APL2.txt.
*/
package base
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/natefinch/lumberjack"
)
// ConsoleLogger is a file logger with a default output of stderr, and tunable log level/keys.
type ConsoleLogger struct {
FileLogger
LogLevel *LogLevel
LogKeyMask *LogKeyMask
ColorEnabled bool
// isStderr is true when the console logger is configured with no FileOutput
isStderr bool
// ConsoleLoggerConfig stores the initial config used to instantiate ConsoleLogger
config ConsoleLoggerConfig
}
type ConsoleLoggerConfig struct {
FileLoggerConfig
LogLevel *LogLevel `json:"log_level,omitempty"` // Log Level for the console output
LogKeys []string `json:"log_keys,omitempty"` // Log Keys for the console output
ColorEnabled *bool `json:"color_enabled,omitempty"` // Log with color for the console output
// FileOutput can be used to override the default stderr output, and write to the file specified instead.
FileOutput string `json:"file_output,omitempty"`
}
// NewConsoleLogger returns a new ConsoleLogger from a config.
func NewConsoleLogger(shouldLogLocation bool, config *ConsoleLoggerConfig) (*ConsoleLogger, error) {
if config == nil {
config = &ConsoleLoggerConfig{}
}
// validate and set defaults
if err := config.init(); err != nil {
return nil, err
}
logKey := ToLogKey(config.LogKeys)
isStderr := config.FileOutput == "" && *config.Enabled
logger := &ConsoleLogger{
LogLevel: config.LogLevel,
LogKeyMask: &logKey,
ColorEnabled: *config.ColorEnabled && isStderr,
FileLogger: FileLogger{
Enabled: AtomicBool{},
logger: log.New(config.Output, "", 0),
config: config.FileLoggerConfig,
},
isStderr: isStderr,
config: *config,
}
logger.Enabled.Set(*config.Enabled)
// Only create the collateBuffer channel and worker if required.
if *config.CollationBufferSize > 1 {
logger.collateBuffer = make(chan string, *config.CollationBufferSize)
logger.flushChan = make(chan struct{}, 1)
logger.collateBufferWg = &sync.WaitGroup{}
// Start up a single worker to consume messages from the buffer
go logCollationWorker(logger.collateBuffer, logger.flushChan, logger.collateBufferWg, logger.logger, *config.CollationBufferSize, consoleLoggerCollateFlushTimeout)
}
// We can only log the console log location itself when logging has previously been set up and is being re-initialized from a config.
if shouldLogLocation {
if *config.Enabled {
consoleOutput := "stderr"
if config.FileOutput != "" {
consoleOutput = config.FileOutput
}
Consolef(LevelInfo, KeyNone, "Logging: Console to %v", consoleOutput)
} else {
Consolef(LevelInfo, KeyNone, "Logging: Console disabled")
}
}
return logger, nil
}
func (l *ConsoleLogger) logf(format string, args ...interface{}) {
if l.collateBuffer != nil {
l.collateBufferWg.Add(1)
l.collateBuffer <- fmt.Sprintf(format, args...)
} else {
l.logger.Printf(format, args...)
}
}
// shouldLog returns true if the given logLevel and logKey should get logged.
func (l *ConsoleLogger) shouldLog(logLevel LogLevel, logKey LogKey) bool {
if l == nil || l.logger == nil {
return false
}
// Log level disabled
if !l.LogLevel.Enabled(logLevel) {
return false
}
// Log key All should always log at this point, unless KeyNone is set
if logKey == KeyAll && !l.LogKeyMask.Enabled(KeyNone) {
return true
}
// Finally, check the specific log key is enabled
return l.LogKeyMask.Enabled(logKey)
}
func (l *ConsoleLogger) getConsoleLoggerConfig() *ConsoleLoggerConfig {
c := ConsoleLoggerConfig{}
if l != nil {
// Copy config struct to avoid mutating running config
c = l.config
}
c.FileLoggerConfig = *l.getFileLoggerConfig()
c.LogLevel = l.LogLevel
c.LogKeys = l.LogKeyMask.EnabledLogKeys()
return &c
}
// init validates and sets any defaults for the given ConsoleLoggerConfig
func (lcc *ConsoleLoggerConfig) init() error {
if lcc == nil {
return errors.New("nil LogConsoleConfig")
}
if err := lcc.initRotationConfig("console", 0, 0); err != nil {
return err
}
// Default to os.Stderr if alternative output is not set
if lcc.Output == nil && lcc.FileOutput == "" {
lcc.Output = os.Stderr
} else if lcc.FileOutput != "" {
// Otherwise check permissions on the given output and create a Lumberjack logger
if err := validateLogFileOutput(lcc.FileOutput); err != nil {
return err
}
lcc.Output = &lumberjack.Logger{
Filename: filepath.FromSlash(lcc.FileOutput),
MaxSize: *lcc.Rotation.MaxSize,
MaxAge: *lcc.Rotation.MaxAge,
Compress: false,
}
}
// Default to disabled only when a log key or log level has not been specified
if lcc.Enabled == nil {
if lcc.LogLevel != nil || len(lcc.LogKeys) > 0 {
lcc.Enabled = BoolPtr(true)
} else {
lcc.Enabled = BoolPtr(false)
}
}
// Turn off console logging if disabled
if !*lcc.Enabled {
newLevel := LevelNone
lcc.LogLevel = &newLevel
lcc.LogKeys = []string{}
}
// Default log level
if lcc.LogLevel == nil {
newLevel := LevelInfo
lcc.LogLevel = &newLevel
} else if *lcc.LogLevel < LevelNone || *lcc.LogLevel > levelCount {
return fmt.Errorf("invalid log level: %v", *lcc.LogLevel)
}
// Always enable the HTTP log key
lcc.LogKeys = append(lcc.LogKeys, logKeyNames[KeyHTTP])
// If ColorEnabled is not explicitly set, use the value of $SG_COLOR
if lcc.ColorEnabled == nil {
// Ignore error parsing this value to treat it as false.
color, _ := strconv.ParseBool(os.Getenv("SG_COLOR"))
lcc.ColorEnabled = &color
}
// Default to consoleLoggerCollateBufferSize if a collation buffer size is not set
if lcc.CollationBufferSize == nil {
bufferSize := 0
if *lcc.LogLevel >= LevelInfo {
bufferSize = defaultConsoleLoggerCollateBufferSize
}
lcc.CollationBufferSize = &bufferSize
}
return nil
}
func newConsoleLoggerOrPanic(config *ConsoleLoggerConfig) *ConsoleLogger {
logger, err := NewConsoleLogger(false, config)
if err != nil {
panic(err)
}
return logger
}