-
Notifications
You must be signed in to change notification settings - Fork 53
/
simple_logger.go
181 lines (160 loc) · 4.95 KB
/
simple_logger.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
// Package logging handles logging throughout Atlantis.
package logging
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"unicode"
)
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_simple_logging.go SimpleLogging
// SimpleLogging is the interface that our SimpleLogger implements.
// It's really only used for mocking when we need to test what's being logged.
type SimpleLogging interface {
Debug(format string, a ...interface{})
Info(format string, a ...interface{})
Warn(format string, a ...interface{})
Err(format string, a ...interface{})
Log(level LogLevel, format string, a ...interface{})
// Underlying returns the underlying logger.
Underlying() *log.Logger
// GetLevel returns the current log level.
GetLevel() LogLevel
}
// SimpleLogger wraps the standard logger with leveled logging
// and the ability to store log history for later adding it
// to a VCS comment.
type SimpleLogger struct {
// Source is added as a prefix to each log entry.
// It's useful if you want to trace a log entry back to a
// context, for example a pull request id.
Source string
// History stores all log entries ever written using
// this logger. This is safe for short-lived loggers
// like those used during plan/apply commands.
History bytes.Buffer
Logger *log.Logger
KeepHistory bool
Level LogLevel
}
type LogLevel int
const (
Debug LogLevel = iota
Info
Warn
Error
)
// NewSimpleLogger creates a new logger.
// source is added as a prefix to each log entry. It's useful if you want to
// trace a log entry back to a specific context, for example a pull request id.
// logger is the underlying logger. If nil will create a logger from stdlib.
// keepHistory set to true will store all log entries written using this logger.
// level will set the level at which logs >= than that level will be written.
// If keepHistory is set to true, we'll store logs at all levels, regardless of
// what level is set to.
func NewSimpleLogger(source string, logger *log.Logger, keepHistory bool, level LogLevel) *SimpleLogger {
if logger == nil {
flags := log.LstdFlags
if level == Debug {
// If we're using debug logging, we also have the logger print the
// filename the log comes from with log.Lshortfile.
flags = flags | log.Lshortfile
}
logger = log.New(os.Stderr, "", flags)
}
return &SimpleLogger{
Source: source,
Logger: logger,
Level: level,
KeepHistory: keepHistory,
}
}
// NewNoopLogger creates a logger instance that discards all logs and never
// writes them. Used for testing.
func NewNoopLogger() *SimpleLogger {
logger := log.New(os.Stderr, "", 0)
logger.SetOutput(ioutil.Discard)
return &SimpleLogger{
Source: "",
Logger: logger,
Level: Info,
KeepHistory: false,
}
}
// ToLogLevel converts a log level string to a valid LogLevel object.
// If the string doesn't match a level, it will return Info.
func ToLogLevel(levelStr string) LogLevel {
switch levelStr {
case "debug":
return Debug
case "info":
return Info
case "warn":
return Warn
case "error":
return Error
}
return Info
}
// Debug logs at debug level.
func (l *SimpleLogger) Debug(format string, a ...interface{}) {
l.Log(Debug, format, a...)
}
// Info logs at info level.
func (l *SimpleLogger) Info(format string, a ...interface{}) {
l.Log(Info, format, a...)
}
// Warn logs at warn level.
func (l *SimpleLogger) Warn(format string, a ...interface{}) {
l.Log(Warn, format, a...)
}
// Err logs at error level.
func (l *SimpleLogger) Err(format string, a ...interface{}) {
l.Log(Error, format, a...)
}
// Log writes the log at level.
func (l *SimpleLogger) Log(level LogLevel, format string, a ...interface{}) {
levelStr := l.levelToString(level)
msg := l.capitalizeFirstLetter(fmt.Sprintf(format, a...))
// Only log this message if configured to log at this level.
if l.Level <= level {
// Calling .Output instead of Printf so we can change the calldepth
// param to 3. The default is 2 which would identify the log as coming
// from this file and line every time instead of our caller's.
l.Logger.Output(3, fmt.Sprintf("[%s] %s: %s\n", levelStr, l.Source, msg)) // nolint: errcheck
}
// Keep history at all log levels.
if l.KeepHistory {
l.saveToHistory(levelStr, msg)
}
}
// Underlying returns the underlying logger.
func (l *SimpleLogger) Underlying() *log.Logger {
return l.Logger
}
// GetLevel returns the current log level of the logger.
func (l *SimpleLogger) GetLevel() LogLevel {
return l.Level
}
func (l *SimpleLogger) saveToHistory(level string, msg string) {
l.History.WriteString(fmt.Sprintf("[%s] %s\n", level, msg))
}
func (l *SimpleLogger) capitalizeFirstLetter(s string) string {
runes := []rune(s)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}
func (l *SimpleLogger) levelToString(level LogLevel) string {
switch level {
case Debug:
return "DEBUG"
case Info:
return "INFO"
case Warn:
return "WARN"
case Error:
return "ERROR"
}
return "NOLEVEL"
}