Skip to content

Commit 02853d1

Browse files
committed
refactor(logger): 标准化日志格式,固定字段顺序
- 实现固定字段顺序:timestamp > level > file > func > message > 自定义字段 - 引入 LogEntry 结构化日志条目,提升类型安全性 - 采用手动 JSON 构建避免 Go map 随机性问题 - 消除重复字段:移除 log_level/log_file 重复定义 - 优化日志序列化性能,使用 strings.Builder 和 sonic - 增强日志可读性和解析效率 技术改进: - logger/logger.go: 完全重构日志序列化逻辑 - main.go: 修复重复字段命名(config_level/config_file) - 字段过滤机制:自动跳过重复的系统字段 - 向后兼容:保持现有日志API接口不变 影响:显著改善调试体验和日志分析效率
1 parent 26c246a commit 02853d1

File tree

2 files changed

+97
-15
lines changed

2 files changed

+97
-15
lines changed

logger/logger.go

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,28 @@ func (l *Logger) shouldLog(level Level) bool {
141141
return atomic.LoadInt64(&l.level) <= int64(level)
142142
}
143143

144+
// LogEntry 有序日志条目结构 - 确保字段输出顺序固定
145+
type LogEntry struct {
146+
Timestamp string `json:"timestamp"`
147+
Level string `json:"level"`
148+
File string `json:"file,omitempty"`
149+
Func string `json:"func,omitempty"`
150+
Message string `json:"message"`
151+
Fields map[string]any `json:"-"` // 动态字段,单独处理
152+
}
153+
144154
// log 内部日志记录方法(优化版本)
145155
func (l *Logger) log(level Level, msg string, fields []Field) {
146156
if !l.shouldLog(level) {
147157
return
148158
}
149159

150-
// 构建日志条目
151-
entry := map[string]any{
152-
"timestamp": time.Now().Format("2006-01-02T15:04:05.000Z07:00"),
153-
"level": levelNames[level],
154-
"message": msg,
160+
// 构建标准日志条目
161+
entry := &LogEntry{
162+
Timestamp: time.Now().Format("2006-01-02T15:04:05.000Z07:00"),
163+
Level: levelNames[level],
164+
Message: msg,
165+
Fields: make(map[string]any),
155166
}
156167

157168
// 按需获取调用者信息(优化:可配置)
@@ -160,28 +171,33 @@ func (l *Logger) log(level Level, msg string, fields []Field) {
160171
if idx := strings.LastIndex(file, "/"); idx >= 0 {
161172
file = file[idx+1:]
162173
}
163-
entry["file"] = fmt.Sprintf("%s:%d", file, line)
174+
entry.File = fmt.Sprintf("%s:%d", file, line)
164175
// 提取函数名,便于快速定位日志来源
165176
if fn := runtime.FuncForPC(pc); fn != nil {
166177
name := fn.Name()
167178
// 裁剪包路径,仅保留最后的符号名
168179
if dot := strings.LastIndex(name, "."); dot >= 0 && dot < len(name)-1 {
169180
name = name[dot+1:]
170181
}
171-
entry["func"] = name
182+
entry.Func = name
172183
}
173184
}
174185
}
175186

176-
// 添加字段
187+
// 收集动态字段,过滤重复的系统字段
177188
for _, field := range fields {
178-
entry[field.Key] = field.Value
189+
// 跳过重复的系统字段
190+
if field.Key == "level" || field.Key == "log_level" ||
191+
field.Key == "timestamp" || field.Key == "message" ||
192+
field.Key == "file" || field.Key == "log_file" ||
193+
field.Key == "func" {
194+
continue
195+
}
196+
entry.Fields[field.Key] = field.Value
179197
}
180198

181-
// 优化的JSON序列化(使用sonic高性能库)
182-
var jsonData []byte
183-
// 使用sonic的高性能序列化
184-
jsonData, _ = sonic.Marshal(entry)
199+
// 使用自定义序列化确保字段顺序
200+
jsonData := l.marshalLogEntry(entry)
185201

186202
// 直接输出日志 - log.Logger本身已经线程安全!
187203
l.logger.Println(string(jsonData))
@@ -192,6 +208,71 @@ func (l *Logger) log(level Level, msg string, fields []Field) {
192208
}
193209
}
194210

211+
// marshalLogEntry 自定义日志条目序列化,确保字段顺序
212+
func (l *Logger) marshalLogEntry(entry *LogEntry) []byte {
213+
// 手动构建JSON字符串确保字段顺序:timestamp > level > file > func > message > 其他字段
214+
var b strings.Builder
215+
b.WriteString(`{"timestamp":"`)
216+
b.WriteString(entry.Timestamp)
217+
b.WriteString(`","level":"`)
218+
b.WriteString(entry.Level)
219+
b.WriteString(`"`)
220+
221+
// 添加可选字段
222+
if entry.File != "" {
223+
b.WriteString(`,"file":"`)
224+
b.WriteString(entry.File)
225+
b.WriteString(`"`)
226+
}
227+
if entry.Func != "" {
228+
b.WriteString(`,"func":"`)
229+
b.WriteString(entry.Func)
230+
b.WriteString(`"`)
231+
}
232+
233+
b.WriteString(`,"message":"`)
234+
// 转义message中的特殊字符
235+
escapedMsg, _ := sonic.MarshalString(entry.Message)
236+
// 移除外层引号
237+
if len(escapedMsg) >= 2 {
238+
b.WriteString(escapedMsg[1:len(escapedMsg)-1])
239+
}
240+
b.WriteString(`"`)
241+
242+
// 添加动态字段(按键名排序确保一致性)
243+
if len(entry.Fields) > 0 {
244+
// 对字段名进行排序确保输出一致性
245+
var keys []string
246+
for k := range entry.Fields {
247+
keys = append(keys, k)
248+
}
249+
// 简单排序(保持性能)
250+
for i := 0; i < len(keys)-1; i++ {
251+
for j := i + 1; j < len(keys); j++ {
252+
if keys[i] > keys[j] {
253+
keys[i], keys[j] = keys[j], keys[i]
254+
}
255+
}
256+
}
257+
258+
for _, k := range keys {
259+
v := entry.Fields[k]
260+
b.WriteString(`,"`)
261+
b.WriteString(k)
262+
b.WriteString(`":`)
263+
// 序列化字段值
264+
if fieldJSON, err := sonic.Marshal(v); err == nil {
265+
b.Write(fieldJSON)
266+
} else {
267+
b.WriteString(`null`)
268+
}
269+
}
270+
}
271+
272+
b.WriteString(`}`)
273+
return []byte(b.String())
274+
}
275+
195276
// SetLevel 设置日志级别(优化:原子操作)
196277
func SetLevel(level Level) {
197278
atomic.StoreInt64(&defaultLogger.level, int64(level))

main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ func main() {
2020
logger.Reinitialize()
2121

2222
// 显示当前日志级别设置(仅在DEBUG级别时显示详细信息)
23+
// 注意:移除重复的系统字段,这些信息已包含在日志结构中
2324
logger.Debug("日志系统初始化完成",
24-
logger.String("log_level", os.Getenv("LOG_LEVEL")),
25-
logger.String("log_file", os.Getenv("LOG_FILE")))
25+
logger.String("config_level", os.Getenv("LOG_LEVEL")),
26+
logger.String("config_file", os.Getenv("LOG_FILE")))
2627

2728
// 🚀 启动时主动初始化token系统
2829
logger.Info("正在初始化token系统...")

0 commit comments

Comments
 (0)