-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogger.go
120 lines (100 loc) · 2.79 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
// logfmt is a small and opinionated logging package.
//
// The only possible output format is [logfmt], all string values are
// quoted, keys are sorted, [time.Time] values are in UTC and
// formatted using [time.RFC3339].
//
// [logfmt]: https://brandur.org/logfmt
package logfmt
import (
"bytes"
"fmt"
"io"
"sort"
"strconv"
"time"
"unicode"
)
// Logger is the basic and only type of logger.
type Logger struct {
w io.Writer
}
// NewLogger creates a new [Logger].
func NewLogger(w io.Writer) *Logger {
return &Logger{w}
}
var now func() time.Time = time.Now().UTC
// Log writes the given msg and labels to w.
func (l *Logger) Log(msg string, labels Labels) (int, error) {
var buf bytes.Buffer
buf.WriteString("ts=")
buf.WriteString(now().Format(time.RFC3339))
buf.WriteString(" msg=")
buf.WriteString(strconv.Quote(msg))
keys := make([]string, 0, len(labels))
for k := range labels {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteRune(' ')
buf.WriteString(k)
buf.WriteRune('=')
buf.WriteString(Format(labels[k]))
}
buf.WriteByte('\n')
return l.w.Write(buf.Bytes())
}
// Logf writes a formatted message and labels to w. It is equivalent
// calling [Log] with a previously formatted string.
func (l *Logger) Logf(format string, labels Labels, v ...any) (int, error) {
return l.Log(fmt.Sprintf(format, v...), labels)
}
// Format transform any value into a string in an opinionated way:
// - String, error and [fmt.Stringer] values are quoted.
// - Floating values uses the %g Format with a maximum of 6 significant digits.
// - [time.Time] are converted to UTC and use [time.RFC3339] Format.
// - Integer values use [strconv.FormatUint] and [strconv.FormatInt].
// - All other values are formatted as %v and quoted if they have a space.
func Format(v any) string {
switch x := v.(type) {
case string:
return strconv.Quote(x)
case uint8:
return strconv.FormatUint(uint64(x), 10)
case uint16:
return strconv.FormatUint(uint64(x), 10)
case uint32:
return strconv.FormatUint(uint64(x), 10)
case uint64:
return strconv.FormatUint(x, 10)
case int8:
return strconv.FormatInt(int64(x), 10)
case int16:
return strconv.FormatInt(int64(x), 10)
case int32:
return strconv.FormatInt(int64(x), 10)
case int64:
return strconv.FormatInt(x, 10)
case float32:
return strconv.FormatFloat(float64(x), 'g', 6, 32)
case float64:
return strconv.FormatFloat(x, 'g', 6, 64)
case time.Time:
return x.UTC().Format(time.RFC3339)
case error:
return strconv.Quote(x.Error())
case fmt.Stringer:
return strconv.Quote(x.String())
default:
s := fmt.Sprint(x)
for _, r := range s {
if unicode.IsSpace(r) {
return strconv.Quote(s)
}
}
return s
}
}
// Labels is a custom type for mapping keys and values.
type Labels map[string]any