hnakra/rotate/rotate.go
2023-05-25 18:08:56 -04:00

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
}