diff --git a/examples/zinx_logger/server.go b/examples/zinx_logger/server.go index 10250d71..cb4d3f34 100644 --- a/examples/zinx_logger/server.go +++ b/examples/zinx_logger/server.go @@ -41,9 +41,14 @@ func (t *TestRouter) PostHandle(req ziface.IRequest) { } } +func onLog(a []byte) { + +} + func main() { s := znet.NewServer() s.AddRouter(1, &TestRouter{}) zlog.SetLogger(new(MyLogger)) + zlog.StdZinxLog.SetOnLogListen(&onLog) s.Serve() } diff --git a/zlog/logger_core.go b/zlog/logger_core.go index 1948d9c3..a326ab2d 100644 --- a/zlog/logger_core.go +++ b/zlog/logger_core.go @@ -109,14 +109,16 @@ type ZinxLoggerCore struct { // file swap lock // (文件交换锁) fsLock sync.Mutex + + onLogHook func([]byte) } /* - NewZinxLog Create a new log +NewZinxLog Create a new log - out: The file io for standard output - prefix: The prefix of the log - flag: The flag of the log header information +out: The file io for standard output +prefix: The prefix of the log +flag: The flag of the log header information */ func NewZinxLog(out io.Writer, prefix string, flag int) *ZinxLoggerCore { @@ -135,13 +137,17 @@ func CleanZinxLog(log *ZinxLoggerCore) { log.closeFile() } +func (log *ZinxLoggerCore) SetLogHook(f func([]byte)) { + log.onLogHook = f +} + /* - formatHeader generates the header information for a log entry. +formatHeader generates the header information for a log entry. - t: The current time. - file: The file name of the source code invoking the log function. - line: The line number of the source code invoking the log function. - level: The log level of the current log entry. +t: The current time. +file: The file name of the source code invoking the log function. +line: The line number of the source code invoking the log function. +level: The log level of the current log entry. */ func (log *ZinxLoggerCore) formatHeader(t time.Time, file string, line int, level int) { var buf *bytes.Buffer = &log.buf @@ -250,6 +256,10 @@ func (log *ZinxLoggerCore) OutPut(level int, s string) error { _, err = log.out.Write(log.buf.Bytes()) } + if log.onLogHook != nil { + log.onLogHook(log.buf.Bytes()) + } + return err } diff --git a/zutils/witer.go b/zutils/witer.go new file mode 100644 index 00000000..e5f64dd6 --- /dev/null +++ b/zutils/witer.go @@ -0,0 +1,266 @@ +package zutils + +import ( + "bufio" + "bytes" + "io" + "os" + "path/filepath" + "strings" + "sync" + "time" +) + +const ( + sizeMiB = 1024 * 1024 + defMaxAge = 31 + defMaxSize = 64 //MiB +) + +var _ io.WriteCloser = (*Writer)(nil) + +type Writer struct { + maxAge int // 最大保留天数 + maxSize int64 // 单个日志最大容量 默认 64MB + size int64 // 累计大小 + fpath string // 文件目录 完整路径 fpath=fdir+fname+fsuffix + fdir string // + fname string // 文件名 + fsuffix string // 文件后缀名 默认 .log + created time.Time // 文件创建日期 + creates []byte // 文件创建日期 + cons bool // 标准输出 默认 false + file *os.File + bw *bufio.Writer + mu sync.Mutex +} + +func New(path string) *Writer { + w := &Writer{ + fpath: path, //dir1/dir2/app.log + mu: sync.Mutex{}, + } + w.fdir = filepath.Dir(w.fpath) //dir1/dir2 + w.fsuffix = filepath.Ext(w.fpath) //.log + w.fname = strings.TrimSuffix(filepath.Base(w.fpath), w.fsuffix) //app + if w.fsuffix == "" { + w.fsuffix = ".log" + } + w.maxSize = sizeMiB * defMaxSize + w.maxAge = defMaxAge + os.MkdirAll(filepath.Dir(w.fpath), 0755) + go w.daemon() + return w +} +func (w *Writer) daemon() { + for range time.NewTicker(time.Second * 5).C { + w.flush() + } +} + +// SetMaxAge 最大保留天数 +func (w *Writer) SetMaxAge(ma int) { + w.mu.Lock() + w.maxAge = ma + w.mu.Unlock() +} + +// SetMaxSize 单个日志最大容量 +func (w *Writer) SetMaxSize(ms int64) { + if ms < 1 { + return + } + w.mu.Lock() + w.maxSize = ms + w.mu.Unlock() +} + +// SetCons 同时输出控制台 +func (w *Writer) SetCons(b bool) { + w.mu.Lock() + w.cons = b + w.mu.Unlock() +} + +func (w *Writer) Write(p []byte) (n int, err error) { + w.mu.Lock() + defer w.mu.Unlock() + if w.cons { + os.Stderr.Write(p) + } + if w.file == nil { + if err := w.rotate(); err != nil { + os.Stderr.Write(p) + return 0, err + } + } + + t := time.Now() + var b []byte + year, month, day := t.Date() + b = appendInt(b, year, 4) + b = append(b, '-') + b = appendInt(b, int(month), 2) + b = append(b, '-') + b = appendInt(b, day, 2) + + // 按天切割 + if !bytes.Equal(w.creates[:10], b) { //2023-04-05 + go w.delete() // 每天检测一次旧文件 + if err := w.rotate(); err != nil { + return 0, err + } + } + // 按大小切割 + if w.size+int64(len(p)) >= w.maxSize { + if err := w.rotate(); err != nil { + return 0, err + } + } + // n, err = w.file.Write(p) + n, err = w.bw.Write(p) + w.size += int64(n) + if err != nil { + return n, err + } + return +} + +// rotate 切割文件 +func (w *Writer) rotate() error { + now := time.Now() + if w.file != nil { + w.bw.Flush() + w.file.Sync() + w.file.Close() + // 保存 + fbak := w.fname + w.time2name(w.created) + w.fsuffix + os.Rename(w.fpath, filepath.Join(w.fdir, fbak)) + w.size = 0 + } + finfo, err := os.Stat(w.fpath) + w.created = now + if err == nil { + w.size = finfo.Size() + w.created = finfo.ModTime() + } + w.creates = w.created.AppendFormat(nil, time.RFC3339) + fout, err := os.OpenFile(w.fpath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + return err + } + w.file = fout + w.bw = bufio.NewWriter(w.file) + return nil +} + +// 删除旧日志 +func (w *Writer) delete() { + if w.maxAge <= 0 { + return + } + dir := filepath.Dir(w.fpath) + fakeNow := time.Now().AddDate(0, 0, -w.maxAge) + dirs, err := os.ReadDir(dir) + if err != nil { + return + } + for _, path := range dirs { + name := path.Name() + if path.IsDir() { + continue + } + t, err := w.name2time(name) + // 只删除满足格式的文件 + if err == nil && t.Before(fakeNow) { + os.Remove(filepath.Join(dir, name)) + } + } +} +func (w *Writer) name2time(name string) (time.Time, error) { + name = strings.TrimPrefix(name, filepath.Base(w.fname)) + name = strings.TrimSuffix(name, w.fsuffix) + return time.Parse(".2006-01-02-150405", name) +} +func (w *Writer) time2name(t time.Time) string { + return t.Format(".2006-01-02-150405") +} + +func (w *Writer) Close() error { + w.flush() + return w.close() +} + +// close closes the file if it is open. +func (w *Writer) close() error { + w.mu.Lock() + defer w.mu.Unlock() + if w.file == nil { + return nil + } + w.file.Sync() + err := w.file.Close() + w.file = nil + return err +} + +func (w *Writer) flush() error { + w.mu.Lock() + defer w.mu.Unlock() + if w.bw == nil { + return nil + } + return w.bw.Flush() +} + +// appendInt appends the decimal form of x to b and returns the result. +// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's. +// Duplicates functionality in strconv, but avoids dependency. +func appendInt(b []byte, x int, width int) []byte { + u := uint(x) + if x < 0 { + b = append(b, '-') + u = uint(-x) + } + + // 2-digit and 4-digit fields are the most common in time formats. + utod := func(u uint) byte { return '0' + byte(u) } + switch { + case width == 2 && u < 1e2: + return append(b, utod(u/1e1), utod(u%1e1)) + case width == 4 && u < 1e4: + return append(b, utod(u/1e3), utod(u/1e2%1e1), utod(u/1e1%1e1), utod(u%1e1)) + } + + // Compute the number of decimal digits. + var n int + if u == 0 { + n = 1 + } + for u2 := u; u2 > 0; u2 /= 10 { + n++ + } + + // Add 0-padding. + for pad := width - n; pad > 0; pad-- { + b = append(b, '0') + } + + // Ensure capacity. + if len(b)+n <= cap(b) { + b = b[:len(b)+n] + } else { + b = append(b, make([]byte, n)...) + } + + // Assemble decimal in reverse order. + i := len(b) - 1 + for u >= 10 && i > 0 { + q := u / 10 + b[i] = utod(u - q*10) + u = q + i-- + } + b[i] = utod(u) + return b +}