117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
|
// Package rotate provides an io.Writer that changes the file it writes to based
|
||
|
// on the current date. It always writes to a file inside of the directory it is
|
||
|
// given, and only switches files once it has finished writing a line and the
|
||
|
// date has changed. It is intended to be used with log.SetOutput().
|
||
|
package rotate
|
||
|
|
||
|
import "os"
|
||
|
import "io"
|
||
|
import "time"
|
||
|
import "errors"
|
||
|
import "path/filepath"
|
||
|
|
||
|
type logger struct {
|
||
|
directory string
|
||
|
file io.WriteCloser
|
||
|
previousDay int
|
||
|
closed bool
|
||
|
}
|
||
|
|
||
|
// New returns a new logger that will always log to directory/YYYY-MM-DD.log.
|
||
|
// When its close method is called, it will close any open file and stop
|
||
|
// accepting calls to Write. A closed logger may not be re-opened.
|
||
|
func New (directory string) (io.WriteCloser, error) {
|
||
|
logger := &logger { directory: directory }
|
||
|
err := logger.updateCurrentFile()
|
||
|
if err != nil { return nil, err }
|
||
|
return logger, nil
|
||
|
}
|
||
|
|
||
|
func (logger *logger) Write (buffer []byte) (n int, err error) {
|
||
|
if logger.closed {
|
||
|
return 0, errors.New("rotate: attempt to write to closed logger")
|
||
|
}
|
||
|
|
||
|
if logger.file == nil {
|
||
|
err = logger. updateCurrentFile()
|
||
|
if err != nil { return }
|
||
|
}
|
||
|
|
||
|
for buffer != nil {
|
||
|
var outgoing []byte
|
||
|
var justWrote int
|
||
|
outgoing, buffer = splitLine(buffer)
|
||
|
justWrote, err = logger.file.Write(outgoing)
|
||
|
n += justWrote
|
||
|
if err != nil { return }
|
||
|
|
||
|
err = logger.updateCurrentFile()
|
||
|
if err != nil { return }
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (logger *logger) Close () error {
|
||
|
return logger.file.Close()
|
||
|
}
|
||
|
|
||
|
func (logger *logger) updateCurrentFile() (err error) {
|
||
|
if logger.closed { return }
|
||
|
|
||
|
// yes this is not a correct measure of days since 1970 but all we need
|
||
|
// to know is "has the date changed?"
|
||
|
now := time.Now()
|
||
|
currentDay := (now.Year() - 1970) * 366 + now.YearDay()
|
||
|
|
||
|
if currentDay > logger.previousDay || logger.file == nil {
|
||
|
if logger.file != nil { logger.file.Close() }
|
||
|
|
||
|
logger.file, err = os.OpenFile(
|
||
|
filepath.Join(logger.directory, now.Format("2006-01-02.log")),
|
||
|
os.O_WRONLY | os.O_APPEND | os.O_CREATE,
|
||
|
0660)
|
||
|
if err != nil { return }
|
||
|
}
|
||
|
|
||
|
logger.previousDay = currentDay
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func splitLine (buffer []byte) (left, right []byte) {
|
||
|
index := indexOf(buffer, '\n')
|
||
|
if index < 1 { return buffer, nil }
|
||
|
index += 1
|
||
|
return buffer[:index], buffer[index:]
|
||
|
}
|
||
|
|
||
|
func indexOf[T comparable] (haystack []T, needle T) int {
|
||
|
for index, element := range haystack {
|
||
|
if element == needle {
|
||
|
return index
|
||
|
}
|
||
|
}
|
||
|
return -1
|
||
|
}
|
||
|
|
||
|
type lineWriter struct {
|
||
|
destination func (line []byte) (int, error)
|
||
|
}
|
||
|
|
||
|
func LineWriter (destination func (line []byte) (int, error)) io.Writer {
|
||
|
return &lineWriter { destination: destination }
|
||
|
}
|
||
|
|
||
|
func (writer lineWriter) Write (buffer []byte) (n int, err error) {
|
||
|
for buffer != nil {
|
||
|
var outgoing []byte
|
||
|
var justWrote int
|
||
|
outgoing, buffer = splitLine(buffer)
|
||
|
justWrote, err = writer.destination(outgoing)
|
||
|
n += justWrote
|
||
|
if err != nil { return }
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|