lint: add golangci-lint error and fix errors

This commit is contained in:
Bruno Carlin 2022-05-31 12:23:48 +02:00
parent a6a981948d
commit 344505ea9d
7 changed files with 1770 additions and 109 deletions

1625
.golangci.yml Normal file

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,46 +1,56 @@
//go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9 // +build !windows,!nacl,!plan9
package logging package logging
import ( import (
"errors"
"fmt" "fmt"
"log/syslog" "log/syslog"
"strings" "strings"
) )
var errUnknownFacility = errors.New("unknown facility")
// //
// Syslog Backend // Syslog Backend
// //
// SyslogBackend writes the logs to a syslog system // SyslogBackend writes the logs to a syslog system.
type SyslogBackend struct { type SyslogBackend struct {
w *syslog.Writer w *syslog.Writer
formatter *Formatter formatter Formatter
level Level level Level
} }
// NewSyslogBackend initializes a new connection to a syslog server with the // NewSyslogBackend initializes a new connection to a syslog server with the
// given facility. // given facility.
// tag can contain an identifier for the log stream. It defaults to os.Arg[0]. // tag can contain an identifier for the log stream. It defaults to os.Arg[0].
func NewSyslogBackend(facilityName string, tag string) (Backend, error) { func NewSyslogBackend(facilityName, tag string) (*SyslogBackend, error) {
f, err := facility(facilityName) f, err := facility(facilityName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
w, err := syslog.New(f, tag) w, err := syslog.New(f, tag)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("cannot initialize syslog: %w", err)
} }
sb := &SyslogBackend{ sb := &SyslogBackend{
w: w, w: w,
formatter: &basicFormatter, formatter: basicFormatter,
} }
return sb, nil return sb, nil
} }
// Write sends an entry to the syslog server // Write sends an entry to the syslog server.
func (sb *SyslogBackend) Write(r *Record) (err error) { func (sb *SyslogBackend) Write(r *Record) error {
text := (*sb.formatter)(r) var err error
text := sb.formatter(r)
switch r.Level { switch r.Level {
case Debug: case Debug:
err = sb.w.Debug(text) err = sb.w.Debug(text)
@ -55,34 +65,40 @@ func (sb *SyslogBackend) Write(r *Record) (err error) {
case Fatal: case Fatal:
err = sb.w.Emerg(text) err = sb.w.Emerg(text)
} }
return err
return fmt.Errorf("cannot log to syslog: %w", err)
} }
// SetFormatter defines the formatter for this backend // SetFormatter defines the formatter for this backend.
func (sb *SyslogBackend) SetFormatter(f *Formatter) { func (sb *SyslogBackend) SetFormatter(f Formatter) {
sb.formatter = f sb.formatter = f
} }
// SetLevel changes the log level of the backend // SetLevel changes the log level of the backend.
func (sb *SyslogBackend) SetLevel(level Level) { func (sb *SyslogBackend) SetLevel(level Level) {
sb.level = level sb.level = level
} }
// Level returns the log level set for this backend // Level returns the log level set for this backend.
func (sb *SyslogBackend) Level() Level { func (sb *SyslogBackend) Level() Level {
return sb.level return sb.level
} }
// Reopen is a no-op // Reopen is a no-op.
func (sb *SyslogBackend) Reopen() error { func (*SyslogBackend) Reopen() error {
return nil return nil
} }
// Close closes the connection to the syslog daemon // Close closes the connection to the syslog daemon.
func (sb *SyslogBackend) Close() error { func (sb *SyslogBackend) Close() error {
return sb.w.Close() if err := sb.w.Close(); err != nil {
return fmt.Errorf("cannot close syslog: %w", err)
} }
return nil
}
//nolint:gochecknoglobals // global var is used by design
var facilities = map[string]syslog.Priority{ var facilities = map[string]syslog.Priority{
"kern": syslog.LOG_KERN, "kern": syslog.LOG_KERN,
"user": syslog.LOG_USER, "user": syslog.LOG_USER,
@ -109,7 +125,7 @@ var facilities = map[string]syslog.Priority{
func facility(name string) (syslog.Priority, error) { func facility(name string) (syslog.Priority, error) {
p, ok := facilities[strings.ToLower(name)] p, ok := facilities[strings.ToLower(name)]
if !ok { if !ok {
return 0, fmt.Errorf("Facility '%s' does not exist", name) return 0, fmt.Errorf("facility '%s' does not exist: %w", name, errUnknownFacility)
} }
return p, nil return p, nil

View file

@ -7,37 +7,39 @@ import (
"sync" "sync"
) )
const exitCodeFatal = 100
// Logger is a facility that writes logs to one or more backands (files, // Logger is a facility that writes logs to one or more backands (files,
// stdout/stderr, syslog, etc.) which can be configured independently // stdout/stderr, syslog, etc.) which can be configured independently
// //
// Loggers are concurrent-safe. // Loggers are concurrent-safe.
type Logger struct { type Logger struct {
sync.Mutex mutex sync.Mutex
name string name string
backends []Backend backends []Backend
} }
// NewLogger initializes a new Logger with no backend and with the default log level. // NewLogger initializes a new Logger with no backend and with the default log level.
func NewLogger(name string) (l *Logger) { func NewLogger(name string) *Logger {
l = &Logger{ l := &Logger{
name: name, name: name,
} }
return
return l
} }
// AddBackend add a new Backend to the logger. All set backends are kept. // AddBackend add a new Backend to the logger. All set backends are kept.
func (l *Logger) AddBackend(b Backend) { func (l *Logger) AddBackend(b Backend) {
l.Lock() l.mutex.Lock()
defer l.Unlock() defer l.mutex.Unlock()
l.backends = append(l.backends, b) l.backends = append(l.backends, b)
} }
// SetBackend sets the backend list to the logger. Any existing backend will be lost. // SetBackend sets the backend list to the logger. Any existing backend will be lost.
func (l *Logger) SetBackend(b ...Backend) { func (l *Logger) SetBackend(b ...Backend) {
l.Lock() l.mutex.Lock()
defer l.Unlock() defer l.mutex.Unlock()
l.backends = b l.backends = b
} }
@ -50,11 +52,11 @@ func (l *Logger) SetLevel(level Level) {
} }
type buffer struct { type buffer struct {
level Level
logger *Logger logger *Logger
level Level
} }
func (b *buffer) Write(p []byte) (n int, err error) { func (b *buffer) Write(p []byte) (int, error) {
b.logger.Log(b.level, string(p)) b.logger.Log(b.level, string(p))
return len(p), nil return len(p), nil
} }
@ -70,82 +72,86 @@ func (l *Logger) AsStdLog(level Level) *log.Logger {
} }
// Log sends a record containing the message `m` to the registered backends // Log sends a record containing the message `m` to the registered backends
// whose level is at least `level` // whose level is at least `level`.
func (l *Logger) Log(level Level, m string) { func (l *Logger) Log(level Level, m string) {
l.Lock() l.mutex.Lock()
defer l.Unlock() defer l.mutex.Unlock()
r := NewRecord(l.name, level, m) r := NewRecord(l.name, level, m)
for _, backend := range l.backends { for _, backend := range l.backends {
if r.Level >= backend.Level() { if r.Level >= backend.Level() {
_ = backend.Write(r) if err := backend.Write(r); err != nil {
//revive:disable-next-line:unhandled-error stop error handling recursion!
fmt.Fprintf(os.Stderr, "Cannot write logs: %v", err)
}
} }
} }
} }
// Debug logs a message with the Debug level // Debug logs a message with the Debug level.
func (l *Logger) Debug(text string) { func (l *Logger) Debug(text string) {
l.Log(Debug, text) l.Log(Debug, text)
} }
// Debugf formats the message with given args and logs the result with the // Debugf formats the message with given args and logs the result with the
// Debug level // Debug level.
func (l *Logger) Debugf(text string, args ...interface{}) { func (l *Logger) Debugf(text string, args ...interface{}) {
l.Debug(fmt.Sprintf(text, args...)) l.Debug(fmt.Sprintf(text, args...))
} }
// Info logs a message with the Info level // Info logs a message with the Info level.
func (l *Logger) Info(text string) { func (l *Logger) Info(text string) {
l.Log(Info, text) l.Log(Info, text)
} }
// Infof formats the message with given args and logs the result with the // Infof formats the message with given args and logs the result with the
// Info level // Info level.
func (l *Logger) Infof(text string, args ...interface{}) { func (l *Logger) Infof(text string, args ...interface{}) {
l.Info(fmt.Sprintf(text, args...)) l.Info(fmt.Sprintf(text, args...))
} }
// Warning logs a message with the Warning level // Warning logs a message with the Warning level.
func (l *Logger) Warning(text string) { func (l *Logger) Warning(text string) {
l.Log(Warning, text) l.Log(Warning, text)
} }
// Warningf formats the message with given args and logs the result with the // Warningf formats the message with given args and logs the result with the
// Warning level // Warning level.
func (l *Logger) Warningf(text string, args ...interface{}) { func (l *Logger) Warningf(text string, args ...interface{}) {
l.Warning(fmt.Sprintf(text, args...)) l.Warning(fmt.Sprintf(text, args...))
} }
// Error logs a message with the Error level // Error logs a message with the Error level.
func (l *Logger) Error(text string) { func (l *Logger) Error(text string) {
l.Log(Error, text) l.Log(Error, text)
} }
// Errorf formats the message with given args and logs the result with the // Errorf formats the message with given args and logs the result with the
// Error level // Error level.
func (l *Logger) Errorf(text string, args ...interface{}) { func (l *Logger) Errorf(text string, args ...interface{}) {
l.Error(fmt.Sprintf(text, args...)) l.Error(fmt.Sprintf(text, args...))
} }
// Critical logs a message with the Critical level // Critical logs a message with the Critical level.
func (l *Logger) Critical(text string) { func (l *Logger) Critical(text string) {
l.Log(Critical, text) l.Log(Critical, text)
} }
// Criticalf formats the message with given args and logs the result with the // Criticalf formats the message with given args and logs the result with the.
// Critical level // Critical level.
func (l *Logger) Criticalf(text string, args ...interface{}) { func (l *Logger) Criticalf(text string, args ...interface{}) {
l.Critical(fmt.Sprintf(text, args...)) l.Critical(fmt.Sprintf(text, args...))
} }
// Fatal logs a message with the Fatal level // Fatal logs a message with the Fatal level.
func (l *Logger) Fatal(text string) { func (l *Logger) Fatal(text string) {
l.Log(Debug, text) l.Log(Debug, text)
os.Exit(100)
os.Exit(exitCodeFatal) //nolint:revive // This is wanted if fatal is called
} }
// Fatalf formats the message with given args and logs the result with the // Fatalf formats the message with given args and logs the result with the
// Fatal level // Fatal level.
func (l *Logger) Fatalf(text string, args ...interface{}) { func (l *Logger) Fatalf(text string, args ...interface{}) {
l.Fatal(fmt.Sprintf(text, args...)) l.Fatal(fmt.Sprintf(text, args...))
} }

View file

@ -12,27 +12,31 @@ package logging defines the following builtin backends:
A backend can safely be used by multiple loggers. A backend can safely be used by multiple loggers.
It is the caller's responsability to call Close on backends when they are not It is the caller's responsibility to call Close on backends when they are not
used anymore to free their resources. used anymore to free their resources.
*/ */
package logging package logging
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
) )
//nolint:gochecknoglobals // designed this way
var ( var (
loggers map[string]*Logger loggers map[string]*Logger
lock sync.Mutex lock sync.Mutex
errInvalidLogLevel = errors.New("invalid log level")
) )
// Level is the type of log levels // Level is the type of log levels.
type Level byte type Level byte
//nolint: golint //revive:disable:exported
const ( const (
Debug Level = iota Debug Level = iota
Info Info
@ -43,9 +47,12 @@ const (
DefaultLevel = Info DefaultLevel = Info
) )
//revive:enable:exported
//nolint:gochecknoglobals // designed this way
var levelNames = [6]string{"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"} var levelNames = [6]string{"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"}
// Name returns the name of the log level // Name returns the name of the log level.
func (l Level) Name() string { func (l Level) Name() string {
return levelNames[l] return levelNames[l]
} }
@ -58,7 +65,8 @@ func LevelByName(l string) (Level, error) {
return Level(pos), nil return Level(pos), nil
} }
} }
return Debug, fmt.Errorf("Invalid log level %s", l)
return Debug, fmt.Errorf("unknown log level %q: %w", l, errInvalidLogLevel)
} }
// Formatter is the types of the functions that can be used to format a log // Formatter is the types of the functions that can be used to format a log
@ -66,14 +74,15 @@ func LevelByName(l string) (Level, error) {
type Formatter func(*Record) string type Formatter func(*Record) string
// GetLogger returns a logger given its name. if the logger does not exist, it // GetLogger returns a logger given its name. if the logger does not exist, it
// initializes one with the defaults (it logs to stdout with level debug) // initializes one with the defaults (it logs to stdout with level INFO).
func GetLogger(name string) (l *Logger) { func GetLogger(name string) *Logger {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
if name == "" { if name == "" {
name = "default" name = "default"
} }
l, ok := loggers[name] l, ok := loggers[name]
if !ok { if !ok {
l = NewLogger(name) l = NewLogger(name)
@ -82,21 +91,23 @@ func GetLogger(name string) (l *Logger) {
l.SetLevel(DefaultLevel) l.SetLevel(DefaultLevel)
loggers[name] = l loggers[name] = l
} }
return l return l
} }
var defaultFormatter Formatter = func(r *Record) string { func defaultFormatter(r *Record) string {
return fmt.Sprintf("%s [%-8s] %s: %s\n", return fmt.Sprintf("%s [%-8s] %s: %s\n",
r.Timestamp.Format("2006/01/02 15:04:05"), r.Level.Name(), r.Logger, r.Timestamp.Format("2006/01/02 15:04:05"), r.Level.Name(), r.Logger,
strings.TrimSpace(r.Message)) strings.TrimSpace(r.Message))
} }
var basicFormatter Formatter = func(r *Record) string { func basicFormatter(r *Record) string {
return fmt.Sprintf("%s: %s", r.Logger, strings.TrimSpace(r.Message)) return fmt.Sprintf("%s: %s", r.Logger, strings.TrimSpace(r.Message))
} }
//nolint:gochecknoinits // init is used by design
func init() { func init() {
loggers = make(map[string]*Logger, 3) loggers = map[string]*Logger{}
logger := NewLogger("default") logger := NewLogger("default")
backend := NewStdoutBackend() backend := NewStdoutBackend()

View file

@ -3,11 +3,14 @@ package logging
import "testing" import "testing"
func Test_LevelByName(t *testing.T) { func Test_LevelByName(t *testing.T) {
t.Parallel()
for _, levelName := range levelNames { for _, levelName := range levelNames {
l, e := LevelByName(levelName) l, e := LevelByName(levelName)
if e != nil { if e != nil {
t.Errorf("level %s not recognized", levelName) t.Errorf("level %s not recognized", levelName)
} }
if l.Name() != levelName { if l.Name() != levelName {
t.Errorf("expected '%s', got '%s'", levelName, l.Name()) t.Errorf("expected '%s', got '%s'", levelName, l.Name())
} }

View file

@ -5,7 +5,7 @@ import (
) )
// Record contains the data to be logged. It is passed to a formatter to // Record contains the data to be logged. It is passed to a formatter to
// generate the logged message // generate the logged message.
type Record struct { type Record struct {
Logger string Logger string
Timestamp time.Time Timestamp time.Time
@ -13,13 +13,12 @@ type Record struct {
Message string Message string
} }
// NewRecord creates a new record, setting its timestamp to time.Now() // NewRecord creates a new record, setting its timestamp to time.Now().
func NewRecord(name string, l Level, m string) (r *Record) { func NewRecord(name string, l Level, m string) *Record {
r = &Record{ return &Record{
Logger: name, Logger: name,
Level: l, Level: l,
Message: m, Message: m,
Timestamp: time.Now(), Timestamp: time.Now(),
} }
return
} }