mirror of
https://github.com/42wim/matterbridge
synced 2024-11-05 12:00:27 +00:00
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>")
|
|
}
|