123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- // 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 <= w.maxbackup; 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>")
- }
|