Skip to content

Commit d9ebcf7

Browse files
authored
Merge pull request #3 from sunjunnan79/main
feat:基本完成logger部分
2 parents 79913ee + 943748f commit d9ebcf7

6 files changed

Lines changed: 265 additions & 4 deletions

File tree

example/logger/logger.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"github.com/muxi-Infra/muxi-micro/pkg/errs"
6+
"github.com/muxi-Infra/muxi-micro/pkg/logger"
7+
"github.com/muxi-Infra/muxi-micro/pkg/logger/zapx"
8+
)
9+
10+
var DBNOData = errs.NewErr("db_no_data", "db has no data")
11+
12+
var UserNotFound = errs.NewErr("user_not_found", "User not found")
13+
14+
func SearchDB(id int) error {
15+
return DBNOData.WithCause(errors.New("row is 0"))
16+
}
17+
18+
func SearchUser(id int) error {
19+
err := SearchDB(id)
20+
if errors.Is(err, DBNOData) {
21+
return UserNotFound.WithCause(err).WithMeta(map[string]interface{}{
22+
"user_id": id,
23+
})
24+
}
25+
return nil
26+
}
27+
28+
func main() {
29+
l := zapx.NewDefaultZapLogger(logger.EnvTest, true, "./logs")
30+
id := 1
31+
A(id, l)
32+
l.Info("查询出错id:", id)
33+
}
34+
35+
func A(id int, l logger.Logger) {
36+
err := SearchUser(id)
37+
l.Error("查询数据库出错:", err)
38+
}

go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ module github.com/muxi-Infra/muxi-micro
33
go 1.24.2
44

55
require (
6-
github.com/davecgh/go-spew v1.1.1 // indirect
7-
github.com/pmezard/go-difflib v1.0.0 // indirect
6+
go.uber.org/zap v1.27.0
7+
gopkg.in/natefinch/lumberjack.v2 v2.2.1
8+
)
9+
10+
require (
811
github.com/stretchr/testify v1.10.0 // indirect
9-
gopkg.in/yaml.v3 v3.0.1 // indirect
12+
go.uber.org/multierr v1.10.0 // indirect
1013
)

