123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- // Copyright 2014 beego Author. All Rights Reserved.
- //
- // 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
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // 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.
- package logs
- import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "time"
- )
- // fileLogWriter implements LoggerInterface.
- // It writes messages by lines limit, file size limit, or time frequency.
- type fileLogWriter struct {
- sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
- // The opened file
- Filename string `json:"filename"`
- fileWriter *os.File
- // Rotate at line
- MaxLines int `json:"maxlines"`
- maxLinesCurLines int
- // Rotate at size
- MaxSize int `json:"maxsize"`
- maxSizeCurSize int
- // Rotate daily
- Daily bool `json:"daily"`
- MaxDays int64 `json:"maxdays"`
- dailyOpenDate int
- dailyOpenTime time.Time
- Rotate bool `json:"rotate"`
- Level int `json:"level"`
- Perm string `json:"perm"`
- fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
- }
- // newFileWriter create a FileLogWriter returning as LoggerInterface.
- func newFileWriter() Logger {
- w := &fileLogWriter{
- Daily: true,
- MaxDays: 7,
- Rotate: true,
- Level: LevelTrace,
- Perm: "0660",
- }
- return w
- }
- // Init file logger with json config.
- // jsonConfig like:
- // {
- // "filename":"logs/beego.log",
- // "maxLines":10000,
- // "maxsize":1024,
- // "daily":true,
- // "maxDays":15,
- // "rotate":true,
- // "perm":"0600"
- // }
- func (w *fileLogWriter) Init(jsonConfig string) error {
- err := json.Unmarshal([]byte(jsonConfig), w)
- if err != nil {
- return err
- }
- if len(w.Filename) == 0 {
- return errors.New("jsonconfig must have filename")
- }
- w.suffix = filepath.Ext(w.Filename)
- w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
- if w.suffix == "" {
- w.suffix = ".log"
- }
- err = w.startLogger()
- return err
- }
- // start file logger. create log file and set to locker-inside file writer.
- func (w *fileLogWriter) startLogger() error {
- file, err := w.createLogFile()
- if err != nil {
- return err
- }
- if w.fileWriter != nil {
- w.fileWriter.Close()
- }
- w.fileWriter = file
- return w.initFd()
- }
- func (w *fileLogWriter) needRotate(size int, day int) bool {
- return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
- (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
- (w.Daily && day != w.dailyOpenDate)
- }
- // WriteMsg write logger message into file.
- func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
- if level > w.Level {
- return nil
- }
- h, d := formatTimeHeader(when)
- msg = string(h) + msg + "\n"
- if w.Rotate {
- w.RLock()
- if w.needRotate(len(msg), d) {
- w.RUnlock()
- w.Lock()
- if w.needRotate(len(msg), d) {
- if err := w.doRotate(when); err != nil {
- fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
- }
- }
- w.Unlock()
- } else {
- w.RUnlock()
- }
- }
- w.Lock()
- _, err := w.fileWriter.Write([]byte(msg))
- if err == nil {
- w.maxLinesCurLines++
- w.maxSizeCurSize += len(msg)
- }
- w.Unlock()
- return err
- }
- func (w *fileLogWriter) createLogFile() (*os.File, error) {
- // Open the log file
- perm, err := strconv.ParseInt(w.Perm, 8, 64)
- if err != nil {
- return nil, err
- }
- fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
- 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))
- }
- return fd, err
- }
- func (w *fileLogWriter) initFd() error {
- fd := w.fileWriter
- fInfo, err := fd.Stat()
- if err != nil {
- return fmt.Errorf("get stat err: %s\n", err)
- }
- w.maxSizeCurSize = int(fInfo.Size())
- w.dailyOpenTime = time.Now()
- w.dailyOpenDate = w.dailyOpenTime.Day()
- w.maxLinesCurLines = 0
- if w.Daily {
- go w.dailyRotate(w.dailyOpenTime)
- }
- if fInfo.Size() > 0 {
- count, err := w.lines()
- if err != nil {
- return err
- }
- w.maxLinesCurLines = count
- }
- return nil
- }
- 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))
- select {
- case <-tm.C:
- w.Lock()
- if w.needRotate(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) lines() (int, error) {
- 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
- }
- // 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 {
- // file exists
- // Find the next available number
- num := 1
- fName := ""
- _, err := os.Lstat(w.Filename)
- if err != nil {
- //even if the file is not exist or other ,we should RESTART the logger
- goto RESTART_LOGGER
- }
- if w.MaxLines > 0 || w.MaxSize > 0 {
- for ; err == nil && num <= 999; num++ {
- fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
- _, err = os.Lstat(fName)
- }
- } else {
- fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
- _, err = os.Lstat(fName)
- for ; err == nil && num <= 999; num++ {
- fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
- _, err = os.Lstat(fName)
- }
- }
- // return error if the last file checked still existed
- if err == nil {
- return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
- }
- // close fileWriter before rename
- w.fileWriter.Close()
- // Rename the file to its new found name
- // even if occurs error,we MUST guarantee to restart new logger
- err = os.Rename(w.Filename, fName)
- err = os.Chmod(fName, os.FileMode(0440))
- // re-start logger
- RESTART_LOGGER:
- startLoggerErr := w.startLogger()
- go w.deleteOldLog()
- if startLoggerErr != nil {
- return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
- }
- if err != nil {
- return fmt.Errorf("Rotate: %s\n", err)
- }
- return nil
- }
- func (w *fileLogWriter) deleteOldLog() {
- dir := filepath.Dir(w.Filename)
- filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
- defer func() {
- if r := recover(); r != nil {
- fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
- }
- }()
- if info == nil {
- return
- }
- 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
- })
- }
- // Destroy close the file description, close file writer.
- func (w *fileLogWriter) Destroy() {
- w.fileWriter.Close()
- }
- // Flush flush file logger.
- // there are no buffering messages in file logger in memory.
- // flush file means sync file from disk.
- func (w *fileLogWriter) Flush() {
- w.fileWriter.Sync()
- }
- func init() {
- Register(AdapterFile, newFileWriter)
- }
|