file.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // Copyright 2014 beego Author. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package logs
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "os"
  22. "path/filepath"
  23. "strings"
  24. "sync"
  25. "time"
  26. )
  27. // fileLogWriter implements LoggerInterface.
  28. // It writes messages by lines limit, file size limit, or time frequency.
  29. type fileLogWriter struct {
  30. sync.Mutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
  31. // The opened file
  32. Filename string `json:"filename"`
  33. fileWriter *os.File
  34. // Rotate at line
  35. MaxLines int `json:"maxlines"`
  36. maxLinesCurLines int
  37. // Rotate at size
  38. MaxSize int `json:"maxsize"`
  39. maxSizeCurSize int
  40. // Rotate daily
  41. Daily bool `json:"daily"`
  42. MaxDays int64 `json:"maxdays"`
  43. dailyOpenDate int
  44. Rotate bool `json:"rotate"`
  45. Level int `json:"level"`
  46. Perm os.FileMode `json:"perm"`
  47. fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
  48. }
  49. // newFileWriter create a FileLogWriter returning as LoggerInterface.
  50. func newFileWriter() Logger {
  51. w := &fileLogWriter{
  52. Filename: "",
  53. MaxLines: 1000000,
  54. MaxSize: 1 << 28, //256 MB
  55. Daily: true,
  56. MaxDays: 7,
  57. Rotate: true,
  58. Level: LevelTrace,
  59. Perm: 0660,
  60. }
  61. return w
  62. }
  63. // Init file logger with json config.
  64. // jsonConfig like:
  65. // {
  66. // "filename":"logs/beego.log",
  67. // "maxLines":10000,
  68. // "maxsize":1<<30,
  69. // "daily":true,
  70. // "maxDays":15,
  71. // "rotate":true,
  72. // "perm":0600
  73. // }
  74. func (w *fileLogWriter) Init(jsonConfig string) error {
  75. err := json.Unmarshal([]byte(jsonConfig), w)
  76. if err != nil {
  77. return err
  78. }
  79. if len(w.Filename) == 0 {
  80. return errors.New("jsonconfig must have filename")
  81. }
  82. w.suffix = filepath.Ext(w.Filename)
  83. w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
  84. if w.suffix == "" {
  85. w.suffix = ".log"
  86. }
  87. err = w.startLogger()
  88. return err
  89. }
  90. // start file logger. create log file and set to locker-inside file writer.
  91. func (w *fileLogWriter) startLogger() error {
  92. file, err := w.createLogFile()
  93. if err != nil {
  94. return err
  95. }
  96. if w.fileWriter != nil {
  97. w.fileWriter.Close()
  98. }
  99. w.fileWriter = file
  100. return w.initFd()
  101. }
  102. func (w *fileLogWriter) needRotate(size int, day int) bool {
  103. return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
  104. (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
  105. (w.Daily && day != w.dailyOpenDate)
  106. }
  107. // WriteMsg write logger message into file.
  108. func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
  109. if level > w.Level {
  110. return nil
  111. }
  112. h, d := formatTimeHeader(when)
  113. msg = string(h) + msg + "\n"
  114. if w.Rotate {
  115. if w.needRotate(len(msg), d) {
  116. w.Lock()
  117. if w.needRotate(len(msg), d) {
  118. if err := w.doRotate(when); err != nil {
  119. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
  120. }
  121. }
  122. w.Unlock()
  123. }
  124. }
  125. w.Lock()
  126. _, err := w.fileWriter.Write([]byte(msg))
  127. if err == nil {
  128. w.maxLinesCurLines++
  129. w.maxSizeCurSize += len(msg)
  130. }
  131. w.Unlock()
  132. return err
  133. }
  134. func (w *fileLogWriter) createLogFile() (*os.File, error) {
  135. // Open the log file
  136. fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, w.Perm)
  137. return fd, err
  138. }
  139. func (w *fileLogWriter) initFd() error {
  140. fd := w.fileWriter
  141. fInfo, err := fd.Stat()
  142. if err != nil {
  143. return fmt.Errorf("get stat err: %s\n", err)
  144. }
  145. w.maxSizeCurSize = int(fInfo.Size())
  146. w.dailyOpenDate = time.Now().Day()
  147. w.maxLinesCurLines = 0
  148. if fInfo.Size() > 0 {
  149. count, err := w.lines()
  150. if err != nil {
  151. return err
  152. }
  153. w.maxLinesCurLines = count
  154. }
  155. return nil
  156. }
  157. func (w *fileLogWriter) lines() (int, error) {
  158. fd, err := os.Open(w.Filename)
  159. if err != nil {
  160. return 0, err
  161. }
  162. defer fd.Close()
  163. buf := make([]byte, 32768) // 32k
  164. count := 0
  165. lineSep := []byte{'\n'}
  166. for {
  167. c, err := fd.Read(buf)
  168. if err != nil && err != io.EOF {
  169. return count, err
  170. }
  171. count += bytes.Count(buf[:c], lineSep)
  172. if err == io.EOF {
  173. break
  174. }
  175. }
  176. return count, nil
  177. }
  178. // DoRotate means it need to write file in new file.
  179. // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
  180. func (w *fileLogWriter) doRotate(logTime time.Time) error {
  181. _, err := os.Lstat(w.Filename)
  182. if err != nil {
  183. return err
  184. }
  185. // file exists
  186. // Find the next available number
  187. num := 1
  188. fName := ""
  189. if w.MaxLines > 0 || w.MaxSize > 0 {
  190. for ; err == nil && num <= 999; num++ {
  191. fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
  192. _, err = os.Lstat(fName)
  193. }
  194. } else {
  195. fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix)
  196. _, err = os.Lstat(fName)
  197. }
  198. // return error if the last file checked still existed
  199. if err == nil {
  200. return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
  201. }
  202. // close fileWriter before rename
  203. w.fileWriter.Close()
  204. // Rename the file to its new found name
  205. // even if occurs error,we MUST guarantee to restart new logger
  206. renameErr := os.Rename(w.Filename, fName)
  207. // re-start logger
  208. startLoggerErr := w.startLogger()
  209. go w.deleteOldLog()
  210. if startLoggerErr != nil {
  211. return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
  212. }
  213. if renameErr != nil {
  214. return fmt.Errorf("Rotate: %s\n", renameErr)
  215. }
  216. return nil
  217. }
  218. func (w *fileLogWriter) deleteOldLog() {
  219. dir := filepath.Dir(w.Filename)
  220. filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
  221. defer func() {
  222. if r := recover(); r != nil {
  223. fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
  224. }
  225. }()
  226. if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
  227. if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) &&
  228. strings.HasSuffix(filepath.Base(path), w.suffix) {
  229. os.Remove(path)
  230. }
  231. }
  232. return
  233. })
  234. }
  235. // Destroy close the file description, close file writer.
  236. func (w *fileLogWriter) Destroy() {
  237. w.fileWriter.Close()
  238. }
  239. // Flush flush file logger.
  240. // there are no buffering messages in file logger in memory.
  241. // flush file means sync file from disk.
  242. func (w *fileLogWriter) Flush() {
  243. w.fileWriter.Sync()
  244. }
  245. func init() {
  246. Register("file", newFileWriter)
  247. }