diff --git a/example/main.go b/example/main.go index 81859a0..c6a7ab3 100644 --- a/example/main.go +++ b/example/main.go @@ -14,13 +14,20 @@ func main() { loggerx.SetToConsole(), // loggerx.SetTimeZone(time.UTC), loggerx.SetTimeZone(time.FixedZone("CST", 8*3600)), + loggerx.SetEscapeHTML(false), ) + log.WriteAsync().Info(ctx, "{ \"a\": 1, \"b\": 2 }") log.Info(ctx, "哈哈哈2") log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") - log.Info(ctx, "哈哈哈2") + log.Info(ctx, "哈哈哈2\r") + log.Info(ctx, "哈哈哈2\r\n") + log.Info(ctx, "哈哈哈2") + log.Info(ctx, "哈哈哈2<") + log.Info(ctx, "哈哈哈2>") + + for i := 0; i < 100; i++ { + log.WriteAsync().Infof(ctx, "异步 %d", i) + } + + time.Sleep(time.Second) } diff --git a/format.go b/format.go index 1747b85..2894c2d 100644 --- a/format.go +++ b/format.go @@ -1,6 +1,7 @@ package loggerx import ( + "bytes" "context" "encoding/json" "fmt" @@ -32,7 +33,7 @@ func (l *Logger) logger(ctx context.Context, event string, v ...any) { // writeStr := "[" + event + "]" + nowTime + " " + file + ":" + fmt.Sprintf("%d", line) + " " + funcName + " gid:" + getGID() + " " + traceId + " @data@: " + string(by) + "\n\n" - for idx,val := range v { + for idx, val := range v { if _, ok := val.(error); ok { v[idx] = fmt.Sprintf("%+v", val) } @@ -51,7 +52,18 @@ func (l *Logger) logger(ctx context.Context, event string, v ...any) { // fd.Stack = string(debug.Stack()) } - fdb, _ := json.Marshal(fd) + var fdb []byte + if l.option.escapeHTML { + fdb, _ = json.Marshal(fd) + } else { + // 非转义 + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + encoder.Encode(fd) + fdb = buf.Bytes() + } + fdb = bytes.TrimRight(fdb, "\n") ff := []byte("\n[" + event + "]") fdb = append(ff, fdb...) diff --git a/interfaces.go b/interfaces.go index e47b8e5..ef0f8ec 100644 --- a/interfaces.go +++ b/interfaces.go @@ -2,7 +2,8 @@ package loggerx import "context" -type Loggerx interface { +// 简单使用的日志接口 +type LoggerInterface interface { Info(ctx context.Context, args ...any) Infof(ctx context.Context, format string, args ...any) Error(ctx context.Context, args ...any) diff --git a/loggerx.go b/loggerx.go index cdebd9c..8e3c925 100644 --- a/loggerx.go +++ b/loggerx.go @@ -21,11 +21,12 @@ import ( // 需要实现io.Writer接口 type Logger struct { - ctx context.Context - filePath *sync.Map // filePath - mu *sync.Mutex - option loggerOption - channel string + ctx context.Context + filePath *sync.Map // filePath + mu *sync.Mutex + option loggerOption + channel string + writeType writeType // 是否异步落盘,这里作用范围是本条,优先判断这里 } type filePath struct { @@ -45,10 +46,11 @@ func NewLogger(ctx context.Context, opts ...Option) *Logger { } l := &Logger{ - ctx: ctx, - filePath: &sync.Map{}, - mu: &sync.Mutex{}, - option: opt, + ctx: ctx, + filePath: &sync.Map{}, + mu: &sync.Mutex{}, + option: opt, + writeType: writeTypeDefault, } log.SetOutput(l) @@ -87,6 +89,18 @@ func (l *Logger) Channel(ch string) (r *Logger) { return &rr } +func (l *Logger) WriteAsync() (r *Logger) { + rr := *l + rr.writeType = writeTypeAsync + return &rr +} + +func (l *Logger) WriteSync() (r *Logger) { + rr := *l + rr.writeType = writeTypeSync + return &rr +} + // 获取TraceField的字段 func (l *Logger) GetTraceField() string { return l.option.traceField diff --git a/options.go b/options.go index fb1cb9a..b927370 100644 --- a/options.go +++ b/options.go @@ -13,6 +13,7 @@ type loggerOption struct { isGinLog bool isGid bool isPrintFile bool + writeType writeType // 是否异步罗盘 traceField string // trace字段 errorToInfo bool // 错误日志是否写入info日志 days int // 日志保存天数 @@ -20,19 +21,31 @@ type loggerOption struct { fileSplit FileSplit // 文件切割规则 sizeSplit int // 根据文件大小切割 timeZone *time.Location // 时区 + escapeHTML bool } +type writeType uint8 + +const ( + // 0.默认(同步) 1.指定同步 2.指定异步 + writeTypeDefault writeType = iota + writeTypeSync + writeTypeAsync +) + func defaultOptions() loggerOption { return loggerOption{ isGinLog: true, isGid: true, isPrintFile: true, + writeType: writeTypeDefault, // 默认同步 format: "json", dir: "./log", traceField: "trace_id", days: 7, fileSplit: FileSplitTimeE, timeZone: time.Local, + escapeHTML: true, } } @@ -45,6 +58,13 @@ func SetTraceField(traceField string) Option { } } +// 是否异步写入 +func SetWriteAsync() Option { + return func(o *loggerOption) { + o.writeType = writeTypeAsync + } +} + // 打印到控制台 func SetToConsole() Option { return func(o *loggerOption) { @@ -158,3 +178,9 @@ func SetSizeSplit(m int) Option { o.sizeSplit = m } } + +func SetEscapeHTML(b bool) Option { + return func(o *loggerOption) { + o.escapeHTML = b + } +} diff --git a/readme.md b/readme.md index 85524a3..fa902b2 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,3 @@ - # 简介 这个是基于原生log实现的日志存储。 @@ -11,15 +10,17 @@ log.Println("ddddd") # 用法 - # 开发计划 -1. 自动清除过期的日志文件 -2. 支持日志文件压缩 -3. 支持日志文件切割 -4. 支持日志文件归档 -5. 支持多种文件分割类型 - 1. 按照时间分割 - 2. 按照文件大小分割 - 3. 按照日志行数分割 -6. 支持debug 模式 +1. [ ] 自动清除过期的日志文件 +2. [ ] 支持日志文件压缩 +3. [X] 支持日志文件切割 +4. [ ] 支持日志文件归档 +5. [ ] 支持多种文件分割类型 + 1. [ ] 按照时间分割 + 2. [ ] 按照文件大小分割 + 3. [ ] 按照日志行数分割 +6. [ ] 支持debug 模式 +7. [X] 添加异步落库,支持全局和单次 + 1. [ ] 优化:异步应该跟实例不应该全局,多实例异步落库将会有BUG +8. [X] 添加支持是否转义html diff --git a/storage.go b/storage.go index 7e8e2da..df7083f 100644 --- a/storage.go +++ b/storage.go @@ -2,9 +2,20 @@ package loggerx import ( "io" + "sync" ) +// 写入,需要判断同步还是异步 func (l *Logger) write(event string, b []byte) (n int, err error) { + if l.toAsync(event, b) { + // fmt.Println("异步写入") + return len(b), nil + } + return l.store(event, b) +} + +// 实际的存储 +func (l *Logger) store(event string, b []byte) (n int, err error) { if l.option.isPrintFile { f, err := l.getFile(event, false) @@ -30,3 +41,32 @@ func (l *Logger) write(event string, b []byte) (n int, err error) { } return n, err } + +var chanStore = make(chan cacheData, 1000) +var chanOnce = sync.Once{} + +type cacheData struct { + logger *Logger + Event string + Data []byte +} + +func (l *Logger) toAsync(event string, b []byte) bool { + chanOnce.Do(func() { + go func() { + for val := range chanStore { + val.logger.store(val.Event, val.Data) + } + }() + }) + + if l.writeType == writeTypeSync || // 指定同步模式 + (l.writeType == writeTypeDefault && l.option.writeType != writeTypeAsync) { // 默认同步模式 + return false + } + + // 为了避免丢失,还是要阻塞等待 + chanStore <- cacheData{l, event, b} + + return true +}