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
|
package shared
import (
"fmt"
"io"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
type ILogger interface {
Debug(msg interface{}, keyvals ...interface{})
Info(msg interface{}, keyvals ...interface{})
Warn(msg interface{}, keyvals ...interface{})
Error(msg interface{}, keyvals ...interface{})
Printf(format string, args ...interface{})
Debugf(format string, args ...interface{})
Infof(format string, args ...interface{})
Warnf(format string, args ...interface{})
Errorf(format string, args ...interface{})
}
type logLevel int
const (
levelDebug logLevel = iota
levelInfo
levelWarn
levelError
)
// Logger is a minimal stdlib-based logger that always writes to a single
// io.Writer (stderr in production). It replaces the previous charmbracelet/log
// dependency, which pulled in a large terminal-styling dependency tree that is
// pointless for a service whose logs go to the systemd journal.
type Logger struct {
mu sync.Mutex
out io.Writer
level logLevel
}
// NewLogger creates a logger writing to out, filtering by the given level
// string ("Debug", "Info", "Warn", "Error"; anything else means Error).
func NewLogger(out io.Writer, level string) *Logger {
l := levelError
switch level {
case "Debug":
l = levelDebug
case "Info":
l = levelInfo
case "Warn":
l = levelWarn
case "Error":
l = levelError
}
return &Logger{out: out, level: l}
}
func (l *Logger) log(lvl logLevel, tag string, msg string, keyvals ...interface{}) {
if lvl < l.level {
return
}
ts := time.Now().Format("2006-01-02 15:04:05.000")
caller := callerLocation(3)
var b strings.Builder
b.WriteString(ts)
b.WriteByte(' ')
if tag != "" {
b.WriteString(tag)
b.WriteByte(' ')
}
if caller != "" {
b.WriteByte('<')
b.WriteString(caller)
b.WriteString("> ")
}
b.WriteString(msg)
for i := 0; i+1 < len(keyvals); i += 2 {
fmt.Fprintf(&b, " %v=%v", keyvals[i], keyvals[i+1])
}
b.WriteByte('\n')
l.mu.Lock()
defer l.mu.Unlock()
_, _ = io.WriteString(l.out, b.String())
}
func callerLocation(skip int) string {
_, file, line, ok := runtime.Caller(skip)
if !ok {
return ""
}
// Show <pkgdir/file.go:line> to match the previous log format closely.
dir := filepath.Base(filepath.Dir(file))
return fmt.Sprintf("%s/%s:%d", dir, filepath.Base(file), line)
}
func toStr(msg interface{}) string { return fmt.Sprint(msg) }
func (l *Logger) Debug(msg interface{}, keyvals ...interface{}) {
l.log(levelDebug, "DEBU", toStr(msg), keyvals...)
}
func (l *Logger) Info(msg interface{}, keyvals ...interface{}) {
l.log(levelInfo, "INFO", toStr(msg), keyvals...)
}
func (l *Logger) Warn(msg interface{}, keyvals ...interface{}) {
l.log(levelWarn, "WARN", toStr(msg), keyvals...)
}
func (l *Logger) Error(msg interface{}, keyvals ...interface{}) {
l.log(levelError, "ERRO", toStr(msg), keyvals...)
}
// Printf logs unconditionally (like the previous logger's Printf).
func (l *Logger) Printf(format string, args ...interface{}) {
l.log(levelError, "", fmt.Sprintf(format, args...))
}
func (l *Logger) Debugf(format string, args ...interface{}) {
l.log(levelDebug, "DEBU", fmt.Sprintf(format, args...))
}
func (l *Logger) Infof(format string, args ...interface{}) {
l.log(levelInfo, "INFO", fmt.Sprintf(format, args...))
}
func (l *Logger) Warnf(format string, args ...interface{}) {
l.log(levelWarn, "WARN", fmt.Sprintf(format, args...))
}
func (l *Logger) Errorf(format string, args ...interface{}) {
l.log(levelError, "ERRO", fmt.Sprintf(format, args...))
}
|