-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup.go
318 lines (283 loc) · 8.07 KB
/
setup.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
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
314
315
316
317
318
// File: "setup.go"
package xlog
import (
"context"
"fmt"
"io/fs"
"log"
"log/slog" // go>=1.21
"os"
"path"
"path/filepath"
"strconv"
"sync"
"time"
//"golang.org/x/exp/slog" // depricated for go>=1.21
)
// Saved loggers, current log level
var (
defaultLog *log.Logger = log.Default() // initial standtart logger
defaultSlog *slog.Logger = slog.Default() // initial structured logger
currentXlog Xlog = Default() // current global logger
currentLevel Level = DEFAULT_LEVEL // current global log level
defaultLock sync.Mutex
)
// Setup standart simple logger
func SetupLog(logger *log.Logger, conf Conf) {
flag := 0
if conf.Time {
flag |= log.LstdFlags
if conf.TimeUS {
flag |= log.Lmicroseconds
}
}
if conf.Src {
if conf.SrcLong {
flag |= log.Llongfile
} else {
flag |= log.Lshortfile
}
}
if conf.Prefix != "" {
flag |= log.Lmsgprefix
}
out := openFile(conf.File, conf.FileMode)
logger.SetOutput(out)
logger.SetPrefix(conf.Prefix)
logger.SetFlags(flag)
}
// Create new configured standart logger
func NewLog(conf Conf) *log.Logger {
logger := log.New(os.Stdout, "", 0)
SetupLog(logger, conf)
return logger
}
// Create new configures structured logger (default/text/JSON/Tinted handler)
func NewSlog(conf Conf) *slog.Logger {
if !conf.Slog && !conf.JSON && !conf.Tint {
// Don't use Text/JSON/Tint handler, tune standart logger
return newSlogStd(conf)
}
level := ParseLvl(conf.Level)
out := openFile(conf.File, conf.FileMode)
var handler slog.Handler
if conf.Tint {
// Use Tinted Handler
opts := &TintOptions{
AddSource: conf.Src,
SourceLong: conf.SrcLong,
Level: level,
NoLevel: conf.NoLevel,
ReplaceAttr: nil,
TimeFormat: conf.TimeTint,
NoColor: conf.NoColor,
}
if conf.TimeTint == "" {
if conf.Time {
opts.TimeFormat = DEFAULT_TIME_FORMAT
if conf.TimeUS {
opts.TimeFormat = DEFAULT_TIME_FORMAT_US
}
}
}
handler = NewTintHandler(out, opts)
} else {
// Use Text/JSON handler
opts := &slog.HandlerOptions{
AddSource: conf.Src,
Level: level,
}
if ADD_LEVELS || !conf.Time || conf.Src || !conf.SrcLong || conf.NoLevel {
opts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.TimeKey:
if !conf.Time && len(groups) == 0 {
return slog.Attr{} // remove timestamp from log
}
if conf.TimeUS {
t := a.Value.Any().(time.Time)
tstr := t.Format(RFC3339Micro)
return slog.String(slog.TimeKey, tstr)
} else if conf.JSON { // FIX difference between Text and JSON handler
t := a.Value.Any().(time.Time)
tstr := t.Format(RFC3339Milli)
return slog.String(slog.TimeKey, tstr)
}
case slog.SourceKey:
src := a.Value.Any().(*slog.Source)
if src.File == "" { // FIX some bug if slog work as standart logger
return slog.Attr{}
}
if conf.SrcLong { // long: directory + file name
dir, file := filepath.Split(src.File)
src.File = filepath.Join(filepath.Base(dir), file)
} else { // short: only file name
src.File = path.Base(src.File)
}
a.Value = slog.AnyValue(src)
case slog.LevelKey:
if conf.NoLevel {
return slog.Attr{} // remove "level=..." etc
}
if ADD_LEVELS { // add TRACE/NOTICE/FATAL/PANIC
level := a.Value.Any().(slog.Level)
label := Level(level).String()
return slog.String(slog.LevelKey, label)
}
} // switch
return a
}
}
if conf.JSON {
handler = slog.NewJSONHandler(out, opts)
} else {
handler = slog.NewTextHandler(out, opts)
}
}
logger := slog.New(handler)
if conf.AddKey != "" && conf.AddValue != "" {
logger = logger.With(conf.AddKey, conf.AddValue)
}
return logger
}
// Setup standart and structured default global loggers
func Setup(conf Conf) {
// Setup standart logger
l := logDefault()
SetupLog(l, conf)
// Setup structured logger
s := NewSlog(conf)
slog.SetDefault(s)
// Save log level and update global xlog wrapper
defaultLock.Lock()
currentLevel = ParseLvl(conf.Level)
currentXlog = X(s)
defaultLock.Unlock()
// Repeat setup standart logger (stop loop forever)
// TODO: why?
SetupLog(l, conf)
// FIXME: TODO
// В экспериментальном slog есть ошибка:
// При добавлении хендлера для управления уровнем
// логирования некорректно выводятся имена файлов (и строк).
// Что интересно, в Go v1.21 в log/slog всё исправлено.
// Используя Go до версии 1.21 (например 1.20) при включении
// управления уровнем логирования при работе slog через slog.defaultHandler
// в угоду возможности управления уровнями отключаем вывод файлов и строк.
// Если "golang.org/x/exp/slog" доработают это FIX можно будет убрать.
if OLD_SLOG_FIX { // "runtime.Version() < go1.21.0"
if currentLevel != DEFAULT_LEVEL {
stdlog := logDefault()
flag := stdlog.Flags()
flag = flag &^ (log.Lshortfile | log.Llongfile) // sorry...
stdlog.SetFlags(flag)
}
}
}
// Get default standart logger
func logDefault() *log.Logger {
defaultLock.Lock()
defer defaultLock.Unlock()
l := defaultLog
if l == nil {
l = log.Default()
defaultLog = l
}
return l
}
// Get default structured logger
func slogDefault() *slog.Logger {
defaultLock.Lock()
defer defaultLock.Unlock()
l := defaultSlog
if l == nil {
l = slog.Default()
defaultSlog = l
}
return l
}
// Convert file mode string (oct like "0644") to fs.FileMode
func fileMode(mode string) fs.FileMode {
if mode == "" {
mode = FILE_MODE
}
perm, err := strconv.ParseInt(mode, 8, 10)
if err != nil {
//fmt.Fprintf(os.Stderr, "ERROR: bad logfile mode='%s'; set mode=0%03o\n",
// mode, DEFAULT_FILE_MODE)
return DEFAULT_FILE_MODE
}
return fs.FileMode(perm & 0777)
}
// Select (open) log file
func openFile(file, mode string) *os.File {
switch file {
case "stdout", "os.Stdout", "":
return os.Stdout
case "stderr", "os.Stderr":
return os.Stderr
}
perm := fileMode(mode)
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, perm)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: can't create logfile: %v; use os.Stdout\n", err)
return os.Stdout
}
return out
}
// Create custom structured logger based on default standart logger
func newSlogStd(conf Conf) (logger *slog.Logger) {
// Setup standart logger
stdlog := logDefault()
SetupLog(stdlog, conf)
level := ParseLvl(conf.Level)
if level == DEFAULT_LEVEL {
// Don't change default log level
logger = slogDefault()
} else {
// Hook to direct log level
handler := slogDefault().Handler() // slog.defaultHandler
handler = newStdHandler(level, handler)
logger = slog.New(handler)
}
if conf.AddKey != "" && conf.AddValue != "" {
logger = logger.With(conf.AddKey, conf.AddValue)
}
return logger
}
// Help wrapper to direct log level in standart logger mode
type stdHandler struct {
level Level
handler slog.Handler
}
// Create logStdHandler with the given level
func newStdHandler(level Level, h slog.Handler) *stdHandler {
// Optimization: avoid chains of logStdHandlers
if sh, ok := h.(*stdHandler); ok {
h = sh.handler
}
return &stdHandler{level, h}
}
// Enabled() implements Enabled() by reporting whether
func (h *stdHandler) Enabled(_ context.Context, level slog.Level) bool {
return Level(level) >= h.level
}
// Handle() implements Handler.Handle()
func (h *stdHandler) Handle(ctx context.Context, r slog.Record) error {
return h.handler.Handle(ctx, r)
}
// WithAttrs() implements Handler.WithAttrs()
func (h *stdHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
//if len(attrs) == 0 {
// return h
//}
return newStdHandler(h.level, h.handler.WithAttrs(attrs))
}
// WithGroup() implements Handler.WithGroup()
func (h *stdHandler) WithGroup(name string) slog.Handler {
//if name == "" {
// return h
//}
return newStdHandler(h.level, h.handler.WithGroup(name))
}
// EOF: "setup.go"