2014-07-26 08:24:27 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2014-07-26 08:24:27 +04:00
package log
import (
2019-04-02 10:48:31 +03:00
"bufio"
"compress/gzip"
2014-07-26 08:24:27 +04:00
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
2020-08-11 23:05:34 +03:00
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2014-07-26 08:24:27 +04:00
)
2019-04-02 10:48:31 +03:00
// FileLogger implements LoggerProvider.
2014-07-26 08:24:27 +04:00
// It writes messages by lines limit, file size limit, or time frequency.
2019-04-02 10:48:31 +03:00
type FileLogger struct {
2019-04-07 03:25:14 +03:00
WriterLogger
2014-07-26 08:24:27 +04:00
mw * MuxWriter
// The opened file
Filename string ` json:"filename" `
// Rotate at size
2016-11-26 14:53:29 +03:00
Maxsize int ` json:"maxsize" `
maxsizeCursize int
2014-07-26 08:24:27 +04:00
// Rotate daily
2016-11-26 14:53:29 +03:00
Daily bool ` json:"daily" `
Maxdays int64 ` json:"maxdays" `
dailyOpenDate int
2014-07-26 08:24:27 +04:00
Rotate bool ` json:"rotate" `
2019-04-02 10:48:31 +03:00
Compress bool ` json:"compress" `
CompressionLevel int ` json:"compressionLevel" `
2014-07-26 08:24:27 +04:00
2019-04-02 10:48:31 +03:00
startLock sync . Mutex // Only one log can write to the file
2014-07-26 08:24:27 +04:00
}
2016-11-26 14:53:29 +03:00
// MuxWriter an *os.File writer with locker.
2014-07-26 08:24:27 +04:00
type MuxWriter struct {
2019-04-02 10:48:31 +03:00
mu sync . Mutex
fd * os . File
owner * FileLogger
2014-07-26 08:24:27 +04:00
}
2016-11-26 14:53:29 +03:00
// Write writes to os.File.
2019-04-02 10:48:31 +03:00
func ( mw * MuxWriter ) Write ( b [ ] byte ) ( int , error ) {
mw . mu . Lock ( )
defer mw . mu . Unlock ( )
mw . owner . docheck ( len ( b ) )
return mw . fd . Write ( b )
}
// Close the internal writer
func ( mw * MuxWriter ) Close ( ) error {
return mw . fd . Close ( )
2014-07-26 08:24:27 +04:00
}
2016-11-26 14:53:29 +03:00
// SetFd sets os.File in writer.
2019-04-02 10:48:31 +03:00
func ( mw * MuxWriter ) SetFd ( fd * os . File ) {
if mw . fd != nil {
mw . fd . Close ( )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
mw . fd = fd
}
// NewFileLogger create a FileLogger returning as LoggerProvider.
func NewFileLogger ( ) LoggerProvider {
log := & FileLogger {
Filename : "" ,
2022-01-20 20:46:10 +03:00
Maxsize : 1 << 28 , // 256 MB
2019-04-02 10:48:31 +03:00
Daily : true ,
Maxdays : 7 ,
Rotate : true ,
Compress : true ,
CompressionLevel : gzip . DefaultCompression ,
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
log . Level = TRACE
2014-07-26 08:24:27 +04:00
// use MuxWriter instead direct use os.File for lock write when rotate
2019-04-02 10:48:31 +03:00
log . mw = new ( MuxWriter )
log . mw . owner = log
return log
2014-07-26 08:24:27 +04:00
}
// Init file logger with json config.
// config like:
2022-08-31 05:15:45 +03:00
//
2014-07-26 08:24:27 +04:00
// {
// "filename":"log/gogs.log",
// "maxsize":1<<30,
// "daily":true,
// "maxdays":15,
// "rotate":true
// }
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) Init ( config string ) error {
if err := json . Unmarshal ( [ ] byte ( config ) , log ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "Unable to parse JSON: %w" , err )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
if len ( log . Filename ) == 0 {
2014-07-26 08:24:27 +04:00
return errors . New ( "config must have filename" )
}
2019-04-02 10:48:31 +03:00
// set MuxWriter as Logger's io.Writer
2019-04-07 03:25:14 +03:00
log . NewWriterLogger ( log . mw )
2019-04-02 10:48:31 +03:00
return log . StartLogger ( )
2014-07-26 08:24:27 +04:00
}
2016-11-26 14:53:29 +03:00
// StartLogger start file logger. create log file and set to locker-inside file writer.
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) StartLogger ( ) error {
fd , err := log . createLogFile ( )
2014-07-26 08:24:27 +04:00
if err != nil {
return err
}
2019-04-02 10:48:31 +03:00
log . mw . SetFd ( fd )
return log . initFd ( )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) docheck ( size int ) {
log . startLock . Lock ( )
defer log . startLock . Unlock ( )
if log . Rotate && ( ( log . Maxsize > 0 && log . maxsizeCursize >= log . Maxsize ) ||
( log . Daily && time . Now ( ) . Day ( ) != log . dailyOpenDate ) ) {
if err := log . DoRotate ( ) ; err != nil {
fmt . Fprintf ( os . Stderr , "FileLogger(%q): %s\n" , log . Filename , err )
2014-07-26 08:24:27 +04:00
return
}
}
2019-04-02 10:48:31 +03:00
log . maxsizeCursize += size
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) createLogFile ( ) ( * os . File , error ) {
2014-07-26 08:24:27 +04:00
// Open the log file
2022-01-20 20:46:10 +03:00
return os . OpenFile ( log . Filename , os . O_WRONLY | os . O_APPEND | os . O_CREATE , 0 o660 )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) initFd ( ) error {
fd := log . mw . fd
2014-07-26 08:24:27 +04:00
finfo , err := fd . Stat ( )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "get stat: %w" , err )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
log . maxsizeCursize = int ( finfo . Size ( ) )
log . dailyOpenDate = time . Now ( ) . Day ( )
2014-07-26 08: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
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) DoRotate ( ) error {
_ , err := os . Lstat ( log . Filename )
2014-07-26 08:24:27 +04:00
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999 ; num ++ {
2019-04-02 10:48:31 +03:00
fname = log . Filename + fmt . Sprintf ( ".%s.%03d" , time . Now ( ) . Format ( "2006-01-02" ) , num )
2014-07-26 08:24:27 +04:00
_ , err = os . Lstat ( fname )
2019-04-02 10:48:31 +03:00
if log . Compress && err != nil {
_ , err = os . Lstat ( fname + ".gz" )
}
2014-07-26 08:24:27 +04:00
}
// return error if the last file checked still existed
if err == nil {
2019-04-02 10:48:31 +03:00
return fmt . Errorf ( "rotate: cannot find free log number to rename %s" , log . Filename )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
fd := log . mw . fd
2014-07-26 08:24:27 +04:00
fd . Close ( )
// close fd before rename
// Rename the file to its newfound home
2021-07-15 18:46:07 +03:00
if err = util . Rename ( log . Filename , fname ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "Rotate: %w" , err )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
if log . Compress {
go compressOldLogFile ( fname , log . CompressionLevel )
}
2014-07-26 08:24:27 +04:00
// re-start logger
2019-04-02 10:48:31 +03:00
if err = log . StartLogger ( ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "Rotate StartLogger: %w" , err )
2014-07-26 08:24:27 +04:00
}
2019-04-02 10:48:31 +03:00
go log . deleteOldLog ( )
2014-07-26 08:24:27 +04:00
}
return nil
}
2019-04-02 10:48:31 +03:00
func compressOldLogFile ( fname string , compressionLevel int ) error {
reader , err := os . Open ( fname )
if err != nil {
return err
}
defer reader . Close ( )
buffer := bufio . NewReader ( reader )
2022-01-20 20:46:10 +03:00
fw , err := os . OpenFile ( fname + ".gz" , os . O_WRONLY | os . O_CREATE , 0 o660 )
2019-04-02 10:48:31 +03:00
if err != nil {
return err
}
defer fw . Close ( )
zw , err := gzip . NewWriterLevel ( fw , compressionLevel )
if err != nil {
return err
}
defer zw . Close ( )
_ , err = buffer . WriteTo ( zw )
if err != nil {
zw . Close ( )
fw . Close ( )
2020-08-11 23:05:34 +03:00
util . Remove ( fname + ".gz" )
2019-04-02 10:48:31 +03:00
return err
}
reader . Close ( )
2020-08-11 23:05:34 +03:00
return util . Remove ( fname )
2019-04-02 10:48:31 +03:00
}
func ( log * FileLogger ) deleteOldLog ( ) {
dir := filepath . Dir ( log . Filename )
2023-01-16 19:21:44 +03:00
_ = filepath . WalkDir ( dir , func ( path string , d os . DirEntry , err error ) ( returnErr error ) {
2014-08-27 12:39:36 +04:00
defer func ( ) {
if r := recover ( ) ; r != nil {
returnErr = fmt . Errorf ( "Unable to delete old log '%s', error: %+v" , path , r )
}
} ( )
2023-01-16 19:21:44 +03:00
if err != nil {
return err
}
if d . IsDir ( ) {
return nil
}
info , err := d . Info ( )
if err != nil {
return err
}
if info . ModTime ( ) . Unix ( ) < ( time . Now ( ) . Unix ( ) - 60 * 60 * 24 * log . Maxdays ) {
2019-04-02 10:48:31 +03:00
if strings . HasPrefix ( filepath . Base ( path ) , filepath . Base ( log . Filename ) ) {
2020-08-11 23:05:34 +03:00
if err := util . Remove ( path ) ; err != nil {
2022-10-24 22:29:17 +03:00
returnErr = fmt . Errorf ( "Failed to remove %s: %w" , path , err )
2016-12-01 02:56:15 +03:00
}
2014-07-26 08:24:27 +04:00
}
}
2014-08-27 12:39:36 +04:00
return returnErr
2014-07-26 08:24:27 +04:00
} )
}
2022-02-25 12:20:50 +03:00
// Content returns the content accumulated in the content provider
func ( log * FileLogger ) Content ( ) ( string , error ) {
b , err := os . ReadFile ( log . Filename )
if err != nil {
return "" , err
}
return string ( b ) , nil
}
2016-11-26 14:53:29 +03:00
// Flush flush file logger.
2014-07-26 08:24:27 +04:00
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
2019-04-02 10:48:31 +03:00
func ( log * FileLogger ) Flush ( ) {
2019-06-12 22:41:28 +03:00
_ = log . mw . fd . Sync ( )
2019-04-02 10:48:31 +03:00
}
2020-07-06 03:07:07 +03:00
// ReleaseReopen releases and reopens log files
func ( log * FileLogger ) ReleaseReopen ( ) error {
closingErr := log . mw . fd . Close ( )
startingErr := log . StartLogger ( )
if startingErr != nil {
if closingErr != nil {
return fmt . Errorf ( "Error during closing: %v Error during starting: %v" , closingErr , startingErr )
}
return startingErr
}
return closingErr
}
2019-04-02 10:48:31 +03:00
// GetName returns the default name for this implementation
func ( log * FileLogger ) GetName ( ) string {
return "file"
2014-07-26 08:24:27 +04:00
}
func init ( ) {
2019-04-02 10:48:31 +03:00
Register ( "file" , NewFileLogger )
2014-07-26 08:24:27 +04:00
}