github.com/gregpr07/bsc@v1.1.2/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  }