forked from lug/matterbridge
		
	
		
			
				
	
	
		
			265 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.
 | |
| 
 | |
| package log4go
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // This log writer sends output to a file
 | |
| type FileLogWriter struct {
 | |
| 	rec chan *LogRecord
 | |
| 	rot chan bool
 | |
| 
 | |
| 	// The opened file
 | |
| 	filename string
 | |
| 	file     *os.File
 | |
| 
 | |
| 	// The logging format
 | |
| 	format string
 | |
| 
 | |
| 	// File header/trailer
 | |
| 	header, trailer string
 | |
| 
 | |
| 	// Rotate at linecount
 | |
| 	maxlines          int
 | |
| 	maxlines_curlines int
 | |
| 
 | |
| 	// Rotate at size
 | |
| 	maxsize         int
 | |
| 	maxsize_cursize int
 | |
| 
 | |
| 	// Rotate daily
 | |
| 	daily          bool
 | |
| 	daily_opendate int
 | |
| 
 | |
| 	// Keep old logfiles (.001, .002, etc)
 | |
| 	rotate    bool
 | |
| 	maxbackup int
 | |
| }
 | |
| 
 | |
| // This is the FileLogWriter's output method
 | |
| func (w *FileLogWriter) LogWrite(rec *LogRecord) {
 | |
| 	w.rec <- rec
 | |
| }
 | |
| 
 | |
| func (w *FileLogWriter) Close() {
 | |
| 	close(w.rec)
 | |
| 	w.file.Sync()
 | |
| }
 | |
| 
 | |
| // NewFileLogWriter creates a new LogWriter which writes to the given file and
 | |
| // has rotation enabled if rotate is true.
 | |
| //
 | |
| // If rotate is true, any time a new log file is opened, the old one is renamed
 | |
| // with a .### extension to preserve it.  The various Set* methods can be used
 | |
| // to configure log rotation based on lines, size, and daily.
 | |
| //
 | |
| // The standard log-line format is:
 | |
| //   [%D %T] [%L] (%S) %M
 | |
| func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
 | |
| 	w := &FileLogWriter{
 | |
| 		rec:       make(chan *LogRecord, LogBufferLength),
 | |
| 		rot:       make(chan bool),
 | |
| 		filename:  fname,
 | |
| 		format:    "[%D %T] [%L] (%S) %M",
 | |
| 		rotate:    rotate,
 | |
| 		maxbackup: 999,
 | |
| 	}
 | |
| 
 | |
| 	// open the file for the first time
 | |
