Skip to content

Commit 9af2695

Browse files
committed
logger: add specific lab logger
This logger substitutes the default Go logger from `log` package, allowing a leveled log throughout the code, such as enabling `--debug` option to print out debug message. It'll also help to silence debug messages from external libs that require specific leveled loggers to not print debug messages to stdout (e.g. go-retryablehttp). In general, the output message from this logger should pretty similar to what we had already with the default logger. Signed-off-by: Bruno Meneguele <bmeneg@redhat.com>
1 parent bef39e0 commit 9af2695

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed

internal/logger/logger.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package logger
2+
3+
import (
4+
"errors"
5+
"io"
6+
"log"
7+
"os"
8+
"runtime"
9+
"strconv"
10+
"strings"
11+
"sync"
12+
)
13+
14+
// Logger levels available
15+
const (
16+
LOG_ERROR = iota
17+
LOG_INFO
18+
LOG_DEBUG
19+
)
20+
21+
// This internal logger will have a different log.Logger for each level,
22+
// allowing different destination (file or fd) in different levels and
23+
// also different prefixes.
24+
type logger struct {
25+
level int
26+
errorLogger *log.Logger
27+
warnLogger *log.Logger
28+
infoLogger *log.Logger
29+
debugLogger *log.Logger
30+
}
31+
32+
// Internal instance that is used by anyone getting it through GetInstance()
33+
var internalLogger *logger
34+
35+
// A way to avoid multiple initialization of the same logger
36+
var once sync.Once
37+
38+
// GetInstance returns the default lab internal logger
39+
func GetInstance() *logger {
40+
once.Do(func() {
41+
internalLogger = &logger{
42+
// Set INFO as default level. The user can change it
43+
level: LOG_INFO,
44+
// Setting Lmsgprefix preffix make the prefix to be printed before
45+
// the actual message, but after the LstdFlags (date and time)
46+
errorLogger: log.New(os.Stderr, "ERROR: ", log.LstdFlags|log.Lmsgprefix),
47+
warnLogger: log.New(os.Stdout, "WARNING: ", log.LstdFlags|log.Lmsgprefix),
48+
infoLogger: log.New(os.Stdout, "", log.LstdFlags|log.Lmsgprefix),
49+
debugLogger: log.New(os.Stdout, "DEBUG: ", log.LstdFlags|log.Lmsgprefix),
50+
}
51+
})
52+
return internalLogger
53+
}
54+
55+
// SetLogLevel set the level of the internal logger.
56+
// Allowed values are LOG_{ERROR,INFO,DEBUG}.
57+
func (l *logger) SetLogLevel(level int) error {
58+
if !(level >= LOG_ERROR && level <= LOG_DEBUG) {
59+
return errors.New("invalid log level")
60+
}
61+
l.level = level
62+
return nil
63+
}
64+
65+
// LogLevel return de current log level of the internal logger
66+
func (l *logger) LogLevel() int {
67+
return l.level
68+
}
69+
70+
// SetStdDest sets what's the desired stdout and stderr for the internal
71+
// log. It can be any io.Writer value.
72+
func (l *logger) SetStdDest(stdout io.Writer, stderr io.Writer) {
73+
l.errorLogger.SetOutput(stderr)
74+
l.warnLogger.SetOutput(stdout)
75+
l.infoLogger.SetOutput(stdout)
76+
l.debugLogger.SetOutput(stdout)
77+
}
78+
79+
// printKeysAndValues prints the keys and valus, as pairs, passed to those
80+
// functions in the way expected by go-retryablehttp LeveledLogger interface
81+
func printKeysAndValues(l *log.Logger, keysAndValues ...interface{}) {
82+
for i := 0; i <= len(keysAndValues)/2; i += 2 {
83+
l.Printf("\t%s = %s\n", keysAndValues[i], keysAndValues[i+1])
84+
}
85+
}
86+
87+
// addFileLinePrefix prepend the file name and line number to the message being
88+
// printed.
89+
func addFileLinePrefix(msg string) string {
90+
var file string
91+
92+
// Using runtime.Caller() with calldepth == 2 is enough for getting the
93+
// logger function callers
94+
_, filePath, line, ok := runtime.Caller(2)
95+
if ok {
96+
fileParts := strings.Split(filePath, "/")
97+
file = fileParts[len(fileParts)-1]
98+
} else {
99+
// Not sure if there's a better name or line number for an unknown caller
100+
file = "???"
101+
line = 0
102+
}
103+
104+
prefix := []string{file, ":", strconv.Itoa(line), ":"}
105+
// When called from Error, Warn, Info or Debug(), the Print() used
106+
// doesn't know about this additional prefix we're adding, so we
107+
// need to add the space between it and the msg ourselves.
108+
if len(strings.TrimSpace(msg)) > 0 {
109+
prefix = append(prefix, " ")
110+
}
111+
112+
prefixedMsg := append(prefix, msg)
113+
return strings.Join(prefixedMsg, "")
114+
}
115+
116+
// Fatal prints the values and exit the program with os.Exit()
117+
func (l *logger) Fatal(values ...interface{}) {
118+
values = append([]interface{}{addFileLinePrefix("")}, values...)
119+
l.errorLogger.Fatal(values...)
120+
}
121+
122+
// Fatal prints formated strings and exit the program with os.Exit()
123+
func (l *logger) Fatalf(format string, values ...interface{}) {
124+
values = append([]interface{}{addFileLinePrefix("")}, values...)
125+
l.errorLogger.Fatalf("%s "+format, values...)
126+
}
127+
128+
// Fatal prints the values in a new line and exit the program with os.Exit()
129+
func (l *logger) Fatalln(values ...interface{}) {
130+
values = append([]interface{}{addFileLinePrefix("")}, values...)
131+
l.errorLogger.Fatalln(values...)
132+
}
133+
134+
// Error prints error messages (prefixed with "ERROR:").
135+
// These parameters match the retryablehttp.LeveledLogger, which we want to
136+
// satisfy for silencing their debug messages being printed in the stdout.
137+
// Error message are always printed, regardless the log level.
138+
func (l *logger) Error(msg string, keysAndValues ...interface{}) {
139+
if l.level >= LOG_ERROR {
140+
l.errorLogger.Print(addFileLinePrefix(msg))
141+
printKeysAndValues(l.errorLogger, keysAndValues...)
142+
}
143+
}
144+
145+
// Errorf prints formated error message (prefixed with "ERROR:").
146+
// Error message are always printed, regardless the log level.
147+
func (l *logger) Errorf(format string, values ...interface{}) {
148+
if l.level >= LOG_ERROR {
149+
values = append([]interface{}{addFileLinePrefix("")}, values...)
150+
l.errorLogger.Printf("%s "+format, values...)
151+
}
152+
}
153+
154+
// Errorln prints error values in a new line (prefixed with "ERROR:").
155+
// Error message are always printed, regardless the log level.
156+
func (l *logger) Errorln(values ...interface{}) {
157+
if l.level >= LOG_ERROR {
158+
values = append([]interface{}{addFileLinePrefix("")}, values...)
159+
l.errorLogger.Println(values...)
160+
}
161+
}
162+
163+
// Warn prints warning messages (prefixed with "WARNING:").
164+
// These parameters match the retryablehttp.LeveledLogger, which we want to
165+
// satisfy for silencing their debug messages being printed in the stdout.
166+
// Warning messages require at least LOG_INFO level.
167+
func (l *logger) Warn(msg string, keysAndValues ...interface{}) {
168+
if l.level >= LOG_INFO {
169+
l.warnLogger.Print(addFileLinePrefix(msg))
170+
printKeysAndValues(l.warnLogger, keysAndValues...)
171+
}
172+
}
173+
174+
// Warnf prints formated warning message (prefixed with "WARNING:").
175+
// Warning messages require at least LOG_INFO level.
176+
func (l *logger) Warnf(format string, values ...interface{}) {
177+
if l.level >= LOG_INFO {
178+
values = append([]interface{}{addFileLinePrefix("")}, values...)
179+
l.warnLogger.Printf("%s "+format, values...)
180+
}
181+
}
182+
183+
// Warnln prints warning values in a new line (prefixed with "WARNING:").
184+
// Warning messages require at least LOG_INFO level.
185+
func (l *logger) Warnln(values ...interface{}) {
186+
if l.level >= LOG_INFO {
187+
values = append([]interface{}{addFileLinePrefix("")}, values...)
188+
l.warnLogger.Println(values...)
189+
}
190+
}
191+
192+
// Info prints informational messages (prefixed with "INFO:").
193+
// These parameters match the retryablehttp.LeveledLogger, which we want to
194+
// satisfy for silencing their debug messages being printed in the stdout.
195+
// Info messages require at least LOG_INFO level.
196+
func (l *logger) Info(msg string, keysAndValues ...interface{}) {
197+
if l.level >= LOG_INFO {
198+
l.infoLogger.Print(addFileLinePrefix(msg))
199+
printKeysAndValues(l.infoLogger, keysAndValues...)
200+
}
201+
}
202+
203+
// Infof prints formated informational message (prefixed with "INFO:").
204+
// Info messages require at least LOG_INFO level.
205+
func (l *logger) Infof(format string, values ...interface{}) {
206+
if l.level >= LOG_INFO {
207+
values = append([]interface{}{addFileLinePrefix("")}, values...)
208+
l.infoLogger.Printf("%s "+format, values...)
209+
}
210+
}
211+
212+
// Infoln prints info values in a new line (prefixed with "INFO:").
213+
// Info messages require at least LOG_INFO level.
214+
func (l *logger) Infoln(values ...interface{}) {
215+
if l.level >= LOG_INFO {
216+
values = append([]interface{}{addFileLinePrefix("")}, values...)
217+
l.infoLogger.Println(values...)
218+
}
219+
}
220+
221+
// Debug prints warning messages (prefixed with "DEBUG:").
222+
// These parameters match the retryablehttp.LeveledLogger, which we want to
223+
// satisfy for silencing thier debug messages being printed in the stdout.
224+
// Debug messages require at least LOG_DEBUG level.
225+
func (l *logger) Debug(msg string, keysAndValues ...interface{}) {
226+
if l.level >= LOG_DEBUG {
227+
l.debugLogger.Print(addFileLinePrefix(msg))
228+
printKeysAndValues(l.debugLogger, keysAndValues...)
229+
}
230+
}
231+
232+
// Debugf prints formated debug message (prefixed with "DEBUG:").
233+
// Debug messages require at least LOG_DEBUG level.
234+
func (l *logger) Debugf(format string, values ...interface{}) {
235+
if l.level >= LOG_DEBUG {
236+
values = append([]interface{}{addFileLinePrefix("")}, values...)
237+
l.debugLogger.Printf("%s "+format, values...)
238+
}
239+
}
240+
241+
// Debugln prints debug values in a new line (prefixed with "DEBUG:").
242+
// Debug messages require at least LOG_DEBUG level.
243+
func (l *logger) Debugln(values ...interface{}) {
244+
if l.level >= LOG_DEBUG {
245+
values = append([]interface{}{addFileLinePrefix("")}, values...)
246+
l.debugLogger.Println(values...)
247+
}
248+
}

0 commit comments

Comments
 (0)