├── go.mod ├── logger ├── version.go ├── util.go ├── stack.go ├── option.go └── logger.go ├── test ├── 0_28_0_test.go ├── 0_24_0_test.go ├── 0_25_0_test.go ├── 0_29_0_test.go ├── 0_26_0_test.go ├── 0_2_0_test.go └── 0_27_0_test.go ├── LICENSE ├── bench └── bench_test.go ├── README.md └── README_en.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/donnie4w/go-logger 2 | 3 | go 1.22.4 4 | 5 | require github.com/donnie4w/gofer v0.1.8 6 | 7 | require github.com/google/btree v1.1.3 // indirect 8 | -------------------------------------------------------------------------------- /logger/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, donnie 2 | // All rights reserved. 3 | // Use of t source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // github.com/donnie4w/go-logger 7 | 8 | package logger 9 | 10 | const ( 11 | VERSION string = "0.28.0" 12 | ) 13 | -------------------------------------------------------------------------------- /test/0_28_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "testing" 6 | ) 7 | 8 | func TestOption4mixed(t *testing.T) { 9 | logger.SetOption(&logger.Option{Console: true, FileOption: &logger.FileMixedMode{Filename: "testmixid.log", Maxbackup: 10, IsCompress: true, Timemode: logger.MODE_DAY, Maxsize: 1 << 20}}) 10 | for i := 0; i < 10000; i++ { 11 | logger.Debug("this is a debug message", 1111111111111111111) 12 | logger.Info("this is a info message", 2222222222222222222) 13 | logger.Warn("this is a warn message", 33333333333333333) 14 | logger.Error("this is a error message", 4444444444444444444) 15 | logger.Fatal("this is a fatal message", 555555555555555555) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/0_24_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "log/slog" 6 | "path/filepath" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | func TestSlog(t *testing.T) { 12 | replace := func(groups []string, a slog.Attr) slog.Attr { 13 | if a.Key == slog.SourceKey { 14 | source := a.Value.Any().(*slog.Source) 15 | source.File = filepath.Base(source.File) 16 | } 17 | return a 18 | } 19 | loggingFile := logger.NewLogger() 20 | loggingFile.SetRollingFile("./1", "slogfile.txt", 100, logger.KB) 21 | h := slog.NewJSONHandler(loggingFile, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}) 22 | log := slog.New(h) 23 | for i := 0; i < 1000; i++ { 24 | log.Info("this is a info message:" + strconv.Itoa(i)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/0_25_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestOption4time(t *testing.T) { 10 | logger.SetOption(&logger.Option{Level: logger.LEVEL_INFO, Console: true, FileOption: &logger.FileTimeMode{Filename: "testlogtime.log", Maxbackup: 3, IsCompress: false, Timemode: logger.MODE_MONTH}}) 11 | for i := 0; i < 100; i++ { 12 | logger.Debug("this is a debug message", 1111111111111111111) 13 | logger.Info("this is a info message", 2222222222222222222) 14 | logger.Warn("this is a warn message", 33333333333333333) 15 | logger.Error("this is a error message", 4444444444444444444) 16 | logger.Fatal("this is a fatal message", 555555555555555555) 17 | time.Sleep(2 * time.Second) 18 | } 19 | } 20 | 21 | func TestOption4size(t *testing.T) { 22 | logger.SetOption(&logger.Option{Level: logger.LEVEL_DEBUG, Console: true, FileOption: &logger.FileSizeMode{Filename: "testlog.log", Maxsize: 1 << 10, Maxbackup: 10, IsCompress: false}}) 23 | for i := 0; i < 20; i++ { 24 | logger.Debug("this is a debug message", 1111111111111111111) 25 | logger.Info("this is a info message", 2222222222222222222) 26 | logger.Warn("this is a warn message", 33333333333333333) 27 | logger.Error("this is a error message", 4444444444444444444) 28 | logger.Fatal("this is a fatal message", 555555555555555555) 29 | } 30 | time.Sleep(1 * time.Second) 31 | } 32 | -------------------------------------------------------------------------------- /test/0_29_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "testing" 6 | ) 7 | 8 | // 通过 Formatter 构造json格式输出 9 | // 说明: 10 | // {level} 日志级别数据 11 | // {time} 日志时间 12 | // {file} 日志文件 13 | // {message} 日志主题内容 14 | func Test_formater(t *testing.T) { 15 | logger.SetOption(&logger.Option{Console: true, Formatter: `{"level":"{level}","time":"{time}","file":"{file}","message":"{message}"}` + "\n"}) 16 | logger.Debug("this is a debug message") 17 | logger.Info("this is a info message") 18 | logger.Warn("this is a warn message") 19 | logger.Error("this is a error message") 20 | } 21 | 22 | // 自定义json格式的参数 23 | // 比如修改LEVEL的格式,同理,可以修改时间的格式 24 | func Test_formater2(t *testing.T) { 25 | levelFmt := func(level logger.LEVELTYPE) string { 26 | switch level { 27 | case logger.LEVEL_DEBUG: 28 | return "debug" 29 | case logger.LEVEL_INFO: 30 | return "info" 31 | case logger.LEVEL_FATAL: 32 | return "fatal" 33 | case logger.LEVEL_WARN: 34 | return "warn" 35 | case logger.LEVEL_ERROR: 36 | return "error" 37 | default: 38 | return "unknown" 39 | } 40 | } 41 | logger.SetOption(&logger.Option{Console: true, Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_DATE | logger.FORMAT_TIME, Formatter: `{"level":"{level}","time":"{time}","message":"{message}"}` + "\n", AttrFormat: &logger.AttrFormat{SetLevelFmt: levelFmt}}) 42 | logger.Debug("this is a debug message") 43 | logger.Info("this is a info message") 44 | logger.Warn("this is a warn message") 45 | logger.Error("this is a error message") 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014 donnie4w 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /logger/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, donnie 2 | // All rights reserved. 3 | // Use of t source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // github.com/donnie4w/go-logger 7 | 8 | package logger 9 | 10 | import ( 11 | "github.com/donnie4w/gofer/buffer" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | var _current = "" 19 | var _lastUpdate time.Time 20 | 21 | func getfileInfo(flag *_FORMAT, fileName *string, line *int, funcName *string, filebuf *buffer.Buffer) { 22 | if *flag&(FORMAT_SHORTFILENAME|FORMAT_LONGFILENAME|FORMAT_RELATIVEFILENAME) != 0 { 23 | if *flag&FORMAT_SHORTFILENAME != 0 { 24 | short := *fileName 25 | for i := len(*fileName) - 1; i > 0; i-- { 26 | if (*fileName)[i] == '/' { 27 | short = (*fileName)[i+1:] 28 | break 29 | } 30 | } 31 | fileName = &short 32 | } else if *flag&FORMAT_RELATIVEFILENAME != 0 { 33 | if time.Since(_lastUpdate) > time.Second || _current == "" { 34 | if c, err := os.Getwd(); err == nil { 35 | _current = c 36 | } 37 | } 38 | _lastUpdate = time.Now() 39 | if _current != "" { 40 | if relative, err := filepath.Rel(_current, *fileName); err == nil { 41 | fileName = &relative 42 | } 43 | } 44 | } 45 | filebuf.Write([]byte(*fileName)) 46 | if *flag&FORMAT_FUNC != 0 && funcName != nil && *funcName != "" { 47 | filebuf.WriteByte(':') 48 | filebuf.WriteString(*funcName) 49 | } 50 | filebuf.WriteByte(':') 51 | filebuf.Write(itoa(*line, -1)) 52 | } 53 | } 54 | 55 | func funcname(str string) string { 56 | if lastDotIndex := strings.LastIndex(str, "."); lastDotIndex != -1 { 57 | return str[lastDotIndex+1:] 58 | } 59 | return str 60 | } 61 | -------------------------------------------------------------------------------- /test/0_26_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/donnie4w/go-logger/logger" 6 | "testing" 7 | ) 8 | 9 | func TestCustomHandler(t *testing.T) { 10 | levelname := func(level logger.LEVELTYPE) string { 11 | switch level { 12 | case logger.LEVEL_DEBUG: 13 | return "debug" 14 | case logger.LEVEL_INFO: 15 | return "info" 16 | case logger.LEVEL_FATAL: 17 | return "fatal" 18 | case logger.LEVEL_WARN: 19 | return "warn" 20 | case logger.LEVEL_ERROR: 21 | return "error" 22 | default: 23 | return "unknown" 24 | } 25 | } 26 | 27 | logger.SetOption(&logger.Option{Console: true, CustomHandler: func(lc *logger.LogContext) bool { 28 | fmt.Println("level:", levelname(lc.Level)) 29 | fmt.Println("message:", fmt.Sprint(lc.Args...)) 30 | if lc.Level == logger.LEVEL_ERROR { 31 | return false //if error mesaage , do not print 32 | } 33 | return true 34 | }, 35 | }) 36 | logger.Debug("this is a debug message") 37 | logger.Info("this is a info message") 38 | logger.Warn("this is a warn message") 39 | logger.Error("this is a error message") 40 | } 41 | 42 | func TestStacktrace(t *testing.T) { 43 | logger.SetOption(&logger.Option{Console: true, Stacktrace: logger.LEVEL_WARN, Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_DATE | logger.FORMAT_TIME | logger.FORMAT_SHORTFILENAME | logger.FORMAT_FUNC}) 44 | logger.Debug("this is a debug message") 45 | Stacktrace1() 46 | } 47 | 48 | func Stacktrace1() { 49 | logger.Info("this is a info message") 50 | Stacktrace2() 51 | } 52 | 53 | func Stacktrace2() { 54 | logger.Warn("this is a warn message") 55 | Stacktrace3() 56 | } 57 | 58 | func Stacktrace3() { 59 | logger.Error("this is a error message") 60 | logger.Fatal("this is a fatal message") 61 | } 62 | 63 | func TestLevelOptions(t *testing.T) { 64 | logger.SetLevelOption(logger.LEVEL_DEBUG, &logger.LevelOption{Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_TIME | logger.FORMAT_SHORTFILENAME}) 65 | logger.SetLevelOption(logger.LEVEL_INFO, &logger.LevelOption{Format: logger.FORMAT_LEVELFLAG}) 66 | logger.SetLevelOption(logger.LEVEL_WARN, &logger.LevelOption{Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_TIME | logger.FORMAT_SHORTFILENAME | logger.FORMAT_DATE | logger.FORMAT_FUNC}) 67 | 68 | logger.Debug("this is a debug message") 69 | logger.Info("this is a info message") 70 | logger.Warn("this is a warn message") 71 | } 72 | -------------------------------------------------------------------------------- /bench/bench_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "log/slog" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | func BenchmarkSerialLogger(b *testing.B) { 12 | log := logger.NewLogger() 13 | log.SetRollingFile("", "logger2.log", 500, logger.MB) 14 | log.SetConsole(false) 15 | b.ResetTimer() 16 | for i := 0; i < b.N; i++ { 17 | log.Debug(">>>>>>this is debug message>>>>>>this is debug message") 18 | } 19 | } 20 | 21 | func BenchmarkParallelLogger(b *testing.B) { 22 | log := logger.NewLogger() 23 | log.SetRollingFile("", "logger2.log", 200, logger.MB) 24 | log.SetConsole(false) 25 | b.SetParallelism(20) 26 | b.ResetTimer() 27 | var i int64 = 0 28 | b.RunParallel(func(pb *testing.PB) { 29 | if i == 30000 { 30 | return 31 | } 32 | i++ 33 | for pb.Next() { 34 | log.Debug(">>>>>>this is debug message>>>>>>this is debug message") 35 | } 36 | 37 | }) 38 | } 39 | 40 | // slog 41 | func BenchmarkSerialSlog(b *testing.B) { 42 | replace := func(groups []string, a slog.Attr) slog.Attr { 43 | if a.Key == slog.SourceKey { 44 | source := a.Value.Any().(*slog.Source) 45 | source.File = filepath.Base(source.File) 46 | } 47 | return a 48 | } 49 | out, _ := os.OpenFile(`slog.log`, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666) 50 | h := slog.NewTextHandler(out, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}) 51 | log := slog.New(h) 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | log.Info(">>>this is debug message") 55 | } 56 | } 57 | 58 | func BenchmarkParallelSLog(b *testing.B) { 59 | replace := func(groups []string, a slog.Attr) slog.Attr { 60 | if a.Key == slog.SourceKey { 61 | source := a.Value.Any().(*slog.Source) 62 | source.File = filepath.Base(source.File) 63 | } 64 | return a 65 | } 66 | out, _ := os.OpenFile(`slog2.log`, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666) 67 | h := slog.NewTextHandler(out, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}) 68 | log := slog.New(h) 69 | b.ResetTimer() 70 | b.RunParallel(func(pb *testing.PB) { 71 | for pb.Next() { 72 | log.Info(">>>this is debug message") 73 | } 74 | }) 75 | } 76 | 77 | func BenchmarkMixedMode(b *testing.B) { 78 | goLogger := logger.NewLogger() 79 | goLogger.SetOption(&logger.Option{Level: logger.LEVEL_DEBUG, Console: false, FileOption: &logger.FileMixedMode{Filename: "testmixed.log", Maxsize: 200 << 20, Maxbackup: 10, IsCompress: false}}) 80 | b.ResetTimer() 81 | b.RunParallel(func(pb *testing.PB) { 82 | for pb.Next() { 83 | goLogger.Info("this is info message") 84 | } 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /test/0_2_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | /*控制台打印,直接调用打印方法Debug(),Info()等方法*/ 10 | func Test_Global(t *testing.T) { 11 | logger.SetRollingDaily(``, "logger.log") 12 | // SetConsole(false) 13 | logger.SetFormat(logger.FORMAT_DATE | logger.FORMAT_LONGFILENAME) //设置后,下面日志格式只打印日期+短文件信息 14 | logger.Debug("this is debug message") 15 | logger.SetFormat(logger.FORMAT_DATE | logger.FORMAT_RELATIVEFILENAME) //设置后,下面日志格式只打印日期+短文件信息 16 | logger.Info("this is info message") 17 | logger.SetFormat(logger.FORMAT_DATE | logger.FORMAT_TIME | logger.FORMAT_SHORTFILENAME) //设置后,下面日志格式只打印日期+短文件信息 18 | logger.Warn("this is warning message") 19 | // SetLevel(FATAL) //设置为FATAL后,下面Error()级别小于FATAL,将不打印出来 20 | logger.Error("this is error message") 21 | logger.SetFormat(logger.FORMAT_LEVELFLAG | logger.FORMAT_DATE | logger.FORMAT_MICROSECONDS | logger.FORMAT_SHORTFILENAME) 22 | logger.SetFormatter("{message}|{level} {time} {file}\n") 23 | // SetFormat(FORMAT_NANO) 24 | logger.Fatal("this is fatal message") 25 | } 26 | 27 | /*设置日志文件*/ 28 | func Test_NewLogger(t *testing.T) { 29 | /*获取全局log单例,单日志文件项目日志建议使用单例*/ 30 | //log := GetStaticLogger() 31 | /*获取新的log实例,要求不同日志文件时,使用多实例对象*/ 32 | log := logger.NewLogger() 33 | /*按日期分割日志文件,也是默认设置值*/ 34 | // log.SetRollingDaily(`D:\cfoldTest`, "log.txt") 35 | log.SetRollingByTime("", "newlogger.txt", logger.MODE_DAY) 36 | /*按日志文件大小分割日志文件*/ 37 | // log.SetRollingFile("", "log1.txt", 3, KB) 38 | // log.SetRollingFileLoop(`D:\cfoldTest`, "log1.txt", 3, KB, 5) 39 | /* 设置打印级别 OFF,DEBUG,INFO,WARN,ERROR,FATAL 40 | log.SetLevel(OFF) 设置OFF后,将不再打印后面的日志 默认日志级别为ALL,打印级别*/ 41 | /* 日志写入文件时,同时在控制台打印出来,设置为false后将不打印在控制台,默认值true*/ 42 | // log.SetConsole(false) 43 | log.Debug("this is debug message") 44 | log.SetFormat(logger.FORMAT_LONGFILENAME) //设置后将打印出文件全部路径信息 45 | log.Info("this is info message") 46 | log.SetFormat(logger.FORMAT_MICROSECONDS | logger.FORMAT_SHORTFILENAME) //设置日志格式,时间+短文件名 47 | log.Warn("this is warning message") 48 | log.SetLevel(logger.LEVEL_FATAL) //设置为FATAL后,下面Error()级别小于FATAL,将不打印出来 49 | log.Error("this is error message") 50 | log.Fatal("this is fatal message") 51 | time.Sleep(2 * time.Second) 52 | } 53 | 54 | func Test_formatter(t *testing.T) { 55 | log := logger.NewLogger() 56 | log.SetFormatter("{time} {file} {level} >> {message}\n") 57 | log.Debug("this is debug message") 58 | log.Warn("this is info message") 59 | log.Error("this is error message") 60 | log.Fatal("this is fatal message") 61 | 62 | log.Debugf("this is debug message: %s", time.Now().Format("2006-01-02 15:04:05")) 63 | } 64 | -------------------------------------------------------------------------------- /test/0_27_0_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/donnie4w/go-logger/logger" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func Test_AttrFormat(t *testing.T) { 10 | attrformat := &logger.AttrFormat{ 11 | SetLevelFmt: func(level logger.LEVELTYPE) string { 12 | switch level { 13 | case logger.LEVEL_DEBUG: 14 | return "debug:" 15 | case logger.LEVEL_INFO: 16 | return "info:" 17 | case logger.LEVEL_WARN: 18 | return "warn:" 19 | case logger.LEVEL_ERROR: 20 | return "error>>>>" 21 | case logger.LEVEL_FATAL: 22 | return "[fatal]" 23 | default: 24 | return "[unknown]" 25 | } 26 | }, 27 | SetTimeFmt: func() (string, string, string) { 28 | s := time.Now().Format("2006-01-02 15:04:05") 29 | return s, "", "" 30 | }, 31 | } 32 | logger.SetOption(&logger.Option{AttrFormat: attrformat, Console: true, FileOption: &logger.FileTimeMode{Filename: "testlogtime.log", Maxbackup: 3, IsCompress: false, Timemode: logger.MODE_MONTH}}) 33 | logger.Debug("this is a debug message", 1111111111111111111) 34 | logger.Info("this is a info message", 2222222222222222222) 35 | logger.Warn("this is a warn message", 33333333333333333) 36 | logger.Error("this is a error message", 4444444444444444444) 37 | logger.Fatal("this is a fatal message", 555555555555555555) 38 | } 39 | 40 | func Test_AttrFormat2(t *testing.T) { 41 | attrformat := &logger.AttrFormat{ 42 | SetBodyFmt: func(level logger.LEVELTYPE, bs []byte) []byte { 43 | //处理日志末尾换行符 44 | if size := len(bs); bs[size-1] == '\n' { 45 | bs = append(bs[:size-1], []byte("\x1b[0m\n")...) 46 | } else { 47 | bs = append(bs, []byte("\x1b[0m\n")...) 48 | } 49 | switch level { 50 | case logger.LEVEL_DEBUG: 51 | return append([]byte("\x1b[34m"), bs...) 52 | case logger.LEVEL_INFO: 53 | return append([]byte("\x1b[32m"), bs...) 54 | case logger.LEVEL_WARN: 55 | return append([]byte("\x1b[33m"), bs...) 56 | case logger.LEVEL_ERROR: 57 | return append([]byte("\x1b[31m"), bs...) 58 | case logger.LEVEL_FATAL: 59 | return append([]byte("\x1b[41m"), bs...) 60 | default: 61 | return bs 62 | } 63 | }, 64 | } 65 | logger.SetOption(&logger.Option{AttrFormat: attrformat, Console: true, FileOption: &logger.FileTimeMode{Filename: "testlogtime.log", Maxbackup: 3, IsCompress: false, Timemode: logger.MODE_MONTH}}) 66 | logger.Debug("this is a debug message:", 111111111111111110) 67 | logger.Info("this is a info message:", 222222222222222220) 68 | logger.Warn("this is a warn message:", 333333333333333330) 69 | logger.Error("this is a error message:", 4444444444444444440) 70 | logger.Fatal("this is a fatal message:", 5555555555555555550) 71 | } 72 | 73 | func Test_format(t *testing.T) { 74 | logger.SetRollingFile("", "test.log", 10, logger.MB) 75 | logger.Debugf("this is a debugf message:%d", 1) 76 | logger.Infof("this is a infof message:%s", "hi,logger") 77 | logger.Warnf("this is a warnf message:%x,%x", 14, 15) 78 | logger.Errorf("this is a errorf message:%f", 44.4444) 79 | logger.Fatalf("this is a fatalf message:%t", true) 80 | logger.Debugf("this is a debugf message:%p", new(int)) 81 | } 82 | -------------------------------------------------------------------------------- /logger/stack.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, donnie 2 | // All rights reserved. 3 | // Use of t source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // github.com/donnie4w/go-logger 7 | 8 | package logger 9 | 10 | import ( 11 | "github.com/donnie4w/gofer/buffer" 12 | pool "github.com/donnie4w/gofer/pool/buffer" 13 | "runtime" 14 | ) 15 | 16 | type callStack struct { 17 | stack []*callerInfo 18 | } 19 | 20 | func (c *callStack) reset() { 21 | if len(c.stack) > 0 { 22 | c.stack = c.stack[:0] 23 | } 24 | } 25 | 26 | var callStackPool = pool.NewPool[callStack](func() *callStack { 27 | return &callStack{stack: make([]*callerInfo, 0)} 28 | }, func(c *callStack) { 29 | c.reset() 30 | }) 31 | 32 | type callerInfo struct { 33 | FileName string 34 | Line int 35 | FuncName string 36 | } 37 | 38 | func (c *callerInfo) reset() { 39 | c.FileName, c.FuncName = "", "" 40 | } 41 | 42 | var callerInfoPool = pool.NewPool[callerInfo](func() *callerInfo { 43 | return &callerInfo{} 44 | }, func(c *callerInfo) { 45 | c.reset() 46 | }) 47 | 48 | func (cs *callStack) Push(fileName string, line int) { 49 | ci := callerInfoPool.Get() 50 | ci.FileName, ci.Line = fileName, line 51 | cs.stack = append(cs.stack, ci) 52 | } 53 | 54 | func (cs *callStack) PushWithFunc(fileName string, line int, funcName string) { 55 | ci := callerInfoPool.Get() 56 | ci.FileName, ci.Line, ci.FuncName = fileName, line, funcName 57 | cs.stack = append(cs.stack, ci) 58 | } 59 | 60 | func (cs *callStack) Pop(flag _FORMAT, fileBuffer *buffer.Buffer) { 61 | for i, ci := range cs.stack { 62 | getfileInfo(&flag, &ci.FileName, &ci.Line, &ci.FuncName, fileBuffer) 63 | if i < len(cs.stack)-1 { 64 | fileBuffer.WriteByte('#') 65 | } 66 | callerInfoPool.Put(&ci) 67 | } 68 | return 69 | } 70 | 71 | func collectCallStack(depth int, formatfunc bool, stack *callStack, recursion bool) *callStack { 72 | if depth <= 0 { 73 | return stack 74 | } 75 | if stack == nil { 76 | stack = callStackPool.Get() 77 | } 78 | i := 1 79 | for { 80 | var pcs [1]uintptr 81 | if more := runtime.Callers(depth+i, pcs[:]); more == 0 { 82 | return stack 83 | } 84 | f, ok := m.Get(pcs[0]) 85 | if !ok { 86 | f, _ = runtime.CallersFrames([]uintptr{pcs[0]}).Next() 87 | m.Put(pcs[0], f) 88 | } 89 | if formatfunc { 90 | stack.PushWithFunc(f.File, f.Line, funcname(f.Func.Name())) 91 | } else { 92 | stack.Push(f.File, f.Line) 93 | } 94 | if !recursion { 95 | break 96 | } 97 | i++ 98 | } 99 | return stack 100 | } 101 | 102 | //func collectCallStack(depth int, formatfunc bool, stack *callStack, recursion bool) *callStack { 103 | // if depth <= 0 { 104 | // return stack 105 | // } 106 | // 107 | // pcs := make([]uintptr, 0, 8) 108 | // n := runtime.Callers(depth+1, pcs[:cap(pcs)]) 109 | // for n == cap(pcs) { 110 | // newCap := cap(pcs) + 8 111 | // pcs = make([]uintptr, 0, newCap) 112 | // n = runtime.Callers(depth+1, pcs[:cap(pcs)]) 113 | // } 114 | // 115 | // if stack == nil { 116 | // stack = callStackPool.Get() 117 | // } 118 | // 119 | // pcs = pcs[:n] 120 | // for i := 0; i < n; i++ { 121 | // f, ok := m.Get(pcs[i]) 122 | // if !ok { 123 | // f, _ = runtime.CallersFrames(pcs[i : i+1]).Next() 124 | // m.Put(pcs[i], f) 125 | // } 126 | // if formatfunc { 127 | // stack.PushWithFunc(f.File, f.Line, funcname(f.Func.Name())) 128 | // } else { 129 | // stack.Push(f.File, f.Line) 130 | // } 131 | // if !recursion { 132 | // break 133 | // } 134 | // } 135 | // return stack 136 | //} 137 | -------------------------------------------------------------------------------- /logger/option.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, donnie 2 | // All rights reserved. 3 | // Use of t source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // github.com/donnie4w/go-logger 7 | 8 | package logger 9 | 10 | // FileOption defines the configuration interface for log file rotation. 11 | // It provides settings for file rotation mode, time-based rotation, file path, 12 | // maximum file size, maximum backup count, and compression options. 13 | // FileOption 定义了日志文件切割的配置接口, 14 | // 提供了文件切割模式、时间切割、文件路径、最大文件大小、最大备份数和压缩选项等配置项。 15 | type FileOption interface { 16 | Cutmode() _CUTMODE // Returns the file rotation mode type / 返回文件切割模式类型 17 | TimeMode() _MODE_TIME // Returns the time-based rotation mode / 返回时间切割模式 18 | FilePath() string // Returns the log file path / 返回日志文件路径 19 | MaxSize() int64 // Returns the maximum file size (in bytes) / 返回最大文件大小(字节) 20 | MaxBackup() int // Returns the maximum number of backup files / 返回最大备份文件数量 21 | Compress() bool // Returns whether compression is enabled / 返回是否启用压缩 22 | } 23 | 24 | // FileSizeMode defines the configuration for file rotation based on file size. 25 | // FileSizeMode 定义了按文件大小切割的配置。 26 | type FileSizeMode struct { 27 | Filename string // The path to the log file / 日志文件路径 28 | Maxsize int64 // The maximum file size; when exceeded, a rotation will occur / 文件最大大小,超过该大小时进行切割 29 | Maxbackup int // The maximum number of backup files to keep / 保留的最大备份文件数量 30 | IsCompress bool // Whether to enable compression for backup files / 是否启用备份文件的压缩 31 | } 32 | 33 | func (f *FileSizeMode) Cutmode() _CUTMODE { 34 | return _SIZEMODE // Indicates rotation by file size / 表示按文件大小进行切割 35 | } 36 | 37 | func (f *FileSizeMode) TimeMode() _MODE_TIME { 38 | return MODE_HOUR // This function is not used in this mode 39 | } 40 | 41 | func (f *FileSizeMode) FilePath() string { 42 | return f.Filename // Returns the log file path / 返回日志文件路径 43 | } 44 | 45 | func (f *FileSizeMode) MaxSize() int64 { 46 | return f.Maxsize // Returns the maximum file size limit / 返回最大文件大小限制 47 | } 48 | 49 | func (f *FileSizeMode) MaxBackup() int { 50 | return f.Maxbackup // Returns the maximum number of backup files / 返回最大备份文件数量 51 | } 52 | 53 | func (f *FileSizeMode) Compress() bool { 54 | return f.IsCompress // Returns whether compression is enabled / 返回是否启用压缩 55 | } 56 | 57 | // FileTimeMode defines the configuration for file rotation based on time. 58 | // FileTimeMode 定义了按时间切割的配置。 59 | type FileTimeMode struct { 60 | Filename string // The path to the log file / 日志文件路径 61 | Timemode _MODE_TIME // The time-based rotation mode / 时间切割模式 62 | Maxbackup int // The maximum number of backup files to keep / 保留的最大备份文件数量 63 | IsCompress bool // Whether to enable compression for backup files / 是否启用备份文件的压缩 64 | } 65 | 66 | func (f *FileTimeMode) Cutmode() _CUTMODE { 67 | return _TIMEMODE // Indicates rotation by time / 表示按时间切割 68 | } 69 | 70 | func (f *FileTimeMode) TimeMode() _MODE_TIME { 71 | return f.Timemode // Returns the time-based rotation mode / 返回时间切割模式 72 | } 73 | 74 | func (f *FileTimeMode) FilePath() string { 75 | return f.Filename // Returns the log file path / 返回日志文件路径 76 | } 77 | 78 | func (f *FileTimeMode) MaxSize() int64 { 79 | return 0 // No size limitation for time-based rotation / 按时间切割不考虑文件大小限制 80 | } 81 | 82 | func (f *FileTimeMode) MaxBackup() int { 83 | return f.Maxbackup // Returns the maximum number of backup files / 返回最大备份文件数量 84 | } 85 | 86 | func (f *FileTimeMode) Compress() bool { 87 | return f.IsCompress // Returns whether compression is enabled / 返回是否启用压缩 88 | } 89 | 90 | // FileMixedMode defines the configuration for file rotation based on both time and file size. 91 | // FileMixedMode 定义了按时间和文件大小混合切割的配置。 92 | type FileMixedMode struct { 93 | Filename string // The path to the log file / 日志文件路径 94 | Timemode _MODE_TIME // The time-based rotation mode / 时间切割模式 95 | Maxsize int64 // The maximum file size; when exceeded, a rotation will occur / 文件最大大小,超过该大小时进行切割 96 | Maxbackup int // The maximum number of backup files to keep / 保留的最大备份文件数量 97 | IsCompress bool // Whether to enable compression for backup files / 是否启用备份文件的压缩 98 | } 99 | 100 | func (f *FileMixedMode) Cutmode() _CUTMODE { 101 | return _MIXEDMODE // Indicates rotation by both time and size (mixed mode) / 表示按时间和大小进行混合切割 102 | } 103 | 104 | func (f *FileMixedMode) TimeMode() _MODE_TIME { 105 | return f.Timemode // Returns the time-based rotation mode / 返回时间切割模式 106 | } 107 | 108 | func (f *FileMixedMode) FilePath() string { 109 | return f.Filename // Returns the log file path / 返回日志文件路径 110 | } 111 | 112 | func (f *FileMixedMode) MaxSize() int64 { 113 | return f.Maxsize // Returns the maximum file size for rotation / 返回文件最大大小 114 | } 115 | 116 | func (f *FileMixedMode) MaxBackup() int { 117 | return f.Maxbackup // Returns the maximum number of backup files / 返回最大备份文件数量 118 | } 119 | 120 | func (f *FileMixedMode) Compress() bool { 121 | return f.IsCompress // Returns whether compression is enabled / 返回是否启用压缩 122 | } 123 | 124 | // Option represents a configuration option for the Logging struct. 125 | // It includes various settings such as log level, console output, format, formatter, file options, and a custom handler. 126 | type Option struct { 127 | Level LEVELTYPE // Log level, e.g., DEBUG, INFO, WARN, ERROR, etc. 128 | Console bool // Whether to also output logs to the console. 129 | Format _FORMAT // Log format. 130 | Formatter string // Formatting string for customizing the log output format. 131 | FileOption FileOption // File-specific options for the log handler. 132 | Stacktrace LEVELTYPE // Log level, e.g., DEBUG, INFO, WARN, ERROR, etc. 133 | // CustomHandler 134 | // 135 | // When customHandler returns false, the println function returns without executing further prints. Returning true allows the subsequent print operations to continue. 136 | // 137 | // customHandler返回false时,println函数返回,不再执行后续的打印,返回true时,继续执行后续打印。 138 | CustomHandler func(lc *LogContext) bool // Custom log handler function allowing users to define additional log processing logic. 139 | 140 | // AttrFormat defines a set of customizable formatting functions for log entries. 141 | // This structure allows users to specify custom formats for log levels, timestamps, 142 | // and message bodies, enabling highly flexible log formatting. 143 | AttrFormat *AttrFormat 144 | 145 | // CallDepth Custom function call depth 146 | CallDepth int 147 | } 148 | 149 | type LogContext struct { 150 | Level LEVELTYPE 151 | Args []any 152 | } 153 | 154 | type LevelOption struct { 155 | Format _FORMAT // Log format. 156 | Formatter string // Formatting string for customizing the log output format. 157 | } 158 | 159 | // AttrFormat defines a set of customizable formatting functions for log entries. 160 | // This structure allows users to specify custom formats for log levels, timestamps, 161 | // and message bodies, enabling highly flexible log formatting. 162 | // 163 | // Example usage: 164 | // 165 | // attrFormat := &AttrFormat{ 166 | // SetLevelFmt: func(level LEVELTYPE) string { 167 | // switch level { 168 | // case LEVEL_DEBUG: 169 | // return "DEBUG:" 170 | // case LEVEL_INFO: 171 | // return "INFO :" 172 | // case LEVEL_WARN: 173 | // return "WARN :" 174 | // case LEVEL_ERROR: 175 | // return "ERROR:" 176 | // case LEVEL_FATAL: 177 | // return "FATAL:" 178 | // default: 179 | // return "UNKNOWN:" 180 | // } 181 | // }, 182 | // SetTimeFmt: func() (string, string, string) { 183 | // now := time.Now().Format("2006-01-02 15:04:05") 184 | // return now, "", "" 185 | // }, 186 | // SetBodyFmt: func(level LEVELTYPE, msg []byte) []byte { 187 | // switch level { 188 | // case LEVEL_DEBUG: 189 | // return append([]byte("\033[34m"), append(msg, '\033', '[', '0', 'm')...) // Blue for DEBUG 190 | // case LEVEL_INFO: 191 | // return append([]byte("\033[32m"), append(msg, '\033', '[', '0', 'm')...) // Green for INFO 192 | // case LEVEL_WARN: 193 | // return append([]byte("\033[33m"), append(msg, '\033', '[', '0', 'm')...) // Yellow for WARN 194 | // case LEVEL_ERROR: 195 | // return append([]byte("\033[31m"), append(msg, '\033', '[', '0', 'm')...) // Red for ERROR 196 | // case LEVEL_FATAL: 197 | // return append([]byte("\033[41m"), append(msg, '\033', '[', '0', 'm')...) // Red background for FATAL 198 | // default: 199 | // return msg 200 | // } 201 | // }, 202 | // } 203 | type AttrFormat struct { 204 | // SetLevelFmt defines a function to format log levels. 205 | // This function receives a log level of type LEVELTYPE and returns a formatted string. 206 | // The string represents the level prefix in log entries, such as "DEBUG:" for debug level or "FATAL:" for fatal level. 207 | // 208 | // Example: 209 | // SetLevelFmt: func(level LEVELTYPE) string { 210 | // if level == LEVEL_DEBUG { 211 | // return "DEBUG:" 212 | // } 213 | // return "INFO:" 214 | // } 215 | SetLevelFmt func(level LEVELTYPE) string 216 | 217 | // SetTimeFmt defines a function to format timestamps for log entries. 218 | // This function returns three strings representing different components of a timestamp. 219 | // It allows custom handling of dates, times, or other time-based information. 220 | // 221 | // Example: 222 | // SetTimeFmt: func() (string, string, string) { 223 | // currentTime := time.Now().Format("2006-01-02 15:04:05") 224 | // return currentTime, "", "" 225 | // } 226 | SetTimeFmt func() (string, string, string) 227 | 228 | // SetBodyFmt defines a function to customize the format of log message bodies. 229 | // This function receives the log level and the message body in byte slice format, allowing 230 | // modifications such as adding colors, handling line breaks, or appending custom suffixes. 231 | // 232 | // Example: 233 | // SetBodyFmt: func(level LEVELTYPE, msg []byte) []byte { 234 | // if level == LEVEL_ERROR { 235 | // return append([]byte("ERROR MESSAGE: "), msg...) 236 | // } 237 | // return msg 238 | // } 239 | SetBodyFmt func(level LEVELTYPE, msg []byte) []byte 240 | } 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## go-logger 高性能golang日志库 [[English]](https://github.com/donnie4w/go-logger/blob/master/README_en.md) 2 | 3 | ------------ 4 | 5 | ### 性能特点 6 | 7 | 1. **极高并发性能**:极高的并发写数据性能,比官方库或同类型日志库高**10倍或以上** 8 | 2. **极低内存占用**:是官方库与同类型日志库的几分之一 9 | 10 | ### 功能特点 11 | 12 | 1. **日志级别设置**:支持动态调整日志级别,以便在不同环境下控制日志的详细程度。 13 | 2. **格式化输出**:支持自定义日志的输出格式,包括时间戳、日志级别、日志位置 等元素。 14 | 3. **文件数回滚**:支持按照日志文件数自动文件回滚,并防止日志文件数过多。 15 | 4. **文件压缩**:支持压缩归档日志文件。 16 | 5. **支持标准库log/slog日志文件管理**:支持标准库文件切割,压缩等功能。 17 | 6. **外部处理函数**:支持自定义外部处理函数。 18 | 7. **日志堆栈信息**:支持日志记录点可以回溯到程序入口点的所有函数调用序列,包括每一步函数调用的文件名,函数名,行号 19 | 8. **日志级别独立日志格式输出**:支持不同日志级别 指定不同的日志输出格式。 20 | 21 | ### `go-logger` + `slog` 22 | 23 | - **日志文件管理**: 支持 直接作为go 标准库 `log/slog` 的日志文件管理器,实现 `slog`的日志文件按小时,天,月份,文件大小等多种方式进行日志文件切割,同时也支持按文件大小切分日志文件后,压缩归档日志文件。 24 | - **一致的性能表现**: `go-logger` + slog 内存分配与性能 与 slog直接写日志文件一致。 25 | - 详细参见[使用文档](https://tlnet.top/logdoc "使用文档") 26 | 27 | ### [使用文档](https://tlnet.top/logdoc "使用文档") 28 | 29 | ------------ 30 | 31 | ### 一. 设置日志打印格式 (`SetFormat`) 32 | 33 | ##### 如: SetFormat(FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME) 34 | 35 | ##### 默认格式: `FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME` 36 | 37 | ```text 38 | 不格式化,只打印日志内容 FORMAT_NANO 无格式 39 | 长文件名及行数 FORMAT_LONGFILENAME 全路径 40 | 短文件名及行数 FORMAT_SHORTFILENAME 如:logging_test.go:10 41 | 相对路径文件名及行数 FORMAT_RELATIVEFILENAME 如:logger/test/logging_test.go:10 42 | 精确到日期 FORMAT_DATE 如:2023/02/14 43 | 精确到秒 FORMAT_TIME 如:01:33:27 44 | 精确到微秒 FORMAT_MICROSECONDS 如:01:33:27.123456 45 | 日志级别标识 FORMAT_LEVELFLAG 如:[Debug],[Info],[Warn][Error][Fatal] 46 | 调用函数 FORMAT_FUNC 调用函数的函数名,若设置,则出现在文件名之后 47 | ``` 48 | #### 示例: 49 | 50 | ```go 51 | logger.SetFormat(logger.FORMAT_LEVELFLAG | logger.FORMAT_LONGFILENAME | logger.FORMAT_TIME) 52 | logger.Error("错误信息:文件未找到") 53 | // 输出: 54 | // [ERROR]/usr/log/logging/main.go:20 10:45:00: 错误信息:文件未找到 55 | ``` 56 | 57 | ### 二. 设置日志标识输出格式 (`SetFormatter`) 58 | 59 | ###### `SetFormatter("{level} {time} {file} {message}\n")` 60 | 61 | ##### 默认格式:`"{level} {time} {file} {message}\n"` 62 | 63 | ```text 64 | {level} 日志级别信息:如[Debug],[Info],[Warn],[Error],[Fatal] 65 | {time} 日志时间信息 66 | {file} 文件位置行号信息 67 | {message} 日志内容 68 | ``` 69 | 70 | ##### 说明:除了关键标识 `{message}` `{time}` `{file}` `{level}` 外,其他内容原样输出,如 | , 空格,换行 等 71 | 72 | ###### 通过修改 `formatter`,可以自由定义输出格式,例如: 73 | 74 | ```go 75 | logger.SetFormatter("{time} - {level} - {file} - {message}\n") 76 | logger.Info("日志初始化完成") 77 | // 输出: 78 | // 2023/08/09 10:30:00 - [INFO] - main.go:12 - 日志初始化完成 79 | ``` 80 | 81 | ------------ 82 | 83 | ### 三. 日志级别 (`SetLevel`) (`SetLevelOption`) 84 | 85 | ##### DEBUG < INFO < WARN < ERROR < FATAL 86 | 87 | ###### 关闭所有日志 `SetLevel(OFF)` 88 | 89 | `go-logger` 支持多种日志级别,从 `DEBUG` 到 `FATAL`,并可以通过 `SetLevel` 方法设置日志的输出级别: 90 | 91 | ```go 92 | logger.SetLevel(logger.INFO) 93 | logger.Debug("调试信息:这条日志不会被打印") 94 | logger.Info("信息:这条日志会被打印") 95 | ``` 96 | 97 | ##### 此外,可以通过 `SetLevelOption` 为不同的日志级别设置独立的日志输出格式: 98 | 99 | ```go 100 | logger.SetLevelOption(logger.LEVEL_DEBUG, &logger.LevelOption{ 101 | Format: logger.FORMAT_SHORTFILENAME | logger.FORMAT_TIME, 102 | }) 103 | logger.SetLevelOption(logger.LEVEL_WARN, &logger.LevelOption{ 104 | Format: logger.FORMAT_LONGFILENAME | logger.FORMAT_DATE | logger.FORMAT_FUNC, 105 | }) 106 | ``` 107 | 108 | ##### 示例:分别给不同的日志级别设置不一样的输出格式 109 | ```go 110 | func TestLevelOptions(t *testing.T) { 111 | SetLevelOption(LEVEL_DEBUG, &LevelOption{Format: FORMAT_LEVELFLAG | FORMAT_TIME | FORMAT_SHORTFILENAME}) 112 | SetLevelOption(LEVEL_INFO, &LevelOption{Format: FORMAT_LEVELFLAG}) 113 | SetLevelOption(LEVEL_WARN, &LevelOption{Format: FORMAT_LEVELFLAG | FORMAT_TIME | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_FUNC}) 114 | 115 | Debug("this is a debug message") 116 | Info("this is a info message") 117 | Warn("this is a warn message") 118 | } 119 | ``` 120 | ###### 执行结果 121 | ```go 122 | [DEBUG]18:53:55 logging_test.go:176 this is a debug message 123 | [INFO]this is a info message 124 | [WARN]2024/08/07 18:53:55 logging_test.go:TestLevelOptions:178 this is a warn message 125 | ``` 126 | 127 | ### 四. 文件日志管理 128 | 129 | ##### go-logger支持日志信息写入文件,并提供文件分割的多种策略与压缩备份等特性 130 | 131 | ###### 使用全局log对象时,直接调用设置方法: 132 | ```go 133 | SetRollingDaily() 按日期分割 134 | SetRollingByTime() 可按 小时,天,月 分割日志 135 | SetRollingFile() 指定文件大小分割日志 136 | SetRollingFileLoop() 指定文件大小分割日志,并指定保留最大日志文件数 137 | SetGzipOn(true) 压缩分割的日志文件 138 | ``` 139 | #### 多实例: 140 | ```go 141 | log1 := NewLogger() 142 | log1.SetRollingDaily("", "logMonitor.log") 143 | 144 | log12:= NewLogger() 145 | log2.SetRollingDaily("", "logBusiness.log") 146 | ``` 147 | #### 1. 按日期分割日志文件 148 | ```go 149 | log.SetRollingDaily("/var/logs", "log.txt") 150 | // 每天按 log_20221015.txt格式 分割 151 | //若 log_20221015.txt已经存在,则生成 log_20221015.1.txt ,log_20221015.2.txt等文件 152 | 153 | log.SetRollingByTime("/var/logs", "log.txt",MODE_MONTH) 154 | //按月份分割日志,跨月时,保留上月份日志,如: 155 | // log_202210.txt 156 | // log_202211.txt 157 | // log_202212.txt 158 | 159 | log.SetRollingByTime("/var/logs", "log.txt",MODE_HOUR) 160 | //按小时分割日志, 如: 161 | // log_2022101506.txt 162 | // log_2022101507.txt 163 | // log_2022101508.txt 164 | ``` 165 | #### 2. 按文件大小分割日志文件 166 | ```go 167 | log.SetRollingFile("/var/logs", "log.txt", 300, MB) 168 | //当文件超过300MB时,按log.1.txt,log.2.txt 格式备份 169 | //目录参数可以为空,则默认当前目录。 170 | 171 | log.SetRollingFileLoop("/var/logs", "log.txt", 300, MB, 50) 172 | //设置日志文件大小最大为300M 173 | //日志文件只保留最新的50个 174 | ``` 175 | 176 | ------ 177 | 178 | ### 五. Option参数 (`SetOption`) 179 | 180 | ###### 通过 `Option` 参数可以更加灵活地配置日志。`Option` 包含多个配置项,使得日志配置更加清晰和易于维护。 181 | 182 | ```go 183 | logger.SetOption(&logger.Option{ 184 | Level: logger.LEVEL_DEBUG, 185 | Console: false, 186 | Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_SHORTFILENAME | logger.FORMAT_DATE | logger.FORMAT_TIME, 187 | Formatter: "{level} {time} {file}:{message}\n", 188 | FileOption: &logger.FileSizeMode{ 189 | Filename: "app.log", 190 | Maxsize: 1 << 30, // 1GB 191 | Maxbuckup: 10, 192 | IsCompress: true, 193 | }, 194 | }) 195 | ``` 196 | 197 | ##### 属性说明: 198 | ```text 199 | Level :日志级别 200 | Console :控制台打印 201 | Format :日志格式,默认:FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME 202 | Formatter :日志输出 默认:"{level}{time} {file} {mesaage}\n" 203 | FileOption :日志文件接口参数 204 | Stacktrace :开启日志堆栈信息记录的日志级别 205 | CustomHandler :自定义日志处理函数,返回true时,继续执行打印程序,返回false时,不再执行打印程序 206 | AttrFormat :日志属性格式化 207 | ``` 208 | 1. #### FileOption介绍 209 | 210 | - ###### FileOption为接口,有FileSizeMode与FileTimeMode两个实现对象 211 | - ###### `FileTimeMode` 按时间滚动备份日志文件 212 | ```text 213 | Filename 日志文件路径 214 | Timemode 按小时,天,月份:MODE_HOUR,MODE_DAY,MODE_MONTH 215 | Maxbuckup 最多备份日志文件数 216 | IsCompress 备份文件是否压缩 217 | ``` 218 | 219 | - ###### `FileSizeMode` 按文件大小滚动备份日志文件 220 | ```text 221 | Filename 日志文件路径 222 | Maxsize 日志文件大小的最大值,超过则滚动备份 223 | Maxbuckup 最多备份日志文件数 224 | IsCompress 备份文件是否压缩 225 | ``` 226 | 227 | - ###### `FileMixedMode` 按文件大小和时间混合模式滚动备份日志文件 228 | ```text 229 | Filename 日志文件路径 230 | Maxsize 日志文件大小的最大值,超过则滚动备份 231 | Timemode 按小时,天,月份:MODE_HOUR,MODE_DAY,MODE_MONTH 232 | Maxbuckup 最多备份日志文件数 233 | IsCompress 备份文件是否压缩 234 | ``` 235 | 236 | - ##### SetOption 示例1 `FileTimeMode` 237 | ```go 238 | // debug级别,关闭控制台日志打印,按天备份日志,最多日志文件数位10,备份时压缩文件,日志文件名为 testlogtime.log 239 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileTimeMode{Filename: "testlogtime.log", Maxbuckup: 10, IsCompress: true, Timemode: MODE_DAY}}) 240 | ``` 241 | - ##### SetOption 示例2 `FileSizeMode` 242 | ```go 243 | // debug级别,关闭控制台日志打印,按文件大小备份日志,按每文件大小为1G时备份一个文件, 最多日志文件数位10,备份时压缩文件,日志文件名为 testlog.log 244 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileSizeMode{Filename: "testlog.log", Maxsize: 1<<30, Maxbuckup: 10, IsCompress: true}}) 245 | ``` 246 | 247 | - ##### SetOption 示例3 `FileMixedMode` 248 | ```go 249 | // debug级别,关闭控制台日志打印,按天同时按文件大小备份日志,最多日志文件数位10,备份时压缩文件,日志文件名为 mixedlog.log 250 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileSizeMode{Filename: "mixedlog.log", Maxsize: 1<<30, Maxbuckup: 10, IsCompress: true, Timemode: MODE_DAY}}) 251 | ``` 252 | 253 | 254 | 2. ##### **Stacktrace** 堆栈日志 255 | 256 | - ###### 栈追踪日志功能可以记录日志记录点到程序入口点的所有函数调用序列,包括每一步函数调用的文件名、函数名和行号。这对于调试和错误分析非常有帮助。 257 | - 当日志级别为 `WARN` 或更高时,日志记录将包含完整的调用栈信息。 258 | - **示例** 259 | 260 | ```go 261 | func TestStacktrace(t *testing.T) { 262 | SetOption(&Option{Console: true, Stacktrace: LEVEL_WARN, Format: FORMAT_LEVELFLAG | FORMAT_DATE | FORMAT_TIME | FORMAT_SHORTFILENAME | FORMAT_FUNC}) 263 | Debug("this is a debug message") 264 | Stacktrace1() 265 | } 266 | 267 | func Stacktrace1() { 268 | Info("this is a info message") 269 | Stacktrace2() 270 | } 271 | 272 | func Stacktrace2() { 273 | Warn("this is a warn message") 274 | Stacktrace3() 275 | } 276 | 277 | func Stacktrace3() { 278 | Error("this is a error message") 279 | Fatal("this is a fatal message") 280 | } 281 | ``` 282 | 283 | ##### 执行结果 284 | ```go 285 | [DEBUG]2024/08/07 20:22:40 logging_test.go:TestStacktrace:151 this is a debug message 286 | [INFO]2024/08/07 20:22:40 logging_test.go:Stacktrace1:156 this is a info message 287 | [WARN]2024/08/07 20:22:40 logging_test.go:Stacktrace2:161#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is a warn message 288 | [ERROR]2024/08/07 20:22:40 logging_test.go:Stacktrace3:166#logging_test.go:Stacktrace2:162#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is a error message 289 | [FATAL]2024/08/07 20:22:40 logging_test.go:Stacktrace3:167#logging_test.go:Stacktrace2:162#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is a fatal message 290 | ``` 291 | 292 | 3. #### `CustomHandler` 自定义函数,可以自定义处理逻辑 293 | - **示例** 294 | ```go 295 | func TestCustomHandler(t *testing.T) { 296 | SetOption(&Option{Console: true, CustomHandler: func(lc *LogContext) bool { 297 | fmt.Println("level:", levelname(lc.Level)) 298 | fmt.Println("message:", fmt.Sprint(lc.Args...)) 299 | if lc.Level == LEVEL_ERROR { 300 | return false //if error mesaage , do not print 301 | } 302 | return true 303 | }, 304 | }) 305 | Debug("this is a debug message") 306 | Info("this is a info message") 307 | Warn("this is a warn message") 308 | Error("this is a error message") 309 | } 310 | ``` 311 | - ##### 执行结果:根据CustomHandler的逻辑,不打印Error日志 312 | ```go 313 | level: debug 314 | message: this is a debug message 315 | [DEBUG]2024/08/07 18:51:56 logging_test.go:126 this is a debug message 316 | level: info 317 | message: this is a info message 318 | [INFO]2024/08/07 18:51:56 logging_test.go:127 this is a info message 319 | level: warn 320 | message: this is a warn message 321 | [WARN]2024/08/07 18:51:56 logging_test.go:128 this is a warn message 322 | level: error 323 | message: this is a error message 324 | ``` 325 | 326 | 4. #### `AttrFormat` 日志属性自定义函数 327 | 328 | ```go 329 | func Test_AttrFormat(t *testing.T) { 330 | attrformat := &logger.AttrFormat{ 331 | SetLevelFmt: func(level logger.LEVELTYPE) string { 332 | switch level { 333 | case logger.LEVEL_DEBUG: 334 | return "debug:" 335 | case logger.LEVEL_INFO: 336 | return "info:" 337 | case logger.LEVEL_WARN: 338 | return "warn:" 339 | case logger.LEVEL_ERROR: 340 | return "error>>>>" 341 | case logger.LEVEL_FATAL: 342 | return "[fatal]" 343 | default: 344 | return "[unknown]" 345 | } 346 | }, 347 | SetTimeFmt: func() (string, string, string) { 348 | s := time.Now().Format("2006-01-02 15:04:05") 349 | return s, "", "" 350 | }, 351 | } 352 | logger.SetOption(&logger.Option{AttrFormat: attrformat, Console: true, FileOption: &logger.FileTimeMode{Filename: "testlogtime.log", Maxbuckup: 3, IsCompress: false, Timemode: logger.MODE_MONTH}}) 353 | logger.Debug("this is a debug message:", 1111111111111111111) 354 | logger.Info("this is a info message:", 2222222222222222222) 355 | logger.Warn("this is a warn message:", 33333333333333333) 356 | logger.Error("this is a error message:", 4444444444444444444) 357 | logger.Fatal("this is a fatal message:", 555555555555555555) 358 | } 359 | ``` 360 | 361 | 362 | ------------ 363 | 364 | ### 六. 控制台日志设置 (`SetConsole`) 365 | ```go 366 | //全局log: 367 | SetConsole(false) //控制台不打日志,默认值true 368 | //实例log: 369 | log.SetConsole(false) //控制台不打日志,默认值true 370 | ``` 371 | *** 372 | 373 | ### 七. 校正打印时间 (`TIME_DEVIATION`) 374 | ###### 有时在分布式环境中,可能存在不同机器时间不一致的问题,`go-logger` 允许通过 `TIME_DEVIATION` 参数来进行时间校正。 375 | 376 | ```go 377 | logger.TIME_DEVIATION = 1000 // 将日志时间校正 +1微妙 378 | ``` 379 | 380 | ------ 381 | 382 | ## 性能压测数据: (详细数据可以参考[使用文档](https://tlnet.top/logdoc)) 383 | 384 | | 日志记录器 | 核心数 | 操作次数 | 每操作耗时(ns) | 内存分配(B) | 分配次数 | 385 | |-------------------------|-------|------------|--------------|------------|--------| 386 | | Serial_NativeLog | 4 | 598,425 | 4,095 | 248 | 2 | 387 | | Serial_NativeLog | 8 | 589,526 | 4,272 | 248 | 2 | 388 | | Serial_Zap | 4 | 485,172 | 4,943 | 352 | 6 | 389 | | Serial_Zap | 8 | 491,910 | 4,851 | 353 | 6 | 390 | | Serial_GoLogger | 4 | 527,454 | 3,987 | 80 | 2 | 391 | | Serial_GoLogger | 8 | 574,303 | 4,083 | 80 | 2 | 392 | | Serial_Slog | 4 | 498,553 | 4,952 | 344 | 6 | 393 | | Serial_Slog | 8 | 466,743 | 4,942 | 344 | 6 | 394 | | Serial_SlogAndGoLogger | 4 | 443,798 | 5,149 | 344 | 6 | 395 | | Serial_SlogAndGoLogger | 8 | 460,762 | 5,208 | 344 | 6 | 396 | | Parallel_NativeLog | 4 | 424,681 | 5,176 | 248 | 2 | 397 | | Parallel_NativeLog | 8 | 479,988 | 5,045 | 248 | 2 | 398 | | Parallel_Zap | 4 | 341,937 | 6,736 | 352 | 6 | 399 | | Parallel_Zap | 8 | 353,247 | 6,517 | 353 | 6 | 400 | | Parallel_GoLogger | 4 | 4,240,896 | 549.9 | 163 | 3 | 401 | | Parallel_GoLogger | 8 | 4,441,388 | 550.4 | 128 | 3 | 402 | | Parallel_Slog | 4 | 477,423 | 4,972 | 344 | 6 | 403 | | Parallel_Slog | 8 | 447,642 | 5,064 | 344 | 6 | 404 | | Parallel_SlogAndGoLogger| 4 | 424,813 | 5,242 | 345 | 6 | 405 | | Parallel_SlogAndGoLogger| 8 | 425,070 | 5,215 | 345 | 6 | 406 | 407 | ### 性能分析说明 408 | 409 | 1. **NativeLog**:go自带log库 410 | 2. **Zap**:uber高性能日志库 411 | 3. **GoLogger**:go-logger 412 | 4. **Slog**:go自带的slog库 413 | 5. **SlogAndGoLogger**:使用go-logger作为slog的日志文件管理库 414 | 415 | 416 | ### 性能分析 417 | 418 | | 库 | 测试类型 | 并发数 | 平均时间(ns/op) | 内存分配(B/op) | 内存分配次数(allocs/op) | 419 | |------------------|----------------|--------|------------------|------------------|--------------------------| 420 | | **NativeLog** | Serial | 4 | 3956 | 248 | 2 | 421 | | | | 8 | 4044 | 248 | 2 | 422 | | | Parallel | 4 | 4916 | 248 | 2 | 423 | | | | 8 | 5026 | 248 | 2 | 424 | | **Zap** | Serial | 4 | 4815 | 352 | 6 | 425 | | | | 8 | 4933 | 353 | 6 | 426 | | | Parallel | 4 | 6773 | 352 | 6 | 427 | | | | 8 | 6610 | 353 | 6 | 428 | | **GoLogger** | Serial | 4 | 4010 | 80 | 2 | 429 | | | | 8 | 3966 | 80 | 2 | 430 | | | Parallel | 4 | 568.1 | 165 | 3 | 431 | | | | 8 | 576.0 | 128 | 3 | 432 | | **slog** | Serial | 4 | 4914 | 344 | 6 | 433 | | | | 8 | 4921 | 344 | 6 | 434 | | | Parallel | 4 | 4952 | 344 | 6 | 435 | | | | 8 | 5075 | 344 | 6 | 436 | | **slog + GoLogger** | Serial | 4 | 5058 | 344 | 6 | 437 | | | | 8 | 5046 | 344 | 6 | 438 | | | Parallel | 4 | 5150 | 345 | 6 | 439 | | | | 8 | 5250 | 345 | 6 | 440 | 441 | ### 性能分析 442 | 443 | 1. **NativeLog(log库)**: 444 | - **串行性能**: 具有相对较低的延迟(3956 ns/op 和 4044 ns/op),且内存占用较少(248 B/op)。 445 | - **并行性能**: 在并发测试中,NativeLog 的性能也保持稳定,延迟(4916 ns/op 和 5026 ns/op)仍然低于其他库。 446 | 447 | 2. **Zap(zap库)**: 448 | - **串行性能**: Zap 的串行性能稍逊色于 log,延迟略高(4815 ns/op 和 4933 ns/op),并且内存占用较高(352 B/op)。 449 | - **并行性能**: Zap 在并行测试中表现较差,延迟明显高于其他库,达到 6773 ns/op 和 6610 ns/op,显示出其在高并发情况下的瓶颈。 450 | 451 | 3. **GoLogger(go-logger)**: 452 | - **串行性能**: 在串行性能上表现良好,延迟(4010 ns/op 和 3966 ns/op),内存使用最低(80 B/op)。 453 | - **并行性能**: 在并行测试中表现优异,延迟显著低于其他库,仅为 568.1 ns/op 和 576.0 ns/op,显示了其极高的并发处理能力。 454 | 455 | 4. **slog(slog库)**: 456 | - **串行性能**: slog 的串行性能在所有库中属于中等水平(4914 ns/op 和 4921 ns/op),内存占用相对较高(344 B/op)。 457 | - **并行性能**: 在并行情况下,slog 的性能表现中规中矩(4952 ns/op 和 5075 ns/op)。 458 | 459 | 5. **slog + GoLogger(slog+go-logger)**: 460 | - **串行性能**: 当结合 slog 和 GoLogger 时,性能表现为(5058 ns/op 和 5046 ns/op),内存占用(344 B/op)与单独使用slog库相同。 461 | - **并行性能**: 并行测试中,组合使用的性能(5150 ns/op 和 5250 ns/op)。 462 | 463 | ------- 464 | 465 | ### 高并发场景中,go-logger的性能比同类型的日志库高10倍以上 466 | 467 | ##### 在高并发场景中,`go-logger` 的性能显著优于其他日志库,尤其是在处理大量并发日志写入时。以下是根据你提供的基准测试数据,分析的各个日志库的性能: 468 | 469 | | 库名 | 并发性能(ns/op) | 内存分配(B/op) | 内存分配次数(allocs/op) | 备注 | 470 | |---------------------|-----------------------|-------------------|----------------------------|--------------| 471 | | **NativeLog** | 4916 - 5026 | 248 | 2 | Go自带日志库,性能中等 | 472 | | **Zap** | 6610 - 6773 | 352 | 6 | 性能一般,适合常规场景 | 473 | | **GoLogger** | **568.1** - **576.0** | 165 | 3 | 极高性能,适合高并发场景 | 474 | | **Slog** | 4952 - 5075 | 344 | 6 | 性能一般,适合常规使用 | 475 | | **SlogAndGoLogger** | 5150 - 5250 | 345 | 6 | 与单独使用Slog类似 | 476 | 477 | ### 分析 478 | 479 | 1. **GoLogger(go-logger)**: 480 | - 在高并发环境下,其性能表现极为出色,延迟在 `568.1 ns/op` 和 `576.0 ns/op` 之间,耗时远低于其他库。 481 | - 内存分配显著更少(165 B/op),这意味着在高负载情况下能更有效地管理内存,减少GC压力。 482 | 483 | 2. **NativeLog(log)**: 484 | - 性能适中,延迟在 `4916 ns/op` 到 `5026 ns/op` 之间,内存分配较高(248 B/op),在高并发场景下可能导致性能下降。 485 | 486 | 3. **Zap(zap)**: 487 | - 性能较低,延迟在 `6773 ns/op` 到 `6610 ns/op` 之间,内存分配较多(352 B/op),适合普通使用场景。 488 | 489 | 4. **Slog(slog)**: 490 | - 性能一般,延迟在 `4952 ns/op` 到 `5075 ns/op` 之间,适合普通使用场景。 491 | 492 | 5. **SlogAndGoLogger(slog+go-logger)**: 493 | - 性能稍低于log,与单独使用slog类似,适合使用slog库并且需要管理日志文件的场景。 494 | 495 | ### 结论 496 | 497 | ##### 在高并发场景中,`go-logger` 的性能几乎是其他库的10倍以上,这是由于其优化的内存管理和更快的日志写入速度。它是处理大量并发日志写入的最佳选择,尤其是在对性能要求极高的应用中,推荐优先考虑使用 `go-logger`。 498 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | ## go-logger: A high-performance logging library for Golang [[中文]](https://github.com/donnie4w/go-logger/blob/master/README.md) 2 | 3 | ------------ 4 | 5 | ### Performance Highlights 6 | 7 | 1. **Exceptional Concurrency**: Handles concurrent writes with performance **over 10 times** faster than the standard library and similar logging libraries. 8 | 2. **Minimal Memory Usage**: Utilizes a fraction of the memory required by the standard library and comparable logging libraries. 9 | 10 | ### Key Features 11 | 12 | 1. **Log Level Configuration**: Dynamically adjust log levels to control the verbosity of logs across different environments. 13 | 2. **Customizable Formatting**: Flexible log formatting options for timestamps, log levels, file location, and more. 14 | 3. **File Rotation**: Supports automatic log file rotation based on file count, ensuring manageable log storage. 15 | 4. **File Compression**: Archives log files through compression. 16 | 5. **Standard Library Log Management**: Supports file splitting, compression, and other management features for the Go `log` and `slog` libraries. 17 | 6. **Custom Handlers**: Allows for external handlers to process log output in custom ways. 18 | 7. **Stack Trace Logging**: Records the complete call stack, showing file names, function names, and line numbers for each call step. 19 | 8. **Level-Specific Log Formatting**: Configure independent output formats for different log levels. 20 | 21 | ### `go-logger` + `slog` 22 | 23 | - **File Management**: Can manage `slog` log files with flexible rotation options based on hours, days, months, or file size, with optional compression. 24 | - **Consistent Performance**: Maintains memory allocation and performance consistent with direct `slog` file writes. 25 | - For detailed usage, see the [documentation](https://tlnet.top/logdoc "Documentation"). 26 | 27 | ### [Documentation](https://tlnet.top/logdoc "Documentation") 28 | 29 | ------------ 30 | 31 | ### 1. Setting Log Output Format (`SetFormat`) 32 | 33 | ##### Example: SetFormat(FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME) 34 | 35 | ##### Default Format: `FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME` 36 | 37 | ```plaintext 38 | No formatting, logs message content only FORMAT_NANO No format 39 | Long filename and line number FORMAT_LONGFILENAME Full path 40 | Short filename and line number FORMAT_SHORTFILENAME e.g., logging_test.go:10 41 | Relative path and line number FORMAT_RELATIVEFILENAME e.g., logger/test/logging_test.go:10 42 | Date precision FORMAT_DATE e.g., 2023/02/14 43 | Second precision FORMAT_TIME e.g., 01:33:27 44 | Microsecond precision FORMAT_MICROSECONDS e.g., 01:33:27.123456 45 | Log level indicator FORMAT_LEVELFLAG e.g., [Debug],[Info],[Warn][Error][Fatal] 46 | Function name FORMAT_FUNC Function name appears after filename if set 47 | ``` 48 | 49 | #### Example: 50 | 51 | ```go 52 | logger.SetFormat(logger.FORMAT_LEVELFLAG | logger.FORMAT_LONGFILENAME | logger.FORMAT_TIME) 53 | logger.Error("Error: File not found") 54 | // Output: 55 | // [ERROR]/usr/log/logging/main.go:20 10:45:00: Error: File not found 56 | ``` 57 | 58 | ### 2. Setting Log Identifier Output Format (`SetFormatter`) 59 | 60 | ###### `SetFormatter("{level} {time} {file} {message}\n")` 61 | 62 | ##### Default Format: `"{level} {time} {file} {message}\n"` 63 | 64 | ```plaintext 65 | {level} Log level info: e.g., [Debug],[Info],[Warn],[Error],[Fatal] 66 | {time} Log timestamp 67 | {file} File and line info 68 | {message} Log content 69 | ``` 70 | 71 | ##### Note: Elements `{message}`, `{time}`, `{file}`, `{level}` are recognized; all other characters (e.g., | , spaces, newlines) are output as-is. 72 | 73 | ###### Modify `formatter` to define custom formats, for instance: 74 | 75 | ```go 76 | logger.SetFormatter("{time} - {level} - {file} - {message}\n") 77 | logger.Info("Logger initialized") 78 | // Output: 79 | // 2023/08/09 10:30:00 - [INFO] - main.go:12 - Logger initialized 80 | ``` 81 | 82 | ------------ 83 | 84 | ### 3. Log Levels (`SetLevel`, `SetLevelOption`) 85 | 86 | ##### Log Level Order: DEBUG < INFO < WARN < ERROR < FATAL 87 | 88 | ###### Disable all logs with `SetLevel(OFF)` 89 | 90 | `go-logger` supports a wide range of log levels, from `DEBUG` to `FATAL`, configurable with `SetLevel` to control output verbosity: 91 | 92 | ```go 93 | logger.SetLevel(logger.INFO) 94 | logger.Debug("Debug info: this will not be logged") 95 | logger.Info("Info: this will be logged") 96 | ``` 97 | 98 | ##### Additionally, use `SetLevelOption` to set distinct formats for each log level: 99 | 100 | ```go 101 | logger.SetLevelOption(logger.LEVEL_DEBUG, &logger.LevelOption{ 102 | Format: logger.FORMAT_SHORTFILENAME | logger.FORMAT_TIME, 103 | }) 104 | logger.SetLevelOption(logger.LEVEL_WARN, &logger.LevelOption{ 105 | Format: logger.FORMAT_LONGFILENAME | logger.FORMAT_DATE | logger.FORMAT_FUNC, 106 | }) 107 | ``` 108 | 109 | ##### Example: Setting distinct formats for different log levels 110 | 111 | ```go 112 | func TestLevelOptions(t *testing.T) { 113 | SetLevelOption(LEVEL_DEBUG, &LevelOption{Format: FORMAT_LEVELFLAG | FORMAT_TIME | FORMAT_SHORTFILENAME}) 114 | SetLevelOption(LEVEL_INFO, &LevelOption{Format: FORMAT_LEVELFLAG}) 115 | SetLevelOption(LEVEL_WARN, &LevelOption{Format: FORMAT_LEVELFLAG | FORMAT_TIME | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_FUNC}) 116 | 117 | Debug("This is a debug message") 118 | Info("This is an info message") 119 | Warn("This is a warning message") 120 | } 121 | ``` 122 | ##### Output: 123 | ```plaintext 124 | [DEBUG]18:53:55 logging_test.go:176 This is a debug message 125 | [INFO]This is an info message 126 | [WARN]2024/08/07 18:53:55 logging_test.go:TestLevelOptions:178 This is a warning message 127 | ``` 128 | 129 | ### 4. Log File Management 130 | 131 | ##### `go-logger` supports file logging with various rotation strategies and backup options. 132 | 133 | ###### To use the global log object, call configuration methods directly: 134 | ```go 135 | SetRollingDaily() Rotate by day 136 | SetRollingByTime() Rotate by hour, day, or month 137 | SetRollingFile() Rotate based on file size 138 | SetRollingFileLoop() Rotate by size with a set maximum number of files 139 | SetGzipOn(true) Enable log file compression 140 | ``` 141 | #### Multiple Instances: 142 | ```go 143 | log1 := NewLogger() 144 | log1.SetRollingDaily("", "logMonitor.log") 145 | 146 | log2 := NewLogger() 147 | log2.SetRollingDaily("", "logBusiness.log") 148 | ``` 149 | #### 1. Rotate Log Files by Date 150 | ```go 151 | log.SetRollingDaily("/var/logs", "log.txt") 152 | // Daily log files named like log_20221015.txt, and if it exists, will append .1, .2, etc. 153 | 154 | log.SetRollingByTime("/var/logs", "log.txt",MODE_MONTH) 155 | // Monthly rotation keeps previous months' logs, e.g.: 156 | // log_202210.txt, log_202211.txt, log_202212.txt 157 | 158 | log.SetRollingByTime("/var/logs", "log.txt",MODE_HOUR) 159 | // Rotate hourly, e.g.: 160 | // log_2022101506.txt, log_2022101507.txt, log_2022101508.txt 161 | ``` 162 | #### 2. Rotate Log Files by Size 163 | ```go 164 | log.SetRollingFile("/var/logs", "log.txt", 300, MB) 165 | // Backup when file exceeds 300MB as log.1.txt, log.2.txt, etc. 166 | 167 | log.SetRollingFileLoop("/var/logs", "log.txt", 300, MB, 50) 168 | // Set max size to 300MB with up to 50 recent log files kept 169 | ``` 170 | 171 | ------ 172 | 173 | ### 5. Option Parameters (`SetOption`) 174 | 175 | ###### `Option` parameters provide flexible configurations, organizing log settings for clearer maintenance. 176 | 177 | ```go 178 | logger.SetOption(&logger.Option{ 179 | Level: logger.LEVEL_DEBUG, 180 | Console: false, 181 | Format: logger.FORMAT_LEVELFLAG | logger.FORMAT_SHORTFILENAME | logger.FORMAT_DATE | logger.FORMAT_TIME, 182 | Formatter: "{level} {time} {file}:{message}\n", 183 | FileOption: &logger.FileSizeMode{ 184 | Filename: "app.log", 185 | Maxsize: 1 << 30, // 1GB 186 | Maxbuckup: 10, 187 | IsCompress: true, 188 | }, 189 | }) 190 | ``` 191 | 192 | ##### Properties: 193 | ```plaintext 194 | Level : Log level 195 | Console : Print to console 196 | Format : Log format, default: FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME 197 | Formatter : Log output format, default: "{level}{time} {file} {message}\n" 198 | FileOption : Log file settings 199 | Stacktrace : Stack trace logging level 200 | CustomHandler : Custom log handler function; return true to continue, false to skip log entry 201 | AttrFormat : Custom attribute formatting 202 | ``` 203 | 204 | 1. FileOption Overview 205 | 206 | - ###### FileOption is an interface with two implementation classes: `FileSizeMode` and `FileTimeMode`. 207 | - ###### `FileTimeMode` rotates log files based on time. 208 | ```text 209 | Filename Log file path 210 | Timemode Rotation interval by hour, day, or month: MODE_HOUR, MODE_DAY, MODE_MONTH 211 | Maxbackup Maximum number of log file backups 212 | IsCompress Whether the backup file is compressed 213 | ``` 214 | 215 | - ###### `FileSizeMode` rotates log files based on file size. 216 | ```text 217 | Filename Log file path 218 | Maxsize Maximum log file size; rotation occurs when size is exceeded 219 | Maxbackup Maximum number of log file backups 220 | IsCompress Whether the backup file is compressed 221 | ``` 222 | 223 | - ###### `FileMixedMode` rotates log files based on file size and time. 224 | ```text 225 | Filename Log file path 226 | Timemode Rotation interval by hour, day, or month: MODE_HOUR, MODE_DAY, MODE_MONTH 227 | Maxsize Maximum log file size; rotation occurs when size is exceeded 228 | Maxbackup Maximum number of log file backups 229 | IsCompress Whether the backup file is compressed 230 | ``` 231 | 232 | 233 | 234 | - ##### SetOption Example 1 235 | ```go 236 | // debug level, disable console log printing, daily log rotation, maximum of 10 log files, compress backups, log file named testlogtime.log 237 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileTimeMode{Filename: "testlogtime.log", Maxbackup: 10, IsCompress: true, Timemode: MODE_DAY}}) 238 | ``` 239 | 240 | - ##### SetOption Example 2 241 | ```go 242 | // debug level, disable console log printing, rotate log files by file size, rotate at 1G per file, maximum of 10 log files, compress backups, log file named testlog.log 243 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileSizeMode{Filename: "testlog.log", Maxsize: 1<<30, Maxbackup: 10, IsCompress: true}}) 244 | ``` 245 | 246 | - ##### SetOption Example 3 247 | ```go 248 | // debug level, disable console log printing, rotate log files by file size and time, maximum of 10 log files, compress backups, log file named mixedlog.log 249 | SetOption(&Option{Level: LEVEL_DEBUG, Console: false, FileOption: &FileSizeMode{Filename: "mixedlog.log", Maxsize: 1<<30, Maxbackup: 10, IsCompress: true, Timemode: MODE_DAY}}) 250 | ``` 251 | 252 | 2. Stacktrace Log 253 | 254 | - ###### The stacktrace log feature records the entire function call sequence from the log point to the program entry, including each step's file name, function name, and line number. This is highly useful for debugging and error analysis. 255 | - When the log level is `WARN` or higher, the log entry will include full call stack information. 256 | - **Example** 257 | 258 | ```go 259 | func TestStacktrace(t *testing.T) { 260 | SetOption(&Option{Console: true, Stacktrace: LEVEL_WARN, Format: FORMAT_LEVELFLAG | FORMAT_DATE | FORMAT_TIME | FORMAT_SHORTFILENAME | FORMAT_FUNC}) 261 | Debug("this is a debug message") 262 | Stacktrace1() 263 | } 264 | 265 | func Stacktrace1() { 266 | Info("this is an info message") 267 | Stacktrace2() 268 | } 269 | 270 | func Stacktrace2() { 271 | Warn("this is a warn message") 272 | Stacktrace3() 273 | } 274 | 275 | func Stacktrace3() { 276 | Error("this is an error message") 277 | Fatal("this is a fatal message") 278 | } 279 | ``` 280 | 281 | ##### Execution Result 282 | ```go 283 | [DEBUG]2024/08/07 20:22:40 logging_test.go:TestStacktrace:151 this is a debug message 284 | [INFO]2024/08/07 20:22:40 logging_test.go:Stacktrace1:156 this is an info message 285 | [WARN]2024/08/07 20:22:40 logging_test.go:Stacktrace2:161#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is a warn message 286 | [ERROR]2024/08/07 20:22:40 logging_test.go:Stacktrace3:166#logging_test.go:Stacktrace2:162#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is an error message 287 | [FATAL]2024/08/07 20:22:40 logging_test.go:Stacktrace3:167#logging_test.go:Stacktrace2:162#logging_test.go:Stacktrace1:157#logging_test.go:TestStacktrace:152#testing.go:tRunner:1689#asm_amd64.s:goexit:1695 this is a fatal message 288 | ``` 289 | 290 | 3. `CustomHandler` - Custom Function for Handling Logic 291 | 292 | - **Example** 293 | ```go 294 | func TestCustomHandler(t *testing.T) { 295 | SetOption(&Option{Console: true, CustomHandler: func(lc *LogContext) bool { 296 | fmt.Println("level:", levelname(lc.Level)) 297 | fmt.Println("message:", fmt.Sprint(lc.Args...)) 298 | if lc.Level == LEVEL_ERROR { 299 | return false // Do not print if it's an error message 300 | } 301 | return true 302 | }, 303 | }) 304 | Debug("this is a debug message") 305 | Info("this is an info message") 306 | Warn("this is a warn message") 307 | Error("this is an error message") 308 | } 309 | ``` 310 | - ##### Execution Result: Error logs are not printed based on `CustomHandler` logic. 311 | ```go 312 | level: debug 313 | message: this is a debug message 314 | [DEBUG]2024/08/07 18:51:56 logging_test.go:126 this is a debug message 315 | level: info 316 | message: this is an info message 317 | [INFO]2024/08/07 18:51:56 logging_test.go:127 this is an info message 318 | level: warn 319 | message: this is a warn message 320 | [WARN]2024/08/07 18:51:56 logging_test.go:128 this is a warn message 321 | level: error 322 | message: this is an error message 323 | ``` 324 | 325 | 4. `AttrFormat` - Custom Attribute Format Function for Logs 326 | 327 | ```go 328 | func Test_AttrFormat(t *testing.T) { 329 | attrformat := &logger.AttrFormat{ 330 | SetLevelFmt: func(level logger.LEVELTYPE) string { 331 | switch level { 332 | case logger.LEVEL_DEBUG: 333 | return "debug:" 334 | case logger.LEVEL_INFO: 335 | return "info:" 336 | case logger.LEVEL_WARN: 337 | return "warn:" 338 | case logger.LEVEL_ERROR: 339 | return "error>>>>" 340 | case logger.LEVEL_FATAL: 341 | return "[fatal]" 342 | default: 343 | return "[unknown]" 344 | } 345 | }, 346 | SetTimeFmt: func() (string, string, string) { 347 | s := time.Now().Format("2006-01-02 15:04:05") 348 | return s, "", "" 349 | }, 350 | } 351 | logger.SetOption(&logger.Option{AttrFormat: attrformat, Console: true, FileOption: &logger.FileTimeMode{Filename: "testlogtime.log", Maxbackup: 3, IsCompress: false, Timemode: logger.MODE_MONTH}}) 352 | logger.Debug("this is a debug message:", 1111111111111111111) 353 | logger.Info("this is an info message:", 2222222222222222222) 354 | logger.Warn("this is a warn message:", 33333333333333333) 355 | logger.Error("this is an error message:", 4444444444444444444) 356 | logger.Fatal("this is a fatal message:", 555555555555555555) 357 | } 358 | ``` 359 | 360 | --- 361 | 362 | ### 6. Console Log Setting (`SetConsole`) 363 | ```go 364 | // Global log: 365 | SetConsole(false) // Disable console logging, default value is true 366 | // Instance log: 367 | log.SetConsole(false) // Disable console logging, default value is true 368 | ``` 369 | 370 | ### 7. Adjusting Log Print Time (`TIME_DEVIATION`) 371 | ###### In distributed environments, time inconsistencies may occur across machines. `go-logger` allows time adjustment using the `TIME_DEVIATION` parameter. 372 | 373 | ```go 374 | logger.TIME_DEVIATION = 1000 // Adjust log time by +1 microsecond 375 | ``` 376 | ------- 377 | 378 | ## Performance Benchmark Data: (Detailed data can be referenced in the [Usage Documentation](https://tlnet.top/logdoc)) 379 | 380 | | Logger | Core Count | Operations | Time per Op (ns) | Memory Allocation (B) | Allocations | 381 | |-------------------------|------------|-------------|-------------------|-----------------------|-------------| 382 | | Serial_NativeLog | 4 | 598,425 | 4,095 | 248 | 2 | 383 | | Serial_NativeLog | 8 | 589,526 | 4,272 | 248 | 2 | 384 | | Serial_Zap | 4 | 485,172 | 4,943 | 352 | 6 | 385 | | Serial_Zap | 8 | 491,910 | 4,851 | 353 | 6 | 386 | | Serial_GoLogger | 4 | 527,454 | 3,987 | 80 | 2 | 387 | | Serial_GoLogger | 8 | 574,303 | 4,083 | 80 | 2 | 388 | | Serial_Slog | 4 | 498,553 | 4,952 | 344 | 6 | 389 | | Serial_Slog | 8 | 466,743 | 4,942 | 344 | 6 | 390 | | Serial_SlogAndGoLogger | 4 | 443,798 | 5,149 | 344 | 6 | 391 | | Serial_SlogAndGoLogger | 8 | 460,762 | 5,208 | 344 | 6 | 392 | | Parallel_NativeLog | 4 | 424,681 | 5,176 | 248 | 2 | 393 | | Parallel_NativeLog | 8 | 479,988 | 5,045 | 248 | 2 | 394 | | Parallel_Zap | 4 | 341,937 | 6,736 | 352 | 6 | 395 | | Parallel_Zap | 8 | 353,247 | 6,517 | 353 | 6 | 396 | | Parallel_GoLogger | 4 | 4,240,896 | 549.9 | 163 | 3 | 397 | | Parallel_GoLogger | 8 | 4,441,388 | 550.4 | 128 | 3 | 398 | | Parallel_Slog | 4 | 477,423 | 4,972 | 344 | 6 | 399 | | Parallel_Slog | 8 | 447,642 | 5,064 | 344 | 6 | 400 | | Parallel_SlogAndGoLogger| 4 | 424,813 | 5,242 | 345 | 6 | 401 | | Parallel_SlogAndGoLogger| 8 | 425,070 | 5,215 | 345 | 6 | 402 | 403 | ### Performance Analysis specification 404 | 405 | 1. **NativeLog**: Go's built-in logging library 406 | 2. **Zap**: Uber’s high-performance logging library 407 | 3. **GoLogger**: go-logger 408 | 4. **Slog**: Go’s built-in Slog library 409 | 5. **Slog + GoLogger**: Slog using GoLogger for log file management 410 | 411 | ### Performance Analysis 412 | 413 | | Library | Test Type | Concurrency | Avg. Time (ns/op) | Mem Allocation (B/op) | Allocations (allocs/op) | 414 | |-------------------|------------------|-------------|--------------------|------------------------|--------------------------| 415 | | **NativeLog** | Serial | 4 | 3956 | 248 | 2 | 416 | | | | 8 | 4044 | 248 | 2 | 417 | | | Parallel | 4 | 4916 | 248 | 2 | 418 | | | | 8 | 5026 | 248 | 2 | 419 | | **Zap** | Serial | 4 | 4815 | 352 | 6 | 420 | | | | 8 | 4933 | 353 | 6 | 421 | | | Parallel | 4 | 6773 | 352 | 6 | 422 | | | | 8 | 6610 | 353 | 6 | 423 | | **GoLogger** | Serial | 4 | 4010 | 80 | 2 | 424 | | | | 8 | 3966 | 80 | 2 | 425 | | | Parallel | 4 | 568.1 | 165 | 3 | 426 | | | | 8 | 576.0 | 128 | 3 | 427 | | **Slog** | Serial | 4 | 4914 | 344 | 6 | 428 | | | | 8 | 4921 | 344 | 6 | 429 | | | Parallel | 4 | 4952 | 344 | 6 | 430 | | | | 8 | 5075 | 344 | 6 | 431 | | **Slog + GoLogger** | Serial | 4 | 5058 | 344 | 6 | 432 | | | | 8 | 5046 | 344 | 6 | 433 | | | Parallel | 4 | 5150 | 345 | 6 | 434 | | | | 8 | 5250 | 345 | 6 | 435 | 436 | ### Performance Analysis 437 | 438 | 1. **NativeLog (log)**: 439 | - **Serial Performance**: Offers relatively low latency (3956 ns/op and 4044 ns/op) with low memory usage (248 B/op). 440 | - **Parallel Performance**: Performance remains stable under parallel testing with latency (4916 ns/op and 5026 ns/op), still lower than other libraries. 441 | 442 | 2. **Zap (zap)**: 443 | - **Serial Performance**: Slightly lower than log, with higher latency (4815 ns/op and 4933 ns/op) and higher memory usage (352 B/op). 444 | - **Parallel Performance**: Performs worse in parallel testing, with latency peaking at 6773 ns/op and 6610 ns/op, highlighting limitations under high concurrency. 445 | 446 | 3. **GoLogger (go-logger)**: 447 | - **Serial Performance**: Performs well with latency (4010 ns/op and 3966 ns/op), and the lowest memory usage (80 B/op). 448 | - **Parallel Performance**: Excellent parallel performance with the lowest latency at 568.1 ns/op and 576.0 ns/op, showing high concurrency capabilities. 449 | 450 | 4. **Slog (slog)**: 451 | - **Serial Performance**: Average performance among all libraries, with latency (4914 ns/op and 4921 ns/op) and higher memory usage (344 B/op). 452 | - **Parallel Performance**: Consistent performance with latency (4952 ns/op and 5075 ns/op) under parallel testing. 453 | 454 | 5. **Slog + GoLogger (slog+go-logger)**: 455 | - **Serial Performance**: Combined performance (5058 ns/op and 5046 ns/op) with memory usage (344 B/op) similar to slog alone. 456 | - **Parallel Performance**: Slightly lower performance than log alone, making it suitable when using slog with additional log management from GoLogger. 457 | 458 | ------- 459 | 460 | ### GoLogger demonstrates 10x higher performance than similar libraries in high-concurrency environments 461 | 462 | ##### GoLogger shows a significant advantage in handling high-concurrency log writing, with benchmark data revealing its superiority over other logging libraries: 463 | 464 | | Library | Concurrency Performance (ns/op) | Memory Allocation (B/op) | Allocation Count (allocs/op) | Notes | 465 | |-----------------|---------------------------------|---------------------------|------------------------------|------------------------| 466 | | **NativeLog** | 4916 - 5026 | 248 | 2 | Go’s default logger, medium performance | 467 | | **Zap** | 6610 - 6773 | 352 | 6 | Moderate performance, suited for general use | 468 | | **GoLogger** | **568.1 - 576.0** | 165 | 3 | Superior performance, ideal for high-concurrency | 469 | | **Slog** | 4952 - 5075 | 344 | 6 | Average performance, suitable for regular use | 470 | | **Slog+GoLogger** | 5150 - 5250 | 345 | 6 | Similar to standalone Slog | 471 | 472 | ### Analysis 473 | 474 | 1. **GoLogger (go-logger)**: 475 | - Outstanding performance in high-concurrency scenarios, with latency of `568.1 ns/op` and `576.0 ns/op`, much faster than other libraries. 476 | - Lower memory allocations (165 B/op) reduce GC pressure, optimizing memory management under high load. 477 | 478 | 2. **NativeLog (log)**: 479 | - Moderate performance with latency between `4916 ns/op` and `5026 ns/op`, higher memory usage (248 B/op) may 480 | 481 | 3. **Zap (zap)**: 482 | - Has the most consistent performance in single-threaded environments but suffers significant latency in high-concurrency scenarios (latency peaks at 6773 ns/op). 483 | 484 | 4. **Slog (slog)**: 485 | - General performance with latency between `4952 ns/op` and `5075 ns/op`, suitable for standard usage scenarios, providing stable results without high concurrency optimizations. 486 | 487 | 5. **SlogAndGoLogger (slog+go-logger)**: 488 | - Slightly lower performance than log, similar to using slog alone; suitable when combining slog’s standardized logging with GoLogger’s file management functionality, ideal for use cases prioritizing both features and performance. 489 | 490 | ### Conclusion 491 | 492 | ##### In high-concurrency scenarios, `go-logger` performs nearly 10 times faster than other libraries, thanks to its optimized memory management and faster logging speed. It is the optimal choice for handling large volumes of concurrent log writes, particularly in applications with strict performance requirements, and is highly recommended for such cases. -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, donnie 2 | // All rights reserved. 3 | // Use of t source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // github.com/donnie4w/go-logger 7 | 8 | package logger 9 | 10 | import ( 11 | "bytes" 12 | "compress/gzip" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "os" 17 | "path/filepath" 18 | "regexp" 19 | "runtime" 20 | "sort" 21 | "strings" 22 | "sync" 23 | "sync/atomic" 24 | "time" 25 | 26 | "github.com/donnie4w/gofer/buffer" 27 | . "github.com/donnie4w/gofer/fastio" 28 | "github.com/donnie4w/gofer/hashmap" 29 | ) 30 | 31 | type LEVELTYPE int8 32 | type _UNIT int64 33 | type _MODE_TIME uint8 34 | type _CUTMODE int //dailyRolling ,rollingFile 35 | type _FORMAT int 36 | 37 | const ( 38 | _DATEFORMAT_DAY = "20060102" 39 | _DATEFORMAT_HOUR = "2006010215" 40 | _DATEFORMAT_MONTH = "200601" 41 | default_format = FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME 42 | default_level = LEVEL_ALL 43 | ) 44 | 45 | var static_lo = NewLogger() 46 | 47 | var TIME_DEVIATION time.Duration 48 | 49 | const ( 50 | _ = iota 51 | KB _UNIT = 1 << (iota * 10) 52 | MB 53 | GB 54 | TB 55 | ) 56 | 57 | const ( 58 | MODE_HOUR _MODE_TIME = 1 59 | MODE_DAY _MODE_TIME = 2 60 | MODE_MONTH _MODE_TIME = 3 61 | ) 62 | 63 | const ( 64 | // FORMAT_NANO 65 | // 66 | // no format, Only log content is printed 67 | // 无其他格式,只打印日志内容 68 | FORMAT_NANO _FORMAT = 64 69 | 70 | // FORMAT_LONGFILENAME 71 | // 72 | // full file name and line number 73 | // 长文件名(文件绝对路径)及行数 74 | FORMAT_LONGFILENAME = _FORMAT(8) 75 | 76 | // FORMAT_SHORTFILENAME 77 | // 78 | // final file name element and line number 79 | // 短文件名及行数 80 | FORMAT_SHORTFILENAME = _FORMAT(16) 81 | 82 | // FORMAT_RELATIVEFILENAME 83 | // 84 | // relative file name element and line number 85 | // 相对路径文件名及行数 86 | FORMAT_RELATIVEFILENAME = _FORMAT(256) 87 | 88 | // FORMAT_DATE 89 | // 90 | // the date in the local time zone: 2009/01/23 91 | // 日期时间精确到天 92 | FORMAT_DATE = _FORMAT(1) 93 | 94 | // FORMAT_TIME 95 | // 96 | // the time in the local time zone: 01:23:23 97 | // 时间精确到秒 98 | FORMAT_TIME = _FORMAT(2) 99 | 100 | // FORMAT_MICROSECONDS 101 | // 102 | // microsecond resolution: 01:23:23.123123. 103 | // 时间精确到微秒 104 | FORMAT_MICROSECONDS = _FORMAT(4) 105 | 106 | // FORMAT_LEVELFLAG 107 | // 108 | //Log level flag. e.g. [DEBUG],[INFO],[WARN],[ERROR],[FATAL] 109 | // 日志级别表示 110 | FORMAT_LEVELFLAG = _FORMAT(32) 111 | 112 | // FORMAT_FUNC 113 | // 114 | // the func of caller 115 | // 调用的函数名 116 | FORMAT_FUNC = _FORMAT(128) 117 | ) 118 | 119 | const ( 120 | 121 | // LEVEL_ALL is the lowest level,If the log level is this level, logs of other levels can be printed 122 | // 日志级别:ALL 打印所有日志 123 | LEVEL_ALL LEVELTYPE = iota 124 | 125 | // LEVEL_DEBUG debug log level 126 | // 日志级别:DEBUG 小于INFO 127 | LEVEL_DEBUG 128 | 129 | // LEVEL_INFO info log level 130 | // 日志级别:INFO 小于 WARN 131 | LEVEL_INFO 132 | 133 | // LEVEL_WARN warn log level 134 | // 日志级别:WARN 小于 ERROR 135 | LEVEL_WARN 136 | 137 | // LEVEL_ERROR error log level 138 | // 日志级别:ERROR 小于 FATAL 139 | LEVEL_ERROR 140 | 141 | // LEVEL_FATAL fatal log level 142 | // 日志级别:FATAL 小于 OFF 143 | LEVEL_FATAL 144 | 145 | // LEVEL_OFF means none of the logs can be printed 146 | // 日志级别:off 不打印任何日志 147 | LEVEL_OFF 148 | ) 149 | 150 | var _DEBUG, _INFO, _WARN, _ERROR, _FATALE = []byte("[DEBUG]"), []byte("[INFO]"), []byte("[WARN]"), []byte("[ERROR]"), []byte("[FATAL]") 151 | 152 | const ( 153 | _TIMEMODE _CUTMODE = 1 154 | _SIZEMODE _CUTMODE = 2 155 | _MIXEDMODE _CUTMODE = 3 156 | ) 157 | 158 | // 使用常量定义标志位组合 159 | const ( 160 | timeFlags = FORMAT_DATE | FORMAT_TIME | FORMAT_MICROSECONDS 161 | fileFlags = FORMAT_SHORTFILENAME | FORMAT_LONGFILENAME | FORMAT_RELATIVEFILENAME 162 | ) 163 | 164 | // SetFormat sets the logging format to the specified format type. 165 | // 166 | // 设置打印格式: FORMAT_LEVELFLAG | FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME 167 | // 168 | // Parameters: 169 | // - format: The desired format for log entries, represented by the _FORMAT type. 170 | // 171 | // Returns: 172 | // - *Logging: A Logging instance to enable method chaining. 173 | func SetFormat(format _FORMAT) *Logging { 174 | return static_lo.SetFormat(format) 175 | } 176 | 177 | // SetLevel sets the logging level to the specified level type. 178 | // 179 | // 设置控制台日志级别,默认 LEVEL_ALL, 其他: LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN 180 | // 181 | // Parameters: 182 | // - level: The logging level (e.g., LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN), represented by the LEVELTYPE type. 183 | // 184 | // Returns: 185 | // - *Logging: A Logging instance to enable method chaining. 186 | func SetLevel(level LEVELTYPE) *Logging { 187 | return static_lo.SetLevel(level) 188 | } 189 | 190 | // SetFormatter specifies a custom format string for the logging output. 191 | // 192 | // 设置输出格式,默认: "{level}{time} {file} {message}\n" 193 | // 194 | // Parameters: 195 | // - formatter: A string defining the format for log entries, allowing custom log entry layouts. 196 | // 197 | // Returns: 198 | // - *Logging: A Logging instance to enable method chaining. 199 | func SetFormatter(formatter string) *Logging { 200 | return static_lo.SetFormatter(formatter) 201 | } 202 | 203 | // SetConsole print logs on the console or not. default true 204 | func SetConsole(on bool) *Logging { 205 | return static_lo.SetConsole(on) 206 | 207 | } 208 | 209 | // GetStaticLogger 210 | // return the default log object 211 | // 获得全局Logger对象 212 | func GetStaticLogger() *Logging { 213 | return static_lo 214 | } 215 | 216 | // SetRollingFile when the log file(fileDir+`\`+fileName) exceeds the specified size(maxFileSize), it will be backed up with a specified file name 217 | // Parameters: 218 | // - fileDir :directory where log files are stored, If it is the current directory, you also can set it to "" 219 | // - fileName : log file name 220 | // - maxFileSize : maximum size of a log file 221 | // - unit : size unit : KB,MB,GB,TB 222 | // 223 | // Use SetOption() instead. 224 | func SetRollingFile(fileDir, fileName string, maxFileSize int64, unit _UNIT) (l *Logging, err error) { 225 | return SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, 0) 226 | } 227 | 228 | // SetRollingDaily yesterday's log data is backed up to a specified log file each day 229 | // Parameters: 230 | // - fileDir :directory where log files are stored, If it is the current directory, you also can set it to "" 231 | // - fileName : log file name 232 | // 233 | // Use SetOption() instead. 234 | func SetRollingDaily(fileDir, fileName string) (l *Logging, err error) { 235 | return SetRollingByTime(fileDir, fileName, MODE_DAY) 236 | } 237 | 238 | // SetRollingFileLoop like SetRollingFile,but only keep (maxFileNum) current files 239 | // - maxFileNum : the number of files that are retained 240 | // 241 | // Use SetOption() instead. 242 | func SetRollingFileLoop(fileDir, fileName string, maxFileSize int64, unit _UNIT, maxFileNum int) (l *Logging, err error) { 243 | return static_lo.SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, maxFileNum) 244 | } 245 | 246 | // SetRollingByTime like SetRollingDaily,but supporte hourly backup ,dayly backup and monthly backup 247 | // mode : MODE_HOUR MODE_DAY MODE_MONTH 248 | // 249 | // Use SetOption() instead. 250 | func SetRollingByTime(fileDir, fileName string, mode _MODE_TIME) (l *Logging, err error) { 251 | return static_lo.SetRollingByTime(fileDir, fileName, mode) 252 | } 253 | 254 | // SetGzipOn when set true, the specified backup file of both SetRollingFile and SetRollingFileLoop will be save as a compressed file 255 | // 256 | // Use SetOption() instead. 257 | func SetGzipOn(is bool) (l *Logging) { 258 | return static_lo.SetGzipOn(is) 259 | } 260 | 261 | // SetOption 配置对象 262 | // 263 | // e.g. 264 | // 265 | // SetOption(&Option{Level: LEVEL_DEBUG, Console: true, FileOption: &FileSizeMode{Filename: "test.log", Maxsize: 500, Maxbackup: 3, IsCompress: false}}) 266 | func SetOption(option *Option) *Logging { 267 | return static_lo.SetOption(option) 268 | } 269 | 270 | // Debug logs a message at the DEBUG level using the default logging instance. 271 | // Accepts any number of arguments to format the log entry. 272 | // 273 | // Parameters: 274 | // - v: Variadic arguments to be logged. 275 | // 276 | // Returns: 277 | // - *Logging: A Logging instance for possible further usage. 278 | func Debug(v ...any) *Logging { 279 | return println(nil, LEVEL_DEBUG, 2, v...) 280 | } 281 | 282 | // Info logs a message at the INFO level using the default logging instance. 283 | // Accepts any number of arguments to format the log entry. 284 | // 285 | // Parameters: 286 | // - v: Variadic arguments to be logged. 287 | // 288 | // Returns: 289 | // - *Logging: A Logging instance for possible further usage. 290 | func Info(v ...any) *Logging { 291 | return println(nil, LEVEL_INFO, 2, v...) 292 | } 293 | 294 | // Warn logs a message at the WARN level using the default logging instance. 295 | // Accepts any number of arguments to format the log entry. 296 | // 297 | // Parameters: 298 | // - v: Variadic arguments to be logged. 299 | // 300 | // Returns: 301 | // - *Logging: A Logging instance for possible further usage. 302 | func Warn(v ...any) *Logging { 303 | return println(nil, LEVEL_WARN, 2, v...) 304 | } 305 | 306 | // Error logs a message at the ERROR level using the default logging instance. 307 | // Accepts any number of arguments to format the log entry. 308 | // 309 | // Parameters: 310 | // - v: Variadic arguments to be logged. 311 | // 312 | // Returns: 313 | // - *Logging: A Logging instance for possible further usage. 314 | func Error(v ...any) *Logging { 315 | return println(nil, LEVEL_ERROR, 2, v...) 316 | } 317 | 318 | // Fatal logs a message at the FATAL level using the default logging instance and may terminate the application. 319 | // Accepts any number of arguments to format the log entry. 320 | // 321 | // Parameters: 322 | // - v: Variadic arguments to be logged. 323 | // 324 | // Returns: 325 | // - *Logging: A Logging instance for possible further usage. 326 | func Fatal(v ...any) *Logging { 327 | return println(nil, LEVEL_FATAL, 2, v...) 328 | } 329 | 330 | // Debugf logs a formatted message at the DEBUG level using the default logging instance. 331 | // Takes a format string followed by variadic arguments to be formatted. 332 | // 333 | // Parameters: 334 | // - format: The format string for the log entry. 335 | // - v: Variadic arguments to be formatted according to the format string. 336 | // 337 | // Returns: 338 | // - *Logging: A Logging instance for possible further usage. 339 | func Debugf(format string, v ...any) *Logging { 340 | return println(&format, LEVEL_DEBUG, 2, v...) 341 | } 342 | 343 | // Infof logs a formatted message at the INFO level using the default logging instance. 344 | // Takes a format string followed by variadic arguments to be formatted. 345 | // 346 | // Parameters: 347 | // - format: The format string for the log entry. 348 | // - v: Variadic arguments to be formatted according to the format string. 349 | // 350 | // Returns: 351 | // - *Logging: A Logging instance for possible further usage. 352 | func Infof(format string, v ...any) *Logging { 353 | return println(&format, LEVEL_INFO, 2, v...) 354 | } 355 | 356 | // Warnf logs a formatted message at the WARN level using the default logging instance. 357 | // Takes a format string followed by variadic arguments to be formatted. 358 | // 359 | // Parameters: 360 | // - format: The format string for the log entry. 361 | // - v: Variadic arguments to be formatted according to the format string. 362 | // 363 | // Returns: 364 | // - *Logging: A Logging instance for possible further usage. 365 | func Warnf(format string, v ...any) *Logging { 366 | return println(&format, LEVEL_WARN, 2, v...) 367 | } 368 | 369 | // Errorf logs a formatted message at the ERROR level using the default logging instance. 370 | // Takes a format string followed by variadic arguments to be formatted. 371 | // 372 | // Parameters: 373 | // - format: The format string for the log entry. 374 | // - v: Variadic arguments to be formatted according to the format string. 375 | // 376 | // Returns: 377 | // - *Logging: A Logging instance for possible further usage. 378 | func Errorf(format string, v ...any) *Logging { 379 | return println(&format, LEVEL_ERROR, 2, v...) 380 | } 381 | 382 | // Fatalf logs a formatted message at the FATAL level using the default logging instance and may terminate the application. 383 | // Takes a format string followed by variadic arguments to be formatted. 384 | // 385 | // Parameters: 386 | // - format: The format string for the log entry. 387 | // - v: Variadic arguments to be formatted according to the format string. 388 | // 389 | // Returns: 390 | // - *Logging: A Logging instance for possible further usage. 391 | func Fatalf(format string, v ...any) *Logging { 392 | return println(&format, LEVEL_FATAL, 2, v...) 393 | } 394 | 395 | func println(format *string, level LEVELTYPE, calldepth int, v ...any) *Logging { 396 | return static_lo.println(format, level, k1(calldepth), v...) 397 | } 398 | 399 | func fprintln(format *string, _format _FORMAT, level, stacktrace LEVELTYPE, calldepth int, formatter *string, attrFormat *AttrFormat, v ...any) { 400 | var bs []byte 401 | if format == nil { 402 | bs = fmt.Append([]byte{}, v...) 403 | } else { 404 | bs = fmt.Appendf([]byte{}, *format, v...) 405 | } 406 | consolewrite(bs, level, stacktrace, _format, k1(calldepth), formatter, attrFormat) 407 | } 408 | 409 | func getlevelname(level LEVELTYPE) (levelname []byte) { 410 | switch level { 411 | case LEVEL_ALL: 412 | levelname = []byte("ALL") 413 | case LEVEL_DEBUG: 414 | levelname = _DEBUG 415 | case LEVEL_INFO: 416 | levelname = _INFO 417 | case LEVEL_WARN: 418 | levelname = _WARN 419 | case LEVEL_ERROR: 420 | levelname = _ERROR 421 | case LEVEL_FATAL: 422 | levelname = _FATALE 423 | default: 424 | levelname = []byte{} 425 | } 426 | return 427 | } 428 | 429 | // Logging is the primary data structure for configuring and managing logging behavior. 430 | type Logging struct { 431 | _level LEVELTYPE // Log level, e.g., DEBUG, INFO, WARN, ERROR, etc. 432 | _format _FORMAT // Log format. 433 | _rwLock *sync.RWMutex // Read-write lock for concurrent safe access to the logging struct. 434 | _fileDir string // Directory path where log files are stored. 435 | _fileName string // Base name of the log file. 436 | _maxSize int64 // Maximum size of a single log file. 437 | _unit _UNIT // Size unit, e.g., Byte, KB, MB, etc. 438 | _cutmode _CUTMODE // Log file cutting mode, e.g., by size or by time. 439 | _mode _MODE_TIME // Time-based rolling mode for log files, e.g., daily, weekly, etc. 440 | _filehandler *fileHandler // File handler for operations on log files. 441 | _isFileWell bool // Indicates whether the log file is in good condition. 442 | _formatter string // Formatting string for customizing the log output format. 443 | _maxBackup int // Maximum number of backup log files to keep. 444 | _isConsole bool // Whether to also output logs to the console. 445 | _gzip bool // Whether to enable GZIP compression for old log files. 446 | prevTime int64 // The timestamp of last print 447 | callDepth int // the depth of function call 448 | stacktrace LEVELTYPE // Log level, e.g., DEBUG, INFO, WARN, ERROR, etc. 449 | customHandler func(lc *LogContext) bool // Custom log handler function allowing users to define additional log processing logic. 450 | atStart atomic.Int32 451 | atStop atomic.Int32 452 | leveloption [5]*LevelOption 453 | attrFormat *AttrFormat 454 | tmTimer *time.Timer 455 | err error 456 | } 457 | 458 | // NewLogger creates and returns a new instance of the Logging struct. 459 | // This function initializes a Logging object with default values or specific configurations as needed. 460 | func NewLogger() (log *Logging) { 461 | log = &Logging{_level: default_level, _cutmode: _TIMEMODE, _rwLock: new(sync.RWMutex), _format: default_format, _isConsole: true} 462 | log.newfileHandler() 463 | return 464 | } 465 | 466 | // SetConsole sets the flag to determine whether log messages should also be output to the console. 467 | // This method modifies the _isConsole field of the Logging struct and returns a pointer to the Logging instance for method chaining. 468 | func (t *Logging) SetConsole(_isConsole bool) *Logging { 469 | t._isConsole = _isConsole 470 | return t 471 | } 472 | 473 | // Debug logs a message at the DEBUG level. 474 | // Accepts any number of arguments to format the log entry. 475 | // Returns the Logging instance for method chaining. 476 | // 477 | // Parameters: 478 | // - v: Variadic arguments to be logged. 479 | // 480 | // Returns: 481 | // - *Logging: The current Logging instance for chaining. 482 | func (t *Logging) Debug(v ...any) *Logging { 483 | return t.println(nil, LEVEL_DEBUG, 2, v...) 484 | } 485 | 486 | // Info logs a message at the INFO level. 487 | // Accepts any number of arguments to format the log entry. 488 | // Returns the Logging instance for method chaining. 489 | // 490 | // Parameters: 491 | // - v: Variadic arguments to be logged. 492 | // 493 | // Returns: 494 | // - *Logging: The current Logging instance for chaining. 495 | func (t *Logging) Info(v ...any) *Logging { 496 | return t.println(nil, LEVEL_INFO, 2, v...) 497 | } 498 | 499 | // Warn logs a message at the WARN level. 500 | // Accepts any number of arguments to format the log entry. 501 | // Returns the Logging instance for method chaining. 502 | // 503 | // Parameters: 504 | // - v: Variadic arguments to be logged. 505 | // 506 | // Returns: 507 | // - *Logging: The current Logging instance for chaining. 508 | func (t *Logging) Warn(v ...any) *Logging { 509 | return t.println(nil, LEVEL_WARN, 2, v...) 510 | } 511 | 512 | // Error logs a message at the ERROR level. 513 | // Accepts any number of arguments to format the log entry. 514 | // Returns the Logging instance for method chaining. 515 | // 516 | // Parameters: 517 | // - v: Variadic arguments to be logged. 518 | // 519 | // Returns: 520 | // - *Logging: The current Logging instance for chaining. 521 | func (t *Logging) Error(v ...any) *Logging { 522 | return t.println(nil, LEVEL_ERROR, 2, v...) 523 | } 524 | 525 | // Fatal logs a message at the FATAL level and may terminate the application. 526 | // Accepts any number of arguments to format the log entry. 527 | // Returns the Logging instance for method chaining. 528 | // 529 | // Parameters: 530 | // - v: Variadic arguments to be logged. 531 | // 532 | // Returns: 533 | // - *Logging: The current Logging instance for chaining. 534 | func (t *Logging) Fatal(v ...any) *Logging { 535 | return t.println(nil, LEVEL_FATAL, 2, v...) 536 | } 537 | 538 | // Debugf logs a formatted message at the DEBUG level. 539 | // Takes a format string followed by variadic arguments to be formatted. 540 | // Returns the Logging instance for method chaining. 541 | // 542 | // Parameters: 543 | // - format: The format string for the log entry. 544 | // - v: Variadic arguments to be formatted according to the format string. 545 | // 546 | // Returns: 547 | // - *Logging: The current Logging instance for chaining. 548 | func (t *Logging) Debugf(format string, v ...any) *Logging { 549 | return t.println(&format, LEVEL_DEBUG, 2, v...) 550 | } 551 | 552 | // Infof logs a formatted message at the INFO level. 553 | // Takes a format string followed by variadic arguments to be formatted. 554 | // Returns the Logging instance for method chaining. 555 | // 556 | // Parameters: 557 | // - format: The format string for the log entry. 558 | // - v: Variadic arguments to be formatted according to the format string. 559 | // 560 | // Returns: 561 | // - *Logging: The current Logging instance for chaining. 562 | func (t *Logging) Infof(format string, v ...any) *Logging { 563 | return t.println(&format, LEVEL_INFO, 2, v...) 564 | } 565 | 566 | // Warnf logs a formatted message at the WARN level. 567 | // Takes a format string followed by variadic arguments to be formatted. 568 | // Returns the Logging instance for method chaining. 569 | // 570 | // Parameters: 571 | // - format: The format string for the log entry. 572 | // - v: Variadic arguments to be formatted according to the format string. 573 | // 574 | // Returns: 575 | // - *Logging: The current Logging instance for chaining. 576 | func (t *Logging) Warnf(format string, v ...any) *Logging { 577 | return t.println(&format, LEVEL_WARN, 2, v...) 578 | } 579 | 580 | // Errorf logs a formatted message at the ERROR level. 581 | // Takes a format string followed by variadic arguments to be formatted. 582 | // Returns the Logging instance for method chaining. 583 | // 584 | // Parameters: 585 | // - format: The format string for the log entry. 586 | // - v: Variadic arguments to be formatted according to the format string. 587 | // 588 | // Returns: 589 | // - *Logging: The current Logging instance for chaining. 590 | func (t *Logging) Errorf(format string, v ...any) *Logging { 591 | return t.println(&format, LEVEL_ERROR, 2, v...) 592 | } 593 | 594 | // Fatalf logs a formatted message at the FATAL level and may terminate the application. 595 | // Takes a format string followed by variadic arguments to be formatted. 596 | // Returns the Logging instance for method chaining. 597 | // 598 | // Parameters: 599 | // - format: The format string for the log entry. 600 | // - v: Variadic arguments to be formatted according to the format string. 601 | // 602 | // Returns: 603 | // - *Logging: The current Logging instance for chaining. 604 | func (t *Logging) Fatalf(format string, v ...any) *Logging { 605 | return t.println(&format, LEVEL_FATAL, 2, v...) 606 | } 607 | 608 | func (t *Logging) WriteBin(bs []byte) (bakfn string, err error) { 609 | if t._isFileWell { 610 | var openFileErr error 611 | if t._filehandler.mustBackUp(len(bs)) { 612 | bakfn, err, openFileErr = t.backUp() 613 | } 614 | if openFileErr == nil { 615 | t._rwLock.RLock() 616 | _, err = t._filehandler.write(bs) 617 | t._rwLock.RUnlock() 618 | } 619 | } else { 620 | err = errors.New("no log file found") 621 | } 622 | return 623 | } 624 | func (t *Logging) Write(bs []byte) (n int, err error) { 625 | if t._isFileWell { 626 | var openFileErr error 627 | if t._filehandler.mustBackUp(len(bs)) { 628 | _, err, openFileErr = t.backUp() 629 | } 630 | if openFileErr == nil { 631 | t._rwLock.RLock() 632 | n, err = t._filehandler.write(bs) 633 | t._rwLock.RUnlock() 634 | } 635 | } else { 636 | err = errors.New("no log file found") 637 | } 638 | return 639 | } 640 | 641 | // SetFormat sets the logging format to the specified format type. 642 | // 643 | // Parameters: 644 | // - format: The desired format for log entries, represented by the _FORMAT type. 645 | // 646 | // Returns: 647 | // - *Logging: A Logging instance to enable method chaining. 648 | func (t *Logging) SetFormat(format _FORMAT) *Logging { 649 | t._format = format 650 | return t 651 | } 652 | 653 | // SetLevel sets the logging level to the specified level type. 654 | // 655 | // Parameters: 656 | // - level: The logging level (e.g., DEBUG, INFO, WARN), represented by the LEVELTYPE type. 657 | // 658 | // Returns: 659 | // - *Logging: A Logging instance to enable method chaining. 660 | func (t *Logging) SetLevel(level LEVELTYPE) *Logging { 661 | t._level = level 662 | return t 663 | } 664 | 665 | // SetFormatter specifies a custom format string for the logging output. 666 | // 667 | // default: "{level}{time} {file} {message}\n" 668 | // 669 | // Parameters: 670 | // - formatter: A string defining the format for log entries, allowing custom log entry layouts. 671 | // 672 | // Returns: 673 | // - *Logging: A Logging instance to enable method chaining. 674 | func (t *Logging) SetFormatter(formatter string) *Logging { 675 | t._formatter = formatter 676 | return t 677 | } 678 | 679 | // SetRollingFile 680 | // 681 | // Use SetOption() instead. 682 | // 按日志文件大小分割日志文件 683 | // fileDir 日志文件夹路径 684 | // fileName 日志文件名 685 | // maxFileSize 日志文件大小最大值 686 | // unit 日志文件大小单位 687 | func (t *Logging) SetRollingFile(fileDir, fileName string, maxFileSize int64, unit _UNIT) (l *Logging, err error) { 688 | return t.SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, 0) 689 | } 690 | 691 | // SetRollingFileLoop 692 | // 693 | // Use SetOption() instead. 694 | // 按日志文件大小分割日志文件,指定保留的最大日志文件数 695 | // fileDir 日志文件夹路径 696 | // fileName 日志文件名 697 | // maxFileSize 日志文件大小最大值 698 | // unit 日志文件大小单位 699 | // maxFileNum 留的日志文件数 700 | func (t *Logging) SetRollingFileLoop(fileDir, fileName string, maxFileSize int64, unit _UNIT, maxBackup int) (l *Logging, err error) { 701 | t._rwLock.Lock() 702 | defer t._rwLock.Unlock() 703 | if fileDir == "" { 704 | fileDir, _ = os.Getwd() 705 | } 706 | t._fileDir, t._fileName, t._maxSize, t._maxBackup, t._unit = fileDir, fileName, maxFileSize, maxBackup, unit 707 | t._cutmode = _SIZEMODE 708 | if t._filehandler != nil { 709 | t._filehandler.close() 710 | } 711 | t.newfileHandler() 712 | if err = t._filehandler.openFileHandler(); err == nil { 713 | t._isFileWell = true 714 | } 715 | t.err = err 716 | return t, err 717 | } 718 | 719 | // SetRollingDaily 720 | // 721 | // Use SetOption() instead. 722 | // 按日期分割日志文件 723 | // fileDir 日志文件夹路径 724 | // fileName 日志文件名 725 | func (t *Logging) SetRollingDaily(fileDir, fileName string) (*Logging, error) { 726 | return t.SetRollingByTime(fileDir, fileName, MODE_DAY) 727 | } 728 | 729 | // SetRollingByTime 730 | // 731 | // 指定按 小时,天,月 分割日志文件 732 | // fileDir 日志文件夹路径 733 | // fileName 日志文件名 734 | // mode 指定 小时,天,月 735 | func (t *Logging) SetRollingByTime(fileDir, fileName string, mode _MODE_TIME) (l *Logging, err error) { 736 | t._rwLock.Lock() 737 | defer t._rwLock.Unlock() 738 | if fileDir == "" { 739 | fileDir, _ = os.Getwd() 740 | } 741 | t._fileDir, t._fileName, t._mode = fileDir, fileName, mode 742 | t._cutmode = _TIMEMODE 743 | if t._filehandler != nil { 744 | t._filehandler.close() 745 | } 746 | t.newfileHandler() 747 | if err = t._filehandler.openFileHandler(); err == nil { 748 | t._isFileWell = true 749 | t.zeroTimer() 750 | } 751 | t.err = err 752 | return t, err 753 | } 754 | 755 | // SetGzipOn 756 | // 757 | // Use SetOption() instead. 758 | func (t *Logging) SetGzipOn(is bool) *Logging { 759 | t._gzip = is 760 | if t._filehandler != nil { 761 | t._filehandler._gzip = is 762 | } 763 | return t 764 | } 765 | 766 | // SetOption applies the configuration options specified in the Option struct to the Logging instance. 767 | // This method updates the fields of the Logging struct according to the provided Option and returns a pointer to the Logging instance for method chaining. 768 | // 769 | // e.g. 770 | // 771 | // SetOption(&Option{Level: LEVEL_DEBUG, Console: true, FileOption: &FileSizeMode{Filename: "test.log", Maxsize: 500, Maxbackup: 3, IsCompress: false}}) 772 | func (t *Logging) SetOption(option *Option) *Logging { 773 | t._rwLock.Lock() 774 | defer t._rwLock.Unlock() 775 | t.getOptionArgs(option) 776 | if option.FileOption != nil { 777 | if abspath, err := filepath.Abs(option.FileOption.FilePath()); err == nil { 778 | t._fileDir = filepath.Dir(abspath) 779 | } else { 780 | fprintln(nil, default_format, LEVEL_ERROR, 0, 1, nil, nil, err.Error()) 781 | t._fileDir, _ = os.Getwd() 782 | } 783 | t._fileName = filepath.Base(option.FileOption.FilePath()) 784 | if t._filehandler != nil { 785 | t._filehandler.close() 786 | } 787 | t.newfileHandler() 788 | if err := t._filehandler.openFileHandler(); err == nil { 789 | t._isFileWell = true 790 | } else { 791 | fprintln(nil, default_format, LEVEL_ERROR, 0, 1, nil, nil, err.Error()) 792 | t.err = err 793 | } 794 | 795 | if t._cutmode&_TIMEMODE == _TIMEMODE { 796 | t.zeroTimer() 797 | } 798 | } 799 | return t 800 | } 801 | 802 | func (t *Logging) getOptionArgs(option *Option) { 803 | if option.Formatter != "" { 804 | t._formatter = option.Formatter 805 | } 806 | if option.AttrFormat != nil { 807 | t.attrFormat = option.AttrFormat 808 | } 809 | if option.Format != 0 { 810 | t._format = option.Format 811 | } 812 | t._isConsole = option.Console 813 | t.callDepth = option.CallDepth 814 | t.customHandler = option.CustomHandler 815 | t.stacktrace = option.Stacktrace 816 | t._level = option.Level 817 | if option.FileOption != nil { 818 | t._cutmode = option.FileOption.Cutmode() 819 | if t._cutmode != _TIMEMODE && t._cutmode != _SIZEMODE && t._cutmode != _MIXEDMODE { 820 | t._cutmode = _MIXEDMODE 821 | } 822 | t._maxBackup, t._gzip = option.FileOption.MaxBackup(), option.FileOption.Compress() 823 | if t._cutmode&_SIZEMODE == _SIZEMODE { 824 | t._maxSize, t._unit = option.FileOption.MaxSize(), 1 825 | if t._maxSize <= 0 { 826 | t._maxSize = 1 << 30 827 | } 828 | } 829 | if t._cutmode&_TIMEMODE == _TIMEMODE { 830 | t._mode = option.FileOption.TimeMode() 831 | if t._mode != MODE_DAY && t._mode != MODE_HOUR && t._mode != MODE_MONTH { 832 | t._mode = MODE_DAY 833 | } 834 | } 835 | } 836 | } 837 | 838 | func (t *Logging) newfileHandler() { 839 | t._filehandler = new(fileHandler) 840 | t._filehandler.logger, t._filehandler._fileDir, t._filehandler._fileName, t._filehandler._maxSize, t._filehandler._cutmode, t._filehandler._unit, t._filehandler._maxbackup, t._filehandler._mode, t._filehandler._gzip = t, t._fileDir, t._fileName, t._maxSize, t._cutmode, t._unit, t._maxBackup, t._mode, t._gzip 841 | } 842 | 843 | func (t *Logging) backUp() (bakfn string, err, openFileErr error) { 844 | t._rwLock.Lock() 845 | defer t._rwLock.Unlock() 846 | if !t._filehandler.mustBackUp(0) { 847 | return 848 | } 849 | if err = t._filehandler.close(); err != nil { 850 | fprintln(nil, t._format, LEVEL_ERROR, t.stacktrace, 1, nil, nil, err.Error()) 851 | return 852 | } 853 | 854 | for i := 0; i < 16; i++ { 855 | if bakfn, err = t._filehandler.rename(); err != nil { 856 | fprintln(nil, t._format, LEVEL_ERROR, t.stacktrace, 1, nil, nil, err.Error()) 857 | <-time.After(time.Millisecond) 858 | } else { 859 | break 860 | } 861 | } 862 | 863 | if openFileErr = t._filehandler.openFileHandler(); openFileErr != nil { 864 | fprintln(nil, t._format, LEVEL_ERROR, t.stacktrace, 1, nil, nil, openFileErr.Error()) 865 | t.err = openFileErr 866 | } 867 | return 868 | } 869 | 870 | func (t *Logging) println(format *string, _level LEVELTYPE, calldepth int, v ...any) *Logging { 871 | if t._level > _level { 872 | return t 873 | } 874 | if t.err != nil { 875 | return t 876 | } 877 | if t.customHandler != nil && !t.customHandler(&LogContext{Level: _level, Args: v}) { 878 | return t 879 | } 880 | var buf *buffer.Buffer 881 | defer func() { 882 | if buf != nil { 883 | buf.Free() 884 | } 885 | }() 886 | var bs []byte 887 | if t.callDepth > 0 { 888 | calldepth += t.callDepth 889 | } 890 | 891 | if t._isFileWell { 892 | if t._format != FORMAT_NANO { 893 | if format == nil { 894 | bs = fmt.Append([]byte{}, v...) 895 | } else { 896 | bs = fmt.Appendf([]byte{}, *format, v...) 897 | } 898 | if ol := t.leveloption[_level-1]; ol != nil { 899 | buf = getOutBuffer(bs, _level, ol.Format, k1(calldepth), &ol.Formatter, t.stacktrace, t.attrFormat) 900 | } else { 901 | buf = getOutBuffer(bs, _level, t._format, k1(calldepth), &t._formatter, t.stacktrace, t.attrFormat) 902 | } 903 | if t.attrFormat != nil && t.attrFormat.SetBodyFmt != nil { 904 | bs = t.attrFormat.SetBodyFmt(_level, buf.Bytes()) 905 | } else { 906 | bs = buf.Bytes() 907 | } 908 | } else { 909 | if format == nil { 910 | bs = fmt.Appendln([]byte{}, v...) 911 | } else { 912 | bs = fmt.Appendf([]byte{}, *format+"\n", v...) 913 | } 914 | } 915 | var openFileErr error 916 | if t._filehandler.mustBackUp(len(bs)) { 917 | _, openFileErr, _ = t.backUp() 918 | } 919 | if openFileErr == nil { 920 | t._rwLock.RLock() 921 | t._filehandler.write(bs) 922 | t._rwLock.RUnlock() 923 | } 924 | } 925 | if t._isConsole { 926 | if bs != nil { 927 | consolewriter(bs, false) 928 | } else { 929 | if ol := t.leveloption[_level-1]; ol != nil { 930 | fprintln(format, ol.Format, _level, t.stacktrace, k1(calldepth), &ol.Formatter, t.attrFormat, v...) 931 | } else { 932 | fprintln(format, t._format, _level, t.stacktrace, k1(calldepth), &t._formatter, t.attrFormat, v...) 933 | } 934 | } 935 | } 936 | return t 937 | } 938 | 939 | func SetLevelOption(level LEVELTYPE, option *LevelOption) *Logging { 940 | return static_lo.SetLevelOption(level, option) 941 | } 942 | 943 | func (t *Logging) SetLevelOption(level LEVELTYPE, option *LevelOption) *Logging { 944 | if level > LEVEL_ALL && level < LEVEL_OFF { 945 | t.leveloption[level-1] = option 946 | } else if level == LEVEL_ALL { 947 | for i := 0; i < len(t.leveloption); i++ { 948 | t.leveloption[i] = option 949 | } 950 | } 951 | return t 952 | } 953 | 954 | type fileHandler struct { 955 | logger *Logging 956 | fileHandle File 957 | _fileDir string 958 | _fileName string 959 | file *os.File 960 | _maxSize int64 961 | _fileSize int64 962 | _fileSize2 int64 963 | _lastPrint int64 964 | _prevPrint int64 965 | _unit _UNIT 966 | _cutmode _CUTMODE 967 | _maxbackup int 968 | _gzip bool 969 | _mode _MODE_TIME 970 | } 971 | 972 | func (t *fileHandler) openFileHandler() (e error) { 973 | if t._fileDir == "" || t._fileName == "" { 974 | e = errors.New("log filePath is null or error") 975 | return 976 | } 977 | e = mkdirAll(t._fileDir) 978 | if e != nil { 979 | return 980 | } 981 | fname := filepath.Join(t._fileDir, t._fileName) 982 | if t.file, e = os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); e == nil { 983 | t.fileHandle, e = New(t.file) 984 | } 985 | if e != nil { 986 | fprintln(nil, default_format, LEVEL_ERROR, 0, 1, nil, nil, e.Error()) 987 | return 988 | } 989 | if fs, err := t.file.Stat(); err == nil { 990 | t._fileSize = fs.Size() 991 | atomic.StoreInt64(&t._fileSize2, t._fileSize) 992 | t._lastPrint = fs.ModTime().Unix() 993 | } else { 994 | e = err 995 | } 996 | return 997 | } 998 | 999 | func (t *fileHandler) addFileSize(size int64) { 1000 | atomic.AddInt64(&t._fileSize, size) 1001 | } 1002 | 1003 | func (t *fileHandler) write(bs []byte) (n int, e error) { 1004 | defer recoverable(&e) 1005 | if bs != nil { 1006 | if n, e = t.fileHandle.Write(bs); e == nil { 1007 | if n > 0 { 1008 | t.addFileSize(int64(n)) 1009 | } 1010 | if t._cutmode&_TIMEMODE == _TIMEMODE { 1011 | t._lastPrint = loctime().Unix() 1012 | if t._prevPrint > 0 && t._lastPrint-t._prevPrint > 2 && t.logger.tmTimer == nil { 1013 | t.logger.zeroTimer() 1014 | } else if t.logger.tmTimer != nil { 1015 | t.logger.zeroTimerStop() 1016 | } 1017 | t._prevPrint = t._lastPrint 1018 | } 1019 | } 1020 | } 1021 | return 1022 | } 1023 | 1024 | func (t *fileHandler) mustBackUp(addsize int) bool { 1025 | if t._fileSize == 0 { 1026 | return false 1027 | } 1028 | if t._cutmode&_TIMEMODE == _TIMEMODE { 1029 | if t._lastPrint > 0 && !isCurrentTime(t._mode, t._lastPrint) { 1030 | return true 1031 | } 1032 | } 1033 | if t._cutmode&_SIZEMODE == _SIZEMODE { 1034 | if addsize > 0 { 1035 | if atomic.AddInt64(&t._fileSize2, int64(addsize)) >= t._maxSize*int64(t._unit) { 1036 | return true 1037 | } 1038 | } 1039 | return t._fileSize > 0 && atomic.LoadInt64(&t._fileSize) >= t._maxSize*int64(t._unit) 1040 | } 1041 | return false 1042 | } 1043 | 1044 | func (t *fileHandler) rename() (bckupfilename string, err error) { 1045 | if t._cutmode&_TIMEMODE == _TIMEMODE { 1046 | bckupfilename = getBackupDayliFileName(t._lastPrint, t._fileDir, t._fileName, t._mode, t._gzip) 1047 | } else { 1048 | bckupfilename, err = getBackupRollFileName(t._fileDir, t._fileName, t._gzip) 1049 | } 1050 | if bckupfilename != "" && err == nil { 1051 | oldPath := filepath.Join(t._fileDir, t._fileName) 1052 | newPath := filepath.Join(t._fileDir, bckupfilename) 1053 | if err = os.Rename(oldPath, newPath); err == nil { 1054 | go func() { 1055 | defer recoverable(nil) 1056 | if t._gzip { 1057 | if err = lgzip(newPath+".gz", bckupfilename, newPath); err == nil { 1058 | os.Remove(newPath) 1059 | } 1060 | } 1061 | if t._maxbackup > 0 { 1062 | maxbackup(t._fileDir, t._fileName, t._maxbackup) 1063 | } 1064 | }() 1065 | } 1066 | } 1067 | return 1068 | } 1069 | 1070 | func (t *fileHandler) close() (err error) { 1071 | defer recoverable(&err) 1072 | if t.fileHandle != nil { 1073 | err = t.fileHandle.Close() 1074 | } else if t.file != nil { 1075 | err = t.file.Close() 1076 | } 1077 | return 1078 | } 1079 | 1080 | //func tomorSecond(mode _MODE_TIME) int64 { 1081 | // now := loctime() 1082 | // switch mode { 1083 | // case MODE_DAY: 1084 | // return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()).Unix() 1085 | // case MODE_HOUR: 1086 | // return time.Date(now.Year(), now.Month(), now.Day(), now.Hour()+1, 0, 0, 0, now.Location()).Unix() 1087 | // case MODE_MONTH: 1088 | // return time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).AddDate(0, 0, 1).Unix() 1089 | // default: 1090 | // return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()).Unix() 1091 | // } 1092 | //} 1093 | 1094 | func isCurrentTime(mode _MODE_TIME, timestamp int64) bool { 1095 | now := loctime() 1096 | switch mode { 1097 | case MODE_DAY: 1098 | return timestamp >= time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix() 1099 | case MODE_HOUR: 1100 | return timestamp >= time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()).Unix() 1101 | case MODE_MONTH: 1102 | return timestamp >= time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, now.Location()).Unix() 1103 | } 1104 | return false 1105 | } 1106 | 1107 | func backupStr4Time(mode _MODE_TIME, now time.Time) string { 1108 | switch mode { 1109 | case MODE_HOUR: 1110 | return now.Format(_DATEFORMAT_HOUR) 1111 | case MODE_MONTH: 1112 | return now.Format(_DATEFORMAT_MONTH) 1113 | default: 1114 | return now.Format(_DATEFORMAT_DAY) 1115 | } 1116 | } 1117 | 1118 | //func _yestStr(mode _MODE_TIME, now time.Time) string { 1119 | // //now := loctime() 1120 | // switch mode { 1121 | // case MODE_DAY: 1122 | // return now.AddDate(0, 0, -1).Format(_DATEFORMAT_DAY) 1123 | // case MODE_HOUR: 1124 | // return now.Add(-1 * time.Hour).Format(_DATEFORMAT_HOUR) 1125 | // case MODE_MONTH: 1126 | // return now.AddDate(0, -1, 0).Format(_DATEFORMAT_MONTH) 1127 | // default: 1128 | // return now.AddDate(0, 0, -1).Format(_DATEFORMAT_DAY) 1129 | // } 1130 | //} 1131 | 1132 | func getBackupDayliFileName(unixTimestamp int64, dir, filename string, mode _MODE_TIME, isGzip bool) (bckupfilename string) { 1133 | timeStr := backupStr4Time(mode, time.Unix(unixTimestamp, 0)) 1134 | index := strings.LastIndex(filename, ".") 1135 | if index <= 0 { 1136 | index = len(filename) 1137 | } 1138 | fname := filename[:index] 1139 | suffix := filename[index:] 1140 | bckupfilename = fmt.Sprint(fname, "_", timeStr, suffix) 1141 | if isGzip { 1142 | if isFileExist(fmt.Sprint(filepath.Join(dir, bckupfilename), ".gz")) { 1143 | bckupfilename = _getBackupfilename(1, dir, fmt.Sprint(fname, "_", timeStr), suffix, isGzip) 1144 | } 1145 | } else { 1146 | if isFileExist(fmt.Sprint(filepath.Join(dir, bckupfilename))) { 1147 | bckupfilename = _getBackupfilename(1, dir, fmt.Sprint(fname, "_", timeStr), suffix, isGzip) 1148 | } 1149 | } 1150 | return 1151 | } 1152 | 1153 | func _getDirList(dir string) ([]os.DirEntry, error) { 1154 | f, err := os.Open(dir) 1155 | if err != nil { 1156 | return nil, err 1157 | } 1158 | defer f.Close() 1159 | return f.ReadDir(-1) 1160 | } 1161 | 1162 | func getBackupRollFileName(dir, filename string, isGzip bool) (bckupfilename string, er error) { 1163 | list, err := _getDirList(dir) 1164 | if err != nil { 1165 | er = err 1166 | return 1167 | } 1168 | index := strings.LastIndex(filename, ".") 1169 | if index <= 0 { 1170 | index = len(filename) 1171 | } 1172 | fname := filename[:index] 1173 | suffix := filename[index:] 1174 | i := 1 1175 | for _, fd := range list { 1176 | pattern := fmt.Sprint(`^`, fname, `_[\d]{1,}`, suffix, `$`) 1177 | if isGzip { 1178 | pattern = fmt.Sprint(`^`, fname, `_[\d]{1,}`, suffix, `.gz$`) 1179 | } 1180 | if matchString(pattern, fd.Name()) { 1181 | i++ 1182 | } 1183 | } 1184 | bckupfilename = _getBackupfilename(i, dir, fname, suffix, isGzip) 1185 | return 1186 | } 1187 | 1188 | func _getBackupfilename(count int, dir, filename, suffix string, isGzip bool) (bckupfilename string) { 1189 | bckupfilename = fmt.Sprint(filename, "_", count, suffix) 1190 | if isGzip { 1191 | if isFileExist(fmt.Sprint(filepath.Join(dir, bckupfilename), ".gz")) { 1192 | return _getBackupfilename(count+1, dir, filename, suffix, isGzip) 1193 | } 1194 | } else { 1195 | if isFileExist(fmt.Sprint(filepath.Join(dir, bckupfilename))) { 1196 | return _getBackupfilename(count+1, dir, filename, suffix, isGzip) 1197 | } 1198 | } 1199 | return 1200 | } 1201 | 1202 | func consolewrite(s []byte, level, stacktrace LEVELTYPE, flag _FORMAT, calldepth int, formatter *string, attrFormat *AttrFormat) { 1203 | if flag != FORMAT_NANO { 1204 | buf := getOutBuffer(s, level, flag, k1(calldepth), formatter, stacktrace, attrFormat) 1205 | defer buf.Free() 1206 | if attrFormat != nil && attrFormat.SetBodyFmt != nil { 1207 | consolewriter(attrFormat.SetBodyFmt(level, buf.Bytes()), false) 1208 | } else { 1209 | consolewriter(buf.Bytes(), false) 1210 | } 1211 | } else { 1212 | if attrFormat != nil && attrFormat.SetBodyFmt != nil { 1213 | consolewriter(attrFormat.SetBodyFmt(level, s), false) 1214 | } else { 1215 | consolewriter(s, true) 1216 | } 1217 | } 1218 | } 1219 | 1220 | func consolewriter(bs []byte, newline bool) { 1221 | if newline { 1222 | os.Stdout.Write(append(bs, '\n')) 1223 | } else { 1224 | os.Stdout.Write(bs) 1225 | } 1226 | } 1227 | 1228 | func k1(calldepth int) int { 1229 | return calldepth + 1 1230 | } 1231 | 1232 | func getOutBuffer(s []byte, level LEVELTYPE, format _FORMAT, calldepth int, formatter *string, stacktrace LEVELTYPE, attrFormat *AttrFormat) *buffer.Buffer { 1233 | return output(format, k1(calldepth), s, level, formatter, stacktrace, attrFormat) 1234 | } 1235 | 1236 | func mkdirAll(dir string) (e error) { 1237 | _, er := os.Stat(dir) 1238 | b := er == nil || os.IsExist(er) 1239 | if !b { 1240 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 1241 | if os.IsPermission(err) { 1242 | e = err 1243 | } 1244 | } 1245 | } 1246 | return 1247 | } 1248 | 1249 | func maxbackup(dir, filename string, maxcount int) { 1250 | ext := filepath.Ext(filename) 1251 | name := filename[:len(filename)-len(ext)] 1252 | if entries, err := os.ReadDir(dir); err == nil { 1253 | if len(entries) > maxcount { 1254 | sort.Slice(entries, func(i, j int) bool { 1255 | f1, _ := entries[i].Info() 1256 | f2, _ := entries[j].Info() 1257 | return f1.ModTime().Unix() < f2.ModTime().Unix() 1258 | }) 1259 | rms := make([]string, 0) 1260 | for _, entry := range entries { 1261 | if !entry.IsDir() { 1262 | parrent := fmt.Sprint("^", name, "(_\\d+){0,}", "_\\d+", ext, "(\\.gz){0,}$") 1263 | if matchString(parrent, entry.Name()) { 1264 | filePath := filepath.Join(dir, entry.Name()) 1265 | rms = append(rms, filePath) 1266 | } 1267 | } 1268 | } 1269 | if len(rms) > maxcount { 1270 | for i := 0; i < len(rms)-maxcount; i++ { 1271 | os.Remove(rms[i]) 1272 | } 1273 | } 1274 | } 1275 | } 1276 | } 1277 | 1278 | func isFileExist(path string) bool { 1279 | _, err := os.Stat(path) 1280 | return err == nil || os.IsExist(err) 1281 | } 1282 | 1283 | func recoverable(err *error) { 1284 | if e := recover(); e != nil { 1285 | if err != nil { 1286 | *err = fmt.Errorf("%v", e) 1287 | } 1288 | } 1289 | } 1290 | 1291 | func matchString(pattern string, s string) bool { 1292 | b, err := regexp.MatchString(pattern, s) 1293 | if err != nil { 1294 | b = false 1295 | } 1296 | return b 1297 | } 1298 | 1299 | func loctime() time.Time { 1300 | if TIME_DEVIATION != 0 { 1301 | return time.Now().Add(TIME_DEVIATION) 1302 | } else { 1303 | return time.Now() 1304 | } 1305 | } 1306 | 1307 | func lgzip(gzfile, gzname, srcfile string) (err error) { 1308 | var gf *os.File 1309 | if gf, err = os.Create(gzfile); err == nil { 1310 | defer gf.Close() 1311 | var f1 *os.File 1312 | if f1, err = os.Open(srcfile); err == nil { 1313 | defer f1.Close() 1314 | gw := gzip.NewWriter(gf) 1315 | defer gw.Close() 1316 | gw.Header.Name = gzname 1317 | var buf bytes.Buffer 1318 | io.Copy(&buf, f1) 1319 | _, err = gw.Write(buf.Bytes()) 1320 | } 1321 | } 1322 | return 1323 | } 1324 | 1325 | var m = hashmap.NewLimitHashMap[uintptr, runtime.Frame](1 << 13) 1326 | 1327 | func output(flag _FORMAT, calldepth int, s []byte, level LEVELTYPE, formatter *string, stacktrace LEVELTYPE, attrFormat *AttrFormat) (buf *buffer.Buffer) { 1328 | var callstack *callStack 1329 | if flag&(FORMAT_SHORTFILENAME|FORMAT_LONGFILENAME|FORMAT_RELATIVEFILENAME) != 0 { 1330 | callstack = collectCallStack(k1(calldepth), flag&FORMAT_FUNC != 0, callstack, stacktrace > LEVEL_ALL && stacktrace <= level) 1331 | } 1332 | return formatmsg(s, loctime(), callstack, flag, level, formatter, attrFormat) 1333 | } 1334 | 1335 | func formatmsg(msg []byte, t time.Time, callstack *callStack, flag _FORMAT, level LEVELTYPE, formatter *string, attrFormat *AttrFormat) (buf *buffer.Buffer) { 1336 | buf = buffer.NewBufferByPool() 1337 | var levelbuf, timebuf, filebuf *buffer.Buffer 1338 | is_default_formatter := formatter == nil || *formatter == "" 1339 | if is_default_formatter { 1340 | levelbuf, timebuf, filebuf = buf, buf, buf 1341 | } else { 1342 | levelbuf = buffer.NewBufferWithCapacity(7) 1343 | timebuf = buffer.NewBufferWithCapacity(20) 1344 | filebuf = buffer.NewBuffer() 1345 | } 1346 | if flag&FORMAT_LEVELFLAG != 0 { 1347 | if attrFormat != nil && attrFormat.SetLevelFmt != nil { 1348 | levelbuf.WriteString(attrFormat.SetLevelFmt(level)) 1349 | } else { 1350 | levelbuf.Write(getlevelname(level)) 1351 | } 1352 | } 1353 | if flag&timeFlags != 0 { 1354 | if attrFormat != nil && attrFormat.SetTimeFmt != nil { 1355 | datestr, timestr, microsecond := attrFormat.SetTimeFmt() 1356 | if flag&FORMAT_DATE != 0 && datestr != "" { 1357 | timebuf.WriteString(datestr) 1358 | } 1359 | if flag&(FORMAT_TIME|FORMAT_MICROSECONDS) != 0 { 1360 | timebuf.WriteString(timestr) 1361 | if flag&FORMAT_MICROSECONDS != 0 { 1362 | timebuf.WriteString(microsecond) 1363 | } 1364 | } 1365 | } else { 1366 | if flag&FORMAT_DATE != 0 { 1367 | year, month, day := t.Date() 1368 | timebuf.Write(itoa(year, 4)) 1369 | timebuf.WriteByte('/') 1370 | timebuf.Write(itoa(int(month), 2)) 1371 | timebuf.WriteByte('/') 1372 | timebuf.Write(itoa(day, 2)) 1373 | timebuf.WriteByte(' ') 1374 | } 1375 | if flag&(FORMAT_TIME|FORMAT_MICROSECONDS) != 0 { 1376 | hour, min, sec := t.Clock() 1377 | timebuf.Write(itoa(hour, 2)) 1378 | timebuf.WriteByte(':') 1379 | timebuf.Write(itoa(min, 2)) 1380 | timebuf.WriteByte(':') 1381 | timebuf.Write(itoa(sec, 2)) 1382 | if flag&FORMAT_MICROSECONDS != 0 { 1383 | timebuf.WriteByte('.') 1384 | timebuf.Write(itoa(t.Nanosecond()/1e3, 6)) 1385 | } 1386 | } 1387 | } 1388 | if is_default_formatter { 1389 | timebuf.WriteByte(' ') 1390 | } 1391 | } 1392 | if flag&fileFlags != 0 { 1393 | if callstack != nil { 1394 | callstack.Pop(flag, filebuf) 1395 | callStackPool.Put(&callstack) 1396 | } 1397 | if is_default_formatter { 1398 | filebuf.WriteByte(' ') 1399 | } 1400 | } 1401 | if is_default_formatter { 1402 | buf.Write(msg) 1403 | buf.WriteByte('\n') 1404 | } else { 1405 | parseAndFormatLog(formatter, buf, levelbuf, timebuf, filebuf, msg) 1406 | } 1407 | return 1408 | } 1409 | 1410 | func parseAndFormatLog(formatStr *string, buf, levelbuf, timebuf, filebuf *buffer.Buffer, msg []byte) { 1411 | if formatStr == nil || *formatStr == "" { 1412 | buf.Write(msg) 1413 | return 1414 | } 1415 | inPlaceholder := false 1416 | placeholder := "" 1417 | for _, c := range *formatStr { 1418 | if inPlaceholder { 1419 | if c == '}' { 1420 | inPlaceholder = false 1421 | switch placeholder { 1422 | case "level": 1423 | buf.Write(levelbuf.Bytes()) 1424 | case "time": 1425 | buf.Write(timebuf.Bytes()) 1426 | case "file": 1427 | buf.Write(filebuf.Bytes()) 1428 | case "message": 1429 | buf.Write(msg) 1430 | default: 1431 | buf.WriteByte('{') 1432 | buf.WriteString(placeholder) 1433 | buf.WriteByte('}') 1434 | } 1435 | placeholder = "" 1436 | } else if c == '{' { 1437 | buf.WriteByte('{') 1438 | buf.WriteString(placeholder) 1439 | placeholder = "" 1440 | } else { 1441 | placeholder += string(c) 1442 | } 1443 | } else if c == '{' { 1444 | inPlaceholder = true 1445 | } else { 1446 | buf.WriteByte(byte(c)) 1447 | } 1448 | } 1449 | } 1450 | 1451 | func itoa(i int, wid int) []byte { 1452 | var b [20]byte 1453 | bp := len(b) - 1 1454 | for i >= 10 || wid > 1 { 1455 | wid-- 1456 | q := i / 10 1457 | b[bp] = byte('0' + i - q*10) 1458 | bp-- 1459 | i = q 1460 | } 1461 | b[bp] = byte('0' + i) 1462 | return b[bp:] 1463 | } 1464 | 1465 | func (t *Logging) zeroTimerStop() { 1466 | if t.atStop.CompareAndSwap(0, 1) { 1467 | defer t.atStop.Store(0) 1468 | if t.tmTimer != nil { 1469 | t.tmTimer.Stop() 1470 | t.tmTimer = nil 1471 | } 1472 | } 1473 | } 1474 | 1475 | func (t *Logging) zeroTimer() { 1476 | if t.atStart.CompareAndSwap(0, 1) { 1477 | defer t.atStart.Store(0) 1478 | if t.tmTimer == nil { 1479 | t.tmTimer = time.AfterFunc(timeUntilNextWholeHour(), t.zeroCheck) 1480 | } 1481 | } 1482 | } 1483 | 1484 | func (t *Logging) zeroCheck() { 1485 | defer recoverable(nil) 1486 | if t._filehandler.mustBackUp(0) { 1487 | t.backUp() 1488 | } 1489 | t.zeroTimer() 1490 | } 1491 | 1492 | func timeUntilNextWholeHour() (r time.Duration) { 1493 | now := time.Now() 1494 | nextWholeHour := time.Date(now.Year(), now.Month(), now.Day(), now.Hour()+1, 0, 1, 0, now.Location()) 1495 | if r = nextWholeHour.Sub(now); r < time.Second { 1496 | r = time.Second 1497 | } 1498 | return 1499 | } 1500 | --------------------------------------------------------------------------------