package logging import ( "fmt" "io" "os" "path/filepath" ) // Backend is the interface that specifies the methods that a backend must // implement. type Backend interface { Write(*Record) error SetFormatter(Formatter) SetLevel(Level) Level() Level Reopen() error Close() error } // // Backend to write in file-like objects // var _ Backend = &FileBackend{} // FileBackend is a backend that writes to a file. type FileBackend struct { formatter Formatter l io.Writer filepath string level Level } // NewStdoutBackend creates a new backend to write the logs on the standard // output. func NewStdoutBackend() *FileBackend { return &FileBackend{ l: os.Stdout, formatter: defaultFormatter, } } // NewStderrBackend creates a new backend to write the logs on the error output. func NewStderrBackend() *FileBackend { return &FileBackend{ l: os.Stderr, formatter: defaultFormatter, } } // NewFileBackend creates a new backend to write the logs in a given file. func NewFileBackend(filename string) (*FileBackend, error) { fd, err := os.OpenFile(filepath.Clean(filename), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600) if err != nil { return nil, fmt.Errorf("cannot open log file %s: %w", filename, err) } b := &FileBackend{ l: fd, formatter: defaultFormatter, filepath: filename, } return b, nil } // NewIoBackend creates a new backend to write the logs in a given io.Writer. func NewIoBackend(buf io.Writer) *FileBackend { return &FileBackend{ l: buf, formatter: defaultFormatter, } } func (b FileBackend) Write(r *Record) error { text := b.formatter(r) _, err := io.WriteString(b.l, text) return fmt.Errorf("cannot write logs: %w", err) } // SetLevel changes the log level of the backend. func (b *FileBackend) SetLevel(l Level) { b.level = l } // Level returns the log level set for this backend. func (b *FileBackend) Level() Level { return b.level } // SetFormatter defines the formatter for this backend. func (b *FileBackend) SetFormatter(f Formatter) { b.formatter = f } // Reopen closes and reopens the file it writes to. It should be used after log // rotation. func (b *FileBackend) Reopen() error { if err := b.Close(); err != nil { return err } fd, err := os.OpenFile(b.filepath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600) if err != nil { return fmt.Errorf("cannot open log file %s: %w", b.filepath, err) } b.l = fd return nil } // Close closes the underlying file used by the backend. func (b *FileBackend) Close() error { if b.filepath == "" { return nil } if c, ok := b.l.(io.Closer); ok { if err := c.Close(); err != nil { return fmt.Errorf("cannot close log file: %w", err) } } return nil } // // Noop Backend // var _ Backend = &NoopBackend{} // NoopBackend does nothing and discards all log entries without writing them anywhere. type NoopBackend struct{} // NewNoopBackend creates a noop backend. func NewNoopBackend() (*NoopBackend, error) { return &NoopBackend{}, nil } // Write is a noop. func (*NoopBackend) Write(_ *Record) error { return nil } // SetFormatter is a noop. func (*NoopBackend) SetFormatter(_ Formatter) {} // SetLevel is a noop. func (*NoopBackend) SetLevel(_ Level) {} // Level always returns DefeultLevel. func (*NoopBackend) Level() Level { return DefaultLevel } // Reopen is a noop. func (*NoopBackend) Reopen() error { return nil } // Close is a noop. func (*NoopBackend) Close() error { return nil }