├── internal ├── sketches │ ├── sketch1 │ │ ├── sketch1.go │ │ └── sketch1_test.go │ └── sketch2 │ │ ├── sketch2.go │ │ └── sketch2_test.go ├── demos │ ├── main2x │ │ └── main.go │ ├── main3x │ │ └── main.go │ └── main1x │ │ └── main.go └── utils │ ├── utils_test.go │ └── utils.go ├── values_test.go ├── zaplogw ├── zap_logw_test.go └── zap_logw.go ├── Makefile ├── skip_test.go ├── .gitignore ├── skips_test.go ├── zaplogs ├── zap_stdout_test.go ├── zap_stdout.go ├── zap_logs_test.go └── zap_logs.go ├── go.mod ├── LICENSE ├── newzap_test.go ├── subzap_test.go ├── newzap.go ├── config_test.go ├── subzap.go ├── values.go ├── skips.go ├── skip.go ├── zaplumberjack ├── zap_lumberjack_test.go └── zap_lumberjack.go ├── go.sum ├── .github └── workflows │ └── release.yml ├── config.go ├── README.zh.md └── README.md /internal/sketches/sketch1/sketch1.go: -------------------------------------------------------------------------------- 1 | package sketch1 2 | -------------------------------------------------------------------------------- /internal/sketches/sketch2/sketch2.go: -------------------------------------------------------------------------------- 1 | package sketch2 2 | -------------------------------------------------------------------------------- /values_test.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func TestZapLog(t *testing.T) { 10 | ZAP.LOG.Debug("abc", zap.Int("num", 123)) 11 | ZAP.SUG.Debug("abc", "-|-", "num", "-|-", 123) 12 | } 13 | -------------------------------------------------------------------------------- /zaplogw/zap_logw_test.go: -------------------------------------------------------------------------------- 1 | package zaplogw 2 | 3 | import ( 4 | "log/slog" 5 | "testing" 6 | 7 | "github.com/yyle88/zaplog" 8 | ) 9 | 10 | func TestZapLogw_Debug(t *testing.T) { 11 | zpw := NewZapLogw(zaplog.ZAPS.Skip1.SUG) 12 | zpw.Debug("abc") 13 | zpw.Debug("xyz", "num", 123) 14 | zpw.Debug("uvw", "res", "x") 15 | } 16 | 17 | func TestSlogExample(t *testing.T) { 18 | slog.Info("message", "x", 1, "y", 2, "z", 3) 19 | } 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COVERAGE_DIR ?= .coverage.out 2 | 3 | # cp from: https://github.com/yyle88/mutexmap/blob/842d6f3d77bba067fd85e355b5b4ab896f712070/Makefile#L4 4 | test: 5 | @if [ -d $(COVERAGE_DIR) ]; then rm -r $(COVERAGE_DIR); fi 6 | @mkdir $(COVERAGE_DIR) 7 | make test-with-flags TEST_FLAGS='-v -race -covermode atomic -coverprofile $$(COVERAGE_DIR)/combined.txt -bench=. -benchmem -timeout 20m' 8 | 9 | test-with-flags: 10 | @go test $(TEST_FLAGS) ./... 11 | -------------------------------------------------------------------------------- /skip_test.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func TestSkipLogs_Skip(t *testing.T) { 10 | LOGS.Skip(0).Debug("s", zap.Int("i", 0)) 11 | func() { 12 | LOGS.Skip(1).Debug("s", zap.Int("i", 1)) 13 | }() 14 | 15 | caseSkipLogs(t, 1) 16 | } 17 | 18 | func caseSkipLogs(t *testing.T, skipDepth int) { 19 | zapLog := LOGS.Skip(skipDepth) 20 | zapLog.Debug("abc", zap.Int("skip", skipDepth)) 21 | if skipDepth < 10 { 22 | caseSkipLogs(t, skipDepth+1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/demos/main2x/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/yyle88/zaplog" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | func main() { 9 | { 10 | zaplog.LOG.Info("abc", zap.String("xyz", "uvw")) 11 | zaplog.LOG.Error("abc", zap.String("xyz", "uvw")) 12 | zaplog.LOG.Debug("abc", zap.String("xyz", "uvw")) 13 | zaplog.LOG.Warn("abc", zap.String("xyz", "uvw")) 14 | } 15 | { 16 | zaplog.SUG.Infof("abc xyz=%v", "uvw") 17 | zaplog.SUG.Errorf("abc xyz=%v", "uvw") 18 | zaplog.SUG.Debugf("abc xyz=%v", "uvw") 19 | zaplog.SUG.Warnf("abc xyz=%v", "uvw") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | -------------------------------------------------------------------------------- /skips_test.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func TestSkipZaps_Skip0(t *testing.T) { 10 | ZAPS.Skip0.LOG.Debug("msg") 11 | } 12 | 13 | func TestSkipZaps_Skip(t *testing.T) { 14 | ZAPS.Skip(0).LOG.Debug("s", zap.Int("i", 0)) 15 | func() { 16 | ZAPS.Skip(1).LOG.Debug("s", zap.Int("i", 1)) 17 | }() 18 | 19 | caseSkipZaps(t, 1) 20 | } 21 | 22 | func caseSkipZaps(t *testing.T, skipDepth int) { 23 | zpn := ZAPS.Skip(skipDepth) 24 | zpn.LOG.Debug("abc", zap.Int("skip", skipDepth)) 25 | if skipDepth < 10 { 26 | caseSkipLogs(t, skipDepth+1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/sketches/sketch1/sketch1_test.go: -------------------------------------------------------------------------------- 1 | package sketch1 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/stretchr/testify/require" 8 | "github.com/yyle88/zaplog" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | func TestExample(t *testing.T) { 13 | //假如你不想让自定义日志打印,也可以这样 14 | zaplog.SetLog(zap.NewNop()) 15 | 16 | zaplog.LOG.Debug("abc") //这条日志不会被打印 17 | 18 | require.Panics(t, func() { 19 | zaplog.LOG.Panic("abc") //这条日志不会被打印,但还是会 panic(ce.Message) 的 20 | }) 21 | 22 | require.Panics(t, func() { 23 | zaplog.LOG.Panic("abc", zap.Error(errors.New("wrong"))) //这里panic不会打印wrong信息 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /zaplogs/zap_stdout_test.go: -------------------------------------------------------------------------------- 1 | package zaplogs 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | func TestGetStdoutZapLogger(t *testing.T) { 11 | zapLog := GetStdoutZapLogger(zapcore.DebugLevel) 12 | zapLog.Debug("abc") 13 | zapLog.Error("abc", zap.String("xyz", "uvw")) 14 | zapLog.Info("123") 15 | zapLog.Warn("abc") 16 | } 17 | 18 | func TestNewStdoutZapLogger(t *testing.T) { 19 | zapLog := NewStdoutZapLogger(false, zapcore.DebugLevel, 0) 20 | zapLog.Debug("abc") 21 | zapLog.Error("abc", zap.String("xyz", "uvw")) 22 | zapLog.Info("123") 23 | zapLog.Warn("abc") 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yyle88/zaplog 2 | 3 | go 1.22.6 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/stretchr/testify v1.11.1 8 | github.com/yyle88/mutexmap v1.0.15 9 | go.uber.org/zap v1.27.1 10 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 15 | github.com/kr/pretty v0.3.1 // indirect 16 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 17 | github.com/rogpeppe/go-internal v1.13.1 // indirect 18 | go.uber.org/multierr v1.11.0 // indirect 19 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /internal/demos/main3x/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/yyle88/zaplog" 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func main() { 10 | { 11 | zapLog, err := zaplog.NewZapLog(zaplog.NewConfig()) 12 | if err != nil { 13 | panic(errors.Wrap(err, "wrong")) 14 | } 15 | zaplog.SetLog(zapLog.With(zap.String("kkk", "vvv"))) 16 | } 17 | { 18 | zaplog.LOG.Info("abc", zap.String("xyz", "uvw")) 19 | zaplog.LOG.Error("abc", zap.String("xyz", "uvw")) 20 | zaplog.LOG.Debug("abc", zap.String("xyz", "uvw")) 21 | zaplog.LOG.Warn("abc", zap.String("xyz", "uvw")) 22 | } 23 | { 24 | zaplog.SUG.Infof("abc xyz=%v", "uvw") 25 | zaplog.SUG.Errorf("abc xyz=%v", "uvw") 26 | zaplog.SUG.Debugf("abc xyz=%v", "uvw") 27 | zaplog.SUG.Warnf("abc xyz=%v", "uvw") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestGetValuePointer(t *testing.T) { 10 | type exampleType struct { 11 | v string 12 | } 13 | a := exampleType{v: "a"} 14 | p := GetValuePointer(a) 15 | require.Equal(t, "a", p.v) 16 | } 17 | 18 | func TestGetPointerValue(t *testing.T) { 19 | type exampleType struct { 20 | v string 21 | } 22 | p := &exampleType{v: "a"} 23 | a := GetPointerValue(p) 24 | require.Equal(t, "a", a.v) 25 | } 26 | 27 | func TestPathUnescape(t *testing.T) { 28 | raw := "github.com/yyle88/zaplog/internal/examples/example1x/ZLG%e6%b5%8b%e9%9d%9eASCII%e8%b7%af%e5%be%84.TestZapLog" 29 | //goland:noinspection GoPrintFunctions 30 | t.Log(raw) 31 | res := PathUnescape(raw) 32 | t.Log(res) 33 | } 34 | 35 | func TestRemoveDuplicate(t *testing.T) { 36 | a := []string{"a", "b", "c", "a", "b"} 37 | b := RemoveDuplicate(a) 38 | require.EqualValues(t, []string{"a", "b", "c"}, b) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 yangyile-yyle88 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/sketches/sketch2/sketch2_test.go: -------------------------------------------------------------------------------- 1 | package sketch2 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/yyle88/zaplog" 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zaptest/observer" 10 | ) 11 | 12 | func TestExample(t *testing.T) { 13 | core, logs := observer.New(zap.InfoLevel) 14 | zaplog.SetLog(zap.New(core)) 15 | 16 | zaplog.LOG.Debug("abc") 17 | t.Log(logs.Len()) // output: 1. Because we set the level to InfoLevel, Debug messages are not in logs. 18 | 19 | zaplog.LOG.Info("123") 20 | t.Log(logs.Len()) 21 | 22 | zaplog.LOG.Info("xyz", zap.Int("num", 1024), zap.String("msg", "ok")) 23 | t.Log(logs.Len()) 24 | 25 | entries := logs.All() 26 | require.Len(t, entries, 2) 27 | { 28 | item := entries[0] 29 | require.Equal(t, zap.InfoLevel, item.Level) 30 | require.Equal(t, "123", item.Message) 31 | require.Empty(t, item.ContextMap()) 32 | } 33 | { 34 | item := entries[1] 35 | require.Equal(t, zap.InfoLevel, item.Level) 36 | require.Equal(t, "xyz", item.Message) 37 | require.Equal(t, int64(1024), item.ContextMap()["num"]) 38 | require.Equal(t, "ok", item.ContextMap()["msg"]) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /newzap_test.go: -------------------------------------------------------------------------------- 1 | package zaplog_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/yyle88/zaplog" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | func TestDebug(t *testing.T) { 12 | zaplog.LOG.Debug("1", zap.String("a", "0")) 13 | zaplog.LOG.Error("1", zap.Error(errors.New("e"))) 14 | } 15 | 16 | func TestError(t *testing.T) { 17 | zaplog.LOG.Debug("1", zap.String("a", "0")) 18 | zaplog.LOG.Error("1", zap.Error(errors.New("e"))) 19 | } 20 | 21 | func TestDebug2(t *testing.T) { 22 | zaplog.LOG.Debug("ok", zap.String("A", "aaa")) 23 | zaplog.LOG.Debug("ok", zap.String("B", "bbb")) 24 | } 25 | 26 | func TestDebug3(t *testing.T) { 27 | zaplog.LOG.Debug("1") 28 | zaplog.LOG.Error("2", zap.Error(errors.New("x"))) 29 | } 30 | 31 | func TestDebug4(t *testing.T) { 32 | zaplog.SUG.Debug("1") 33 | zaplog.SUG.Error("2", errors.New("x")) 34 | } 35 | 36 | func TestDebug5(t *testing.T) { 37 | zaplog.SUG.Debug(1, 2, 3, 4, 5, 6) 38 | zaplog.SUG.Debugln(1, 2, 3, 4, 5, 6) 39 | zaplog.SUG.Debug("1", 2, 3, 4, "5", 6) 40 | zaplog.SUG.Debugln("1", 2, 3, 4, "5", 6) 41 | zaplog.SUG.Debug() 42 | zaplog.SUG.Debug(0) 43 | zaplog.SUG.Debug([]int{0, 1, 2, 3, 4}) 44 | } 45 | -------------------------------------------------------------------------------- /zaplogs/zap_stdout.go: -------------------------------------------------------------------------------- 1 | package zaplogs 2 | 3 | import ( 4 | "os" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | // GetStdoutZapLogger creates a log that outputs to stdout with debug mode on 11 | // Handy function with default settings 12 | // 13 | // GetStdoutZapLogger 创建输出到 stdout 且启用调试模式的日志实例 14 | // 带有默认设置的便捷函数 15 | func GetStdoutZapLogger(level zapcore.Level) *zap.Logger { 16 | return NewStdoutZapLogger(true, level, 0) 17 | } 18 | 19 | // NewStdoutZapLogger creates a log that outputs to stdout with custom settings 20 | // The skipDepth adjusts skip depth, handy when wrapping log in multiple levels 21 | // Each wrap level should increment skipDepth by 1 to show correct call location 22 | // 23 | // NewStdoutZapLogger 创建输出到 stdout 且可自定义设置的日志实例 24 | // skipDepth 调整跳过深度,在多层封装日志时很有用 25 | // 每层封装应将 skipDepth 增加 1 以显示正确的调用位置 26 | func NewStdoutZapLogger(debug bool, level zapcore.Level, skipDepth int) *zap.Logger { 27 | writeSyncer := zapcore.AddSync(os.Stdout) 28 | cores := make([]zapcore.Core, 0) 29 | 30 | encoder := NewEncoder(debug) 31 | 32 | cores = append(cores, zapcore.NewCore(encoder, writeSyncer, level)) 33 | tee := zapcore.NewTee(cores...) 34 | 35 | options := NewLoggerOptionsWithSkip(debug, skipDepth) 36 | 37 | zapLog := zap.New(tee, options...) // Create log with adjusted stack depth // 创建调整堆栈深度的日志实例 38 | return zapLog 39 | } 40 | -------------------------------------------------------------------------------- /internal/demos/main1x/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/yyle88/zaplog" 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func main() { 10 | { 11 | zaplog.LOG.Info("abc", zap.String("xyz", "uvw")) 12 | show(zaplog.LOG) 13 | } 14 | { 15 | zaplog.LOGS.Skip0.Info("abc", zap.String("xyz", "uvw")) 16 | show(zaplog.LOGS.Skip1) 17 | } 18 | { 19 | config := zaplog.NewZapConfig(true, "debug", []string{"stdout"}) 20 | zapLog, err := config.Build() 21 | if err != nil { 22 | panic(errors.WithMessage(err, "wrong")) 23 | } 24 | zapLog.Info("abc", zap.String("xyz", "uvw")) 25 | show(zapLog) 26 | } 27 | { 28 | config := zaplog.NewZapConfig(false, "debug", []string{"stdout"}) 29 | zapLog, err := config.Build() 30 | if err != nil { 31 | panic(errors.WithMessage(err, "wrong")) 32 | } 33 | zapLog.Info("abc", zap.String("xyz", "uvw")) 34 | show(zapLog) 35 | } 36 | } 37 | 38 | func show(zapLog *zap.Logger) { 39 | zapLog.Error("abc", zap.String("xyz", "uvw")) 40 | show2(zapLog) 41 | zapLog.Info("========================================") 42 | } 43 | 44 | func show2(zapLog *zap.Logger) { 45 | zapLog = zapLog.With(zap.String("step2", "show2")) 46 | zapLog.Debug("abc", zap.String("xyz", "uvw")) 47 | show3(zapLog) 48 | } 49 | 50 | func show3(zapLog *zap.Logger) { 51 | zapLog = zapLog.With(zap.String("step3", "show3")) 52 | zapLog.Warn("abc", zap.String("xyz", "uvw")) 53 | } 54 | -------------------------------------------------------------------------------- /subzap_test.go: -------------------------------------------------------------------------------- 1 | package zaplog_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yyle88/zaplog" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | func TestZap_SubZap(t *testing.T) { 11 | zp := zaplog.LOGGER.SubZap(zap.String("module", "abc"), zap.String("K", "V")) 12 | zp.LOG.Debug("msg", zap.Int("a", 1), zap.Int("b", 2)) 13 | zp.SUG.Debug(1, 2, 3) 14 | zp.LOG.Error("msg", zap.Int("a", 1), zap.Int("b", 2)) 15 | zp.SUG.Error(1, 2, 3) 16 | } 17 | 18 | func TestZap_NewZap(t *testing.T) { 19 | zp := zaplog.LOGGER.NewZap("module", "abc", zap.String("K", "V")) 20 | zp.LOG.Debug("msg", zap.Int("a", 1), zap.Int("b", 2)) 21 | zp.SUG.Debug(1, 2, 3) 22 | zp.LOG.Error("msg", zap.Int("a", 1), zap.Int("b", 2)) 23 | zp.SUG.Error(1, 2, 3) 24 | } 25 | 26 | func TestZap_SkipLog(t *testing.T) { 27 | zapLog := zaplog.LOGGER.SubZap(zap.String("module", "skip-log-test-case")) 28 | zapLog.LOG.Debug("msg", zap.Int("a", 1), zap.Int("b", 2)) 29 | 30 | run := func(t *testing.T) { 31 | subLog := zapLog.SkipLog(1, zap.Int("c", 3)) 32 | subLog.Debug("abc", zap.Int("a", 1), zap.Int("b", 2)) 33 | } 34 | run(t) 35 | } 36 | 37 | func TestZap_SkipZap(t *testing.T) { 38 | zapLog := zaplog.LOGGER.SubZap(zap.String("module", "skip-zap-test-case")) 39 | zapLog.LOG.Debug("msg", zap.Int("a", 1), zap.Int("b", 2)) 40 | 41 | run := func(t *testing.T) { 42 | subZap := zapLog.SkipZap(1, zap.Int("c", 3)) 43 | subZap.LOG.Debug("abc", zap.Int("a", 1), zap.Int("b", 2)) 44 | } 45 | run(t) 46 | } 47 | -------------------------------------------------------------------------------- /newzap.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // Zap wraps zap.Logger and zap.SugaredLogger in one instance 9 | // Provides both structured and sugared logging interfaces 10 | // 11 | // Zap 将 zap.Logger 和 zap.SugaredLogger 封装在一个实例中 12 | // 同时提供结构化和简化的日志接口 13 | type Zap struct { 14 | LOG *zap.Logger // Structured log with type-safe fields // 带有类型安全字段的结构化日志 15 | SUG *zap.SugaredLogger // Sugared log with concise API // 简化 API 的 sugared 日志 16 | } 17 | 18 | // NewZap creates a new Zap instance from zap.Logger 19 | // Auto creates SugaredLogger from the given log instance 20 | // 21 | // NewZap 从 zap.Logger 创建新的 Zap 实例 22 | // 自动从给定的日志实例创建 SugaredLogger 23 | func NewZap(zapLog *zap.Logger) *Zap { 24 | return &Zap{ 25 | LOG: zapLog, 26 | SUG: zapLog.Sugar(), 27 | } 28 | } 29 | 30 | // NewZapSkip creates a new Zap instance with adjusted skip depth 31 | // Helps when wrapping log in functions 32 | // 33 | // NewZapSkip 创建带有调整跳过深度的新 Zap 实例 34 | // 在函数中封装日志时很有用 35 | func NewZapSkip(zapLog *zap.Logger, skipDepth int) *Zap { 36 | return NewZap(zapLog.WithOptions(zap.AddCallerSkip(skipDepth))) 37 | } 38 | 39 | // MustNewZap creates a new Zap instance from Config, panics on error 40 | // Use when log creation must succeed 41 | // 42 | // MustNewZap 从 Config 创建新的 Zap 实例,出错时 panic 43 | // 在日志实例创建必须成功时使用 44 | func MustNewZap(cfg *Config) *Zap { 45 | zapLog, err := NewZapLog(cfg) 46 | if err != nil { 47 | panic(errors.Wrap(err, "ERROR WHEN NEW ZAP LOG")) 48 | } 49 | return NewZap(zapLog) 50 | } 51 | 52 | // Close flushes any buffered log entries 53 | // Returns error if sync fails 54 | // 55 | // Close 刷新所有缓冲的日志条目 56 | // 如果同步失败则返回错误 57 | func (T *Zap) Close() error { 58 | return T.LOG.Sync() 59 | } 60 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | // Package utils: Internal functions used by zaplog package 2 | // Provides generic operations and string processing functions 3 | // 4 | // utils: zaplog 包使用的内部函数 5 | // 提供泛型操作函数和字符串处理函数 6 | package utils 7 | 8 | import "net/url" 9 | 10 | // GetValuePointer returns a pointer to the given value 11 | // Handy when you need a pointer to a constant or temp value 12 | // 13 | // GetValuePointer 返回给定值的指针 14 | // 当需要常量或临时值的指针时很有用 15 | func GetValuePointer[T any](v T) *T { 16 | return &v 17 | } 18 | 19 | // GetPointerValue returns the value that the pointer references 20 | // Returns zero value if pointer is nil 21 | // 22 | // GetPointerValue 返回指针引用的值 23 | // 如果指针为 nil 则返回零值 24 | func GetPointerValue[T any](v *T) T { 25 | if v != nil { 26 | return *v 27 | } 28 | return Zero[T]() 29 | } 30 | 31 | // Zero returns the zero value of type T 32 | // Zero 返回类型 T 的零值 33 | func Zero[T any]() T { 34 | var zero T 35 | return zero 36 | } 37 | 38 | // PathUnescape decodes URL path-encoded string 39 | // Returns source string if decoding fails 40 | // 41 | // PathUnescape 解码 URL 路径编码的字符串 42 | // 如果解码失败则返回源字符串 43 | func PathUnescape(raw string) string { 44 | res, err := url.PathUnescape(raw) // Handle non-ASCII characters // 处理非 ASCII 字符 45 | if err != nil { 46 | return raw // Return original on error // 出错时返回原始值 47 | } 48 | return res 49 | } 50 | 51 | // RemoveDuplicate removes duplicate elements from slice and keeps the sequence 52 | // Uses map-based seen tracking 53 | // 54 | // RemoveDuplicate 移除切片中的重复元素并保持顺序 55 | // 使用基于 map 的已见追踪 56 | func RemoveDuplicate[T comparable](values []T) []T { 57 | results := make([]T, 0, len(values)) 58 | seen := make(map[T]struct{}, len(values)) 59 | for _, v := range values { 60 | if _, ok := seen[v]; !ok { 61 | seen[v] = struct{}{} 62 | results = append(results, v) 63 | } 64 | } 65 | return results 66 | } 67 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package zaplog_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | "github.com/yyle88/zaplog" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | func TestNewZapConfig(t *testing.T) { 14 | { 15 | config := zaplog.NewZapConfig(true, "debug", []string{"stdout"}) 16 | zapLog, err := config.Build() 17 | require.NoError(t, err) 18 | zapLog.Info("abc", zap.String("xyz", "uvw")) 19 | zapLog.Error("abc", zap.String("xyz", "uvw")) 20 | zapLog.Debug("abc", zap.String("xyz", "uvw")) 21 | zapLog.Warn("abc", zap.String("xyz", "uvw")) 22 | } 23 | { 24 | config := zaplog.NewZapConfig(false, "debug", []string{"stdout"}) 25 | zapLog, err := config.Build() 26 | require.NoError(t, err) 27 | zapLog.Info("abc", zap.String("xyz", "uvw")) 28 | zapLog.Error("abc", zap.String("xyz", "uvw")) 29 | zapLog.Debug("abc", zap.String("xyz", "uvw")) 30 | zapLog.Warn("abc", zap.String("xyz", "uvw")) 31 | } 32 | } 33 | 34 | func TestConfig_AddOutputPaths(t *testing.T) { 35 | tempRoot := t.TempDir() 36 | path := filepath.Join(tempRoot, "test.log") 37 | t.Log(path) 38 | 39 | cfg := zaplog.NewConfig().AddOutputPaths(path) 40 | 41 | logger, err := zaplog.NewZapLog(cfg) 42 | require.NoError(t, err) 43 | 44 | logger.Info("perfect zaplog implementation", zap.String("status", "success")) 45 | 46 | // https://github.com/uber-go/zap/issues/991 47 | // Skip sync to avoid stdout sync errors - it's not the fault of this code, even Google's package has this quirk 48 | // Sometimes the best solution is to accept what we cannot change and focus on what is important 49 | // 跳过 sync 以避免 stdout 同步错误 - 这不是我们的锅,连谷歌的库都有这个毛病 50 | // 有时候最好的解决方案就是接受无法改变的,专注于真正重要的事情 51 | 52 | content, err := os.ReadFile(path) 53 | require.NoError(t, err) 54 | t.Log(string(content)) 55 | require.Contains(t, string(content), "perfect zaplog implementation") 56 | } 57 | -------------------------------------------------------------------------------- /subzap.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import "go.uber.org/zap" 4 | 5 | // SubLog creates a sub-log with extra fields attached 6 | // Fields are added to each log item from the returned instance 7 | // 8 | // SubLog 创建带有额外字段的子日志实例 9 | // 字段会添加到返回实例的每条日志记录中 10 | func (T *Zap) SubLog(field zap.Field, fields ...zap.Field) *zap.Logger { 11 | return T.LOG.With(field).With(fields...) 12 | } 13 | 14 | // NewLog creates a sub-log with a string key-value as context 15 | // Handy method when adding simple string field context 16 | // 17 | // NewLog 使用字符串键值作为上下文创建子日志实例 18 | // 添加简单字符串字段上下文时的便捷方法 19 | func (T *Zap) NewLog(k string, v string, fields ...zap.Field) *zap.Logger { 20 | return T.LOG.With(zap.String(k, v)).With(fields...) 21 | } 22 | 23 | // SubZap creates a sub-Zap instance with extra fields 24 | // Both LOG and SUG in the returned Zap include the fields 25 | // 26 | // SubZap 创建带有额外字段的子 Zap 实例 27 | // 返回的 Zap 中的 LOG 和 SUG 都包含这些字段 28 | func (T *Zap) SubZap(field zap.Field, fields ...zap.Field) *Zap { 29 | return NewZap(T.SubLog(field, fields...)) 30 | } 31 | 32 | // NewZap creates a sub-Zap instance with a string key-value as context 33 | // Handy method when adding simple string field context 34 | // 35 | // NewZap 使用字符串键值作为上下文创建子 Zap 实例 36 | // 添加简单字符串字段上下文时的便捷方法 37 | func (T *Zap) NewZap(k string, v string, fields ...zap.Field) *Zap { 38 | return NewZap(T.NewLog(k, v, fields...)) 39 | } 40 | 41 | // SkipLog creates a log with adjusted skip depth 42 | // Helps when wrapping log calls in functions 43 | // 44 | // SkipLog 创建调整了跳过深度的日志实例 45 | // 在函数中封装日志调用时很有用 46 | func (T *Zap) SkipLog(skipDepth int, fields ...zap.Field) *zap.Logger { 47 | return T.LOG.WithOptions(zap.AddCallerSkip(skipDepth)).With(fields...) 48 | } 49 | 50 | // SkipZap creates a Zap instance with adjusted skip depth 51 | // Helps when wrapping log calls in functions 52 | // 53 | // SkipZap 创建调整了跳过深度的 Zap 实例 54 | // 在函数中封装日志调用时很有用 55 | func (T *Zap) SkipZap(skipDepth int, fields ...zap.Field) *Zap { 56 | return NewZap(T.SkipLog(skipDepth).With(fields...)) 57 | } 58 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | // Package zaplog: Flexible high-performance logging package built on uber-go/zap 2 | // Provides package-wide instances, configuration management, and stack skip tools 3 | // Supports both structured logging (LOG) and sugared logging (SUG) interfaces 4 | // 5 | // zaplog: 基于 uber-go/zap 构建的灵活高性能日志包 6 | // 提供包级日志实例、配置管理和调用栈跳过工具 7 | // 同时支持结构化日志 (LOG) 和简化日志 (SUG) 接口 8 | package zaplog 9 | 10 | import "go.uber.org/zap" 11 | 12 | // LOGGER is the package-wide Zap instance with default configuration 13 | // LOGGER 是使用默认配置的包级 Zap 实例 14 | var LOGGER = MustNewZap(NewConfig()) 15 | 16 | // ZAP is an alias of LOGGER 17 | // ZAP 是 LOGGER 的别名 18 | var ZAP = LOGGER 19 | 20 | // LOG is the most common structured log instance 21 | // LOG 是最常用的结构化日志实例 22 | var LOG = LOGGER.LOG 23 | 24 | // SUG is the sugared log instance with concise API 25 | // SUG 是具有简化 API 的 sugared 日志实例 26 | var SUG = LOGGER.SUG 27 | 28 | // LOGS provides pre-configured logs with various skip depths 29 | // LOGS 提供预配置的不同跳过深度的日志实例 30 | var LOGS = NewSkipLogs(LOG) 31 | 32 | // ZAPS provides pre-configured Zap instances with various skip depths 33 | // ZAPS 提供预配置的不同跳过深度的 Zap 实例 34 | var ZAPS = NewSkipZaps(LOG) 35 | 36 | // SetLog replaces package-wide log variables with custom zap.Logger instance 37 | // Use this function when default log format does not meet the needs 38 | // Package-wide variables make logging handy, packages can define log format and use it 39 | // Recommended to call at system startup, not in business logic execution 40 | // Unit tests show how to reset custom log printing 41 | // 42 | // SetLog 用自定义 zap.Logger 实例替换包级日志变量 43 | // 当默认日志格式不满足需求时使用此函数 44 | // 包级变量让日志使用更方便,其它包可以定义日志格式直接使用 45 | // 推荐在系统启动时调用,而不要在业务逻辑执行期间调用 46 | // 单元测试代码演示了如何重置自定义日志打印 47 | func SetLog(zapLog *zap.Logger) { 48 | LOGGER = NewZap(zapLog) 49 | ZAP = LOGGER 50 | LOG = LOGGER.LOG // Functions below depend on this variable and will reconstruct new objects // 下面的函数依赖于此变量,它们会重新构造新对象 51 | SUG = LOGGER.SUG 52 | 53 | LOGS = NewSkipLogs(zapLog) 54 | ZAPS = NewSkipZaps(zapLog) 55 | } 56 | -------------------------------------------------------------------------------- /skips.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "github.com/yyle88/mutexmap" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // SkipZaps provides pre-configured Zap instances with various skip depths 9 | // Contains common skip depths (0-4) and caches on-demand created ones 10 | // 11 | // SkipZaps 提供预配置的不同跳过深度的 Zap 实例 12 | // 包含常用的跳过深度 (0-4) 并缓存按需创建的实例 13 | type SkipZaps struct { 14 | amino *zap.Logger // Base log instance // 基础日志实例 15 | Skip0 *Zap // No caller skip // 无调用栈跳过 16 | Skip1 *Zap // Skip 1 caller frame // 跳过 1 层调用栈 17 | Skip2 *Zap // Skip 2 caller frames // 跳过 2 层调用栈 18 | Skip3 *Zap // Skip 3 caller frames // 跳过 3 层调用栈 19 | Skip4 *Zap // Skip 4 caller frames // 跳过 4 层调用栈 20 | cache *mutexmap.Map[int, *Zap] // Cache of on-demand created Zap instances // 按需创建的 Zap 实例缓存 21 | } 22 | 23 | // NewSkipZaps creates a new SkipZaps instance with pre-configured skip depths 24 | // Initializes Skip0-Skip4 and creates a blank cache 25 | // 26 | // NewSkipZaps 创建带有预配置跳过深度的新 SkipZaps 实例 27 | // 初始化 Skip0-Skip4 并创建空白缓存 28 | func NewSkipZaps(zapLog *zap.Logger) *SkipZaps { 29 | return &SkipZaps{ 30 | amino: zapLog, 31 | Skip0: NewZapSkip(zapLog, 0), 32 | Skip1: NewZapSkip(zapLog, 1), 33 | Skip2: NewZapSkip(zapLog, 2), 34 | Skip3: NewZapSkip(zapLog, 3), 35 | Skip4: NewZapSkip(zapLog, 4), 36 | cache: mutexmap.NewMap[int, *Zap](0), 37 | } 38 | } 39 | 40 | // Skip returns a Zap instance with the specified skip depth 41 | // Uses pre-configured instances (0-4) or creates/caches new ones on-demand 42 | // 43 | // Skip 返回指定跳过深度的 Zap 实例 44 | // 使用预配置的实例 (0-4) 或按需创建/缓存新实例 45 | func (Z *SkipZaps) Skip(skipDepth int) *Zap { 46 | switch skipDepth { 47 | case 0: 48 | return Z.Skip0 49 | case 1: 50 | return Z.Skip1 51 | case 2: 52 | return Z.Skip2 53 | case 3: 54 | return Z.Skip3 55 | case 4: 56 | return Z.Skip4 57 | default: 58 | if skipDepth > 0 { 59 | res, _ := Z.cache.Getset(skipDepth, func() *Zap { 60 | return NewZapSkip(Z.amino, skipDepth) 61 | }) 62 | return res 63 | } else { 64 | return Z.Skip0 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /skip.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "github.com/yyle88/mutexmap" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // SkipLogs provides pre-configured logs with various skip depths 9 | // Contains common skip depths (0-4) and caches on-demand created ones 10 | // 11 | // SkipLogs 提供预配置的不同跳过深度的日志实例 12 | // 包含常用的跳过深度 (0-4) 并缓存按需创建的实例 13 | type SkipLogs struct { 14 | amino *zap.Logger // Base log instance // 基础日志实例 15 | Skip0 *zap.Logger // No caller skip // 无调用栈跳过 16 | Skip1 *zap.Logger // Skip 1 caller frame // 跳过 1 层调用栈 17 | Skip2 *zap.Logger // Skip 2 caller frames // 跳过 2 层调用栈 18 | Skip3 *zap.Logger // Skip 3 caller frames // 跳过 3 层调用栈 19 | Skip4 *zap.Logger // Skip 4 caller frames // 跳过 4 层调用栈 20 | cache *mutexmap.Map[int, *zap.Logger] // Cache of on-demand created logs // 按需创建的日志实例缓存 21 | } 22 | 23 | // NewSkipLogs creates a new SkipLogs instance with pre-configured skip depths 24 | // Initializes Skip0-Skip4 and creates a blank cache 25 | // 26 | // NewSkipLogs 创建带有预配置跳过深度的新 SkipLogs 实例 27 | // 初始化 Skip0-Skip4 并创建空白缓存 28 | func NewSkipLogs(zapLog *zap.Logger) *SkipLogs { 29 | return &SkipLogs{ 30 | amino: zapLog, 31 | Skip0: newSkipDepth(zapLog, 0), 32 | Skip1: newSkipDepth(zapLog, 1), 33 | Skip2: newSkipDepth(zapLog, 2), 34 | Skip3: newSkipDepth(zapLog, 3), 35 | Skip4: newSkipDepth(zapLog, 4), 36 | cache: mutexmap.NewMap[int, *zap.Logger](0), 37 | } 38 | } 39 | 40 | // Skip returns a log with the specified skip depth 41 | // Uses pre-configured instances (0-4) or creates/caches new ones on-demand 42 | // 43 | // Skip 返回指定跳过深度的日志实例 44 | // 使用预配置的实例 (0-4) 或按需创建/缓存新实例 45 | func (Z *SkipLogs) Skip(skipDepth int) *zap.Logger { 46 | switch skipDepth { 47 | case 0: 48 | return Z.Skip0 49 | case 1: 50 | return Z.Skip1 51 | case 2: 52 | return Z.Skip2 53 | case 3: 54 | return Z.Skip3 55 | case 4: 56 | return Z.Skip4 57 | default: 58 | if skipDepth > 0 { 59 | res, _ := Z.cache.Getset(skipDepth, func() *zap.Logger { 60 | return newSkipDepth(Z.amino, skipDepth) 61 | }) 62 | return res 63 | } else { 64 | return Z.Skip0 65 | } 66 | } 67 | } 68 | 69 | // newSkipDepth creates a log with the specified skip depth 70 | // newSkipDepth 创建指定跳过深度的日志实例 71 | func newSkipDepth(zapLog *zap.Logger, skipDepth int) *zap.Logger { 72 | return zapLog.WithOptions(zap.AddCallerSkip(skipDepth)) 73 | } 74 | -------------------------------------------------------------------------------- /zaplogw/zap_logw.go: -------------------------------------------------------------------------------- 1 | // Package zaplogw: Adapts zap.SugaredLogger to kvs/fields style interfaces 2 | // Provides both Warn and Warning methods to satisfy various logging interface needs 3 | // 4 | // zaplogw: 将 zap.SugaredLogger 适配到 kvs/fields 风格接口 5 | // 同时提供 Warn 和 Warning 方法以满足不同日志接口的需求 6 | package zaplogw 7 | 8 | import "go.uber.org/zap" 9 | 10 | // ZapLogw wraps zap.SugaredLogger to provide kvs/fields style logging interface 11 | // Handy when passing to custom logging interfaces that expect message + key-value data 12 | // Implements both Warn and Warning methods to satisfy various interface definitions 13 | // Note: The sug instance should have appropriate skip to show correct call location 14 | // 15 | // ZapLogw 封装 zap.SugaredLogger 以提供 kvs/fields 风格的日志接口 16 | // 在传递给期望 message + 键值数据 的自定义日志接口时很有用 17 | // 同时实现 Warn 和 Warning 方法以满足不同的接口定义 18 | // 注意: sug 实例应设置适当的跳过以显示正确的调用位置 19 | type ZapLogw struct { 20 | sug *zap.SugaredLogger // Underlying sugared log // 底层 sugared 日志实例 21 | } 22 | 23 | // NewZapLogw creates a new ZapLogw wrapper from zap.SugaredLogger 24 | // NewZapLogw 从 zap.SugaredLogger 创建新的 ZapLogw 封装器 25 | func NewZapLogw(sug *zap.SugaredLogger) *ZapLogw { 26 | return &ZapLogw{sug: sug} 27 | } 28 | 29 | // Debug logs a message with key-value pairs at debug level 30 | // Debug 使用键值对在 debug 级别记录消息 31 | func (l *ZapLogw) Debug(msg string, kvs ...interface{}) { 32 | l.sug.Debugw(msg, kvs...) 33 | } 34 | 35 | // Info logs a message with key-value pairs at info level 36 | // Info 使用键值对在 info 级别记录消息 37 | func (l *ZapLogw) Info(msg string, kvs ...interface{}) { 38 | l.sug.Infow(msg, kvs...) 39 | } 40 | 41 | // Error logs a message with key-value pairs at error level 42 | // Error 使用键值对在 error 级别记录消息 43 | func (l *ZapLogw) Error(msg string, kvs ...interface{}) { 44 | l.sug.Errorw(msg, kvs...) 45 | } 46 | 47 | // Fatal logs a message with key-value pairs at fatal level then calls os.Exit(1) 48 | // Fatal 使用键值对在 fatal 级别记录消息然后调用 os.Exit(1) 49 | func (l *ZapLogw) Fatal(msg string, kvs ...interface{}) { 50 | l.sug.Fatalw(msg, kvs...) 51 | } 52 | 53 | // Panic logs a message with key-value pairs at panic level then panics 54 | // Panic 使用键值对在 panic 级别记录消息然后 panic 55 | func (l *ZapLogw) Panic(msg string, kvs ...interface{}) { 56 | l.sug.Panicw(msg, kvs...) 57 | } 58 | 59 | // Warning logs a message with key-value pairs at warn level (alias) 60 | // Warning 使用键值对在 warn 级别记录消息 (别名) 61 | func (l *ZapLogw) Warning(msg string, kvs ...interface{}) { 62 | l.sug.Warnw(msg, kvs...) 63 | } 64 | 65 | // Warn logs a message with key-value pairs at warn level 66 | // Warn 使用键值对在 warn 级别记录消息 67 | func (l *ZapLogw) Warn(msg string, kvs ...interface{}) { 68 | l.sug.Warnw(msg, kvs...) 69 | } 70 | -------------------------------------------------------------------------------- /zaplumberjack/zap_lumberjack_test.go: -------------------------------------------------------------------------------- 1 | package zaplumberjack 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | func TestNewConfig(t *testing.T) { 14 | config := NewConfig("stdout", "DEBUG") 15 | data, err := json.MarshalIndent(config, "", "\t") 16 | require.NoError(t, err) 17 | t.Log(string(data)) 18 | output := `{ 19 | "filename": "stdout", 20 | "max_size": 500, 21 | "maxbackups": 5, 22 | "maxage": 3650, 23 | "compress": false, 24 | "level": "DEBUG" 25 | }` 26 | require.Equal(t, output, string(data)) 27 | } 28 | 29 | func TestNewZapLumberjackFromConfig(t *testing.T) { 30 | zapLumberjacks := []*ZapLumberjack{ 31 | NewZapLumberjackFromConfig(NewConfig("stdout", "DEBUG")), 32 | NewZapLumberjackFromConfig(NewConfig("stderr", "ERROR")), 33 | } 34 | 35 | { 36 | zapLog := NewLogger(zapLumberjacks, true, 0) 37 | zapLog.Info("123", zap.String("k", "v")) 38 | zapLog.Debug("abc", zap.String("k", "v")) 39 | zapLog.Error("xyz", zap.String("k", "v")) // will be print twice(both to stdout and stderr output) 40 | zapLog.Warn("uvw", zap.String("k", "v")) 41 | } 42 | { 43 | zapLog := NewLogger(zapLumberjacks, false, 0) 44 | zapLog.Info("123", zap.String("k", "v")) 45 | zapLog.Debug("abc", zap.String("k", "v")) 46 | zapLog.Error("xyz", zap.String("k", "v")) // will be print twice(both to stdout and stderr output) 47 | zapLog.Warn("uvw", zap.String("k", "v")) 48 | } 49 | } 50 | 51 | func TestNewZapLumberjack(t *testing.T) { 52 | temp, err := os.MkdirTemp("", "zaplogs_case_simple") 53 | require.NoError(t, err) 54 | defer func() { 55 | require.NoError(t, os.RemoveAll(temp)) 56 | }() 57 | t.Log(temp) 58 | 59 | debugPath := filepath.Join(temp, "debug.log") 60 | errorPath := filepath.Join(temp, "error.log") 61 | 62 | zapLumberjacks := []*ZapLumberjack{ 63 | NewZapLumberjackFromConfig(NewConfig(debugPath, "DEBUG")), 64 | NewZapLumberjackFromConfig(NewConfig(errorPath, "ERROR")), 65 | } 66 | defer func() { 67 | for _, cfg := range zapLumberjacks { 68 | require.NoError(t, cfg.Close()) 69 | } 70 | }() 71 | 72 | zapLog := GetLogger(zapLumberjacks) 73 | for i := 0; i < 3; i++ { 74 | zapLog.Info("123", zap.String("k", "v")) 75 | zapLog.Debug("abc", zap.String("k", "v")) 76 | zapLog.Error("xyz", zap.String("k", "v")) 77 | zapLog.Warn("uvw", zap.String("k", "v")) 78 | } 79 | require.NoError(t, zapLog.Sync()) 80 | 81 | showContent(t, debugPath) 82 | showContent(t, errorPath) 83 | } 84 | 85 | func showContent(t *testing.T, path string) { 86 | t.Log("path:", path) 87 | data, err := os.ReadFile(path) 88 | require.NoError(t, err) 89 | t.Log("data:", string(data)) 90 | } 91 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 5 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 6 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 7 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 8 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 9 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 10 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 11 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 12 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 13 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 15 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 17 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 18 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 19 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 20 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 21 | github.com/yyle88/mutexmap v1.0.15 h1:vqwtvomfzddcuBNg8hofWnILRFK2STJhWU7AufuNS50= 22 | github.com/yyle88/mutexmap v1.0.15/go.mod h1:NqwsKlK+NkL18i4BepeyCgtenXuw4N5UUnEX9XBfPA8= 23 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 24 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 25 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 26 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 27 | go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= 28 | go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 31 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 32 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 33 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: create-release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # 监听 main 分支的 push 操作(编译和测试/代码检查) 7 | tags: 8 | - 'v*' # 监听以 'v' 开头的标签的 push 操作(发布 Release) 9 | 10 | jobs: 11 | lint: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: actions/setup-go@v6 17 | with: 18 | go-version: stable 19 | cache: true 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v9 22 | with: 23 | version: latest 24 | args: --timeout=5m 25 | 26 | test: 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | go: [ "1.22.x", "1.23.x", "1.24.x", "stable" ] 31 | steps: 32 | - uses: actions/checkout@v6 33 | 34 | - uses: actions/setup-go@v6 35 | with: 36 | go-version: ${{ matrix.go }} 37 | cache: true 38 | 39 | - name: Run govulncheck 40 | uses: golang/govulncheck-action@v1 41 | with: 42 | go-version-input: ${{ matrix.go }} 43 | go-package: ./... 44 | continue-on-error: true # 报错时允许工作流继续执行,因为项目依赖的底层包也会有错,很难做到百分百没问题,只打印检测结果就行 45 | 46 | - name: Run test 47 | run: make test COVERAGE_DIR=/tmp/coverage 48 | 49 | - name: Upload test results 50 | uses: actions/upload-artifact@v4 51 | if: always() 52 | with: 53 | name: test-results-${{ matrix.go }} 54 | path: /tmp/coverage/ 55 | retention-days: 30 56 | 57 | - name: Send goveralls coverage 58 | uses: shogo82148/actions-goveralls@v1 59 | with: 60 | path-to-profile: /tmp/coverage/combined.txt 61 | flag-name: Go-${{ matrix.go }} 62 | parallel: true 63 | if: ${{ github.event.repository.fork == false }} # 仅在非 fork 时上传覆盖率 64 | 65 | check-coverage: 66 | name: Check coverage 67 | needs: [ test ] 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: shogo82148/actions-goveralls@v1 71 | with: 72 | parallel-finished: true 73 | if: ${{ github.event.repository.fork == false }} # 仅在非 fork 时检查覆盖率 74 | 75 | # 代码质量分析 76 | code-analysis: 77 | name: CodeQL Analysis 78 | runs-on: ubuntu-latest 79 | permissions: 80 | actions: read 81 | contents: read 82 | security-events: write 83 | steps: 84 | - name: Checkout repository 85 | uses: actions/checkout@v6 86 | 87 | - name: Initialize CodeQL 88 | uses: github/codeql-action/init@v4 89 | with: 90 | languages: go 91 | 92 | - name: Auto Build 93 | uses: github/codeql-action/autobuild@v4 94 | 95 | - name: Perform CodeQL Analysis 96 | uses: github/codeql-action/analyze@v4 97 | 98 | # 发布 Release 99 | release: 100 | name: Release a new version 101 | needs: [ lint, test, check-coverage, code-analysis ] 102 | runs-on: ubuntu-latest 103 | # 仅在推送标签时执行 - && - 仅在非 fork 时执行发布 104 | if: ${{ github.event.repository.fork == false && success() && startsWith(github.ref, 'refs/tags/v') }} 105 | steps: 106 | # 1. 检出代码 107 | - name: Checkout code 108 | uses: actions/checkout@v6 109 | with: 110 | fetch-depth: 0 # 获取完整历史用于生成更好的 release notes 111 | 112 | # 2. 创建 Release 和上传源码包 113 | - name: Create Release 114 | uses: softprops/action-gh-release@v2 115 | with: 116 | generate_release_notes: true 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package zaplog 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/yyle88/zaplog/internal/utils" 6 | "github.com/yyle88/zaplog/zaplogs" 7 | "go.uber.org/zap" 8 | "go.uber.org/zap/zapcore" 9 | ) 10 | 11 | // Config represents the configuration options when creating zap log 12 | // Contains debug mode, log level, output paths, and skip settings 13 | // 14 | // Config 表示创建 zap 日志时的配置选项 15 | // 包含调试模式、日志级别、输出路径和跳过设置 16 | type Config struct { 17 | Debug bool // Enable development mode with detailed output // 启用开发模式,带有详细输出 18 | Level string // Log level (DEBUG, INFO, WARN, ERROR) // 日志级别 (DEBUG, INFO, WARN, ERROR) 19 | OutputPaths []string // Output destinations (stdout, file paths) // 输出目标 (stdout, 文件路径) 20 | Skip int // Skip depth in stack trace // 调用栈跟踪中的跳过深度 21 | } 22 | 23 | // NewConfig creates a new Config with default development settings 24 | // Returns config with debug mode enabled, DEBUG level, and stdout output 25 | // 26 | // NewConfig 使用默认开发设置创建新的 Config 27 | // 返回启用调试模式、DEBUG 级别和 stdout 输出的配置 28 | func NewConfig() *Config { 29 | return &Config{ 30 | Debug: true, 31 | Level: "DEBUG", 32 | OutputPaths: []string{"stdout"}, 33 | Skip: 0, 34 | } 35 | } 36 | 37 | // SetOutputPaths sets the output paths, defaults to stdout when not specified 38 | // Returns the Config instance to support method chaining 39 | // 40 | // SetOutputPaths 设置输出路径,未指定时默认使用 stdout 41 | // 返回 Config 实例以支持方法链式调用 42 | func (c *Config) SetOutputPaths(paths []string) *Config { 43 | if len(paths) == 0 { 44 | paths = []string{"stdout"} 45 | } 46 | c.OutputPaths = utils.RemoveDuplicate(paths) 47 | return c 48 | } 49 | 50 | // AddOutputPaths appends extra output paths to existing configuration 51 | // Auto removes duplicate paths 52 | // 53 | // AddOutputPaths 向现有配置追加额外的输出路径 54 | // 自动移除重复路径 55 | func (c *Config) AddOutputPaths(paths ...string) *Config { 56 | c.OutputPaths = utils.RemoveDuplicate(append(c.OutputPaths, paths...)) 57 | return c 58 | } 59 | 60 | // NewZapLog creates a new zap.Logger from Config settings 61 | // Returns error if log creation fails 62 | // 63 | // NewZapLog 根据 Config 设置创建新的 zap.Logger 64 | // 如果日志实例创建失败则返回错误 65 | func NewZapLog(cfg *Config) (*zap.Logger, error) { 66 | config := NewZapConfig(cfg.Debug, cfg.Level, cfg.OutputPaths) 67 | 68 | var opts []zap.Option 69 | if cfg.Skip > 0 { 70 | opts = append(opts, zap.AddCallerSkip(cfg.Skip)) 71 | } 72 | 73 | zapLog, err := config.Build(opts...) 74 | if err != nil { 75 | return nil, errors.WithMessage(err, "new zap log is wrong") 76 | } 77 | return zapLog, nil 78 | } 79 | 80 | // NewZapConfig creates zap.Config based on debug mode, level and output paths 81 | // Development mode uses trimmed path encoding, production uses short encoding 82 | // 83 | // NewZapConfig 根据调试模式、级别和输出路径创建 zap.Config 84 | // 开发模式使用裁剪路径编码,生产模式使用短编码 85 | func NewZapConfig(debug bool, level string, outputPaths []string) *zap.Config { 86 | var config *zap.Config 87 | if debug { 88 | config = utils.GetValuePointer(zap.NewDevelopmentConfig()) 89 | // Keep stack trace printing, same as default // 保留错误调用栈打印,与默认值相同 90 | config.EncoderConfig.EncodeCaller = zaplogs.NewCallerEncoderTrimPath() 91 | } else { 92 | config = utils.GetValuePointer(zap.NewProductionConfig()) 93 | // Keep caller info in logs, same as default // 保留日志中的调用者信息,与默认值相同 94 | config.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder 95 | } 96 | if len(outputPaths) > 0 { 97 | config.OutputPaths = outputPaths 98 | } 99 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 100 | config.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 101 | config.Level = zap.NewAtomicLevelAt(zaplogs.ParseLevel(level)) 102 | return config 103 | } 104 | -------------------------------------------------------------------------------- /zaplogs/zap_logs_test.go: -------------------------------------------------------------------------------- 1 | package zaplogs 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zapcore" 10 | ) 11 | 12 | func TestParseLevel(t *testing.T) { 13 | require.Equal(t, zap.DebugLevel, ParseLevel("debug")) 14 | require.Equal(t, zap.InfoLevel, ParseLevel("info")) 15 | require.Equal(t, zap.WarnLevel, ParseLevel("warn")) 16 | require.Equal(t, zap.ErrorLevel, ParseLevel("error")) 17 | require.Equal(t, zap.PanicLevel, ParseLevel("panic")) 18 | require.Equal(t, zap.InfoLevel, ParseLevel("unknown")) 19 | } 20 | 21 | func TestNewEncoderUsage(t *testing.T) { 22 | encoder := NewEncoder(false) 23 | core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel) 24 | logger := zap.New(core) 25 | logger.Debug("This is a debug message") 26 | logger.Info("This is an info message") 27 | logger.Warn("This is a warning message") 28 | logger.Error("This is an error message") 29 | } 30 | 31 | func TestNewEncoderDebug(t *testing.T) { 32 | encoder := NewEncoder(true) 33 | core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel) 34 | logger := zap.New(core) 35 | logger.Debug("This is a debug message") 36 | logger.Info("This is an info message") 37 | logger.Warn("This is a warning message") 38 | logger.Error("This is an error message") 39 | } 40 | 41 | func TestNewDevelopmentEncoder(t *testing.T) { 42 | encoder := NewDevelopmentEncoder() 43 | core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel) 44 | logger := zap.New(core) 45 | logger.Debug("This is a debug message with development encoder") 46 | logger.Info("This is an info message with development encoder") 47 | logger.Warn("This is a warning message with development encoder") 48 | logger.Error("This is an error message with development encoder") 49 | } 50 | 51 | func TestNewProductionsEncoder(t *testing.T) { 52 | encoder := NewProductionsEncoder() 53 | core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel) 54 | logger := zap.New(core) 55 | logger.Debug("This is a debug message with production encoder") 56 | logger.Info("This is an info message with production encoder") 57 | logger.Warn("This is a warning message with production encoder") 58 | logger.Error("This is an error message with production encoder") 59 | } 60 | 61 | func TestNewCallerEncoderTrimPath(t *testing.T) { 62 | encoderFunc := NewCallerEncoderTrimPath() 63 | require.NotNil(t, encoderFunc) 64 | 65 | encConfig := zap.NewDevelopmentEncoderConfig() 66 | encConfig.EncodeCaller = encoderFunc 67 | enc := zapcore.NewConsoleEncoder(encConfig) 68 | core := zapcore.NewCore(enc, zapcore.AddSync(os.Stdout), zap.DebugLevel) 69 | logger := zap.New(core, zap.AddCaller()) 70 | 71 | logger.Debug("This is a debug message with custom caller encoder") 72 | logger.Info("This is an info message with custom caller encoder") 73 | logger.Warn("This is a warning message with custom caller encoder") 74 | logger.Error("This is an error message with custom caller encoder") 75 | } 76 | 77 | func TestNewCallerEncoderFullPath(t *testing.T) { 78 | encoderFunc := NewCallerEncoderFullPath() 79 | require.NotNil(t, encoderFunc) 80 | 81 | encConfig := zap.NewDevelopmentEncoderConfig() 82 | encConfig.EncodeCaller = encoderFunc 83 | enc := zapcore.NewConsoleEncoder(encConfig) 84 | core := zapcore.NewCore(enc, zapcore.AddSync(os.Stdout), zap.DebugLevel) 85 | logger := zap.New(core, zap.AddCaller()) 86 | 87 | logger.Debug("This is a debug message with custom caller encoder") 88 | logger.Info("This is an info message with custom caller encoder") 89 | logger.Warn("This is a warning message with custom caller encoder") 90 | logger.Error("This is an error message with custom caller encoder") 91 | } 92 | 93 | func TestNewLoggerOptionsWithSkip(t *testing.T) { 94 | options := NewLoggerOptionsWithSkip(true, 1) 95 | core := zapcore.NewCore( 96 | zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), 97 | zapcore.AddSync(os.Stdout), 98 | zap.DebugLevel, 99 | ) 100 | logger := zap.New(core, options...) 101 | require.NotNil(t, logger) 102 | logger.Debug("This is a debug message with custom options") 103 | logger.Info("This is an info message with custom options") 104 | logger.Warn("This is a warning message with custom options") 105 | logger.Error("This is an error message with custom options") 106 | } 107 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/yyle88/zaplog/release.yml?branch=main&label=BUILD)](https://github.com/yyle88/zaplog/actions/workflows/release.yml?query=branch%3Amain) 2 | [![GoDoc](https://pkg.go.dev/badge/github.com/yyle88/zaplog)](https://pkg.go.dev/github.com/yyle88/zaplog) 3 | [![Coverage Status](https://img.shields.io/coveralls/github/yyle88/zaplog/main.svg)](https://coveralls.io/github/yyle88/zaplog?branch=main) 4 | [![Supported Go Versions](https://img.shields.io/badge/Go-1.22--1.25-lightgrey.svg)](https://go.dev/) 5 | [![GitHub Release](https://img.shields.io/github/release/yyle88/zaplog.svg)](https://github.com/yyle88/zaplog/releases) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/yyle88/zaplog)](https://goreportcard.com/report/github.com/yyle88/zaplog) 7 | 8 | # ZapLog - 灵活且高性能的 Go 日志包 9 | 10 | ZapLog 是一个轻量级、灵活的 Go 日志包,基于快速且结构化的日志库 [zap](https://github.com/uber-go/zap) 构建。 11 | 12 | --- 13 | 14 | 15 | 16 | ## 英文文档 17 | 18 | [ENGLISH README](README.md) 19 | 20 | 21 | ## 安装 22 | 23 | ```bash 24 | go get github.com/yyle88/zaplog 25 | ``` 26 | 27 | ## 核心功能 28 | 29 | ### 1. **基础日志打印** 30 | 31 | 您可以使用 `zaplog.LOG` 记录带有键值对的日志: 32 | 33 | ```go 34 | zaplog.LOG.Debug("调试信息", zap.String("key", "value")) 35 | zaplog.LOG.Error("错误信息", zap.Error(errors.New("错误"))) 36 | ``` 37 | 38 | ### 2. **打印多个键值对** 39 | 40 | 通过传递多个键值对,可以在单条日志语句中打印多个字段: 41 | 42 | ```go 43 | zaplog.LOG.Debug("调试信息", zap.String("key1", "value1"), zap.Int("key2", 2)) 44 | zaplog.LOG.Error("错误信息", zap.Int("key1", 1), zap.Error(errors.New("错误"))) 45 | ``` 46 | 47 | ### 3. **使用 `SugaredLogger`** 48 | 49 | 使用 `zaplog.SUG` 简洁地记录日志,支持变参形式: 50 | 51 | ```go 52 | SUG.Debug("简化日志", "key1", "value1", "key2", 2) 53 | SUG.Error("简化错误", errors.New("错误")) 54 | ``` 55 | 56 | ### 4. **创建子日志(SubZap)** 57 | 58 | 创建带有默认字段的子日志,添加更多上下文信息,使日志更具可读性。使用 `SubZap` 和 `NewZap` 创建子日志: 59 | 60 | #### 使用 `SubZap` 创建子日志: 61 | 62 | ```go 63 | zp := zaplog.LOGGER.SubZap(zap.String("module", "abc"), zap.String("key", "value")) 64 | zp.LOG.Debug("子日志信息", zap.Int("a", 1)) 65 | zp.SUG.Error("简化子日志错误", 1, 2, 3) 66 | ``` 67 | 68 | #### 使用 `NewZap` 创建子日志: 69 | 70 | ```go 71 | zp := zaplog.LOGGER.NewZap("module", "abc", zap.String("key", "value")) 72 | zp.LOG.Debug("子日志信息2", zap.Int("a", 2)) 73 | ``` 74 | 75 | ### 5. **在 SugaredLogger 中处理多参数** 76 | 77 | 在 `SugaredLogger` 中,您可以传递各种类型的参数,包括数组和切片: 78 | 79 | ```go 80 | zaplog.SUG.Debug("调试信息", 1, 2, 3) 81 | zaplog.SUG.Debug([]int{0, 1, 2}) 82 | ``` 83 | 84 | --- 85 | 86 | 87 | 88 | 89 | ## 📄 许可证类型 90 | 91 | MIT 许可证 - 详见 [LICENSE](LICENSE)。 92 | 93 | --- 94 | 95 | ## 💬 联系与反馈 96 | 97 | 非常欢迎贡献代码!报告 BUG、建议功能、贡献代码: 98 | 99 | - 🐛 **问题报告?** 在 GitHub 上提交问题并附上重现步骤 100 | - 💡 **新颖思路?** 创建 issue 讨论 101 | - 📖 **文档疑惑?** 报告问题,帮助我们完善文档 102 | - 🚀 **需要功能?** 分享使用场景,帮助理解需求 103 | - ⚡ **性能瓶颈?** 报告慢操作,协助解决性能问题 104 | - 🔧 **配置困扰?** 询问复杂设置的相关问题 105 | - 📢 **关注进展?** 关注仓库以获取新版本和功能 106 | - 🌟 **成功案例?** 分享这个包如何改善工作流程 107 | - 💬 **反馈意见?** 欢迎提出建议和意见 108 | 109 | --- 110 | 111 | ## 🔧 代码贡献 112 | 113 | 新代码贡献,请遵循此流程: 114 | 115 | 1. **Fork**:在 GitHub 上 Fork 仓库(使用网页界面) 116 | 2. **克隆**:克隆 Fork 的项目(`git clone https://github.com/yourname/repo-name.git`) 117 | 3. **导航**:进入克隆的项目(`cd repo-name`) 118 | 4. **分支**:创建功能分支(`git checkout -b feature/xxx`) 119 | 5. **编码**:实现您的更改并编写全面的测试 120 | 6. **测试**:(Golang 项目)确保测试通过(`go test ./...`)并遵循 Go 代码风格约定 121 | 7. **文档**:面向用户的更改需要更新文档 122 | 8. **暂存**:暂存更改(`git add .`) 123 | 9. **提交**:提交更改(`git commit -m "Add feature xxx"`)确保向后兼容的代码 124 | 10. **推送**:推送到分支(`git push origin feature/xxx`) 125 | 11. **PR**:在 GitHub 上打开 Merge Request(在 GitHub 网页上)并提供详细描述 126 | 127 | 请确保测试通过并包含相关的文档更新。 128 | 129 | --- 130 | 131 | ## 🌟 项目支持 132 | 133 | 非常欢迎通过提交 Merge Request 和报告问题来贡献此项目。 134 | 135 | **项目支持:** 136 | 137 | - ⭐ **给予星标**如果项目对您有帮助 138 | - 🤝 **分享项目**给团队成员和(golang)编程朋友 139 | - 📝 **撰写博客**关于开发工具和工作流程 - 我们提供写作支持 140 | - 🌟 **加入生态** - 致力于支持开源和(golang)开发场景 141 | 142 | **祝你用这个包编程愉快!** 🎉🎉🎉 143 | 144 | 145 | 146 | --- 147 | 148 | ## GitHub 标星点赞 149 | 150 | [![标星点赞](https://starchart.cc/yyle88/zaplog.svg?variant=adaptive)](https://starchart.cc/yyle88/zaplog) 151 | 152 | -------------------------------------------------------------------------------- /zaplogs/zap_logs.go: -------------------------------------------------------------------------------- 1 | // Package zaplogs: Zap log functions and encoder configurations 2 | // Provides level parsing, encoder creation, and custom encoders 3 | // 4 | // zaplogs: Zap 日志函数和编码器配置 5 | // 提供级别解析、编码器创建和自定义编码器 6 | package zaplogs 7 | 8 | import ( 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/yyle88/zaplog/internal/utils" 13 | "go.uber.org/zap" 14 | "go.uber.org/zap/zapcore" 15 | ) 16 | 17 | // ParseLevel converts string level name to zapcore.Level 18 | // Supports debug, info, warn, error, panic (case-insensitive), defaults to info 19 | // 20 | // ParseLevel 将字符串级别名称转换为 zapcore.Level 21 | // 支持 debug, info, warn, error, panic (不区分大小写),默认为 info 22 | func ParseLevel(level string) zapcore.Level { 23 | switch strings.ToLower(level) { 24 | case "debug": 25 | return zap.DebugLevel 26 | case "info": 27 | return zap.InfoLevel 28 | case "warn": 29 | return zap.WarnLevel 30 | case "error": 31 | return zap.ErrorLevel 32 | case "panic": 33 | return zap.PanicLevel 34 | default: 35 | return zap.InfoLevel 36 | } 37 | } 38 | 39 | // NewEncoder creates appropriate encoder based on debug mode 40 | // Development mode uses console encoder, production uses JSON encoder 41 | // 42 | // NewEncoder 根据调试模式创建合适的编码器 43 | // 开发模式使用控制台编码器,生产模式使用 JSON 编码器 44 | func NewEncoder(debug bool) zapcore.Encoder { 45 | if debug { 46 | return NewDevelopmentEncoder() 47 | } else { 48 | return NewProductionsEncoder() 49 | } 50 | } 51 | 52 | // NewDevelopmentEncoder creates console encoder with dev-mode settings 53 | // Uses ISO8601 time format, capital level, and trimmed path 54 | // 55 | // NewDevelopmentEncoder 创建具有开发模式设置的控制台编码器 56 | // 使用 ISO8601 时间格式、大写级别和裁剪路径 57 | func NewDevelopmentEncoder() zapcore.Encoder { 58 | encoderConfig := zap.NewDevelopmentEncoderConfig() 59 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 60 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 61 | encoderConfig.EncodeCaller = NewCallerEncoderTrimPath() 62 | return zapcore.NewConsoleEncoder(encoderConfig) 63 | } 64 | 65 | // NewProductionsEncoder creates JSON encoder with production settings 66 | // Uses ISO8601 time format, capital level, and short path 67 | // 68 | // NewProductionsEncoder 创建具有生产设置的 JSON 编码器 69 | // 使用 ISO8601 时间格式、大写级别和短路径 70 | func NewProductionsEncoder() zapcore.Encoder { 71 | encoderConfig := zap.NewProductionEncoderConfig() 72 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 73 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 74 | encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder // Default but set explicit // 默认值但显式设置 75 | return zapcore.NewJSONEncoder(encoderConfig) 76 | } 77 | 78 | // NewCallerEncoderTrimPath creates encoder with trimmed file path and function name 79 | // Output format: "file.go:line:package.function" 80 | // 81 | // NewCallerEncoderTrimPath 创建带有裁剪文件路径和函数名的编码器 82 | // 输出格式: "file.go:line:package.function" 83 | func NewCallerEncoderTrimPath() func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 84 | return func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 85 | enc.AppendString(strings.Join([]string{caller.TrimmedPath(), utils.PathUnescape(runtime.FuncForPC(caller.PC).Name())}, ":")) 86 | } 87 | } 88 | 89 | // NewCallerEncoderFullPath creates encoder with full file path and function name 90 | // Output format: "/full/path/file.go:line:package.function" 91 | // 92 | // NewCallerEncoderFullPath 创建带有完整文件路径和函数名的编码器 93 | // 输出格式: "/full/path/file.go:line:package.function" 94 | func NewCallerEncoderFullPath() func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 95 | return func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 96 | enc.AppendString(strings.Join([]string{caller.FullPath(), utils.PathUnescape(runtime.FuncForPC(caller.PC).Name())}, ":")) 97 | } 98 | } 99 | 100 | // NewLoggerOptionsWithSkip creates log options with stack trace settings 101 | // Debug mode shows stack trace from warn, production from error 102 | // 103 | // NewLoggerOptionsWithSkip 创建带有堆栈跟踪设置的日志选项 104 | // 调试模式从 warn 级别显示堆栈跟踪,生产模式从 error 级别显示 105 | func NewLoggerOptionsWithSkip(debug bool, skipDepth int) []zap.Option { 106 | var options = []zap.Option{zap.AddCaller(), zap.AddCallerSkip(skipDepth)} 107 | 108 | if debug { 109 | options = append(options, zap.AddStacktrace(zapcore.WarnLevel)) 110 | } else { 111 | options = append(options, zap.AddStacktrace(zapcore.ErrorLevel)) 112 | } 113 | 114 | return options 115 | } 116 | -------------------------------------------------------------------------------- /zaplumberjack/zap_lumberjack.go: -------------------------------------------------------------------------------- 1 | // Package zaplumberjack: Zap log integration with lumberjack log rotation 2 | // Provides auto log file rotation based on size, age, and backup count 3 | // Supports multiple log outputs with various levels and auto-splitting 4 | // 5 | // zaplumberjack: Zap 日志与 lumberjack 日志轮转的集成 6 | // 提供基于大小、时间和备份数量的自动日志文件轮转 7 | // 支持多个不同级别的日志输出和自动分割 8 | package zaplumberjack 9 | 10 | import ( 11 | "os" 12 | 13 | "github.com/pkg/errors" 14 | "github.com/yyle88/zaplog/zaplogs" 15 | "go.uber.org/zap" 16 | "go.uber.org/zap/zapcore" 17 | "gopkg.in/natefinch/lumberjack.v2" 18 | ) 19 | 20 | // Config represents lumberjack configuration with JSON support 21 | // Default JSON config example: 22 | // 23 | // { 24 | // "filename": "stdout", 25 | // "max_size": 500, 26 | // "maxbackups": 5, 27 | // "maxage": 3650, 28 | // "compress": false, 29 | // "level": "debug" 30 | // } 31 | // 32 | // Config 表示支持 JSON 的 lumberjack 配置 33 | // 默认 JSON 配置示例见上 34 | type Config struct { 35 | Filename string `json:"filename"` // Log file path or stdout/stderr // 日志文件路径或 stdout/stderr 36 | MaxSize int `json:"max_size"` // Max size in megabytes before rotation // 轮转前的最大大小 (MB) 37 | MaxBackups int `json:"maxbackups"` // Max count of old log files to retain // 保留的旧日志文件最大数量 38 | MaxAge int `json:"maxage"` // Max days to retain old log files // 保留旧日志文件的最大天数 39 | Compress bool `json:"compress"` // Whether to compress rotated files // 是否压缩轮转的文件 40 | Level string `json:"level"` // Log level (debug, info, warn, error) // 日志级别 41 | } 42 | 43 | // NewConfig creates a new Config with default settings 44 | // MaxSize 500MB, MaxBackups 5, MaxAge 3650 days (10 years), no compression 45 | // 46 | // NewConfig 使用默认设置创建新的 Config 47 | // MaxSize 500MB, MaxBackups 5, MaxAge 3650 天 (10 年), 不压缩 48 | func NewConfig(filename string, level string) *Config { 49 | return &Config{ 50 | Filename: filename, 51 | MaxSize: 500, // 500MB // 500MB 52 | MaxBackups: 5, 53 | MaxAge: 3650, // 10 years // 10 年 54 | Compress: false, 55 | Level: level, 56 | } 57 | } 58 | 59 | // NewZapLumberjackFromConfig creates ZapLumberjack from Config 60 | // NewZapLumberjackFromConfig 从 Config 创建 ZapLumberjack 61 | func NewZapLumberjackFromConfig(cfg *Config) *ZapLumberjack { 62 | logger := &lumberjack.Logger{ 63 | Filename: cfg.Filename, 64 | MaxSize: cfg.MaxSize, 65 | MaxBackups: cfg.MaxBackups, 66 | MaxAge: cfg.MaxAge, 67 | Compress: cfg.Compress, 68 | } 69 | return NewZapLumberjack(logger, zaplogs.ParseLevel(cfg.Level)) 70 | } 71 | 72 | // ZapLumberjack wraps lumberjack.Logger with zap log level 73 | // Combines file rotation with level-based filtering 74 | // 75 | // ZapLumberjack 将 lumberjack.Logger 与 zap 日志级别封装 76 | // 结合文件轮转和基于级别的过滤 77 | type ZapLumberjack struct { 78 | Logger *lumberjack.Logger // Lumberjack logger instance // Lumberjack 日志实例 79 | Level zapcore.Level // Minimum log level // 最低日志级别 80 | } 81 | 82 | // NewZapLumberjack creates a new ZapLumberjack instance 83 | // NewZapLumberjack 创建新的 ZapLumberjack 实例 84 | func NewZapLumberjack(logger *lumberjack.Logger, level zapcore.Level) *ZapLumberjack { 85 | return &ZapLumberjack{ 86 | Logger: logger, 87 | Level: level, 88 | } 89 | } 90 | 91 | // Close closes the lumberjack log instance 92 | // Safe to call multiple times, subsequent calls are no-op 93 | // 94 | // Close 关闭 lumberjack 日志实例 95 | // 可以安全多次调用,后续调用不会有任何效果 96 | func (cfg *ZapLumberjack) Close() error { 97 | if err := cfg.Logger.Close(); err != nil { 98 | return errors.WithMessage(err, "close lumberjack is wrong") 99 | } 100 | return nil 101 | } 102 | 103 | // GetLogger creates a log that outputs to multiple destinations with various levels 104 | // Supports auto log rotation when file exceeds size limit 105 | // Also supports stdout and stderr as output destinations 106 | // 107 | // GetLogger 创建输出到多个不同级别目标的日志实例 108 | // 支持文件超过大小限制时自动轮转 109 | // 同时支持 stdout 和 stderr 作为输出目标 110 | func GetLogger(configs []*ZapLumberjack) *zap.Logger { 111 | return NewLogger(configs, true, 0) 112 | } 113 | 114 | // NewLogger creates a log with custom settings 115 | // skipDepth is handled inside, pass 0 in most cases 116 | // 117 | // NewLogger 使用自定义设置创建日志实例 118 | // skipDepth 已在内部处理,通常传 0 即可 119 | func NewLogger(configs []*ZapLumberjack, debug bool, skipDepth int) *zap.Logger { 120 | if len(configs) <= 0 { 121 | panic("no configs") 122 | } 123 | tee := NewZapTee(configs, zaplogs.NewEncoder(debug)) 124 | options := zaplogs.NewLoggerOptionsWithSkip(debug, skipDepth) 125 | zapLog := zap.New(tee, options...) 126 | return zapLog 127 | } 128 | 129 | // NewZapTee creates a Tee Core that outputs to multiple lumberjack logs 130 | // Enables level-based and paginated logging across multiple destinations 131 | // 132 | // NewZapTee 创建输出到多个 lumberjack 日志实例的 Tee Core 133 | // 实现跨多个目标的基于级别和分页的日志记录 134 | func NewZapTee(configs []*ZapLumberjack, encoder zapcore.Encoder) zapcore.Core { 135 | cores := make([]zapcore.Core, 0) 136 | for _, cfg := range configs { 137 | switch cfg.Logger.Filename { 138 | case "stdout": 139 | cores = append(cores, zapcore.NewCore(encoder, os.Stdout, cfg.Level)) 140 | case "stderr": 141 | cores = append(cores, zapcore.NewCore(encoder, os.Stderr, cfg.Level)) 142 | default: 143 | cores = append(cores, zapcore.NewCore(encoder, zapcore.AddSync(cfg.Logger), cfg.Level)) 144 | } 145 | } 146 | tee := zapcore.NewTee(cores...) 147 | return tee 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/yyle88/zaplog/release.yml?branch=main&label=BUILD)](https://github.com/yyle88/zaplog/actions/workflows/release.yml?query=branch%3Amain) 2 | [![GoDoc](https://pkg.go.dev/badge/github.com/yyle88/zaplog)](https://pkg.go.dev/github.com/yyle88/zaplog) 3 | [![Coverage Status](https://img.shields.io/coveralls/github/yyle88/zaplog/main.svg)](https://coveralls.io/github/yyle88/zaplog?branch=main) 4 | [![Supported Go Versions](https://img.shields.io/badge/Go-1.22--1.25-lightgrey.svg)](https://go.dev/) 5 | [![GitHub Release](https://img.shields.io/github/release/yyle88/zaplog.svg)](https://github.com/yyle88/zaplog/releases) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/yyle88/zaplog)](https://goreportcard.com/report/github.com/yyle88/zaplog) 7 | 8 | # ZapLog - Flexible and High-Performance Logging in Go 9 | 10 | ZapLog is a lightweight, flexible logging package designed to support Go applications, built on top of the fast and structured logging pkg [zap](https://github.com/uber-go/zap). 11 | 12 | --- 13 | 14 | 15 | 16 | ## CHINESE README 17 | 18 | [中文说明](README.zh.md) 19 | 20 | 21 | ## Installation 22 | 23 | ```bash 24 | go get github.com/yyle88/zaplog 25 | ``` 26 | 27 | ## Core Features 28 | 29 | ### 1. **Basic Logging** 30 | 31 | You can log messages with key-value pairs using `zaplog.LOG`: 32 | 33 | ```go 34 | zaplog.LOG.Debug("Debug message", zap.String("key", "value")) 35 | zaplog.LOG.Error("Error message", zap.Error(errors.New("error"))) 36 | ``` 37 | 38 | ### 2. **Logging Multiple Key-Value Pairs** 39 | 40 | Log multiple fields in a single log statement by passing multiple key-value pairs: 41 | 42 | ```go 43 | zaplog.LOG.Debug("Debug message", zap.String("key1", "value1"), zap.Int("key2", 2)) 44 | zaplog.LOG.Error("Error message", zap.Int("key1", 1), zap.Error(errors.New("error"))) 45 | ``` 46 | 47 | ### 3. **Using `SugaredLogger`** 48 | 49 | Use `zaplog.SUG` to make simple logs, which supports variadic arguments: 50 | 51 | ```go 52 | SUG.Debug("Simplified log", "key1", "value1", "key2", 2) 53 | SUG.Error("Simplified error", errors.New("error")) 54 | ``` 55 | 56 | ### 4. **Creating Sub-Loggers (SubZap)** 57 | 58 | Create sub-loggers with default fields to add extra context, making logs more informative. Use `SubZap` and `NewZap` to create sub-loggers: 59 | 60 | #### SubLogger Creation with `SubZap`: 61 | 62 | ```go 63 | zp := zaplog.LOGGER.SubZap(zap.String("module", "abc"), zap.String("key", "value")) 64 | zp.LOG.Debug("Sub-log message", zap.Int("a", 1)) 65 | zp.SUG.Error("Simplified sub-log error", 1, 2, 3) 66 | ``` 67 | 68 | #### SubLogger Creation with `NewZap`: 69 | 70 | ```go 71 | zp := zaplog.LOGGER.NewZap("module", "abc", zap.String("key", "value")) 72 | zp.LOG.Debug("Sub-log message 2", zap.Int("a", 2)) 73 | ``` 74 | 75 | ### 5. **Handling Multiple Arguments in Sugared Logger** 76 | 77 | With `SugaredLogger`, you can pass various argument types, including arrays and slices: 78 | 79 | ```go 80 | zaplog.SUG.Debug("Debug message", 1, 2, 3) 81 | zaplog.SUG.Debug([]int{0, 1, 2}) 82 | ``` 83 | 84 | --- 85 | 86 | 87 | 88 | 89 | ## 📄 License 90 | 91 | MIT License - see [LICENSE](LICENSE). 92 | 93 | --- 94 | 95 | ## 💬 Contact & Feedback 96 | 97 | Contributions are welcome! Report bugs, suggest features, and contribute code: 98 | 99 | - 🐛 **Mistake reports?** Open an issue on GitHub with reproduction steps 100 | - 💡 **Fresh ideas?** Create an issue to discuss 101 | - 📖 **Documentation confusing?** Report it so we can improve 102 | - 🚀 **Need new features?** Share the use cases to help us understand requirements 103 | - ⚡ **Performance issue?** Help us optimize through reporting slow operations 104 | - 🔧 **Configuration problem?** Ask questions about complex setups 105 | - 📢 **Follow project progress?** Watch the repo to get new releases and features 106 | - 🌟 **Success stories?** Share how this package improved the workflow 107 | - 💬 **Feedback?** We welcome suggestions and comments 108 | 109 | --- 110 | 111 | ## 🔧 Development 112 | 113 | New code contributions, follow this process: 114 | 115 | 1. **Fork**: Fork the repo on GitHub (using the webpage UI). 116 | 2. **Clone**: Clone the forked project (`git clone https://github.com/yourname/repo-name.git`). 117 | 3. **Navigate**: Navigate to the cloned project (`cd repo-name`) 118 | 4. **Branch**: Create a feature branch (`git checkout -b feature/xxx`). 119 | 5. **Code**: Implement the changes with comprehensive tests 120 | 6. **Testing**: (Golang project) Ensure tests pass (`go test ./...`) and follow Go code style conventions 121 | 7. **Documentation**: Update documentation to support client-facing changes 122 | 8. **Stage**: Stage changes (`git add .`) 123 | 9. **Commit**: Commit changes (`git commit -m "Add feature xxx"`) ensuring backward compatible code 124 | 10. **Push**: Push to the branch (`git push origin feature/xxx`). 125 | 11. **PR**: Open a merge request on GitHub (on the GitHub webpage) with detailed description. 126 | 127 | Please ensure tests pass and include relevant documentation updates. 128 | 129 | --- 130 | 131 | ## 🌟 Support 132 | 133 | Welcome to contribute to this project via submitting merge requests and reporting issues. 134 | 135 | **Project Support:** 136 | 137 | - ⭐ **Give GitHub stars** if this project helps you 138 | - 🤝 **Share with teammates** and (golang) programming friends 139 | - 📝 **Write tech blogs** about development tools and workflows - we provide content writing support 140 | - 🌟 **Join the ecosystem** - committed to supporting open source and the (golang) development scene 141 | 142 | **Have Fun Coding with this package!** 🎉🎉🎉 143 | 144 | 145 | 146 | --- 147 | 148 | ## GitHub Stars 149 | 150 | [![Stargazers](https://starchart.cc/yyle88/zaplog.svg?variant=adaptive)](https://starchart.cc/yyle88/zaplog) 151 | 152 | --------------------------------------------------------------------------------