~mna/webparts-logrus

fd2e926c1e40a0fca13703cde0aeaf1c2e8ffb97 — Martin Angers 3 years ago
initial commit
7 files changed, 257 insertions(+), 0 deletions(-)

A .gitignore
A .golangci.toml
A README.md
A go.mod
A go.sum
A log.go
A log_test.go
A  => .gitignore +6 -0
@@ 1,6 @@
# environment files (e.g. managed by direnv) and other secrets
/.env*

# output files for different tools, e.g. code coverage
/*.out


A  => .golangci.toml +30 -0
@@ 1,30 @@
[linters]
  disable-all = true
  enable = [
    "deadcode",
    "errcheck",
    "gochecknoinits",
    "gochecknoglobals",
    "gofmt",
    "golint",
    "gosec",
    "gosimple",
    "govet",
    "ineffassign",
    "interfacer",
    "misspell",
    "nakedret",
    "prealloc",
    "staticcheck",
    "structcheck",
    "typecheck",
    "unconvert",
    "unparam",
    "unused",
    "varcheck",
  ]

[issues]
  # regexps of issue texts to exclude
  exclude = [
  ]

A  => README.md +4 -0
@@ 1,4 @@
# webparts-logrus

This repository provides an implementation of the `webparts/log` standard interface
using the `github.com/sirupsen/logrus` package.

A  => go.mod +9 -0
@@ 1,9 @@
module git.sr.ht/~mna/webparts-logrus

go 1.13

require (
	git.sr.ht/~mna/webparts v0.0.0-20191019212904-c02d58f841c6
	github.com/sirupsen/logrus v1.4.2
	github.com/stretchr/testify v1.4.0
)

A  => go.sum +29 -0
@@ 1,29 @@
git.sr.ht/~mna/webparts v0.0.0-20191019212904-c02d58f841c6 h1:5T8PdRxFujkU7/lMfVnDxOTWae281BRurGej++PVqGI=
git.sr.ht/~mna/webparts v0.0.0-20191019212904-c02d58f841c6/go.mod h1:I9CmJibFclVrCKUIpZPCOvEMyx58vu6+Ej3rXE1xWzM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

A  => log.go +72 -0
@@ 1,72 @@
// Package logrus provides a webparts/log implementation using logrus.
package logrus

import (
	"fmt"
	"io"

	"git.sr.ht/~mna/webparts/log"
	"github.com/sirupsen/logrus"
)

// FormatterType defines the type of formatter to use for the logger.
type FormatterType int

// List of supported formatter types.
const (
	JSONFormatter FormatterType = iota
	TextFormatter
)

// New creates a new logger that writes to w. It panics if formatter
// is an invalid value.
func New(w io.Writer, formatter FormatterType) log.Logger {
	var f logrus.Formatter

	switch formatter {
	case JSONFormatter:
		f = new(logrus.JSONFormatter)
	case TextFormatter:
		f = new(logrus.TextFormatter)
	default:
		panic(fmt.Sprintf("invalid formatter type: %d", formatter))
	}

	ll := &logrus.Logger{
		Out:       w,
		Formatter: f,
		Level:     logrus.InfoLevel,
	}
	return logger{ll}
}

// ToErrorWriter returns an io.Writer that can be used with Go's
// standard library logger. Every write will be logged to l as an error.
func ToErrorWriter(l log.Logger) io.Writer {
	// fine to panic if l is not an internal logger struct. Nothing
	// more to do as logger implements io.Writer.
	return l.(logger)
}

type logger struct {
	logrus.FieldLogger
}

func (l logger) WithError(err error) log.Logger {
	entry := l.FieldLogger.WithError(err)
	return logger{entry}
}

func (l logger) WithFields(fields log.Fields) log.Logger {
	entry := l.FieldLogger.WithFields(logrus.Fields(fields))
	return logger{entry}
}

func (l logger) Write(b []byte) (int, error) {
	n := len(b)
	if n > 0 && b[n-1] == '\n' {
		b = b[:n-1]
	}
	l.Error(string(b))
	return n, nil
}

A  => log_test.go +107 -0
@@ 1,107 @@
package logrus

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"testing"

	"git.sr.ht/~mna/webparts/log"
	"github.com/stretchr/testify/require"
)

func TestLog(t *testing.T) {
	var buf bytes.Buffer
	l := New(&buf, TextFormatter)

	cases := []struct {
		desc        string
		action      func()
		lines       int
		contains    []string
		notcontains []string
	}{
		{
			"error level",
			func() { l.Error("eeee") },
			1,
			[]string{"level=error", "msg=eeee"},
			nil,
		},
		{
			"info level",
			func() { l.Info("iiii") },
			1,
			[]string{"level=info", "msg=iiii"},
			nil,
		},
		{
			"with error",
			func() { l.WithError(io.EOF).Error("xxxx") },
			1,
			[]string{"level=error", "error=EOF", "msg=xxxx"},
			nil,
		},
		{
			"with fields",
			func() { l.WithFields(log.Fields{"x": 1, "y": 2}).Info("xxxx") },
			1,
			[]string{"level=info", "x=1", "y=2", "msg=xxxx"},
			[]string{"error="},
		},
		{
			"with error and fields",
			func() { l.WithFields(log.Fields{"x": 1, "y": 2}).WithError(io.EOF).Info("xxxx") },
			1,
			[]string{"level=info", "x=1", "y=2", "msg=xxxx", "error=EOF"},
			nil,
		},
		{
			"error writer",
			func() {
				w := ToErrorWriter(l)
				fmt.Fprintln(w, "xxx")
			},
			1,
			[]string{"level=error", "msg=xxx"},
			[]string{"x=1", "y=2"},
		},
		{
			"error writer with fields",
			func() {
				ll := l.WithFields(log.Fields{"x": 1, "y": 2})
				w := ToErrorWriter(ll)
				fmt.Fprintln(w, "xxx")
			},
			1,
			[]string{"level=error", "msg=xxx", "x=1", "y=2"},
			nil,
		},
	}

	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			buf.Reset()

			c.action()
			str := buf.String()
			t.Log(str)

			require.Equal(t, c.lines, strings.Count(str, "\n"), "lines")
			for _, s := range c.contains {
				require.Contains(t, str, s)
			}
			for _, s := range c.notcontains {
				require.NotContains(t, str, s)
			}
		})
	}
}

func TestLog_InvalidFormatter(t *testing.T) {
	var buf bytes.Buffer
	require.Panics(t, func() {
		New(&buf, 1000)
	})
}