-
-
Notifications
You must be signed in to change notification settings - Fork 20
/
logger.go
272 lines (227 loc) · 8.54 KB
/
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
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package errlog
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
)
var (
gopath = os.Getenv("GOPATH")
)
//Logger interface allows to log an error, or to print source code lines. Check out NewLogger function to learn more about Logger objects and Config.
type Logger interface {
// Debug wraps up Logger debugging funcs related to an error
// If the given error is nil, it returns immediately
// It relies on Logger.Config to determine what will be printed or executed
// It returns whether err != nil
Debug(err error) bool
//PrintSource prints lines based on given opts (see PrintSourceOptions type definition)
PrintSource(lines []string, opts PrintSourceOptions)
//DebugSource debugs a source file
DebugSource(filename string, lineNumber int)
//SetConfig replaces current config with the given one
SetConfig(cfg *Config)
//Config returns current config
Config() *Config
//Disable is used to disable Logger (every call to this Logger will perform NO-OP (no operation)) and return instantly
//Use Disable(true) to disable and Disable(false) to enable again
Disable(bool)
}
//Config holds the configuration for a logger
type Config struct {
PrintFunc func(format string, data ...interface{}) //Printer func (eg: fmt.Printf)
LinesBefore int //How many lines to print *before* the error line when printing source code
LinesAfter int //How many lines to print *after* the error line when printing source code
PrintStack bool //Shall we print stack trace ? yes/no
PrintSource bool //Shall we print source code along ? yes/no
PrintError bool //Shall we print the error of Debug(err) ? yes/no
ExitOnDebugSuccess bool //Shall we os.Exit(1) after Debug has finished logging everything ? (doesn't happen when err is nil)
DisableStackIndentation bool //Shall we print stack vertically instead of indented
Mode int
}
// PrintSourceOptions represents config for (*logger).PrintSource func
type PrintSourceOptions struct {
FuncLine int
StartLine int
EndLine int
Highlighted map[int][]int //map[lineIndex][columnstart, columnEnd] of chars to highlight
}
//logger holds logger object, implementing Logger interface
type logger struct {
config *Config //config for the logger
stackDepthOverload int //stack depth to ignore when reading stack
}
//NewLogger creates a new logger struct with given config
func NewLogger(cfg *Config) Logger {
l := logger{
config: cfg,
stackDepthOverload: 0,
}
l.Doctor()
return &l
}
// Debug wraps up Logger debugging funcs related to an error
// If the given error is nil, it returns immediately
// It relies on Logger.Config to determine what will be printed or executed
func (l *logger) Debug(uErr error) bool {
if l.config.Mode == ModeDisabled {
return uErr != nil
}
l.Doctor()
if uErr == nil {
return false
}
stLines := parseStackTrace(1 + l.stackDepthOverload)
if stLines == nil || len(stLines) < 1 {
l.Printf("Error: %s", uErr)
l.Printf("Errlog tried to debug the error but the stack trace seems empty. If you think this is an error, please open an issue at https://github.com/snwfdhmp/errlog/issues/new and provide us logs to investigate.")
return true
}
if l.config.PrintError {
l.Printf("Error in %s: %s", stLines[0].CallingObject, color.YellowString(uErr.Error()))
}
if l.config.PrintSource {
l.DebugSource(stLines[0].SourcePathRef, stLines[0].SourceLineRef)
}
if l.config.PrintStack {
l.Printf("Stack trace:")
l.printStack(stLines)
}
if l.config.ExitOnDebugSuccess {
os.Exit(1)
}
l.stackDepthOverload = 0
return true
}
//DebugSource prints certain lines of source code of a file for debugging, using (*logger).config as configurations
func (l *logger) DebugSource(filepath string, debugLineNumber int) {
filepathShort := filepath
if gopath != "" {
filepathShort = strings.Replace(filepath, gopath+"/src/", "", -1)
}
b, err := afero.ReadFile(fs, filepath)
if err != nil {
l.Printf("errlog: cannot read file '%s': %s. If sources are not reachable in this environment, you should set PrintSource=false in logger config.", filepath, err)
return
// l.Debug(err)
}
lines := strings.Split(string(b), "\n")
// set line range to print based on config values and debugLineNumber
minLine := debugLineNumber - l.config.LinesBefore
maxLine := debugLineNumber + l.config.LinesAfter
//delete blank lines from range and clean range if out of lines range
deleteBlankLinesFromRange(lines, &minLine, &maxLine)
//free some memory from unused values
lines = lines[:maxLine+1]
//find func line and adjust minLine if below
funcLine := findFuncLine(lines, debugLineNumber)
if funcLine > minLine {
minLine = funcLine + 1
}
//try to find failing line if any
failingLineIndex, columnStart, columnEnd := findFailingLine(lines, funcLine, debugLineNumber)
if failingLineIndex != -1 {
l.Printf("line %d of %s:%d", failingLineIndex+1, filepathShort, failingLineIndex+1)
} else {
l.Printf("error in %s (failing line not found, stack trace says func call is at line %d)", filepathShort, debugLineNumber)
}
l.PrintSource(lines, PrintSourceOptions{
FuncLine: funcLine,
Highlighted: map[int][]int{
failingLineIndex: {columnStart, columnEnd},
},
StartLine: minLine,
EndLine: maxLine,
})
}
// PrintSource prints source code based on opts
func (l *logger) PrintSource(lines []string, opts PrintSourceOptions) {
//print func on first line
if opts.FuncLine != -1 && opts.FuncLine < opts.StartLine {
l.Printf("%s", color.RedString("%d: %s", opts.FuncLine+1, lines[opts.FuncLine]))
if opts.FuncLine < opts.StartLine-1 { // append blank line if minLine is not next line
l.Printf("%s", color.YellowString("..."))
}
}
for i := opts.StartLine; i < opts.EndLine; i++ {
if _, ok := opts.Highlighted[i]; !ok || len(opts.Highlighted[i]) != 2 {
l.Printf("%d: %s", i+1, color.YellowString(lines[i]))
continue
}
hlStart := max(opts.Highlighted[i][0], 0) //highlight column start
hlEnd := min(opts.Highlighted[i][1], len(lines)-1) //highlight column end
l.Printf("%d: %s%s%s", i+1, color.YellowString(lines[i][:hlStart]), color.RedString(lines[i][hlStart:hlEnd+1]), color.YellowString(lines[i][hlEnd+1:]))
}
}
func (l *logger) Doctor() (neededDoctor bool) {
neededDoctor = false
if l.config.PrintFunc == nil {
neededDoctor = true
logrus.Debug("PrintFunc not set for this logger. Replacing with DefaultLoggerPrintFunc.")
l.config.PrintFunc = DefaultLoggerPrintFunc
}
if l.config.LinesBefore < 0 {
neededDoctor = true
logrus.Debugf("LinesBefore is '%d' but should not be <0. Setting to 0.", l.config.LinesBefore)
l.config.LinesBefore = 0
}
if l.config.LinesAfter < 0 {
neededDoctor = true
logrus.Debugf("LinesAfters is '%d' but should not be <0. Setting to 0.", l.config.LinesAfter)
l.config.LinesAfter = 0
}
if neededDoctor && !debugMode {
logrus.Warn("errlog: Doctor() has detected and fixed some problems on your logger configuration. It might have modified your configuration. Check logs by enabling debug. 'errlog.SetDebugMode(true)'.")
}
return
}
func (l *logger) printStack(stLines []StackTraceItem) {
for i := len(stLines) - 1; i >= 0; i-- {
padding := ""
if !l.config.DisableStackIndentation {
for j := 0; j < len(stLines)-1-i; j++ {
padding += " "
}
}
l.Printf("%s (%s:%d)", stLines[i].CallingObject, stLines[i].SourcePathRef, stLines[i].SourceLineRef)
}
}
//Printf is the function used to log
func (l *logger) Printf(format string, data ...interface{}) {
l.config.PrintFunc(format, data...)
}
//Overload adds depths to remove when parsing next stack trace
func (l *logger) Overload(amount int) {
l.stackDepthOverload += amount
}
func (l *logger) SetConfig(cfg *Config) {
l.config = cfg
l.Doctor()
}
func (l *logger) Config() *Config {
return l.config
}
func (l *logger) SetMode(mode int) bool {
if !isIntInSlice(mode, enabledModes) {
return false
}
l.Config().Mode = mode
return true
}
func (l *logger) Disable(shouldDisable bool) {
if shouldDisable {
l.Config().Mode = ModeDisabled
} else {
l.Config().Mode = ModeEnabled
}
}
const (
// ModeDisabled represents the disabled mode (NO-OP)
ModeDisabled = iota + 1
// ModeEnabled represents the enabled mode (Print)
ModeEnabled
)
var (
enabledModes = []int{ModeDisabled, ModeEnabled}
)