You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
6.9 KiB
264 lines
6.9 KiB
// Copyright (C) 2010, Kyle Lemons <[email protected]>. 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>") |
|
}
|
|
|