trpc.group/trpc-go/trpc-go@v1.0.3/log/rollwriter/async_roll_writer.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package rollwriter 15 16 import ( 17 "bytes" 18 "errors" 19 "fmt" 20 "io" 21 "time" 22 23 "github.com/hashicorp/go-multierror" 24 25 "trpc.group/trpc-go/trpc-go/internal/report" 26 ) 27 28 const ( 29 defaultLogQueueSize = 10000 30 defaultWriteLogSize = 4 * 1024 // 4KB 31 defaultLogIntervalInMs = 100 32 defaultDropLog = false 33 ) 34 35 // AsyncRollWriter is the asynchronous rolling log writer which implements zapcore.WriteSyncer. 36 type AsyncRollWriter struct { 37 logger io.WriteCloser 38 opts *AsyncOptions 39 40 logQueue chan []byte 41 sync chan struct{} 42 syncErr chan error 43 close chan struct{} 44 closeErr chan error 45 } 46 47 // NewAsyncRollWriter creates a new AsyncRollWriter. 48 func NewAsyncRollWriter(logger io.WriteCloser, opt ...AsyncOption) *AsyncRollWriter { 49 opts := &AsyncOptions{ 50 LogQueueSize: defaultLogQueueSize, 51 WriteLogSize: defaultWriteLogSize, 52 WriteLogInterval: defaultLogIntervalInMs, 53 DropLog: defaultDropLog, 54 } 55 56 for _, o := range opt { 57 o(opts) 58 } 59 60 w := &AsyncRollWriter{ 61 logger: logger, 62 opts: opts, 63 logQueue: make(chan []byte, opts.LogQueueSize), 64 sync: make(chan struct{}), 65 syncErr: make(chan error), 66 close: make(chan struct{}), 67 closeErr: make(chan error), 68 } 69 70 // Start a new goroutine to write batch logs. 71 go w.batchWriteLog() 72 return w 73 } 74 75 // Write writes logs. It implements io.Writer. 76 func (w *AsyncRollWriter) Write(data []byte) (int, error) { 77 log := make([]byte, len(data)) 78 copy(log, data) 79 if w.opts.DropLog { 80 select { 81 case w.logQueue <- log: 82 default: 83 report.LogQueueDropNum.Incr() 84 return 0, errors.New("async roll writer: log queue is full") 85 } 86 return len(data), nil 87 } 88 w.logQueue <- log 89 return len(data), nil 90 } 91 92 // Sync syncs logs. It implements zapcore.WriteSyncer. 93 func (w *AsyncRollWriter) Sync() error { 94 w.sync <- struct{}{} 95 return <-w.syncErr 96 } 97 98 // Close closes current log file. It implements io.Closer. 99 func (w *AsyncRollWriter) Close() error { 100 err := w.Sync() 101 close(w.close) 102 return multierror.Append(err, <-w.closeErr).ErrorOrNil() 103 } 104 105 // batchWriteLog asynchronously writes logs in batches. 106 func (w *AsyncRollWriter) batchWriteLog() { 107 buffer := bytes.NewBuffer(make([]byte, 0, w.opts.WriteLogSize*2)) 108 ticker := time.NewTicker(time.Millisecond * time.Duration(w.opts.WriteLogInterval)) 109 defer ticker.Stop() 110 111 for { 112 select { 113 case <-ticker.C: 114 if buffer.Len() > 0 { 115 _, err := w.logger.Write(buffer.Bytes()) 116 handleErr(err, "w.logger.Write on tick") 117 buffer.Reset() 118 } 119 case data := <-w.logQueue: 120 if len(data) >= w.opts.WriteLogSize { 121 // If the length of the current data exceeds the expected maximum value, 122 // we directly write it to the underlying logger instead of placing it into the buffer. 123 // This prevents the buffer from being overwhelmed by excessively large data, 124 // which could lead to memory leaks. 125 // Prior to that, we need to write the existing data in the buffer to the underlying logger. 126 _, _ = w.logger.Write(buffer.Bytes()) 127 buffer.Reset() 128 _, _ = w.logger.Write(data) 129 continue 130 } 131 buffer.Write(data) 132 if buffer.Len() >= w.opts.WriteLogSize { 133 _, err := w.logger.Write(buffer.Bytes()) 134 handleErr(err, "w.logger.Write on log queue") 135 buffer.Reset() 136 } 137 case <-w.sync: 138 var err error 139 if buffer.Len() > 0 { 140 _, e := w.logger.Write(buffer.Bytes()) 141 err = multierror.Append(err, e).ErrorOrNil() 142 buffer.Reset() 143 } 144 size := len(w.logQueue) 145 for i := 0; i < size; i++ { 146 v := <-w.logQueue 147 _, e := w.logger.Write(v) 148 err = multierror.Append(err, e).ErrorOrNil() 149 } 150 w.syncErr <- err 151 case <-w.close: 152 w.closeErr <- w.logger.Close() 153 return 154 } 155 } 156 } 157 158 func handleErr(err error, msg string) { 159 if err == nil { 160 return 161 } 162 // Log writer has errors, so output to stdout directly. 163 fmt.Printf("async roll writer err: %+v, msg: %s", err, msg) 164 }