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 }