github.com/phillinzzz/newBsc@v1.1.6/log/async_file_writer.go (about) 1 package log 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 "sync/atomic" 10 "time" 11 ) 12 13 type HourTicker struct { 14 stop chan struct{} 15 C <-chan time.Time 16 } 17 18 func NewHourTicker() *HourTicker { 19 ht := &HourTicker{ 20 stop: make(chan struct{}), 21 } 22 ht.C = ht.Ticker() 23 return ht 24 } 25 26 func (ht *HourTicker) Stop() { 27 ht.stop <- struct{}{} 28 } 29 30 func (ht *HourTicker) Ticker() <-chan time.Time { 31 ch := make(chan time.Time) 32 go func() { 33 hour := time.Now().Hour() 34 ticker := time.NewTicker(time.Second) 35 defer ticker.Stop() 36 for { 37 select { 38 case t := <-ticker.C: 39 if t.Hour() != hour { 40 ch <- t 41 hour = t.Hour() 42 } 43 case <-ht.stop: 44 return 45 } 46 } 47 }() 48 return ch 49 } 50 51 type AsyncFileWriter struct { 52 filePath string 53 fd *os.File 54 55 wg sync.WaitGroup 56 started int32 57 buf chan []byte 58 stop chan struct{} 59 hourTicker *HourTicker 60 } 61 62 func NewAsyncFileWriter(filePath string, bufSize int64) *AsyncFileWriter { 63 absFilePath, err := filepath.Abs(filePath) 64 if err != nil { 65 panic(fmt.Sprintf("get file path of logger error. filePath=%s, err=%s", filePath, err)) 66 } 67 68 return &AsyncFileWriter{ 69 filePath: absFilePath, 70 buf: make(chan []byte, bufSize), 71 stop: make(chan struct{}), 72 hourTicker: NewHourTicker(), 73 } 74 } 75 76 func (w *AsyncFileWriter) initLogFile() error { 77 var ( 78 fd *os.File 79 err error 80 ) 81 82 realFilePath := w.timeFilePath(w.filePath) 83 fd, err = os.OpenFile(realFilePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) 84 if err != nil { 85 return err 86 } 87 88 w.fd = fd 89 _, err = os.Lstat(w.filePath) 90 if err == nil || os.IsExist(err) { 91 err = os.Remove(w.filePath) 92 if err != nil { 93 return err 94 } 95 } 96 97 err = os.Symlink(realFilePath, w.filePath) 98 if err != nil { 99 return err 100 } 101 102 return nil 103 } 104 105 func (w *AsyncFileWriter) Start() error { 106 if !atomic.CompareAndSwapInt32(&w.started, 0, 1) { 107 return errors.New("logger has already been started") 108 } 109 110 err := w.initLogFile() 111 if err != nil { 112 return err 113 } 114 115 w.wg.Add(1) 116 go func() { 117 defer func() { 118 atomic.StoreInt32(&w.started, 0) 119 120 w.flushBuffer() 121 w.flushAndClose() 122 123 w.wg.Done() 124 }() 125 126 for { 127 select { 128 case msg, ok := <-w.buf: 129 if !ok { 130 fmt.Fprintln(os.Stderr, "buf channel has been closed.") 131 return 132 } 133 w.SyncWrite(msg) 134 case <-w.stop: 135 return 136 } 137 } 138 }() 139 return nil 140 } 141 142 func (w *AsyncFileWriter) flushBuffer() { 143 for { 144 select { 145 case msg := <-w.buf: 146 w.SyncWrite(msg) 147 default: 148 return 149 } 150 } 151 } 152 153 func (w *AsyncFileWriter) SyncWrite(msg []byte) { 154 w.rotateFile() 155 if w.fd != nil { 156 w.fd.Write(msg) 157 } 158 } 159 160 func (w *AsyncFileWriter) rotateFile() { 161 select { 162 case <-w.hourTicker.C: 163 if err := w.flushAndClose(); err != nil { 164 fmt.Fprintf(os.Stderr, "flush and close file error. err=%s", err) 165 } 166 if err := w.initLogFile(); err != nil { 167 fmt.Fprintf(os.Stderr, "init log file error. err=%s", err) 168 } 169 default: 170 } 171 } 172 173 func (w *AsyncFileWriter) Stop() { 174 w.stop <- struct{}{} 175 w.wg.Wait() 176 177 w.hourTicker.Stop() 178 } 179 180 func (w *AsyncFileWriter) Write(msg []byte) (n int, err error) { 181 // TODO(wuzhenxing): for the underlying array may change, is there a better way to avoid copying slice? 182 buf := make([]byte, len(msg)) 183 copy(buf, msg) 184 185 select { 186 case w.buf <- buf: 187 default: 188 } 189 return 0, nil 190 } 191 192 func (w *AsyncFileWriter) Flush() error { 193 if w.fd == nil { 194 return nil 195 } 196 return w.fd.Sync() 197 } 198 199 func (w *AsyncFileWriter) flushAndClose() error { 200 if w.fd == nil { 201 return nil 202 } 203 204 err := w.fd.Sync() 205 if err != nil { 206 return err 207 } 208 209 return w.fd.Close() 210 } 211 212 func (w *AsyncFileWriter) timeFilePath(filePath string) string { 213 return filePath + "." + time.Now().Format("2006-01-02_15") 214 }