| 	if err := w.intRotate(); err != nil {
 | |
| 		fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		defer func() {
 | |
| 			if w.file != nil {
 | |
| 				fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
 | |
| 				w.file.Close()
 | |
| 			}
 | |
| 		}()
 | |
| 
 | |
| 		for {
 | |
| 			select {
 | |
| 			case <-w.rot:
 | |
| 				if err := w.intRotate(); err != nil {
 | |
| 					fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
 | |
| 					return
 | |
| 				}
 | |
| 			case rec, ok := <-w.rec:
 | |
| 				if !ok {
 | |
| 					return
 | |
| 				}
 | |
| 				now := time.Now()
 | |
| 				if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
 | |
| 					(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
 | |
| 					(w.daily && now.Day() != w.daily_opendate) {
 | |
| 					if err := w.intRotate(); err != nil {
 | |
| 						fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
 | |
| 						return
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// Perform the write
 | |
| 				n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec))
 | |
| 				if err != nil {
 | |
| 					fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
 | |
| 					return
 | |
| 				}
 | |
| 
 | |
| 				// Update the counts
 | |
| 				w.maxlines_curlines++
 | |
| 				w.maxsize_cursize += n
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Request that the logs rotate
 | |
| func (w *FileLogWriter) Rotate() {
 | |
| 	w.rot <- true
 | |
| }
 | |
| 
 | |
| // If this is called in a threaded context, it MUST be synchronized
 | |
| func (w *FileLogWriter) intRotate() error {
 | |
| 	// Close any log file that may be open
 | |
| 	if w.file != nil {
 | |
| 		fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
 | |
| 		w.file.Close()
 | |
| 	}
 | |
| 
 | |
| 	// If we are keeping log files, move it to the next available number
 | |
| 	if w.rotate {
 | |
| 		_, err := os.Lstat(w.filename)
 | |
| 		if err == nil { // file exists
 | |
| 			// Find the next available number
 | |
| 			num := 1
 | |
| 			fname := ""
 | |
| 			if w.daily && time.Now().Day() != w.daily_opendate {
 | |
| 				yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
 | |
| 
 | |
| 				for ; err == nil && num <= 999; num++ {
 | |
| 					fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num)
 | |
| 					_, 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)
 | |
| 				}
 | |
| 			} else {
 | |
| 				num = w.maxbackup - 1
 | |
| 				for ; num >= 1; num-- {
 | |
| 					fname = w.filename + fmt.Sprintf(".%d", num)
 | |
| 					nfname := w.filename + fmt.Sprintf(".%d", num+1)
 | |
| 					_, err = os.Lstat(fname)
 | |
| 					if err == nil {
 | |
| 						os.Rename(fname, nfname)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			w.file.Close()
 | |
| 			// Rename the file to its newfound home
 | |
| 			err = os.Rename(w.filename, fname)
 | |
| 			if err != nil {
 | |
| 				return fmt.Errorf("Rotate: %s\n", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Open the log file
 | |
| 	fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	w.file = fd
 | |
| 
 | |
| 	now := time.Now()
 | |
| 	fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now}))
 | |
| 
 | |
| 	// Set the daily open date to the current date
 | |
| 	w.daily_opendate = now.Day()
 | |
| 
 | |
| 	// initialize rotation values
 | |
| 	w.maxlines_curlines = 0
 | |
| 	w.maxsize_cursize = 0
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Set the logging format (chainable).  Must be called before the first log
 | |
| // message is written.
 | |
| func (w *FileLogWriter) SetFormat(format string) *FileLogWriter {
 | |
| 	w.format = format
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Set the logfile header and footer (chainable).  Must be called before the first log
 | |
| // message is written.  These are formatted similar to the FormatLogRecord (e.g.
 | |
| // you can use %D and %T in your header/footer for date and time).
 | |
| func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter {
 | |
| 	w.header, w.trailer = head, foot
 | |
| 	if w.maxlines_curlines == 0 {
 | |
| 		fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()}))
 | |
| 	}
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Set rotate at linecount (chainable). Must be called before the first log
 | |
| // message is written.
 | |
| func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
 | |
| 	//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines)
 | |
| 	w.maxlines = maxlines
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Set rotate at size (chainable). Must be called before the first log message
 | |
| // is written.
 | |
| func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
 | |
| 	//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize)
 | |
| 	w.maxsize = maxsize
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Set rotate daily (chainable). Must be called before the first log message is
 | |
| // written.
 | |
| func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
 | |
| 	//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily)
 | |
| 	w.daily = daily
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // Set max backup files. Must be called before the first log message
 | |
| // is written.
 | |
| func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter {
 | |
| 	w.maxbackup = maxbackup
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // SetRotate changes whether or not the old logs are kept. (chainable) Must be
 | |
| // called before the first log message is written.  If rotate is false, the
 | |
| // files are overwritten; otherwise, they are rotated to another file before the
 | |
| // new log is opened.
 | |
| func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter {
 | |
| 	//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate)
 | |
| 	w.rotate = rotate
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| // NewXMLLogWriter is a utility method for creating a FileLogWriter set up to
 | |
| // output XML record log messages instead of line-based ones.
 | |
| func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter {
 | |
| 	return NewFileLogWriter(fname, rotate).SetFormat(
 | |
| 		`	<record level="%L">
 | |
| 		<timestamp>%D %T</timestamp>
 | |
| 		<source>%S</source>
 | |
| 		<message>%M</message>
 | |
| 	</record>`).SetHeadFoot("<log created=\"%D %T\">", "</log>")
 | |
| }
 | 
