github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/single_log.go (about)

     1  package xlog
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/google/safeopen"
    12  	"go.uber.org/multierr"
    13  
    14  	"github.com/benz9527/xboot/lib/infra"
    15  )
    16  
    17  var _ io.WriteCloser = (*singleLog)(nil)
    18  
    19  // singleLog is not thread-safe.
    20  type singleLog struct {
    21  	ctx         context.Context
    22  	filePath    string
    23  	filename    string
    24  	wroteSize   uint64
    25  	mkdirOnce   sync.Once
    26  	currentFile atomic.Pointer[os.File]
    27  }
    28  
    29  func (log *singleLog) Write(p []byte) (n int, err error) {
    30  	select {
    31  	case <-log.ctx.Done():
    32  		return 0, io.EOF
    33  	default:
    34  	}
    35  
    36  	if log.currentFile.Load() == nil {
    37  		if err := log.openOrCreate(); err != nil {
    38  			return 0, err
    39  		}
    40  	}
    41  	n, err = log.currentFile.Load().Write(p)
    42  	log.wroteSize += uint64(n)
    43  	return
    44  }
    45  
    46  func (log *singleLog) Close() error {
    47  	cur := log.currentFile.Load()
    48  	if cur == nil {
    49  		return nil
    50  	}
    51  	if err := cur.Close(); err != nil {
    52  		return err
    53  	}
    54  	log.currentFile.Store(nil)
    55  	return nil
    56  }
    57  
    58  func (log *singleLog) openOrCreate() error {
    59  	if err := log.mkdir(); err != nil {
    60  		return err
    61  	}
    62  
    63  	pathToLog := filepath.Join(log.filePath, log.filename)
    64  	info, err := os.Stat(pathToLog)
    65  	if os.IsNotExist(err) {
    66  		var merr error
    67  		merr = multierr.Append(merr, err)
    68  		if err = log.create(); err != nil {
    69  			return multierr.Append(merr, err)
    70  		}
    71  		return nil
    72  	} else if err != nil {
    73  		log.currentFile.Store(nil)
    74  		return infra.WrapErrorStack(err)
    75  	}
    76  
    77  	if info.IsDir() {
    78  		log.currentFile.Store(nil)
    79  		return infra.NewErrorStack("log file <" + pathToLog + "> is a dir")
    80  	}
    81  
    82  	var f *os.File
    83  	if f, err = safeopen.OpenFileBeneath(log.filePath, log.filename, os.O_WRONLY|os.O_APPEND, 0o644); err != nil {
    84  		return infra.WrapErrorStackWithMessage(err, "failed to open an exists log file: "+pathToLog)
    85  	}
    86  	log.currentFile.Store(f)
    87  	log.wroteSize = uint64(info.Size())
    88  	return nil
    89  }
    90  
    91  func (log *singleLog) mkdir() error {
    92  	var err error = nil
    93  	log.mkdirOnce.Do(func() {
    94  		if log.filePath == "" {
    95  			log.filePath = os.TempDir()
    96  		}
    97  		if log.filePath == os.TempDir() {
    98  			return
    99  		}
   100  		err = os.MkdirAll(log.filePath, 0o644)
   101  	})
   102  	return infra.WrapErrorStack(err)
   103  }
   104  
   105  func (log *singleLog) create() error {
   106  	if err := log.mkdir(); err != nil {
   107  		return err
   108  	}
   109  
   110  	f, err := safeopen.OpenFileBeneath(log.filePath, log.filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644)
   111  	if err != nil {
   112  		return infra.WrapErrorStackWithMessage(err, "unable to create new log file: "+filepath.Join(log.filePath, log.filename))
   113  	}
   114  	log.currentFile.Store(f)
   115  	log.wroteSize = 0
   116  	return nil
   117  }
   118  
   119  func (log *singleLog) initialize() error {
   120  	go func() {
   121  		select {
   122  		case <-log.ctx.Done():
   123  			_ = log.Close()
   124  			return
   125  		}
   126  	}()
   127  	return nil
   128  }
   129  
   130  func SingleLog(ctx context.Context, cfg *FileCoreConfig) io.WriteCloser {
   131  	if cfg == nil || ctx == nil {
   132  		return nil
   133  	}
   134  	log := &singleLog{
   135  		ctx:      ctx,
   136  		filePath: cfg.FilePath,
   137  		filename: cfg.Filename,
   138  	}
   139  	_ = log.initialize()
   140  	return log
   141  }