2014-07-26 00:24:27 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package log
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// FileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type FileLogWriter struct {
* log . Logger
mw * MuxWriter
// The opened file
Filename string ` json:"filename" `
2016-11-26 19:53:29 +08:00
Maxlines int ` json:"maxlines" `
maxlinesCurlines int
2014-07-26 00:24:27 -04:00
// Rotate at size
2016-11-26 19:53:29 +08:00
Maxsize int ` json:"maxsize" `
maxsizeCursize int
2014-07-26 00:24:27 -04:00
// Rotate daily
2016-11-26 19:53:29 +08:00
Daily bool ` json:"daily" `
Maxdays int64 ` json:"maxdays" `
dailyOpenDate int
2014-07-26 00:24:27 -04:00
Rotate bool ` json:"rotate" `
startLock sync . Mutex // Only one log can write to the file
Level int ` json:"level" `
}
2016-11-26 19:53:29 +08:00
// MuxWriter an *os.File writer with locker.
2014-07-26 00:24:27 -04:00
type MuxWriter struct {
sync . Mutex
fd * os . File
}
2016-11-26 19:53:29 +08:00
// Write writes to os.File.
2014-07-26 00:24:27 -04:00
func ( l * MuxWriter ) Write ( b [ ] byte ) ( int , error ) {
l . Lock ( )
defer l . Unlock ( )
return l . fd . Write ( b )
}
2016-11-26 19:53:29 +08:00
// SetFd sets os.File in writer.
2014-07-26 00:24:27 -04:00
func ( l * MuxWriter ) SetFd ( fd * os . File ) {
if l . fd != nil {
l . fd . Close ( )
}
l . fd = fd
}
2016-11-26 19:53:29 +08:00
// NewFileWriter create a FileLogWriter returning as LoggerInterface.
2014-07-26 00:24:27 -04:00
func NewFileWriter ( ) LoggerInterface {
w := & FileLogWriter {
Filename : "" ,
Maxlines : 1000000 ,
Maxsize : 1 << 28 , //256 MB
Daily : true ,
Maxdays : 7 ,
Rotate : true ,
Level : TRACE ,
}
// use MuxWriter instead direct use os.File for lock write when rotate
w . mw = new ( MuxWriter )
// set MuxWriter as Logger's io.Writer
w . Logger = log . New ( w . mw , "" , log . Ldate | log . Ltime )
return w
}
// Init file logger with json config.
// config like:
// {
// "filename":"log/gogs.log",
// "maxlines":10000,
// "maxsize":1<<30,
// "daily":true,
// "maxdays":15,
// "rotate":true
// }
func ( w * FileLogWriter ) Init ( config string ) error {
if err := json . Unmarshal ( [ ] byte ( config ) , w ) ; err != nil {
return err
}
if len ( w . Filename ) == 0 {
return errors . New ( "config must have filename" )
}
return w . StartLogger ( )
}
2016-11-26 19:53:29 +08:00
// StartLogger start file logger. create log file and set to locker-inside file writer.
2014-07-26 00:24:27 -04:00
func ( w * FileLogWriter ) StartLogger ( ) error {
fd , err := w . createLogFile ( )
if err != nil {
return err
}
w . mw . SetFd ( fd )
if err = w . initFd ( ) ; err != nil {
return err
}
return nil
}
func ( w * FileLogWriter ) docheck ( size int ) {
w . startLock . Lock ( )
defer w . startLock . Unlock ( )
2016-11-26 19:53:29 +08:00
if w . Rotate && ( ( w . Maxlines > 0 && w . maxlinesCurlines >= w . Maxlines ) ||
( w . Maxsize > 0 && w . maxsizeCursize >= w . Maxsize ) ||
( w . Daily && time . Now ( ) . Day ( ) != w . dailyOpenDate ) ) {
2014-07-26 00:24:27 -04:00
if err := w . DoRotate ( ) ; err != nil {
fmt . Fprintf ( os . Stderr , "FileLogWriter(%q): %s\n" , w . Filename , err )
return
}
}
2016-11-26 19:53:29 +08:00
w . maxlinesCurlines ++
w . maxsizeCursize += size
2014-07-26 00:24:27 -04:00
}
2016-11-26 19:53:29 +08:00
// WriteMsg writes logger message into file.
2014-07-26 00:24:27 -04:00
func ( w * FileLogWriter ) WriteMsg ( msg string , skip , level int ) error {
if level < w . Level {
return nil
}
n := 24 + len ( msg ) // 24 stand for the length "2013/06/23 21:00:22 [T] "
w . docheck ( n )
w . Logger . Println ( msg )
return nil
}
func ( w * FileLogWriter ) createLogFile ( ) ( * os . File , error ) {
// Open the log file
return os . OpenFile ( w . Filename , os . O_WRONLY | os . O_APPEND | os . O_CREATE , 0660 )
}
func ( w * FileLogWriter ) initFd ( ) error {
fd := w . mw . fd
finfo , err := fd . Stat ( )
if err != nil {
2016-11-26 19:53:29 +08:00
return fmt . Errorf ( "get stat: %s" , err )
2014-07-26 00:24:27 -04:00
}
2016-11-26 19:53:29 +08:00
w . maxsizeCursize = int ( finfo . Size ( ) )
w . dailyOpenDate = time . Now ( ) . Day ( )
2014-07-26 00:24:27 -04:00
if finfo . Size ( ) > 0 {
content , err := ioutil . ReadFile ( w . Filename )
if err != nil {
return err
}
2016-11-26 19:53:29 +08:00
w . maxlinesCurlines = len ( strings . Split ( string ( content ) , "\n" ) )
2014-07-26 00:24:27 -04:00
} else {
2016-11-26 19:53:29 +08:00
w . maxlinesCurlines = 0
2014-07-26 00:24:27 -04:00
}
return nil
}
// DoRotate means it need to write file in new file.
// new file name like xx.log.2013-01-01.2
func ( w * FileLogWriter ) DoRotate ( ) error {
_ , err := os . Lstat ( w . Filename )
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999 ; num ++ {
fname = w . Filename + fmt . Sprintf ( ".%s.%03d" , time . Now ( ) . Format ( "2006-01-02" ) , num )
_ , err = os . Lstat ( fname )
}
// return error if the last file checked still existed
if err == nil {
2016-11-26 19:53:29 +08:00
return fmt . Errorf ( "rotate: cannot find free log number to rename %s" , w . Filename )
2014-07-26 00:24:27 -04:00
}
// block Logger's io.Writer
w . mw . Lock ( )
defer w . mw . Unlock ( )
fd := w . mw . fd
fd . Close ( )
// close fd before rename
// Rename the file to its newfound home
if err = os . Rename ( w . Filename , fname ) ; err != nil {
2016-11-26 19:53:29 +08:00
return fmt . Errorf ( "Rotate: %s" , err )
2014-07-26 00:24:27 -04:00
}
// re-start logger
if err = w . StartLogger ( ) ; err != nil {
2016-11-26 19:53:29 +08:00
return fmt . Errorf ( "Rotate StartLogger: %s" , err )
2014-07-26 00:24:27 -04:00
}
go w . deleteOldLog ( )
}
return nil
}
func ( w * FileLogWriter ) deleteOldLog ( ) {
dir := filepath . Dir ( w . Filename )
2014-08-27 16:39:36 +08:00
filepath . Walk ( dir , func ( path string , info os . FileInfo , err error ) ( returnErr error ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
returnErr = fmt . Errorf ( "Unable to delete old log '%s', error: %+v" , path , r )
}
} ( )
2014-07-26 00:24:27 -04:00
if ! info . IsDir ( ) && info . ModTime ( ) . Unix ( ) < ( time . Now ( ) . Unix ( ) - 60 * 60 * 24 * w . Maxdays ) {
if strings . HasPrefix ( filepath . Base ( path ) , filepath . Base ( w . Filename ) ) {
2016-12-01 00:56:15 +01:00
if err := os . Remove ( path ) ; err != nil {
returnErr = fmt . Errorf ( "Fail to remove %s: %v" , path , err )
}
2014-07-26 00:24:27 -04:00
}
}
2014-08-27 16:39:36 +08:00
return returnErr
2014-07-26 00:24:27 -04:00
} )
}
2016-11-26 19:53:29 +08:00
// Destroy destroy file logger, close file writer.
2014-07-26 00:24:27 -04:00
func ( w * FileLogWriter ) Destroy ( ) {
w . mw . fd . Close ( )
}
2016-11-26 19:53:29 +08:00
// Flush flush file logger.
2014-07-26 00:24:27 -04:00
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func ( w * FileLogWriter ) Flush ( ) {
w . mw . fd . Sync ( )
}
func init ( ) {
Register ( "file" , NewFileWriter )
}