gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package rotatingfilewriter
  4. import (
  5. "bufio"
  6. "compress/gzip"
  7. "errors"
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "sync"
  13. "time"
  14. "code.gitea.io/gitea/modules/graceful/releasereopen"
  15. "code.gitea.io/gitea/modules/util"
  16. )
  17. type Options struct {
  18. Rotate bool
  19. MaximumSize int64
  20. RotateDaily bool
  21. KeepDays int
  22. Compress bool
  23. CompressionLevel int
  24. }
  25. type RotatingFileWriter struct {
  26. mu sync.Mutex
  27. fd *os.File
  28. currentSize int64
  29. openDate int
  30. options Options
  31. cancelReleaseReopen func()
  32. }
  33. var ErrorPrintf func(format string, args ...any)
  34. // errorf tries to print error messages. Since this writer could be used by a logger system, this is the last chance to show the error in some cases
  35. func errorf(format string, args ...any) {
  36. if ErrorPrintf != nil {
  37. ErrorPrintf("rotatingfilewriter: "+format+"\n", args...)
  38. }
  39. }
  40. // Open creates a new rotating file writer.
  41. // Notice: if a file is opened by two rotators, there will be conflicts when rotating.
  42. // In the future, there should be "rotating file manager"
  43. func Open(filename string, options *Options) (*RotatingFileWriter, error) {
  44. if options == nil {
  45. options = &Options{}
  46. }
  47. rfw := &RotatingFileWriter{
  48. options: *options,
  49. }
  50. if err := rfw.open(filename); err != nil {
  51. return nil, err
  52. }
  53. rfw.cancelReleaseReopen = releasereopen.GetManager().Register(rfw)
  54. return rfw, nil
  55. }
  56. func (rfw *RotatingFileWriter) Write(b []byte) (int, error) {
  57. if rfw.options.Rotate && ((rfw.options.MaximumSize > 0 && rfw.currentSize >= rfw.options.MaximumSize) || (rfw.options.RotateDaily && time.Now().Day() != rfw.openDate)) {
  58. if err := rfw.DoRotate(); err != nil {
  59. // if this writer is used by a logger system, it's the logger system's responsibility to handle/show the error
  60. return 0, err
  61. }
  62. }
  63. n, err := rfw.fd.Write(b)
  64. if err == nil {
  65. rfw.currentSize += int64(n)
  66. }
  67. return n, err
  68. }
  69. func (rfw *RotatingFileWriter) Flush() error {
  70. return rfw.fd.Sync()
  71. }
  72. func (rfw *RotatingFileWriter) Close() error {
  73. rfw.mu.Lock()
  74. if rfw.cancelReleaseReopen != nil {
  75. rfw.cancelReleaseReopen()
  76. rfw.cancelReleaseReopen = nil
  77. }
  78. rfw.mu.Unlock()
  79. return rfw.fd.Close()
  80. }
  81. func (rfw *RotatingFileWriter) open(filename string) error {
  82. fd, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660)
  83. if err != nil {
  84. return err
  85. }
  86. rfw.fd = fd
  87. finfo, err := fd.Stat()
  88. if err != nil {
  89. return err
  90. }
  91. rfw.currentSize = finfo.Size()
  92. rfw.openDate = finfo.ModTime().Day()
  93. return nil
  94. }
  95. func (rfw *RotatingFileWriter) ReleaseReopen() error {
  96. return errors.Join(
  97. rfw.fd.Close(),
  98. rfw.open(rfw.fd.Name()),
  99. )
  100. }
  101. // DoRotate the log file creating a backup like xx.2013-01-01.2
  102. func (rfw *RotatingFileWriter) DoRotate() error {
  103. if !rfw.options.Rotate {
  104. return nil
  105. }
  106. rfw.mu.Lock()
  107. defer rfw.mu.Unlock()
  108. prefix := fmt.Sprintf("%s.%s.", rfw.fd.Name(), time.Now().Format("2006-01-02"))
  109. var err error
  110. fname := ""
  111. for i := 1; err == nil && i <= 999; i++ {
  112. fname = prefix + fmt.Sprintf("%03d", i)
  113. _, err = os.Lstat(fname)
  114. if rfw.options.Compress && err != nil {
  115. _, err = os.Lstat(fname + ".gz")
  116. }
  117. }
  118. // return error if the last file checked still existed
  119. if err == nil {
  120. return fmt.Errorf("cannot find free file to rename %s", rfw.fd.Name())
  121. }
  122. fd := rfw.fd
  123. if err := fd.Close(); err != nil { // close file before rename
  124. return err
  125. }
  126. if err := util.Rename(fd.Name(), fname); err != nil {
  127. return err
  128. }
  129. if rfw.options.Compress {
  130. go func() {
  131. err := compressOldFile(fname, rfw.options.CompressionLevel)
  132. if err != nil {
  133. errorf("DoRotate: %v", err)
  134. }
  135. }()
  136. }
  137. if err := rfw.open(fd.Name()); err != nil {
  138. return err
  139. }
  140. go deleteOldFiles(
  141. filepath.Dir(fd.Name()),
  142. filepath.Base(fd.Name()),
  143. time.Now().AddDate(0, 0, -rfw.options.KeepDays),
  144. )
  145. return nil
  146. }
  147. func compressOldFile(fname string, compressionLevel int) error {
  148. reader, err := os.Open(fname)
  149. if err != nil {
  150. return fmt.Errorf("compressOldFile: failed to open existing file %s: %w", fname, err)
  151. }
  152. defer reader.Close()
  153. buffer := bufio.NewReader(reader)
  154. fnameGz := fname + ".gz"
  155. fw, err := os.OpenFile(fnameGz, os.O_WRONLY|os.O_CREATE, 0o660)
  156. if err != nil {
  157. return fmt.Errorf("compressOldFile: failed to open new file %s: %w", fnameGz, err)
  158. }
  159. defer fw.Close()
  160. zw, err := gzip.NewWriterLevel(fw, compressionLevel)
  161. if err != nil {
  162. return fmt.Errorf("compressOldFile: failed to create gzip writer: %w", err)
  163. }
  164. defer zw.Close()
  165. _, err = buffer.WriteTo(zw)
  166. if err != nil {
  167. _ = zw.Close()
  168. _ = fw.Close()
  169. _ = util.Remove(fname + ".gz")
  170. return fmt.Errorf("compressOldFile: failed to write to gz file: %w", err)
  171. }
  172. _ = reader.Close()
  173. err = util.Remove(fname)
  174. if err != nil {
  175. return fmt.Errorf("compressOldFile: failed to delete old file: %w", err)
  176. }
  177. return nil
  178. }
  179. func deleteOldFiles(dir, prefix string, removeBefore time.Time) {
  180. err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) {
  181. defer func() {
  182. if r := recover(); r != nil {
  183. returnErr = fmt.Errorf("unable to delete old file '%s', error: %+v", path, r)
  184. }
  185. }()
  186. if err != nil {
  187. return err
  188. }
  189. if d.IsDir() {
  190. return nil
  191. }
  192. info, err := d.Info()
  193. if err != nil {
  194. return err
  195. }
  196. if info.ModTime().Before(removeBefore) {
  197. if strings.HasPrefix(filepath.Base(path), prefix) {
  198. return util.Remove(path)
  199. }
  200. }
  201. return nil
  202. })
  203. if err != nil {
  204. errorf("deleteOldFiles: failed to delete old file: %v", err)
  205. }
  206. }