-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslogx.go
More file actions
280 lines (247 loc) · 10.2 KB
/
slogx.go
File metadata and controls
280 lines (247 loc) · 10.2 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
// Package slogx: Lightweight package around slog with caller skip support
// Provides zap.AddCallerSkip-like function that standard log/slog package lacks
// Solves caller location issues in logging assist functions with 1:1 API compatible support
//
// slogx: 提供支持 caller skip 的 slog 轻量包装器
// 为标准 log/slog 包提供类似 zap.AddCallerSkip 的功能
// 在保持 1:1 API 兼容的前提下解决日志助手函数中的调用位置问题
package slogx
import (
"context"
"fmt"
"log/slog"
"runtime"
"strconv"
"time"
)
// Logger is a lightweight package that supports AddCallerSkip-like action
// Provides 1:1 API compatible support with slog.Logger while enabling correct caller skip
// Uses runtime.Callers to adjust code position to ensure accurate source location reporting
//
// Logger 是一个轻量包装器,支持 AddCallerSkip 类似的行为
// 与 slog.Logger 提供 1:1 API 兼容性,同时支持正确的调用者跳过
// 使用 runtime.Callers 调整程序计数器,确保准确的源位置报告
type Logger struct {
slog *slog.Logger // Underlying slog.Logger instance // 底层 slog.Logger 实例
skip int // Number of stack frames to skip // 要跳过的栈帧数量
}
// New creates a package with given logger. Logger must not be none
// Sets default skip value to 4 to ensure accurate caller location reporting
// Returns reference to enable method chaining and correct cloning action
//
// New 用给定 logger 创建包装器。Logger 不能为 none
// 设置默认 skip 值为 4,确保准确的调用者位置报告
// 返回指针以支持方法链式调用和正确的克隆行为
func New(slog *slog.Logger) *Logger {
if slog == nil {
panic("slog.Logger can not be none")
}
return &Logger{
slog: slog,
skip: 4,
}
}
// WithCallerSkip returns a new Logger with extra stack frame skip
// Creates a clone with adjusted skip value in nested assist functions
// Enables accurate source location reporting in multi-stage package scenarios
//
// WithCallerSkip 返回一个新的 Logger,增加跳过的栈帧层数
// 为嵌套助手函数创建带有调整 skip 值的克隆
// 在多层包装器场景中实现准确的源位置报告
func (G *Logger) WithCallerSkip(depth int) *Logger {
return &Logger{
slog: G.slog,
skip: G.skip + depth,
}
}
// Skip is a convenience alias of WithCallerSkip method
// Provides short method name in common caller skip operations
//
// Skip 是 WithCallerSkip 方法的便利别名
// 为常用调用者跳过操作提供更短的方法名
func (G *Logger) Skip(depth int) *Logger {
return G.WithCallerSkip(depth)
}
// =============== slog-compatible no-context versions ===============
// =============== slog 兼容的无上下文版本 ===============
// Debug logs a debug message with key-value pairs
// Debug 记录带键值对的调试消息
func (G *Logger) Debug(msg string, args ...any) {
G.logKV(context.Background(), slog.LevelDebug, msg, args...)
}
// Info logs an info message with key-value pairs
// Info 记录带键值对的信息消息
func (G *Logger) Info(msg string, args ...any) {
G.logKV(context.Background(), slog.LevelInfo, msg, args...)
}
// Warn logs a warn message with key-value pairs
// Warn 记录带键值对的警告消息
func (G *Logger) Warn(msg string, args ...any) {
G.logKV(context.Background(), slog.LevelWarn, msg, args...)
}
// Error logs an error message with key-value pairs
// Error 记录带键值对的错误消息
func (G *Logger) Error(msg string, args ...any) {
G.logKV(context.Background(), slog.LevelError, msg, args...)
}
// =============== slog-compatible context versions ===============
// =============== slog 兼容的带上下文版本 ===============
// DebugContext logs a debug message with context and key-value pairs
// DebugContext 记录带上下文和键值对的调试消息
func (G *Logger) DebugContext(ctx context.Context, msg string, args ...any) {
G.logKV(ctx, slog.LevelDebug, msg, args...)
}
// InfoContext logs an info message with context and key-value pairs
// InfoContext 记录带上下文和键值对的信息消息
func (G *Logger) InfoContext(ctx context.Context, msg string, args ...any) {
G.logKV(ctx, slog.LevelInfo, msg, args...)
}
// WarnContext logs a warn message with context and key-value pairs
// WarnContext 记录带上下文和键值对的警告消息
func (G *Logger) WarnContext(ctx context.Context, msg string, args ...any) {
G.logKV(ctx, slog.LevelWarn, msg, args...)
}
// ErrorContext logs an error message with context and key-value pairs
// ErrorContext 记录带上下文和键值对的错误消息
func (G *Logger) ErrorContext(ctx context.Context, msg string, args ...any) {
G.logKV(ctx, slog.LevelError, msg, args...)
}
// =============== slog-compatible structured attribute versions ===============
// =============== slog 兼容的结构化属性版本 ===============
// Log logs a message with key-value pairs at given level (generic logging method)
// Log 在指定级别记录带键值对的消息(通用日志方法)
func (G *Logger) Log(ctx context.Context, level slog.Level, msg string, args ...any) {
G.logKV(ctx, level, msg, args...)
}
// LogAttrs logs a message with structured attributes at given level
// LogAttrs 在指定级别记录带结构化属性的消息
func (G *Logger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
G.logAttrs(ctx, level, msg, attrs...)
}
// =============== slog-compatible With and support functions ===============
// =============== slog 兼容的 With 和工具函数 ===============
// With returns a new Logger that includes the given arguments
// Creates a clone with extra context fields attached to subsequent log entries
//
// With 返回包含给定参数的新 Logger
// 创建一个带有额外上下文字段的克隆,附加到后续日志条目
func (G *Logger) With(args ...any) *Logger {
if len(args) == 0 {
return G
}
return &Logger{
slog: G.slog.With(args...),
skip: G.skip,
}
}
// WithGroup returns a new Logger that groups attributes within the given name
// Creates a clone that nests subsequent attributes within a group namespace
//
// WithGroup 返回在给定名称下分组属性的新 Logger
// 创建一个将后续属性嵌套在组命名空间下的克隆
func (G *Logger) WithGroup(name string) *Logger {
return &Logger{
slog: G.slog.WithGroup(name),
skip: G.skip,
}
}
// Enabled checks if the log instance emits log records at the given log-level
// Enabled 检查日志实例是否在给定日志级别发出日志记录
func (G *Logger) Enabled(ctx context.Context, level slog.Level) bool {
return G.slog.Enabled(ctx, level)
}
// Handler returns the base slog.Handler component
// Handler 返回底层 slog.Handler 组件
func (G *Logger) Handler() slog.Handler {
return G.slog.Handler()
}
// logKV handles KV-pairs logging with stack-frame skip
// Converts KV-pairs to structured attributes with auto conversion
//
// logKV 处理带栈帧跳过的键值对日志记录
// 自动将键值对转换为结构化属性
func (G *Logger) logKV(ctx context.Context, level slog.Level, msg string, kv ...any) {
if !G.slog.Enabled(ctx, level) {
return
}
attrs := G.kvToAttrs(kv)
G.logRecord(ctx, G.slog, level, msg, attrs)
}
// logAttrs handles structured attributes logging with stack-frame skip
// Processes pre-structured slog.Attr values with the correct invocation context
//
// logAttrs 处理带栈帧跳过的结构化属性日志记录
// 处理预结构化的 slog.Attr 值,带有正确的调用上下文
func (G *Logger) logAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
if !G.slog.Enabled(ctx, level) {
return
}
G.logRecord(ctx, G.slog, level, msg, attrs)
}
// logRecord creates and handles a log-record with the correct code position
// Uses runtime.Callers to capture accurate source location with skip adjustment
// Core implementation that enables the correct stack-frame skip function
//
// logRecord 创建并处理带有正确程序计数的日志记录
// 使用 runtime.Callers 捕获准确的源位置,并进行 skip 调整
// 实现正确栈帧跳过功能的核心实现
func (G *Logger) logRecord(ctx context.Context, l *slog.Logger, level slog.Level, msg string, attrs []slog.Attr) {
var pcs [1]uintptr
runtime.Callers(G.skip, pcs[:])
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
if len(attrs) > 0 {
r.AddAttrs(attrs...)
}
_ = l.Handler().Handle(ctx, r)
}
// kvToAttrs converts ("k1", v1, "k2", v2, ...) to []slog.Attr
// Handles odd KV-pairs via creating BAD_KEY_ entries to manage orphaned values
// Converts non-string keys using fmt.Sprintf to ensure type safe conversion
// Provides robust handling of malformed KV sequences
//
// kvToAttrs 将 ("k1", v1, "k2", v2, ...) 转为 []slog.Attr
// 通过创建 BAD_KEY_ 条目来处理奇数键值对,管理孤立值
// 使用 fmt.Sprintf 转换非字符串键,确保类型安全
// 提供对格式错误的键值序列的健壮处理
func (G *Logger) kvToAttrs(kv []any) []slog.Attr {
// Calculate capacity including potential odd value
attrs := make([]slog.Attr, 0, (len(kv)+1)/2)
// Handle paired key-value arguments
for i := 0; i+1 < len(kv); i += 2 {
k := kv[i]
v := kv[i+1]
switch key := k.(type) {
case string:
attrs = append(attrs, slog.Any(key, v))
default:
attrs = append(attrs, slog.Any(G.toString(key), v))
}
}
// Handle odd last value if exists
if len(kv)%2 == 1 {
attrs = append(attrs, slog.Any("BAD_KEY_"+strconv.Itoa(len(kv)-1), kv[(len(kv)-1)]))
}
return attrs
}
// toString converts a value to its string representation
// Uses fmt.Stringer interface when the value implements it, defaults to fmt.Sprintf
//
// toString 将一个值转换为其字符串表示
// 当值实现 fmt.Stringer 接口时使用它,默认使用 fmt.Sprintf
func (G *Logger) toString(x any) string {
switch t := x.(type) {
case string:
return t
case fmt.Stringer:
return t.String()
default:
return fmt.Sprintf("%v", x)
}
}
// =============== Extended API methods ===============
// =============== 扩展 API 方法 ===============
// Sugar returns a SugaredLogger to enable flattened argument logging
// Sugar 返回用于平铺参数日志记录的 SugaredLogger
func (G *Logger) Sugar() *SugaredLogger {
return NewSugaredLogger(G)
}