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"
"io"
"os"
"path/filepath"
)
// Backend is the interface that specifies the methods that a backend must
// implement
// implement.
type Backend interface {
Write(*Record) error
SetFormatter(*Formatter)
SetFormatter(Formatter)
SetLevel(Level)
Level() Level
Reopen() error
@ -23,86 +24,86 @@ type Backend interface {
// FileBackend is a backend that writes to a file.
type FileBackend struct {
formatter Formatter
l io.Writer
formatter *Formatter
level Level
filepath string
level Level
}
// NewStdoutBackend creates a new backend to write the logs on the standard
// output
func NewStdoutBackend() (b *FileBackend) {
b = &FileBackend{
// output.
func NewStdoutBackend() *FileBackend {
return &FileBackend{
l: os.Stdout,
formatter: &defaultFormatter,
formatter: defaultFormatter,
}
return
}
// NewStderrBackend creates a new backend to write the logs on the error output
func NewStderrBackend() (b *FileBackend) {
b = &FileBackend{
// NewStderrBackend creates a new backend to write the logs on the error output.
func NewStderrBackend() *FileBackend {
return &FileBackend{
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) {
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 {
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{
l: fd,
formatter: &defaultFormatter,
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) (b *FileBackend) {
// 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,
formatter: defaultFormatter,
}
}
func (b FileBackend) Write(r *Record) error {
text := (*b.formatter)(r)
text := b.formatter(r)
_, 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) {
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 {
return b.level
}
// SetFormatter defines the formatter for this backend
func (b *FileBackend) SetFormatter(f *Formatter) {
// 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
// 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, 0644) //nolint: gosec
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)
return fmt.Errorf("cannot open log file %s: %w", b.filepath, err)
}
b.l = fd
@ -110,7 +111,7 @@ func (b *FileBackend) Reopen() error {
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 {
if b.filepath == "" {
return nil
@ -118,7 +119,7 @@ func (b *FileBackend) Close() error {
if c, ok := b.l.(io.Closer); ok {
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
//
// 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{}
// NewNoopBackend creates a noop backend
func NewNoopBackend() (Backend, error) {
// NewNoopBackend creates a noop backend.
func NewNoopBackend() (*NoopBackend, error) {
return &NoopBackend{}, nil
}
// Write is a noop
func (nb *NoopBackend) Write(r *Record) error {
// Write is a noop.
func (*NoopBackend) Write(_ *Record) error {
return nil
}
// SetFormatter is a noop
func (nb *NoopBackend) SetFormatter(f *Formatter) {}
// SetFormatter is a noop.
func (*NoopBackend) SetFormatter(_ *Formatter) {}
// SetLevel is a noop
func (nb *NoopBackend) SetLevel(level Level) {}
// SetLevel is a noop.
func (*NoopBackend) SetLevel(_ Level) {}
// Level always returns DefeultLevel
func (nb *NoopBackend) Level() Level {
// Level always returns DefeultLevel.
func (*NoopBackend) Level() Level {
return DefaultLevel
}
// Reopen is a noop
func (nb *NoopBackend) Reopen() error {
// Reopen is a noop.
func (*NoopBackend) Reopen() error {
return nil
}
// Close is a noop
func (nb *NoopBackend) Close() error {
// Close is a noop.
func (*NoopBackend) Close() error {
return nil
}

View file

@ -1,46 +1,56 @@
//go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9
package logging
import (
"errors"
"fmt"
"log/syslog"
"strings"
)
var errUnknownFacility = errors.New("unknown facility")
//
// Syslog Backend
//
// SyslogBackend writes the logs to a syslog system
// SyslogBackend writes the logs to a syslog system.
type SyslogBackend struct {
w *syslog.Writer
formatter *Formatter
formatter Formatter
level Level
}
// NewSyslogBackend initializes a new connection to a syslog server with the
// given facility.
// 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)
if err != nil {
return nil, err
}
w, err := syslog.New(f, tag)
if err != nil {
return nil, err
return nil, fmt.Errorf("cannot initialize syslog: %w", err)
}
sb := &SyslogBackend{
w: w,
formatter: &basicFormatter,
formatter: basicFormatter,
}
return sb, nil
}
// Write sends an entry to the syslog server
func (sb *SyslogBackend) Write(r *Record) (err error) {
text := (*sb.formatter)(r)
// Write sends an entry to the syslog server.
func (sb *SyslogBackend) Write(r *Record) error {
var err error
text := sb.formatter(r)
switch r.Level {
case Debug:
err = sb.w.Debug(text)
@ -55,34 +65,40 @@ func (sb *SyslogBackend) Write(r *Record) (err error) {
case Fatal:
err = sb.w.Emerg(text)
}
return err
return fmt.Errorf("cannot log to syslog: %w", err)
}
// SetFormatter defines the formatter for this backend
func (sb *SyslogBackend) SetFormatter(f *Formatter) {
// SetFormatter defines the formatter for this backend.
func (sb *SyslogBackend) SetFormatter(f Formatter) {
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) {
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 {
return sb.level
}
// Reopen is a no-op
func (sb *SyslogBackend) Reopen() error {
// Reopen is a no-op.
func (*SyslogBackend) Reopen() error {
return nil
}
// Close closes the connection to the syslog daemon
// Close closes the connection to the syslog daemon.
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{
"kern": syslog.LOG_KERN,
"user": syslog.LOG_USER,
@ -109,7 +125,7 @@ var facilities = map[string]syslog.Priority{
func facility(name string) (syslog.Priority, error) {
p, ok := facilities[strings.ToLower(name)]
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

View file

@ -7,37 +7,39 @@ import (
"sync"
)
const exitCodeFatal = 100
// Logger is a facility that writes logs to one or more backands (files,
// stdout/stderr, syslog, etc.) which can be configured independently
//
// Loggers are concurrent-safe.
type Logger struct {
sync.Mutex
mutex sync.Mutex
name string
backends []Backend
}
// NewLogger initializes a new Logger with no backend and with the default log level.
func NewLogger(name string) (l *Logger) {
l = &Logger{
func NewLogger(name string) *Logger {
l := &Logger{
name: name,
}
return
return l
}
// AddBackend add a new Backend to the logger. All set backends are kept.
func (l *Logger) AddBackend(b Backend) {
l.Lock()
defer l.Unlock()
l.mutex.Lock()
defer l.mutex.Unlock()
l.backends = append(l.backends, b)
}
// SetBackend sets the backend list to the logger. Any existing backend will be lost.
func (l *Logger) SetBackend(b ...Backend) {
l.Lock()
defer l.Unlock()
l.mutex.Lock()
defer l.mutex.Unlock()
l.backends = b
}
@ -50,11 +52,11 @@ func (l *Logger) SetLevel(level Level) {
}
type buffer struct {
level Level
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))
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
// whose level is at least `level`
// whose level is at least `level`.
func (l *Logger) Log(level Level, m string) {
l.Lock()
defer l.Unlock()
l.mutex.Lock()
defer l.mutex.Unlock()
r := NewRecord(l.name, level, m)
for _, backend := range l.backends {
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) {
l.Log(Debug, text)
}
// 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{}) {
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) {
l.Log(Info, text)
}
// 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{}) {
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) {
l.Log(Warning, text)
}
// 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{}) {
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) {
l.Log(Error, text)
}
// 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{}) {
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) {
l.Log(Critical, text)
}
// Criticalf formats the message with given args and logs the result with the
// Critical level
// Criticalf formats the message with given args and logs the result with the.
// Critical level.
func (l *Logger) Criticalf(text string, args ...interface{}) {
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) {
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
// Fatal level
// Fatal level.
func (l *Logger) Fatalf(text string, args ...interface{}) {
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.
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.
*/
package logging
import (
"errors"
"fmt"
"strings"
"sync"
)
//nolint:gochecknoglobals // designed this way
var (
loggers map[string]*Logger
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
//nolint: golint
//revive:disable:exported
const (
Debug Level = iota
Info
@ -43,9 +47,12 @@ const (
DefaultLevel = Info
)
//revive:enable:exported
//nolint:gochecknoglobals // designed this way
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 {
return levelNames[l]
}
@ -58,7 +65,8 @@ func LevelByName(l string) (Level, error) {
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
@ -66,14 +74,15 @@ func LevelByName(l string) (Level, error) {
type Formatter func(*Record) string
// 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)
func GetLogger(name string) (l *Logger) {
// initializes one with the defaults (it logs to stdout with level INFO).
func GetLogger(name string) *Logger {
lock.Lock()
defer lock.Unlock()
if name == "" {
name = "default"
}
l, ok := loggers[name]
if !ok {
l = NewLogger(name)
@ -82,21 +91,23 @@ func GetLogger(name string) (l *Logger) {
l.SetLevel(DefaultLevel)
loggers[name] = l
}
return l
}
var defaultFormatter Formatter = func(r *Record) string {
func defaultFormatter(r *Record) string {
return fmt.Sprintf("%s [%-8s] %s: %s\n",
r.Timestamp.Format("2006/01/02 15:04:05"), r.Level.Name(), r.Logger,
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))
}
//nolint:gochecknoinits // init is used by design
func init() {
loggers = make(map[string]*Logger, 3)
loggers = map[string]*Logger{}
logger := NewLogger("default")
backend := NewStdoutBackend()

View file

@ -3,11 +3,14 @@ package logging
import "testing"
func Test_LevelByName(t *testing.T) {
t.Parallel()
for _, levelName := range levelNames {
l, e := LevelByName(levelName)
if e != nil {
t.Errorf("level %s not recognized", levelName)
}
if l.Name() != levelName {
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
// generate the logged message
// generate the logged message.
type Record struct {
Logger string
Timestamp time.Time
@ -13,13 +13,12 @@ type Record struct {
Message string
}
// NewRecord creates a new record, setting its timestamp to time.Now()
func NewRecord(name string, l Level, m string) (r *Record) {
r = &Record{
// NewRecord creates a new record, setting its timestamp to time.Now().
func NewRecord(name string, l Level, m string) *Record {
return &Record{
Logger: name,
Level: l,
Message: m,
Timestamp: time.Now(),
}
return
}