forked from as/log
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlog.go
More file actions
313 lines (278 loc) · 7.53 KB
/
log.go
File metadata and controls
313 lines (278 loc) · 7.53 KB
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Package log implements a simple structured JSON logger
//
// To use, override the package-scoped variables at runtime.
//
// This code may be copied and pasted into your microservice
// and modified to your liking. Put it in a package called
// log. A little copying is better than a little dependency.
package log
import (
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"sync"
"time"
)
// Line allows a log line to be embedded somewhere
type Line = line
type LevelInt int
type LogConfig struct {
MinLevel LevelInt
RegexPassthrough []string
RegexEnabled bool
DebugOn bool
UpdatedAt time.Time
}
const (
LevelDebug LevelInt = iota + 1
LevelInfo
LevelWarn
LevelError
LevelFatal
)
var (
// Service name (can be set in main or elsewhere)
Service = os.Getenv("SVC")
// Time is your time function. Default is a second timestamp.
Time = func() interface{} {
return time.Now().Unix()
}
// Tags are global static fields to publish for this process on
// all log levels and callers
Tags = fields{}
// Default is the level used when calling Printf and Fatalf
Default = Info
Config = LogConfig{
MinLevel: LevelDebug,
RegexPassthrough: []string{},
RegexEnabled: false,
DebugOn: false,
UpdatedAt: time.Time{},
}
compiledRegex = []*regexp.Regexp{}
setMutex = sync.Mutex{}
)
var (
// Info, Warn, and so forth are commonly encountered log "levels".
Info = line{Level: "info", LevelInt: LevelInfo}
Warn = line{Level: "warn", LevelInt: LevelWarn}
Error = line{Level: "error", LevelInt: LevelError}
Fatal = line{Level: "fatal", LevelInt: LevelFatal}
// Debug is a special level, it is only printed if Config.DebugOn is true
Debug = line{Level: "debug", LevelInt: LevelDebug}
)
var stderr = io.Writer(os.Stderr)
// Printf and Fatalf exist to make this package somewhat compatible with
// the go standard log.
func Printf(f string, v ...interface{}) { Default.F(f, v...) }
func Fatalf(f string, v ...interface{}) { Fatal.F(f, v...) }
// SetOutput sets the log output to w. It returns the previous writer used.
func SetOutput(w io.Writer) (old io.Writer) {
old = stderr
stderr = w
return old
}
type line struct {
fn func(line) line
fields
Level string
LevelInt LevelInt
msg string
}
// Printf attaches the formatted message to line and outputs
// the result to Stderr. Callers should call F() when not adding
// extra fields explicitly.
//
// The following fields are pre-declared, and emitted in order:
// (1) svc: value of Service
// (2) time: result of calling Time()
// (3) level: the log level
// (4) msg: the formatted string provided to Printf
//
// Prefer log.Error.F() to log.Error.Printf() unless using Add
func (l line) Printf(f string, v ...interface{}) {
// if msg is debug, checks if log msg should NOT be printed
exitDebugMsg := l.Level == Debug.Level && !Config.DebugOn
// for other levels, checks if log msg should NOT be printed based on min level
exitMinLevelMsg := l.LevelInt < Config.MinLevel
if exitDebugMsg || exitMinLevelMsg {
// check to see if message matches any regex to allow message to be printed
if !RegexPassthrough(f, v...) {
return
}
}
fmt.Fprintln(stderr, l.Msg(f, v...).String())
if l.Level == "fatal" {
panic(trapme(fmt.Sprintf("fatal: "+f, v...)))
}
}
// F is equivalent to Printf
func (l line) F(f string, v ...interface{}) {
l.Printf(f, v...)
}
// Msg returns a copy of l with the msg field set
// to the formatted string argument provided. Most
// callers should use the l.Printf or l.F
func (l line) Msg(f string, v ...interface{}) line {
l.msg = fmt.Sprintf(f, v...)
return l
}
// String returns the line as a string. If the line was created with
// AddFunc the attached func is executed exactly once before
// the string is created
func (l line) String() string {
if l.fn != nil {
fn := l.fn
l.fn = nil
l = fn(l)
l.fn = fn
}
hdr := append(fields{
"svc", Service,
"ts", Time(), // time often gets overwritten
"level", l.Level,
}, Tags...)
hdr = append(hdr, l.fields...)
return append(hdr, "msg", l.msg).String()
}
// Add returns a copy of the line with the custom fields provided
// fields should be provided in pairs, otherwise they are ignored:
//
// Info.Add("railway", "east", "stop", 5).Printf("train stopped")
//
// Add always makes a deep copy.
func (l line) Add(field ...interface{}) line {
l.fields = l.fields.Add(field...)
return l
}
// Export returns the key values as a string slice
// including any set package-scoped tags
func (l line) Export() (kv []string) {
f := append(fields{}, Tags...)
f = append(f, l.fields...)
return f.Export()
}
// AddFunc return a new line with fn attached
// The fn is executed once with every call to l.Printf(),
// l.F(), or any function that calls l.String().
//
// Recursive behavior is not permitted, although it is
// safe to call ln.String() from fn, it is not safe to do
// so with l.
//
// Warning: Use this function at your own risk
func (l line) AddFunc(fn func(ln Line) Line) Line {
l.fn = fn
return l
}
// New returns a log line with an extra field list
func New(fields ...interface{}) line {
return Default.Add(fields...)
}
// Info and the rest of these convert l into another log level
func (l line) Info() line { l.Level = Info.Level; return l }
func (l line) Error() line { l.Level = Error.Level; return l }
func (l line) Warn() line { l.Level = Warn.Level; return l }
func (l line) Fatal() line { l.Level = Fatal.Level; return l }
type fields []interface{}
// Export returns the unquoted set of key value pairs for the fields set.
// If any element of the key-value pair resolves to the empty string, it
// omits that pair.
//
// Invariant: len(kv) % 2 == true, for all calls to Export
func (f fields) Export() (kv []string) {
for i := 0; i+1 < len(f); i += 2 {
key, val := f[i], f[i+1]
if key == "" || val == "" || val == nil {
continue
}
k, v := fmt.Sprint(key), fmt.Sprint(val)
if k == "" || v == "" {
continue
}
kv = append(kv, k, v)
}
return
}
func (f fields) String() (s string) {
sep := ""
for i := 0; i+1 < len(f); i += 2 {
key, val := f[i], f[i+1]
if val == "" || val == nil || zero(val) {
continue
}
s += fmt.Sprintf(`%s%q:%s`, sep, key, quote(val))
sep = ", "
}
return "{" + s + "}"
}
func (l fields) Add(f ...interface{}) fields {
return append(append(fields{}, l...), f...)
}
func quote(v interface{}) string {
if v == nil {
v = ""
}
switch v.(type) {
case fmt.Stringer, error:
v = fmt.Sprint(v)
}
data, _ := json.Marshal(v)
return string(data)
}
type trapme string
// Trap may be used in a defer to suppress stack traces caused
// by a call to Fatal.F or Fatal.Printf. Panics from other sources are
// not affected. Trap calls os.Exit(1) if the panic occured from these
// functions.
//
// func main(){
// defer log.Trap()
//
// }
func Trap() {
v := recover()
if _, ok := v.(trapme); ok {
os.Exit(1)
}
if v != nil {
panic(v) // dont trap other panics
}
}
func zero(v interface{}) bool {
t, ok := v.([]string)
if !ok {
return false
}
return len(t) == 0
}
func SetLogConfig(config LogConfig) error {
setMutex.Lock()
defer setMutex.Unlock()
Config = config
// compile the regexes
for _, exp := range config.RegexPassthrough {
reg, err := regexp.Compile(exp)
if err != nil {
return err
}
compiledRegex = append(compiledRegex, reg)
}
return nil
}
func RegexPassthrough(f string, v ...interface{}) bool {
if !Config.RegexEnabled {
return false
}
setMutex.Lock()
defer setMutex.Unlock()
msg := fmt.Sprintf(f, v...)
for _, reg := range compiledRegex {
if reg.MatchString(msg) {
return true
}
}
return false
}