高热共公日志库
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.

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