Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

407 Zeilen
9.9 KiB

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