// 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 }