Beego/logs/file.go

410 lines
10 KiB
Go
Raw Normal View History

2014-08-18 08:41:43 +00:00
// Copyright 2014 beego Author. All Rights Reserved.
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2013-08-27 15:48:58 +00:00
package logs
import (
2014-11-21 10:12:39 +00:00
"bytes"
2013-08-27 15:48:58 +00:00
"encoding/json"
"errors"
"fmt"
2014-11-21 10:12:39 +00:00
"io"
2013-08-27 15:48:58 +00:00
"os"
2018-04-04 07:59:52 +00:00
"path"
2013-08-27 15:48:58 +00:00
"path/filepath"
"strconv"
2013-08-27 15:48:58 +00:00
"strings"
"sync"
"time"
)
2015-09-11 15:08:24 +00:00
// fileLogWriter implements LoggerInterface.
2013-12-30 15:32:57 +00:00
// It writes messages by lines limit, file size limit, or time frequency.
2015-09-11 15:08:24 +00:00
type fileLogWriter struct {
2016-03-16 10:04:07 +00:00
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
2013-08-27 15:48:58 +00:00
// The opened file
2016-01-13 01:21:55 +00:00
Filename string `json:"filename"`
fileWriter *os.File
2013-08-27 15:48:58 +00:00
2016-01-13 01:21:55 +00:00
// Rotate at line
2016-01-12 11:10:08 +00:00
MaxLines int `json:"maxlines"`
maxLinesCurLines int
2013-08-27 15:48:58 +00:00
MaxFiles int `json:"maxfiles"`
MaxFilesCurFiles int
2013-08-27 15:48:58 +00:00
// Rotate at size
2016-01-12 11:25:33 +00:00
MaxSize int `json:"maxsize"`
maxSizeCurSize int
2013-08-27 15:48:58 +00:00
// Rotate daily
2016-01-12 11:25:33 +00:00
Daily bool `json:"daily"`
MaxDays int64 `json:"maxdays"`
dailyOpenDate int
2016-04-12 07:05:35 +00:00
dailyOpenTime time.Time
2013-08-27 15:48:58 +00:00
2017-12-20 07:56:36 +00:00
// Rotate hourly
Hourly bool `json:"hourly"`
MaxHours int64 `json:"maxhours"`
hourlyOpenDate int
hourlyOpenTime time.Time
2016-01-12 11:25:33 +00:00
Rotate bool `json:"rotate"`
2013-08-27 15:48:58 +00:00
2016-01-12 11:25:33 +00:00
Level int `json:"level"`
2015-09-18 04:12:02 +00:00
Perm string `json:"perm"`
2016-02-03 08:32:59 +00:00
RotatePerm string `json:"rotateperm"`
2016-02-03 08:32:59 +00:00
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
2013-08-27 15:48:58 +00:00
}
2016-02-03 07:06:53 +00:00
// newFileWriter create a FileLogWriter returning as LoggerInterface.
2015-09-11 15:08:24 +00:00
func newFileWriter() Logger {
w := &fileLogWriter{
Daily: true,
MaxDays: 7,
2017-12-20 07:56:36 +00:00
Hourly: false,
MaxHours: 168,
Rotate: true,
RotatePerm: "0440",
Level: LevelTrace,
Perm: "0660",
MaxLines: 10000000,
MaxFiles: 999,
MaxSize: 1 << 28,
2013-08-27 15:48:58 +00:00
}
return w
}
2013-12-30 15:32:57 +00:00
// Init file logger with json config.
2016-01-12 11:10:08 +00:00
// jsonConfig like:
2017-12-20 07:56:36 +00:00
// {
// "filename":"logs/beego.log",
// "maxLines":10000,
// "maxsize":1024,
// "daily":true,
// "maxDays":15,
// "rotate":true,
// "perm":"0600"
// }
2016-01-12 11:10:08 +00:00
func (w *fileLogWriter) Init(jsonConfig string) error {
err := json.Unmarshal([]byte(jsonConfig), w)
2013-08-27 15:48:58 +00:00
if err != nil {
return err
}
2013-12-19 10:15:46 +00:00
if len(w.Filename) == 0 {
2013-08-27 15:48:58 +00:00
return errors.New("jsonconfig must have filename")
}
2016-02-03 08:32:59 +00:00
w.suffix = filepath.Ext(w.Filename)
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
if w.suffix == "" {
w.suffix = ".log"
}
2014-04-02 15:43:37 +00:00
err = w.startLogger()
2013-08-27 15:48:58 +00:00
return err
}
2013-12-30 15:32:57 +00:00
// start file logger. create log file and set to locker-inside file writer.
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) startLogger() error {
2016-01-13 00:21:44 +00:00
file, err := w.createLogFile()
2013-08-27 15:48:58 +00:00
if err != nil {
return err
}
2016-01-13 01:21:55 +00:00
if w.fileWriter != nil {
w.fileWriter.Close()
}
w.fileWriter = file
2015-02-23 03:42:46 +00:00
return w.initFd()
2013-08-27 15:48:58 +00:00
}
2017-12-20 08:19:58 +00:00
func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
2016-01-13 01:21:55 +00:00
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Daily && day != w.dailyOpenDate)
2017-12-20 07:56:36 +00:00
}
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Hourly && hour != w.hourlyOpenDate)
2016-01-13 01:21:55 +00:00
2013-08-27 15:48:58 +00:00
}
2015-09-11 15:08:24 +00:00
// WriteMsg write logger message into file.
2016-01-23 08:24:58 +00:00
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level {
2013-08-27 15:48:58 +00:00
return nil
}
2017-12-20 07:56:36 +00:00
hd, d, h := formatTimeHeader(when)
msg = string(hd) + msg + "\n"
2016-01-13 01:21:55 +00:00
if w.Rotate {
2016-03-16 10:04:07 +00:00
w.RLock()
2017-12-20 07:56:36 +00:00
if w.needRotateHourly(len(msg), h) {
w.RUnlock()
w.Lock()
if w.needRotateHourly(len(msg), h) {
if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else if w.needRotateDaily(len(msg), d) {
2016-03-16 10:04:07 +00:00
w.RUnlock()
2016-01-13 01:21:55 +00:00
w.Lock()
2017-12-20 07:56:36 +00:00
if w.needRotateDaily(len(msg), d) {
if err := w.doRotate(when); err != nil {
2016-01-13 01:21:55 +00:00
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
2016-03-16 10:04:07 +00:00
} else {
w.RUnlock()
2016-01-13 01:21:55 +00:00
}
}
w.Lock()
2016-02-03 07:03:37 +00:00
_, err := w.fileWriter.Write([]byte(msg))
2016-01-13 01:21:55 +00:00
if err == nil {
w.maxLinesCurLines++
w.maxSizeCurSize += len(msg)
}
w.Unlock()
2016-01-12 14:33:52 +00:00
return err
2013-08-27 15:48:58 +00:00
}
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) createLogFile() (*os.File, error) {
2013-08-27 15:48:58 +00:00
// Open the log file
perm, err := strconv.ParseInt(w.Perm, 8, 64)
if err != nil {
return nil, err
}
2018-04-04 07:59:52 +00:00
filepath := path.Dir(w.Filename)
os.MkdirAll(filepath, os.FileMode(perm))
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
2016-06-21 07:52:31 +00:00
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
os.Chmod(w.Filename, os.FileMode(perm))
2016-06-21 07:52:31 +00:00
}
2013-08-27 15:48:58 +00:00
return fd, err
}
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) initFd() error {
2016-01-13 00:21:44 +00:00
fd := w.fileWriter
2016-01-12 11:44:28 +00:00
fInfo, err := fd.Stat()
2013-08-27 15:48:58 +00:00
if err != nil {
2017-04-30 14:41:23 +00:00
return fmt.Errorf("get stat err: %s", err)
2013-08-27 15:48:58 +00:00
}
2016-01-12 11:44:28 +00:00
w.maxSizeCurSize = int(fInfo.Size())
2016-04-12 07:05:35 +00:00
w.dailyOpenTime = time.Now()
w.dailyOpenDate = w.dailyOpenTime.Day()
2017-12-20 07:56:36 +00:00
w.hourlyOpenTime = time.Now()
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
2016-01-12 11:10:08 +00:00
w.maxLinesCurLines = 0
2017-12-20 07:56:36 +00:00
if w.Hourly {
go w.hourlyRotate(w.hourlyOpenTime)
} else if w.Daily {
2016-05-06 04:11:14 +00:00
go w.dailyRotate(w.dailyOpenTime)
}
if fInfo.Size() > 0 && w.MaxLines > 0 {
2014-11-21 10:12:39 +00:00
count, err := w.lines()
2013-08-27 15:48:58 +00:00
if err != nil {
return err
}
2016-01-12 11:10:08 +00:00
w.maxLinesCurLines = count
2013-08-27 15:48:58 +00:00
}
return nil
}
2016-05-06 04:09:00 +00:00
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
y, m, d := openTime.Add(24 * time.Hour).Date()
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
2017-03-17 17:24:45 +00:00
<-tm.C
w.Lock()
2017-12-20 07:56:36 +00:00
if w.needRotateDaily(0, time.Now().Day()) {
if err := w.doRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
}
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
y, m, d := openTime.Add(1 * time.Hour).Date()
h, _, _ := openTime.Add(1 * time.Hour).Clock()
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
<-tm.C
w.Lock()
if w.needRotateHourly(0, time.Now().Hour()) {
2017-03-17 17:24:45 +00:00
if err := w.doRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
2016-05-06 04:09:00 +00:00
}
}
2017-03-17 17:24:45 +00:00
w.Unlock()
2016-05-06 04:09:00 +00:00
}
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) lines() (int, error) {
2014-11-21 10:12:39 +00:00
fd, err := os.Open(w.Filename)
if err != nil {
return 0, err
}
defer fd.Close()
buf := make([]byte, 32768) // 32k
count := 0
lineSep := []byte{'\n'}
for {
c, err := fd.Read(buf)
if err != nil && err != io.EOF {
return count, err
}
count += bytes.Count(buf[:c], lineSep)
if err == io.EOF {
break
}
}
return count, nil
}
2013-12-30 15:32:57 +00:00
// DoRotate means it need to write file in new file.
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *fileLogWriter) doRotate(logTime time.Time) error {
2016-01-13 01:24:27 +00:00
// file exists
// Find the next available number
num := w.MaxFilesCurFiles + 1
2016-01-13 01:24:27 +00:00
fName := ""
2017-12-20 07:56:36 +00:00
format := ""
var openTime time.Time
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
if err != nil {
return err
}
2016-04-12 07:05:35 +00:00
_, err = os.Lstat(w.Filename)
2016-04-12 07:05:35 +00:00
if err != nil {
//even if the file is not exist or other ,we should RESTART the logger
goto RESTART_LOGGER
}
2017-12-20 07:56:36 +00:00
if w.Hourly {
format = "2006010215"
openTime = w.hourlyOpenTime
} else if w.Daily {
format = "2006-01-02"
openTime = w.dailyOpenTime
}
// only when one of them be setted, then the file would be splited
if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= w.MaxFiles; num++ {
2018-07-26 06:34:25 +00:00
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
_, err = os.Lstat(fName)
}
} else {
2018-07-26 06:34:25 +00:00
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
2016-01-13 01:24:27 +00:00
_, err = os.Lstat(fName)
w.MaxFilesCurFiles = num
2016-01-13 01:24:27 +00:00
}
2018-07-26 06:34:25 +00:00
2016-01-13 01:24:27 +00:00
// return error if the last file checked still existed
2016-01-12 11:44:28 +00:00
if err == nil {
2017-04-28 14:36:28 +00:00
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
2016-01-13 01:24:27 +00:00
}
2013-08-27 15:48:58 +00:00
2016-01-13 01:24:27 +00:00
// close fileWriter before rename
w.fileWriter.Close()
2013-08-27 15:48:58 +00:00
2016-01-13 01:24:27 +00:00
// Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger
2016-04-12 07:05:35 +00:00
err = os.Rename(w.Filename, fName)
2017-04-28 14:36:28 +00:00
if err != nil {
goto RESTART_LOGGER
}
err = os.Chmod(fName, os.FileMode(rotatePerm))
2016-04-12 07:05:35 +00:00
RESTART_LOGGER:
2016-01-13 01:24:27 +00:00
startLoggerErr := w.startLogger()
go w.deleteOldLog()
2016-01-13 01:21:55 +00:00
2016-01-13 01:24:27 +00:00
if startLoggerErr != nil {
2017-04-28 14:36:28 +00:00
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
2016-01-13 01:24:27 +00:00
}
2016-04-12 07:05:35 +00:00
if err != nil {
2017-04-28 14:36:28 +00:00
return fmt.Errorf("Rotate: %s", err)
2013-08-27 15:48:58 +00:00
}
return nil
}
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
absolutePath, err := filepath.EvalSymlinks(w.Filename)
if err == nil {
dir = filepath.Dir(absolutePath)
}
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
2016-01-12 14:32:36 +00:00
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
}
}()
2016-04-13 01:05:16 +00:00
if info == nil {
return
}
if w.Hourly {
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
} else if w.Daily {
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
}
return
2013-08-27 15:48:58 +00:00
})
}
2016-01-12 11:10:08 +00:00
// Destroy close the file description, close file writer.
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) Destroy() {
2016-01-13 00:21:44 +00:00
w.fileWriter.Close()
2013-08-27 15:48:58 +00:00
}
2015-09-11 15:08:24 +00:00
// Flush flush file logger.
2013-12-30 15:32:57 +00:00
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
2015-09-11 15:08:24 +00:00
func (w *fileLogWriter) Flush() {
2016-01-13 00:21:44 +00:00
w.fileWriter.Sync()
2013-11-27 09:50:10 +00:00
}
2013-08-27 15:48:58 +00:00
func init() {
2016-03-24 09:38:26 +00:00
Register(AdapterFile, newFileWriter)
2013-08-27 15:48:58 +00:00
}