github.com/qiniu/go-sdk/v7@v7.27.0/internal/uplog/uplog_buffer.go (about)

     1  package uplog
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  	"sync/atomic"
    12  	"time"
    13  
    14  	"github.com/gofrs/flock"
    15  )
    16  
    17  var (
    18  	uplogDisabled bool
    19  	uplogChan     chan<- uplogSerializedEntry
    20  
    21  	uplogFileBufferDirPath      string
    22  	uplogFileBufferDirPathMutex sync.Mutex
    23  
    24  	uplogFileBuffer               *os.File
    25  	uplogFileBufferFileLocker     *flock.Flock
    26  	uplogFileBufferLock           sync.Mutex
    27  	uplogFileBufferThreshold      uint64 = 4 * 1024 * 1024
    28  	uplogMaxStorageBytes          uint64 = 100 * 1024 * 1024
    29  	uplogWriteFileBufferTicker    *time.Ticker
    30  	uplogWriteFileBufferInterval  time.Duration = 1 * time.Minute
    31  	uplogWriteFileBufferTimerLock sync.Mutex
    32  
    33  	getUpToken GetUpToken
    34  )
    35  
    36  const (
    37  	UPLOG_CHANNEL_SIZE       = 1024
    38  	UPLOG_MEMORY_BUFFER_SIZE = 4 * 1024
    39  	UPLOG_FILE_BUFFER_NAME   = "uplog_v4_01.buffer"
    40  	UPLOG_FILE_LOCK_NAME     = "uplog_v4_01.lock"
    41  )
    42  
    43  type uplogSerializedEntry struct {
    44  	serializedUplog []byte
    45  	getUpToken      GetUpToken
    46  }
    47  
    48  func init() {
    49  	uplogChannel := make(chan uplogSerializedEntry, UPLOG_CHANNEL_SIZE)
    50  	uplogWriteFileBufferTicker = time.NewTicker(uplogWriteFileBufferInterval)
    51  	go func() {
    52  		for {
    53  			select {
    54  			case serializedEntry := <-uplogChannel:
    55  				if uplogDisabled {
    56  					continue
    57  				}
    58  				if gut := serializedEntry.getUpToken; gut != nil {
    59  					getUpToken = gut
    60  				}
    61  				uplogBuffer := bytes.NewBuffer(make([]byte, 0, UPLOG_MEMORY_BUFFER_SIZE))
    62  				uplogBuffer.Write(serializedEntry.serializedUplog)
    63  				uplogBuffer.WriteString("\n")
    64  				for uplogBuffer.Len() < (UPLOG_MEMORY_BUFFER_SIZE / 2) {
    65  					select {
    66  					case serializedEntry := <-uplogChannel:
    67  						uplogBuffer.Write(serializedEntry.serializedUplog)
    68  						uplogBuffer.WriteString("\n")
    69  					default:
    70  						goto finishReading
    71  					}
    72  				}
    73  			finishReading:
    74  				_, _ = writeMemoryBufferToFileBuffer(uplogBuffer.Bytes())
    75  			case <-uplogWriteFileBufferTicker.C:
    76  				if fi, err := os.Stat(getUplogFileBufferPath(true)); err == nil && fi.Size() > 0 {
    77  					tryToArchiveFileBuffer(false)
    78  				}
    79  			}
    80  		}
    81  	}()
    82  	uplogChan = uplogChannel
    83  }
    84  
    85  func DisableUplog() {
    86  	uplogDisabled = true
    87  }
    88  
    89  func EnableUplog() {
    90  	uplogDisabled = false
    91  }
    92  
    93  func IsUplogEnabled() bool {
    94  	return !uplogDisabled
    95  }
    96  
    97  func GetUplogMaxStorageBytes() uint64 {
    98  	return atomic.LoadUint64(&uplogMaxStorageBytes)
    99  }
   100  
   101  func SetUplogMaxStorageBytes(max uint64) {
   102  	atomic.StoreUint64(&uplogMaxStorageBytes, max)
   103  }
   104  
   105  func SetUplogFileBufferDirPath(path string) {
   106  	uplogFileBufferDirPathMutex.Lock()
   107  	defer uplogFileBufferDirPathMutex.Unlock()
   108  	uplogFileBufferDirPath = path
   109  }
   110  
   111  func getUplogFileBufferPath(current bool) string {
   112  	var uplogFileBufferPath string
   113  
   114  	uplogFileBufferDirPathMutex.Lock()
   115  	defer uplogFileBufferDirPathMutex.Unlock()
   116  
   117  	if uplogFileBufferDirPath == "" {
   118  		uplogFileBufferPath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", UPLOG_FILE_BUFFER_NAME)
   119  	} else {
   120  		uplogFileBufferPath = filepath.Join(uplogFileBufferDirPath, UPLOG_FILE_BUFFER_NAME)
   121  	}
   122  	if !current {
   123  		uplogFileBufferPath = uplogFileBufferPath + "." + time.Now().UTC().Format("20060102150405.999999999")
   124  	}
   125  	return uplogFileBufferPath
   126  }
   127  
   128  func getUplogFileDirectoryLock() *flock.Flock {
   129  	uplogFileBufferDirPathMutex.Lock()
   130  	defer uplogFileBufferDirPathMutex.Unlock()
   131  	var uplogFileLockPath string
   132  	if uplogFileBufferDirPath == "" {
   133  		uplogFileLockPath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", UPLOG_FILE_LOCK_NAME)
   134  	} else {
   135  		uplogFileLockPath = filepath.Join(uplogFileBufferDirPath, UPLOG_FILE_LOCK_NAME)
   136  	}
   137  	return flock.New(uplogFileLockPath)
   138  }
   139  
   140  func FlushBuffer() error {
   141  	return withUploadFileBuffer(func(w io.WriteCloser) error {
   142  		return w.Close()
   143  	})
   144  }
   145  
   146  func writeMemoryBufferToFileBuffer(data []byte) (n int, err error) {
   147  	if err = withUploadFileBuffer(func(w io.WriteCloser) (e error) {
   148  		for len(data) > 0 {
   149  			n, e = w.Write(data)
   150  			if e != nil {
   151  				return
   152  			}
   153  			data = data[n:]
   154  		}
   155  		return
   156  	}); err != nil {
   157  		return
   158  	}
   159  
   160  	if fi, serr := os.Stat(getUplogFileBufferPath(true)); serr == nil && uint64(fi.Size()) >= uplogFileBufferThreshold {
   161  		tryToArchiveFileBuffer(false)
   162  	}
   163  	return
   164  }
   165  
   166  func tryToArchiveFileBuffer(force bool) {
   167  	var (
   168  		locked bool
   169  		err    error
   170  	)
   171  
   172  	if fileInfo, fileInfoErr := os.Stat(getUplogFileBufferPath(true)); fileInfoErr == nil && fileInfo.Size() == 0 {
   173  		return
   174  	}
   175  
   176  	locker := getUplogFileDirectoryLock()
   177  	if force {
   178  		if err = locker.Lock(); err != nil {
   179  			return
   180  		}
   181  	} else {
   182  		if locked, err = locker.TryLock(); err != nil || !locked {
   183  			return
   184  		}
   185  	}
   186  	defer locker.Close()
   187  
   188  	if err = withUploadFileBuffer(func(w io.WriteCloser) error {
   189  		currentFilePath := getUplogFileBufferPath(true)
   190  		if fileInfo, fileInfoErr := os.Stat(currentFilePath); fileInfoErr == nil && fileInfo.Size() == 0 {
   191  			return nil
   192  		}
   193  		archivedFilePath := getUplogFileBufferPath(false)
   194  		w.Close()
   195  		return os.Rename(currentFilePath, archivedFilePath)
   196  	}); err != nil {
   197  		return
   198  	}
   199  
   200  	resetWriteFileBufferInterval()
   201  	go uploadAllClosedFileBuffers()
   202  }
   203  
   204  type uplogFileBufferWrapper struct{}
   205  
   206  func (uplogFileBufferWrapper) Write(p []byte) (int, error) {
   207  	return uplogFileBuffer.Write(p)
   208  }
   209  
   210  func (uplogFileBufferWrapper) Close() error {
   211  	return closeUplogFileBufferWithoutLock()
   212  }
   213  
   214  func withUploadFileBuffer(fn func(io.WriteCloser) error) (err error) {
   215  	uplogFileBufferLock.Lock()
   216  	defer uplogFileBufferLock.Unlock()
   217  
   218  	uplogFileBufferPath := getUplogFileBufferPath(true)
   219  
   220  	if uplogFileBuffer != nil {
   221  		if uplogFileBuffer.Name() != uplogFileBufferPath {
   222  			closeUplogFileBufferWithoutLock()
   223  		} else if _, err := os.Stat(uplogFileBufferPath); err != nil && os.IsNotExist(err) {
   224  			closeUplogFileBufferWithoutLock()
   225  		}
   226  	}
   227  
   228  	if uplogFileBuffer == nil {
   229  		if err = os.MkdirAll(filepath.Dir(uplogFileBufferPath), 0755); err != nil {
   230  			return
   231  		} else if uplogFileBuffer, err = os.OpenFile(uplogFileBufferPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil {
   232  			return
   233  		}
   234  		uplogFileBufferLockPath := uplogFileBufferPath + ".lock"
   235  		uplogFileBufferFileLocker = flock.New(uplogFileBufferLockPath)
   236  	}
   237  
   238  	if err = uplogFileBufferFileLocker.Lock(); err != nil {
   239  		return
   240  	}
   241  	err = fn(uplogFileBufferWrapper{})
   242  	if uplogFileBufferFileLocker != nil && uplogFileBufferFileLocker.Locked() {
   243  		_ = uplogFileBufferFileLocker.Unlock()
   244  	}
   245  	return
   246  }
   247  
   248  func closeUplogFileBufferWithoutLock() error {
   249  	var err1, err2 error
   250  	if uplogFileBuffer != nil {
   251  		err1 = uplogFileBuffer.Close()
   252  		uplogFileBuffer = nil
   253  	}
   254  	if uplogFileBufferFileLocker != nil {
   255  		err2 = uplogFileBufferFileLocker.Close()
   256  		uplogFileBufferFileLocker = nil
   257  	}
   258  	if err1 != nil {
   259  		return err1
   260  	}
   261  	return err2
   262  }
   263  
   264  func SetWriteFileBufferInterval(d time.Duration) {
   265  	uplogWriteFileBufferTimerLock.Lock()
   266  	defer uplogWriteFileBufferTimerLock.Unlock()
   267  	if d == 0 {
   268  		d = 1 * time.Minute
   269  	}
   270  	uplogWriteFileBufferInterval = d
   271  	uplogWriteFileBufferTicker.Stop()
   272  	uplogWriteFileBufferTicker = time.NewTicker(d)
   273  }
   274  
   275  func GetWriteFileBufferInterval() time.Duration {
   276  	uplogWriteFileBufferTimerLock.Lock()
   277  	defer uplogWriteFileBufferTimerLock.Unlock()
   278  
   279  	return uplogWriteFileBufferInterval
   280  }
   281  
   282  func resetWriteFileBufferInterval() {
   283  	uplogWriteFileBufferTimerLock.Lock()
   284  	defer uplogWriteFileBufferTimerLock.Unlock()
   285  	uplogWriteFileBufferTicker.Stop()
   286  	uplogWriteFileBufferTicker = time.NewTicker(uplogWriteFileBufferInterval)
   287  }
   288  
   289  type multipleFileReader struct {
   290  	paths []string
   291  
   292  	compressor *gzip.Writer
   293  	w          *io.PipeWriter
   294  	r          *io.PipeReader
   295  	err        error
   296  	errLock    sync.Mutex
   297  	wg         sync.WaitGroup
   298  }
   299  
   300  func newMultipleFileReader(paths []string) (*multipleFileReader, error) {
   301  	r, w := io.Pipe()
   302  	compressor, err := gzip.NewWriterLevel(w, gzip.BestCompression)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	mfr := &multipleFileReader{
   307  		paths:      paths,
   308  		r:          r,
   309  		w:          w,
   310  		compressor: compressor,
   311  	}
   312  	mfr.wg.Add(1)
   313  	go mfr.readAllAsync()
   314  	return mfr, nil
   315  }
   316  
   317  func (r *multipleFileReader) readAllAsync() {
   318  	defer r.wg.Done()
   319  	defer r.w.CloseWithError(io.EOF)
   320  	defer r.compressor.Close()
   321  	for _, path := range r.paths {
   322  		if err := r.readAllForPathAsync(path); err != nil {
   323  			r.setError(err)
   324  			return
   325  		}
   326  	}
   327  }
   328  
   329  func (r *multipleFileReader) readAllForPathAsync(path string) error {
   330  	file, err := os.Open(path)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	defer file.Close()
   335  	if _, err = io.Copy(r.compressor, file); err != nil && err != io.EOF {
   336  		return err
   337  	}
   338  	return nil
   339  }
   340  
   341  func (r *multipleFileReader) getError() error {
   342  	r.errLock.Lock()
   343  	defer r.errLock.Unlock()
   344  	return r.err
   345  }
   346  
   347  func (r *multipleFileReader) setError(err error) {
   348  	r.errLock.Lock()
   349  	defer r.errLock.Unlock()
   350  	r.err = err
   351  }
   352  
   353  func (r *multipleFileReader) Read(p []byte) (int, error) {
   354  	if err := r.getError(); err != nil {
   355  		return 0, err
   356  	}
   357  	return r.r.Read(p)
   358  }
   359  
   360  func (r *multipleFileReader) Close() error {
   361  	err := r.r.CloseWithError(io.EOF)
   362  	r.wg.Wait()
   363  	return err
   364  }
   365  
   366  func getArchivedUplogFileBufferPaths(dirPath string) ([]string, error) {
   367  	dirEntries, err := os.ReadDir(dirPath)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	archivedPaths := make([]string, 0, len(dirEntries))
   373  	for _, dirEntry := range dirEntries {
   374  		if !dirEntry.Type().IsRegular() ||
   375  			!strings.HasPrefix(dirEntry.Name(), UPLOG_FILE_BUFFER_NAME+".") ||
   376  			strings.HasSuffix(dirEntry.Name(), ".lock") {
   377  			continue
   378  		}
   379  		archivedPaths = append(archivedPaths, filepath.Join(dirPath, dirEntry.Name()))
   380  	}
   381  	return archivedPaths, nil
   382  }