file.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. "strconv"
  24. "strings"
  25. "sync"
  26. "time"
  27. )
  28. // fileLogWriter implements LoggerInterface.
  29. // It writes messages by lines limit, file size limit, or time frequency.
  30. type fileLogWriter struct {
  31. sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
  32. // The opened file
  33. Filename string `json:"filename"`
  34. fileWriter *os.File
  35. // Rotate at line
  36. MaxLines int `json:"maxlines"`
  37. maxLinesCurLines int
  38. // Rotate at size
  39. MaxSize int `json:"maxsize"`
  40. maxSizeCurSize int
  41. // Rotate daily
  42. Daily bool `json:"daily"`
  43. MaxDays int64 `json:"maxdays"`
  44. dailyOpenDate int
  45. dailyOpenTime time.Time
  46. Rotate bool `json:"rotate"`
  47. Level int `json:"level"`
  48. Perm string `json:"perm"`
  49. fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
  50. }
  51. // newFileWriter create a FileLogWriter returning as LoggerInterface.
  52. func newFileWriter() Logger {
  53. w := &fileLogWriter{
  54. Daily: true,
  55. MaxDays: 7,
  56. Rotate: true,
  57. Level: LevelTrace,
  58. Perm: "0660",
  59. }
  60. return w
  61. }
  62. // Init file logger with json config.
  63. // jsonConfig like:
  64. // {
  65. // "filename":"logs/beego.log",
  66. // "maxLines":10000,
  67. // "maxsize":1024,
  68. // "daily":true,
  69. // "maxDays":15,
  70. // "rotate":true,
  71. // "perm":"0600"
  72. // }
  73. func (w *fileLogWriter) Init(jsonConfig string) error {
  74. err := json.Unmarshal([]byte(jsonConfig), w)
  75. if err != nil {
  76. return err
  77. }
  78. if len(w.Filename) == 0 {
  79. return errors.New("jsonconfig must have filename")
  80. }
  81. w.suffix = filepath.Ext(w.Filename)
  82. w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
  83. if w.suffix == "" {
  84. w.suffix = ".log"
  85. }
  86. err = w.startLogger()
  87. return err
  88. }
  89. // start file logger. create log file and set to locker-inside file writer.
  90. func (w *fileLogWriter) startLogger() error {
  91. file, err := w.createLogFile()
  92. if err != nil {
  93. return err
  94. }
  95. if w.fileWriter != nil {
  96. w.fileWriter.Close()
  97. }
  98. w.fileWriter = file
  99. return w.initFd()
  100. }
  101. func (w *fileLogWriter) needRotate(size int, day int) bool {
  102. return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
  103. (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
  104. (w.Daily && day != w.dailyOpenDate)
  105. }
  106. // WriteMsg write logger message into file.
  107. func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
  108. if level > w.Level {
  109. return nil
  110. }
  111. h, d := formatTimeHeader(when)
  112. msg = string(h) + msg + "\n"
  113. if w.Rotate {
  114. w.RLock()
  115. if w.needRotate(len(msg), d) {
  116. w.RUnlock()
  117. w.Lock()
  118. if w.needRotate(len(msg), d) {
  119. if err := w.doRotate(when); err != nil {
  120. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
  121. }
  122. }
  123. w.Unlock()
  124. } else {
  125. w.RUnlock()
  126. }
  127. }
  128. w.Lock()
  129. _, err := w.fileWriter.Write([]byte(msg))
  130. if err == nil {
  131. w.maxLinesCurLines++
  132. w.maxSizeCurSize += len(msg)
  133. }
  134. w.Unlock()
  135. return err
  136. }
  137. func (w *fileLogWriter) createLogFile() (*os.File, error) {
  138. // Open the log file
  139. perm, err := strconv.ParseInt(w.Perm, 8, 64)
  140. if err != nil {
  141. return nil, err
  142. }
  143. fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
  144. if err == nil {
  145. // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
  146. os.Chmod(w.Filename, os.FileMode(perm))
  147. }
  148. return fd, err
  149. }
  150. func (w *fileLogWriter) initFd() error {
  151. fd := w.fileWriter
  152. fInfo, err := fd.Stat()
  153. if err != nil {
  154. return fmt.Errorf("get stat err: %s\n", err)
  155. }
  156. w.maxSizeCurSize = int(fInfo.Size())
  157. w.dailyOpenTime = time.Now()
  158. w.dailyOpenDate = w.dailyOpenTime.Day()
  159. w.maxLinesCurLines = 0
  160. if w.Daily {
  161. go w.dailyRotate(w.dailyOpenTime)
  162. }
  163. if fInfo.Size() > 0 {
  164. count, err := w.lines()
  165. if err != nil {
  166. return err
  167. }
  168. w.maxLinesCurLines = count
  169. }
  170. return nil
  171. }
  172. func (w *fileLogWriter) dailyRotate(openTime time.Time) {
  173. y, m, d := openTime.Add(24 * time.Hour).Date()
  174. nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
  175. tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
  176. select {
  177. case <-tm.C:
  178. w.Lock()
  179. if w.needRotate(0, time.Now().Day()) {
  180. if err := w.doRotate(time.Now()); err != nil {
  181. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
  182. }
  183. }
  184. w.Unlock()
  185. }
  186. }
  187. func (w *fileLogWriter) lines() (int, error) {
  188. fd, err := os.Open(w.Filename)
  189. if err != nil {
  190. return 0, err
  191. }
  192. defer fd.Close()
  193. buf := make([]byte, 32768) // 32k
  194. count := 0
  195. lineSep := []byte{'\n'}
  196. for {
  197. c, err := fd.Read(buf)
  198. if err != nil && err != io.EOF {
  199. return count, err
  200. }
  201. count += bytes.Count(buf[:c], lineSep)
  202. if err == io.EOF {
  203. break
  204. }
  205. }
  206. return count, nil
  207. }
  208. // DoRotate means it need to write file in new file.
  209. // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
  210. func (w *fileLogWriter) doRotate(logTime time.Time) error {
  211. // file exists
  212. // Find the next available number
  213. num := 1
  214. fName := ""
  215. _, err := os.Lstat(w.Filename)
  216. if err != nil {
  217. //even if the file is not exist or other ,we should RESTART the logger
  218. goto RESTART_LOGGER
  219. }
  220. if w.MaxLines > 0 || w.MaxSize > 0 {
  221. for ; err == nil && num <= 999; num++ {
  222. fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
  223. _, err = os.Lstat(fName)
  224. }
  225. } else {
  226. fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
  227. _, err = os.Lstat(fName)
  228. for ; err == nil && num <= 999; num++ {
  229. fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
  230. _, err = os.Lstat(fName)
  231. }
  232. }
  233. // return error if the last file checked still existed
  234. if err == nil {
  235. return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
  236. }
  237. // close fileWriter before rename
  238. w.fileWriter.Close()
  239. // Rename the file to its new found name
  240. // even if occurs error,we MUST guarantee to restart new logger
  241. err = os.Rename(w.Filename, fName)
  242. err = os.Chmod(fName, os.FileMode(0440))
  243. // re-start logger
  244. RESTART_LOGGER:
  245. startLoggerErr := w.startLogger()
  246. go w.deleteOldLog()
  247. if startLoggerErr != nil {
  248. return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
  249. }
  250. if err != nil {
  251. return fmt.Errorf("Rotate: %s\n", err)
  252. }
  253. return nil
  254. }
  255. func (w *fileLogWriter) deleteOldLog() {
  256. dir := filepath.Dir(w.Filename)
  257. filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
  258. defer func() {
  259. if r := recover(); r != nil {
  260. fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
  261. }
  262. }()
  263. if info == nil {
  264. return
  265. }
  266. if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
  267. if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
  268. strings.HasSuffix(filepath.Base(path), w.suffix) {
  269. os.Remove(path)
  270. }
  271. }
  272. return
  273. })
  274. }
  275. // Destroy close the file description, close file writer.
  276. func (w *fileLogWriter) Destroy() {
  277. w.fileWriter.Close()
  278. }
  279. // Flush flush file logger.
  280. // there are no buffering messages in file logger in memory.
  281. // flush file means sync file from disk.
  282. func (w *fileLogWriter) Flush() {
  283. w.fileWriter.Sync()
  284. }
  285. func init() {
  286. Register(AdapterFile, newFileWriter)
  287. }