go.sum

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
44
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
66
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
7+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
8+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
9+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
10+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
11+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
12+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
13+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
14+
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
815
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
916
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/errs/err.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func (e *Err) MetaData() map[string]interface{} {
3939
func (e *Err) Unwrap() error {
4040
return e.cause
4141
}
42+
4243
func (e *Err) Is(target error) bool {
4344
var t *Err
4445
if errors.As(target, &t) {

pkg/logger/logger.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package logger
2+
3+
type Logger interface {
4+
Info(msg string, fields ...Field)
5+
Debug(msg string, fields ...Field)
6+
Warn(msg string, fields ...Field)
7+
Error(msg string, fields ...Field)
8+
Fatal(msg string, fields ...Field)
9+
With(fields ...Field) Logger
10+
Sync() error
11+
}
12+
13+
type Field interface{}
14+
15+
// ---------- 环境枚举 ----------
16+
type Env int8
17+
18+
const (
19+
EnvUnknown Env = iota
20+
EnvDev // 开发:彩色多行栈,仅控制台
21+
EnvTest // 测试:彩色多行栈到控制台 + JSON 到文件
22+
EnvProd // 生产:全 JSON 单行
23+
)

pkg/logger/zapx/zapx.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package zapx
2+
3+
import (
4+
"fmt"
5+
"github.com/muxi-Infra/muxi-micro/pkg/logger"
6+
"go.uber.org/zap"
7+
"go.uber.org/zap/zapcore"
8+
"gopkg.in/natefinch/lumberjack.v2"
9+
"log"
10+
"os"
11+
"strings"
12+
)
13+
14+
type ZapLogger struct{ l *zap.Logger }
15+
16+
type ZapOption func(*ZapCfg)
17+
18+
type ZapCfg struct {
19+
core zapcore.Core
20+
options []zap.Option
21+
}
22+
23+
func NewDefaultZapLogger(env logger.Env, splitByLevel bool, logDir string) logger.Logger {
24+
return NewZapLogger(
25+
WithDefaultZapCore(env, splitByLevel, logDir),
26+
WithDefaultZapOptions(),
27+
)
28+
}
29+
30+
func NewZapLogger(opts ...ZapOption) logger.Logger {
31+
cfg := &ZapCfg{}
32+
for _, opt := range opts {
33+
opt(cfg)
34+
}
35+
if cfg.core == nil {
36+
log.Panic("缺少 zap-core 核心配置")
37+
}
38+
return &ZapLogger{l: zap.New(cfg.core, cfg.options...)}
39+
}
40+
41+
func WithDefaultZapCore(env logger.Env, splitByLevel bool, logDir string) ZapOption {
42+
return func(cfg *ZapCfg) {
43+
// dev 只需要 stdout,不强制创建 logDir
44+
if env != logger.EnvDev {
45+
if err := os.MkdirAll(logDir, 0755); err != nil {
46+
log.Panicf("无法创建日志目录: %v", err)
47+
}
48+
}
49+
50+
jsonEnc := zapcore.NewJSONEncoder(prodEncoderConfig())
51+
consoleEnc := zapcore.NewConsoleEncoder(devEncoderConfig())
52+
53+
switch env {
54+
// ======== DEV:彩色到控制台 ========
55+
case logger.EnvDev:
56+
cfg.core = zapcore.NewCore(consoleEnc, zapcore.AddSync(os.Stdout), zapcore.DebugLevel)
57+
return
58+
59+
// ======== TEST:控制台彩色 + 文件 JSON ========
60+
case logger.EnvTest:
61+
consoleCore := zapcore.NewCore(consoleEnc, zapcore.AddSync(os.Stdout), zapcore.DebugLevel)
62+
fileCore := buildFileCores(jsonEnc, splitByLevel, logDir, false) // 仅文件
63+
cfg.core = zapcore.NewTee(append([]zapcore.Core{consoleCore}, fileCore...)...)
64+
return
65+
66+
// ======== PROD(默认):全 JSON 单行 ========
67+
case logger.EnvProd:
68+
cores := buildFileCores(jsonEnc, splitByLevel, logDir, true) // stdout+file 共写
69+
cfg.core = zapcore.NewTee(cores...)
70+
71+
default:
72+
log.Panic("非法的环境")
73+
return
74+
}
75+
}
76+
}
77+
78+
// 允许替换 Option
79+
func WithZapOptions(opts ...zap.Option) ZapOption {
80+
return func(cfg *ZapCfg) { cfg.options = opts }
81+
}
82+
83+
// 默认 Option
84+
func WithDefaultZapOptions() ZapOption {
85+
return func(cfg *ZapCfg) {
86+
cfg.options = []zap.Option{
87+
zap.AddCaller(),
88+
zap.AddStacktrace(zapcore.WarnLevel),
89+
zap.AddCallerSkip(1),
90+
}
91+
}
92+
}
93+
94+
// 允许自定义 core
95+
func WithZapCore(core zapcore.Core) ZapOption {
96+
return func(cfg *ZapCfg) { cfg.core = core }
97+
}
98+
99+
// 构造文件相关 core;如果 withStdout=true,则 stdout 也走同 encoder(生产)
100+
func buildFileCores(enc zapcore.Encoder, split bool, dir string, withStdout bool) []zapcore.Core {
101+
var cores []zapcore.Core
102+
stdout := zapcore.AddSync(os.Stdout)
103+
104+
if !split {
105+
var ws zapcore.WriteSyncer
106+
if withStdout {
107+
ws = zapcore.NewMultiWriteSyncer(stdout, zapcore.AddSync(newRotateLogger(fmt.Sprintf("%s/app.log", dir))))
108+
} else {
109+
ws = zapcore.AddSync(newRotateLogger(fmt.Sprintf("%s/app.log", dir)))
110+
}
111+
cores = append(cores, zapcore.NewCore(enc, ws, zapcore.DebugLevel))
112+
return cores
113+
}
114+
115+
levels := []zapcore.Level{
116+
zapcore.DebugLevel, zapcore.InfoLevel, zapcore.WarnLevel,
117+
zapcore.ErrorLevel, zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel,
118+
}
119+
for _, lv := range levels {
120+
fileWS := zapcore.AddSync(newRotateLogger(fmt.Sprintf("%s/%s.log", dir, strings.ToLower(lv.String()))))
121+
var ws zapcore.WriteSyncer
122+
if withStdout {
123+
ws = zapcore.NewMultiWriteSyncer(stdout, fileWS)
124+
} else {
125+
ws = fileWS
126+
}
127+
core := zapcore.NewCore(enc, ws, zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l == lv }))
128+
cores = append(cores, core)
129+
}
130+
return cores
131+
}
132+
133+
// 生产 JSON encoder
134+
func prodEncoderConfig() zapcore.EncoderConfig {
135+
return zapcore.EncoderConfig{
136+
TimeKey: "@timestamp",
137+
LevelKey: "level",
138+
MessageKey: "msg",
139+
CallerKey: "caller",
140+
StacktraceKey: "stacktrace",
141+
EncodeLevel: zapcore.CapitalLevelEncoder,
142+
EncodeTime: zapcore.ISO8601TimeEncoder,
143+
EncodeCaller: zapcore.ShortCallerEncoder,
144+
}
145+
}
146+
147+
// 开发 / 测试 彩色 console encoder
148+
func devEncoderConfig() zapcore.EncoderConfig {
149+
c := prodEncoderConfig()
150+
c.EncodeLevel = zapcore.CapitalColorLevelEncoder
151+
return c
152+
}
153+
154+
// 滚动文件
155+
func newRotateLogger(filename string) *lumberjack.Logger {
156+
return &lumberjack.Logger{
157+
Filename: filename,
158+
MaxSize: 20,
159+
MaxBackups: 5,
160+
MaxAge: 30,
161+
Compress: true,
162+
}
163+
}
164+
165+
func (z *ZapLogger) Info(msg string, fields ...logger.Field) { z.l.Info(msg, convert(fields)...) }
166+
167+
func (z *ZapLogger) Error(msg string, fields ...logger.Field) { z.l.Error(msg, convert(fields)...) }
168+
169+
func (z *ZapLogger) Debug(msg string, fields ...logger.Field) { z.l.Debug(msg, convert(fields)...) }
170+
171+
func (z *ZapLogger) Warn(msg string, fields ...logger.Field) { z.l.Warn(msg, convert(fields)...) }
172+
173+
func (z *ZapLogger) Fatal(msg string, fields ...logger.Field) { z.l.Fatal(msg, convert(fields)...) }
174+
175+
func (z *ZapLogger) With(fields ...logger.Field) logger.Logger {
176+
return &ZapLogger{l: z.l.With(convert(fields)...)}
177+
}
178+
179+
func (z *ZapLogger) Sync() error { return z.l.Sync() }
180+
181+
func convert(fields []logger.Field) []zap.Field {
182+
var res []zap.Field
183+
for _, f := range fields {
184+
if ff, ok := f.(zap.Field); ok {
185+
res = append(res, ff)
186+
}
187+
}
188+
return res
189+
}

0 commit comments

Comments
 (0)