You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

file.go 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package log
  5. import (
  6. "bufio"
  7. "compress/gzip"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. // FileLogger implements LoggerProvider.
  18. // It writes messages by lines limit, file size limit, or time frequency.
  19. type FileLogger struct {
  20. WriterLogger
  21. mw *MuxWriter
  22. // The opened file
  23. Filename string `json:"filename"`
  24. // Rotate at size
  25. Maxsize int `json:"maxsize"`
  26. maxsizeCursize int
  27. // Rotate daily
  28. Daily bool `json:"daily"`
  29. Maxdays int64 `json:"maxdays"`
  30. dailyOpenDate int
  31. Rotate bool `json:"rotate"`
  32. Compress bool `json:"compress"`
  33. CompressionLevel int `json:"compressionLevel"`
  34. startLock sync.Mutex // Only one log can write to the file
  35. }
  36. // MuxWriter an *os.File writer with locker.
  37. type MuxWriter struct {
  38. mu sync.Mutex
  39. fd *os.File
  40. owner *FileLogger
  41. }
  42. // Write writes to os.File.
  43. func (mw *MuxWriter) Write(b []byte) (int, error) {
  44. mw.mu.Lock()
  45. defer mw.mu.Unlock()
  46. mw.owner.docheck(len(b))
  47. return mw.fd.Write(b)
  48. }
  49. // Close the internal writer
  50. func (mw *MuxWriter) Close() error {
  51. return mw.fd.Close()
  52. }
  53. // SetFd sets os.File in writer.
  54. func (mw *MuxWriter) SetFd(fd *os.File) {
  55. if mw.fd != nil {
  56. mw.fd.Close()
  57. }
  58. mw.fd = fd
  59. }
  60. // NewFileLogger create a FileLogger returning as LoggerProvider.
  61. func NewFileLogger() LoggerProvider {
  62. log := &FileLogger{
  63. Filename: "",
  64. Maxsize: 1 << 28, //256 MB
  65. Daily: true,
  66. Maxdays: 7,
  67. Rotate: true,
  68. Compress: true,
  69. CompressionLevel: gzip.DefaultCompression,
  70. }
  71. log.Level = TRACE
  72. // use MuxWriter instead direct use os.File for lock write when rotate
  73. log.mw = new(MuxWriter)
  74. log.mw.owner = log
  75. return log
  76. }
  77. // Init file logger with json config.
  78. // config like:
  79. // {
  80. // "filename":"log/gogs.log",
  81. // "maxsize":1<<30,
  82. // "daily":true,
  83. // "maxdays":15,
  84. // "rotate":true
  85. // }
  86. func (log *FileLogger) Init(config string) error {
  87. if err := json.Unmarshal([]byte(config), log); err != nil {
  88. return err
  89. }
  90. if len(log.Filename) == 0 {
  91. return errors.New("config must have filename")
  92. }
  93. // set MuxWriter as Logger's io.Writer
  94. log.NewWriterLogger(log.mw)
  95. return log.StartLogger()
  96. }
  97. // StartLogger start file logger. create log file and set to locker-inside file writer.
  98. func (log *FileLogger) StartLogger() error {
  99. fd, err := log.createLogFile()
  100. if err != nil {
  101. return err
  102. }
  103. log.mw.SetFd(fd)
  104. return log.initFd()
  105. }
  106. func (log *FileLogger) docheck(size int) {
  107. log.startLock.Lock()
  108. defer log.startLock.Unlock()
  109. if log.Rotate && ((log.Maxsize > 0 && log.maxsizeCursize >= log.Maxsize) ||
  110. (log.Daily && time.Now().Day() != log.dailyOpenDate)) {
  111. if err := log.DoRotate(); err != nil {
  112. fmt.Fprintf(os.Stderr, "FileLogger(%q): %s\n", log.Filename, err)
  113. return
  114. }
  115. }
  116. log.maxsizeCursize += size
  117. }
  118. func (log *FileLogger) createLogFile() (*os.File, error) {
  119. // Open the log file
  120. return os.OpenFile(log.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
  121. }
  122. func (log *FileLogger) initFd() error {
  123. fd := log.mw.fd
  124. finfo, err := fd.Stat()
  125. if err != nil {
  126. return fmt.Errorf("get stat: %v", err)
  127. }
  128. log.maxsizeCursize = int(finfo.Size())
  129. log.dailyOpenDate = time.Now().Day()
  130. return nil
  131. }
  132. // DoRotate means it need to write file in new file.
  133. // new file name like xx.log.2013-01-01.2
  134. func (log *FileLogger) DoRotate() error {
  135. _, err := os.Lstat(log.Filename)
  136. if err == nil { // file exists
  137. // Find the next available number
  138. num := 1
  139. fname := ""
  140. for ; err == nil && num <= 999; num++ {
  141. fname = log.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
  142. _, err = os.Lstat(fname)
  143. if log.Compress && err != nil {
  144. _, err = os.Lstat(fname + ".gz")
  145. }
  146. }
  147. // return error if the last file checked still existed
  148. if err == nil {
  149. return fmt.Errorf("rotate: cannot find free log number to rename %s", log.Filename)
  150. }
  151. fd := log.mw.fd
  152. fd.Close()
  153. // close fd before rename
  154. // Rename the file to its newfound home
  155. if err = os.Rename(log.Filename, fname); err != nil {
  156. return fmt.Errorf("Rotate: %v", err)
  157. }
  158. if log.Compress {
  159. go compressOldLogFile(fname, log.CompressionLevel)
  160. }
  161. // re-start logger
  162. if err = log.StartLogger(); err != nil {
  163. return fmt.Errorf("Rotate StartLogger: %v", err)
  164. }
  165. go log.deleteOldLog()
  166. }
  167. return nil
  168. }
  169. func compressOldLogFile(fname string, compressionLevel int) error {
  170. reader, err := os.Open(fname)
  171. if err != nil {
  172. return err
  173. }
  174. defer reader.Close()
  175. buffer := bufio.NewReader(reader)
  176. fw, err := os.OpenFile(fname+".gz", os.O_WRONLY|os.O_CREATE, 0660)
  177. if err != nil {
  178. return err
  179. }
  180. defer fw.Close()
  181. zw, err := gzip.NewWriterLevel(fw, compressionLevel)
  182. if err != nil {
  183. return err
  184. }
  185. defer zw.Close()
  186. _, err = buffer.WriteTo(zw)
  187. if err != nil {
  188. zw.Close()
  189. fw.Close()
  190. os.Remove(fname + ".gz")
  191. return err
  192. }
  193. reader.Close()
  194. return os.Remove(fname)
  195. }
  196. func (log *FileLogger) deleteOldLog() {
  197. dir := filepath.Dir(log.Filename)
  198. _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
  199. defer func() {
  200. if r := recover(); r != nil {
  201. returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
  202. }
  203. }()
  204. if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*log.Maxdays) {
  205. if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) {
  206. if err := os.Remove(path); err != nil {
  207. returnErr = fmt.Errorf("Failed to remove %s: %v", path, err)
  208. }
  209. }
  210. }
  211. return returnErr
  212. })
  213. }
  214. // Flush flush file logger.
  215. // there are no buffering messages in file logger in memory.
  216. // flush file means sync file from disk.
  217. func (log *FileLogger) Flush() {
  218. _ = log.mw.fd.Sync()
  219. }
  220. // ReleaseReopen releases and reopens log files
  221. func (log *FileLogger) ReleaseReopen() error {
  222. closingErr := log.mw.fd.Close()
  223. startingErr := log.StartLogger()
  224. if startingErr != nil {
  225. if closingErr != nil {
  226. return fmt.Errorf("Error during closing: %v Error during starting: %v", closingErr, startingErr)
  227. }
  228. return startingErr
  229. }
  230. return closingErr
  231. }
  232. // GetName returns the default name for this implementation
  233. func (log *FileLogger) GetName() string {
  234. return "file"
  235. }
  236. func init() {
  237. Register("file", NewFileLogger)
  238. }