-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathroot.go
More file actions
213 lines (189 loc) · 6.6 KB
/
root.go
File metadata and controls
213 lines (189 loc) · 6.6 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
package zlog
import (
"io"
"os"
"sync/atomic"
"unsafe"
)
// globalStackFields is the largest typed-Field count for which the
// global helpers will use a stack-allocated array internally to avoid
// a heap slice. Calls with more fields fall back to a per-call
// `make([]Field, len(args))`. 16 covers the typical structured-log
// shape; 99% of real-world calls have fewer than 10 fields.
const globalStackFields = 16
// defaultLogger is the global logger instance.
var defaultLogger unsafe.Pointer
func init() {
// Initialize with a structured logger writing colored, padded output
// to stdout. TTY/color detection is handled by NewTerminalWriter, so
// this stays sensible when stdout is piped or redirected to a file.
// Users can swap the writer with SetWriter().
logger := NewStructured()
logger.SetWriter(StdoutTerminal())
atomic.StorePointer(&defaultLogger, unsafe.Pointer(logger))
}
// Default returns the current default logger.
func Default() *StructuredLogger {
return (*StructuredLogger)(atomic.LoadPointer(&defaultLogger))
}
// SetDefault sets the default global logger.
func SetDefault(logger *StructuredLogger) {
atomic.StorePointer(&defaultLogger, unsafe.Pointer(logger))
}
// The default-logger global helpers come in two flavors:
//
// - Debug / Info / Warn / Error / Fatal accept either typed Field
// values or alternating key/value pairs. They are the
// backward-compatible API:
//
// zlog.Info("user logged in", "user_id", 12345) // 0 allocs (KV path)
// zlog.Info("user logged in", zlog.Int("user_id", 12345)) // 3 allocs (Field via ...any boxes)
//
// The typed-Field call costs three structural allocations that no
// compiler trick can eliminate: passing a 56-byte Field through a
// ...any parameter heap-boxes each argument at the *callsite*, before
// the function runs.
//
// - DebugF / InfoF / WarnF / ErrorF / FatalF take typed Fields directly
// via ...Field, with no boxing — they are the zero-allocation path
// for high-frequency typed logging:
//
// zlog.InfoF("user logged in", zlog.Int("user_id", 12345)) // 0 allocs
//
// For a logger you hold yourself (logger := zlog.NewStructured()),
// logger.Info(msg, fields...) already has the zero-alloc property —
// the boxing only happens when crossing a ...any variadic.
// Debug logs a debug message via the default logger.
//
// Accepts typed Fields or alternating key/value pairs; see DebugF for
// the zero-allocation typed-Field path.
func Debug(msg string, args ...any) {
dispatchToDefault(LevelDebug, msg, args)
}
// Info logs an info message via the default logger.
//
// Accepts typed Fields or alternating key/value pairs; see InfoF for
// the zero-allocation typed-Field path.
func Info(msg string, args ...any) {
dispatchToDefault(LevelInfo, msg, args)
}
// Warn logs a warning message via the default logger.
//
// Accepts typed Fields or alternating key/value pairs; see WarnF for
// the zero-allocation typed-Field path.
func Warn(msg string, args ...any) {
dispatchToDefault(LevelWarn, msg, args)
}
// Error logs an error message via the default logger.
//
// Accepts typed Fields or alternating key/value pairs; see ErrorF for
// the zero-allocation typed-Field path.
func Error(msg string, args ...any) {
dispatchToDefault(LevelError, msg, args)
}
// Fatal logs a fatal message via the default logger and exits with
// code 1. Always exits, even when the level filters the message.
//
// Accepts typed Fields or alternating key/value pairs; see FatalF for
// the zero-allocation typed-Field path.
func Fatal(msg string, args ...any) {
logger := Default()
if logger.shouldLog(LevelFatal) {
if len(args) == 0 {
logger.logFields(LevelFatal, msg, nil)
} else if !routeTypedFromAny(logger, LevelFatal, msg, args) {
logger.logKV(LevelFatal, msg, args...)
}
}
os.Exit(1)
}
// DebugF logs a debug message with typed fields. Zero allocations
// because ...Field forwards directly without crossing an interface.
func DebugF(msg string, fields ...Field) {
Default().Debug(msg, fields...)
}
// InfoF logs an info message with typed fields. Zero allocations.
func InfoF(msg string, fields ...Field) {
Default().Info(msg, fields...)
}
// WarnF logs a warning message with typed fields. Zero allocations.
func WarnF(msg string, fields ...Field) {
Default().Warn(msg, fields...)
}
// ErrorF logs an error message with typed fields. Zero allocations.
func ErrorF(msg string, fields ...Field) {
Default().Error(msg, fields...)
}
// FatalF logs a fatal message with typed fields and exits with code 1.
// Zero allocations.
func FatalF(msg string, fields ...Field) {
Default().Fatal(msg, fields...)
}
// dispatchToDefault is the shared body of Debug/Info/Warn/Error.
// Routes typed-Field calls through the structured path and untyped
// key/value calls through the KV path. Bare-message calls skip
// argument inspection entirely.
//
//go:inline
func dispatchToDefault(level Level, msg string, args []any) {
logger := Default()
if len(args) == 0 {
// Bare message: no allocation cost, dispatch directly to the
// structured logger (which handles its own level check).
switch level {
case LevelDebug:
logger.Debug(msg)
case LevelInfo:
logger.Info(msg)
case LevelWarn:
logger.Warn(msg)
case LevelError:
logger.Error(msg)
}
return
}
if !logger.shouldLog(level) {
return
}
if routeTypedFromAny(logger, level, msg, args) {
return
}
logger.logKV(level, msg, args...)
}
// routeTypedFromAny detects calls whose every variadic argument is a
// Field and routes them through the structured (typed) path. Returns
// true if it handled the call. Returns false if any argument is not a
// Field — caller should fall back to the KV path.
//
// The 3-alloc structural cost on this path is at the Go callsite (each
// Field boxed into a ...any slot). What we save here is the dispatch +
// fmt.Sprint cost the KV path would otherwise pay on Field arguments.
func routeTypedFromAny(logger *StructuredLogger, level Level, msg string, args []any) bool {
for i := range args {
if _, ok := args[i].(Field); !ok {
return false
}
}
if len(args) <= globalStackFields {
var stack [globalStackFields]Field
for i := range args {
stack[i] = args[i].(Field)
}
logger.logFields(level, msg, stack[:len(args)])
return true
}
fields := make([]Field, len(args))
for i := range args {
fields[i] = args[i].(Field)
}
logger.logFields(level, msg, fields)
return true
}
// SetLevel sets the minimum log level for the default logger.
func SetLevel(level Level) {
Default().SetLevel(level)
}
// SetWriter sets the writer for the default logger.
func SetWriter(w io.Writer) {
Default().SetWriter(w)
}