| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 | // Copyright 2017 Unknwon//// 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 clogimport (	"bytes"	"fmt"	"io"	"io/ioutil"	"log"	"os"	"path/filepath"	"strings"	"time")const (	SIMPLE_DATE_FORMAT = "2006-01-02"	LOG_PREFIX_LENGTH  = len("2017/02/06 21:20:08 "))// FileRotationConfig represents rotation related configurations for file mode logger.// All the settings can take effect at the same time, remain zero values to disable them.type FileRotationConfig struct {	// Do rotation for output files.	Rotate bool	// Rotate on daily basis.	Daily bool	// Maximum size in bytes of file for a rotation.	MaxSize int64	// Maximum number of lines for a rotation.	MaxLines int64	// Maximum lifetime of a output file in days.	MaxDays int64}type FileConfig struct {	// Minimum level of messages to be processed.	Level LEVEL	// Buffer size defines how many messages can be queued before hangs.	BufferSize int64	// File name to outout messages.	Filename string	// Rotation related configurations.	FileRotationConfig}type file struct {	// Indicates whether object is been used in standalone mode.	standalone bool	*log.Logger	Adapter	file         *os.File	filename     string	openDay      int	currentSize  int64	currentLines int64	rotate       FileRotationConfig}func newFile() Logger {	return &file{		Adapter: Adapter{			quitChan: make(chan struct{}),		},	}}// NewFileWriter returns an io.Writer for synchronized file logger in standalone mode.func NewFileWriter(filename string, cfg FileRotationConfig) (io.Writer, error) {	f := &file{		standalone: true,	}	if err := f.Init(FileConfig{		Filename:           filename,		FileRotationConfig: cfg,	}); err != nil {		return nil, err	}	return f, nil}func (f *file) Level() LEVEL { return f.level }var newLineBytes = []byte("\n")func (f *file) initFile() (err error) {	f.file, err = os.OpenFile(f.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)	if err != nil {		return fmt.Errorf("OpenFile '%s': %v", f.filename, err)	}	f.Logger = log.New(f.file, "", log.Ldate|log.Ltime)	return nil}// isExist checks whether a file or directory exists.// It returns false when the file or directory does not exist.func isExist(path string) bool {	_, err := os.Stat(path)	return err == nil || os.IsExist(err)}// rotateFilename returns next available rotate filename with given date.func (f *file) rotateFilename(date string) string {	filename := fmt.Sprintf("%s.%s", f.filename, date)	if !isExist(filename) {		return filename	}	format := filename + ".%03d"	for i := 1; i < 1000; i++ {		filename := fmt.Sprintf(format, i)		if !isExist(filename) {			return filename		}	}	panic("too many log files for yesterday")}func (f *file) deleteOutdatedFiles() {	filepath.Walk(filepath.Dir(f.filename), func(path string, info os.FileInfo, err error) error {		if !info.IsDir() &&			info.ModTime().Before(time.Now().Add(-24*time.Hour*time.Duration(f.rotate.MaxDays))) &&			strings.HasPrefix(filepath.Base(path), filepath.Base(f.filename)) {			os.Remove(path)		}		return nil	})}func (f *file) initRotate() error {	// Gather basic file info for rotation.	fi, err := f.file.Stat()	if err != nil {		return fmt.Errorf("Stat: %v", err)	}	f.currentSize = fi.Size()	// If there is any content in the file, count the number of lines.	if f.rotate.MaxLines > 0 && f.currentSize > 0 {		data, err := ioutil.ReadFile(f.filename)		if err != nil {			return fmt.Errorf("ReadFile '%s': %v", f.filename, err)		}		f.currentLines = int64(bytes.Count(data, newLineBytes)) + 1	}	if f.rotate.Daily {		now := time.Now()		f.openDay = now.Day()		lastWriteTime := fi.ModTime()		if lastWriteTime.Year() != now.Year() ||			lastWriteTime.Month() != now.Month() ||			lastWriteTime.Day() != now.Day() {			if err = f.file.Close(); err != nil {				return fmt.Errorf("Close: %v", err)			}			if err = os.Rename(f.filename, f.rotateFilename(lastWriteTime.Format(SIMPLE_DATE_FORMAT))); err != nil {				return fmt.Errorf("Rename: %v", err)			}			if err = f.initFile(); err != nil {				return fmt.Errorf("initFile: %v", err)			}		}	}	if f.rotate.MaxDays > 0 {		f.deleteOutdatedFiles()	}	return nil}func (f *file) Init(v interface{}) (err error) {	cfg, ok := v.(FileConfig)	if !ok {		return ErrConfigObject{"FileConfig", v}	}	if !isValidLevel(cfg.Level) {		return ErrInvalidLevel{}	}	f.level = cfg.Level	f.filename = cfg.Filename	os.MkdirAll(filepath.Dir(f.filename), os.ModePerm)	if err = f.initFile(); err != nil {		return fmt.Errorf("initFile: %v", err)	}	f.rotate = cfg.FileRotationConfig	if f.rotate.Rotate {		f.initRotate()	}	if !f.standalone {		f.msgChan = make(chan *Message, cfg.BufferSize)	}	return nil}func (f *file) ExchangeChans(errorChan chan<- error) chan *Message {	f.errorChan = errorChan	return f.msgChan}func (f *file) write(msg *Message) int {	f.Logger.Print(msg.Body)	bytesWrote := len(msg.Body)	if !f.standalone {		bytesWrote += LOG_PREFIX_LENGTH	}	if f.rotate.Rotate {		f.currentSize += int64(bytesWrote)		f.currentLines++ // TODO: should I care if log message itself contains new lines?		var (			needsRotate = false			rotateDate  time.Time		)		now := time.Now()		if f.rotate.Daily && now.Day() != f.openDay {			needsRotate = true			rotateDate = now.Add(-24 * time.Hour)		} else if (f.rotate.MaxSize > 0 && f.currentSize >= f.rotate.MaxSize) ||			(f.rotate.MaxLines > 0 && f.currentLines >= f.rotate.MaxLines) {			needsRotate = true			rotateDate = now		}		if needsRotate {			f.file.Close()			if err := os.Rename(f.filename, f.rotateFilename(rotateDate.Format(SIMPLE_DATE_FORMAT))); err != nil {				f.errorChan <- fmt.Errorf("fail to rename rotate file '%s': %v", f.filename, err)			}			if err := f.initFile(); err != nil {				f.errorChan <- fmt.Errorf("fail to init log file '%s': %v", f.filename, err)			}			f.openDay = now.Day()			f.currentSize = 0			f.currentLines = 0		}	}	return bytesWrote}var _ io.Writer = new(file)// Write implements method of io.Writer interface.func (f *file) Write(p []byte) (int, error) {	return f.write(&Message{		Body: string(p),	}), nil}func (f *file) Start() {LOOP:	for {		select {		case msg := <-f.msgChan:			f.write(msg)		case <-f.quitChan:			break LOOP		}	}	for {		if len(f.msgChan) == 0 {			break		}		f.write(<-f.msgChan)	}	f.quitChan <- struct{}{} // Notify the cleanup is done.}func (f *file) Destroy() {	f.quitChan <- struct{}{}	<-f.quitChan	close(f.msgChan)	close(f.quitChan)	f.file.Close()}func init() {	Register(FILE, newFile)}
 |