Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
1ad2b38afe | |||
579ca60d03 | |||
6ce7bf634c | |||
3bb9b9c0a1 | |||
db53d11d61 | |||
bbfc179269 | |||
344505ea9d | |||
a6a981948d |
12 changed files with 1940 additions and 130 deletions
|
@ -1,21 +1,31 @@
|
||||||
# This file is a template, and might need editing before it works on your project.
|
|
||||||
image: golang:latest
|
image: golang:latest
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
#- build
|
#- build
|
||||||
- deploy
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- export PATH=$PATH:$GOPATH/bin
|
||||||
|
- go install gotest.tools/gotestsum@latest
|
||||||
script:
|
script:
|
||||||
- go test -race
|
- gotestsum --junitfile tests.xml -- -coverprofile=coverage.txt -covermode atomic .
|
||||||
|
after_script:
|
||||||
pages:
|
- export PATH=$PATH:$GOPATH/bin
|
||||||
stage: deploy
|
- go install github.com/boumenot/gocover-cobertura@latest
|
||||||
script:
|
- gocover-cobertura < coverage.txt > coverage.xml
|
||||||
- mkdir public
|
- go tool cover -func=coverage.txt | grep "total:"
|
||||||
- echo "<body>go/log page</body>" >public/index.html
|
coverage: '/total:\s+\(statements\)\s+(\d+.\d+\%)/'
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
reports:
|
||||||
- public
|
junit: tests.xml
|
||||||
|
coverage_report:
|
||||||
|
coverage_format: cobertura
|
||||||
|
path: coverage.xml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
image: golangci/golangci-lint
|
||||||
|
script:
|
||||||
|
- golangci-lint run
|
||||||
|
|
1625
.golangci.yml
Normal file
1625
.golangci.yml
Normal file
File diff suppressed because it is too large
Load diff
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -1,11 +1,33 @@
|
||||||
# go/logging
|
# go/logging Changelog
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Uncompatible: log level names hace changed. They were fully
|
## v0.4.1 (2022-06-03)
|
||||||
capitalized, only their first letter is capitalized now: DEBUG -> Debug,
|
|
||||||
INFO -> Info, etc.
|
- Ensure all backends implement the interface `BACKEND`.
|
||||||
- Uncompatible NoopBackend.Level() now returns DefaultLevel instead of Fatal
|
- `FileBackend` and `SyslogBackend` always returned errors for `Write`
|
||||||
- Fix: creates new logger with level DefaultLevel instead of Debug
|
operations.
|
||||||
- Fix: FileBackend now properly closes the file befor reopening it (fixes a
|
|
||||||
|
## v0.4.0 (2022-05-31)
|
||||||
|
|
||||||
|
- Add three new log levels: `Trace`, `Notice` and `Alert` with the following
|
||||||
|
order: `Trace`, `Debug`, `Info`, `Notice`, `Warning`, `Error`, `Critical`,
|
||||||
|
`Alert`, `Fatal`.
|
||||||
|
As Syslog has no equivalent of `Trace`, it is mapped to `Debug`.
|
||||||
|
|
||||||
|
## v0.3.0 (2020-05-17)
|
||||||
|
|
||||||
|
### Incompatible Changes
|
||||||
|
|
||||||
|
- Log level names have changed. They were fully capitalized, only their first
|
||||||
|
letter is capitalized now: DEBUG -> Debug, INFO -> Info, etc.
|
||||||
|
- NoopBackend.Level() now returns DefaultLevel instead of Fatal
|
||||||
|
- New loggers are created with level `DefaultLevel` instead of `Debug`
|
||||||
|
- The `Backend` interface now has a `Close()` method, so that backends can free
|
||||||
|
the resources they use
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- FileBackend now properly closes the file before reopening it (fixes a
|
||||||
potential file descriptor leak)
|
potential file descriptor leak)
|
||||||
|
- Logger methods did not always acquire locks, causing race conditions
|
||||||
|
|
103
backend.go
103
backend.go
|
@ -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
|
||||||
|
@ -21,88 +22,94 @@ type Backend interface {
|
||||||
// Backend to write in file-like objects
|
// Backend to write in file-like objects
|
||||||
//
|
//
|
||||||
|
|
||||||
|
var _ Backend = &FileBackend{}
|
||||||
|
|
||||||
// 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
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot write logs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +117,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 +125,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 +136,38 @@ func (b *FileBackend) Close() error {
|
||||||
// Noop Backend
|
// Noop Backend
|
||||||
//
|
//
|
||||||
|
|
||||||
// NoopBackend does nothing and discards all log entries without writing them anywhere
|
var _ Backend = &NoopBackend{}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +1,114 @@
|
||||||
|
//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
|
var _ Backend = &SyslogBackend{}
|
||||||
|
|
||||||
|
// 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 Trace, Debug:
|
||||||
err = sb.w.Debug(text)
|
err = sb.w.Debug(text)
|
||||||
case Info:
|
case Info:
|
||||||
err = sb.w.Info(text)
|
err = sb.w.Info(text)
|
||||||
|
case Notice:
|
||||||
|
err = sb.w.Notice(text)
|
||||||
case Warning:
|
case Warning:
|
||||||
err = sb.w.Warning(text)
|
err = sb.w.Warning(text)
|
||||||
case Error:
|
case Error:
|
||||||
err = sb.w.Err(text)
|
err = sb.w.Err(text)
|
||||||
case Critical:
|
case Critical:
|
||||||
err = sb.w.Crit(text)
|
err = sb.w.Crit(text)
|
||||||
|
case Alert:
|
||||||
|
err = sb.w.Alert(text)
|
||||||
case Fatal:
|
case Fatal:
|
||||||
err = sb.w.Emerg(text)
|
err = sb.w.Emerg(text)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot log to syslog: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +135,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
|
||||||
|
|
48
backend_test.go
Normal file
48
backend_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriteErrorBuffer struct{}
|
||||||
|
|
||||||
|
func (*WriteErrorBuffer) Write(_ []byte) (int, error) {
|
||||||
|
return 0, errors.New("cannot write")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileBackendWrite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("It should write the logs to the buffer", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
b := NewIoBackend(buf)
|
||||||
|
|
||||||
|
err := b.Write(&Record{
|
||||||
|
Level: Info,
|
||||||
|
Message: "my log line",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, buf.String(), "my log line")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("It should return an error if it cannot write the log", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf := new(WriteErrorBuffer)
|
||||||
|
b := NewIoBackend(buf)
|
||||||
|
|
||||||
|
err := b.Write(&Record{
|
||||||
|
Level: Info,
|
||||||
|
Message: "my log line",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module code.bcarlin.xyz/go/logging
|
module code.bcarlin.xyz/go/logging
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.7.1 // indirect
|
||||||
|
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
97
logger.go
97
logger.go
|
@ -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,119 @@ 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
|
// Trace logs a message with the Trace level.
|
||||||
|
func (l *Logger) Trace(text string) {
|
||||||
|
l.Log(Trace, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracef formats the message with given args and logs the result with the
|
||||||
|
// Trace level.
|
||||||
|
func (l *Logger) Tracef(text string, args ...interface{}) {
|
||||||
|
l.Trace(fmt.Sprintf(text, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Notice logs a message with the Notice level.
|
||||||
|
func (l *Logger) Notice(text string) {
|
||||||
|
l.Log(Notice, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Noticef formats the message with given args and logs the result with the
|
||||||
|
// Notice level.
|
||||||
|
func (l *Logger) Noticef(text string, args ...interface{}) {
|
||||||
|
l.Notice(fmt.Sprintf(text, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Alert logs a message with the Alert level.
|
||||||
|
func (l *Logger) Alert(text string) {
|
||||||
|
l.Log(Alert, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alertf formats the message with given args and logs the result with the.
|
||||||
|
// Alert level.
|
||||||
|
func (l *Logger) Alertf(text string, args ...interface{}) {
|
||||||
|
l.Alert(fmt.Sprintf(text, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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...))
|
||||||
}
|
}
|
||||||
|
|
41
logging.go
41
logging.go
|
@ -12,40 +12,53 @@ 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
|
Trace Level = iota
|
||||||
|
Debug
|
||||||
Info
|
Info
|
||||||
|
Notice
|
||||||
Warning
|
Warning
|
||||||
Error
|
Error
|
||||||
Critical
|
Critical
|
||||||
|
Alert
|
||||||
Fatal
|
Fatal
|
||||||
DefaultLevel = Info
|
DefaultLevel = Info
|
||||||
)
|
)
|
||||||
|
|
||||||
var levelNames = [6]string{"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"}
|
//revive:enable:exported
|
||||||
|
|
||||||
// Name returns the name of the log level
|
//nolint:gochecknoglobals // designed this way
|
||||||
|
var levelNames = [...]string{
|
||||||
|
"TRACE", "DEBUG", "INFO", "NOTICE", "WARNING",
|
||||||
|
"ERROR", "CRITICAL", "ALERT", "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +71,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 +80,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 +97,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()
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue