Skip to content

Commit 4653baa

Browse files
authored
Initial version
2 parents 96c9441 + b02b7a9 commit 4653baa

13 files changed

Lines changed: 398 additions & 0 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Test binary, build with `go test -c`
2+
*.test
3+
4+
# Output of the go coverage tool, specifically when used with LiteIDE
5+
*.out
6+
7+
vendor

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
language: go
2+
3+
go:
4+
- "1.11.x"
5+
- "1.12.x"
6+
7+
env:
8+
- GO111MODULE=on
9+
10+
install:
11+
- make install
12+
13+
script:
14+
- make test
15+
- make check-fmt
16+
17+
after_success:
18+
- make report-coveralls

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
## [1.0.0] - 2019-08-09
10+
### Changes
11+
- Initial version

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.PHONY: test help fmt check-fmt install report-coveralls
2+
3+
help: ## Show the help text
4+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[93m %s\n", $$1, $$2}'
5+
6+
test: ## Run unit tests
7+
@go test -coverprofile=coverage.out -covermode=atomic -race ./...
8+
9+
check-fmt: ## Check file forma
10+
@GOIMP=$$(for f in $$(find . -type f -name "*.go" ! -path "./.cache/*" ! -path "./vendor/*" ! -name "bindata.go") ; do \
11+
goimports -l $$f ; \
12+
done) && echo $$GOIMP && test -z "$$GOIMP"
13+
14+
fmt: ## Format files
15+
@goimports -w $$(find . -name "*.go" -not -path "./vendor/*")
16+
17+
install: ## Installs dependencies
18+
GOPATH=$$GOPATH && go get -u -v \
19+
golang.org/x/tools/cmd/goimports
20+
21+
report-coveralls: ## Reports generated coverage profile to coveralls.io. Intended to be used only from travis
22+
go get github.com/mattn/goveralls && goveralls -coverprofile=coverage.out -service=travis-ci

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# logrusiowriter
2+
## `io.Writer` implementation using logrus
3+
4+
[![Travis CI build status](https://travis-ci.com/cabify/logrusiowriter.svg?branch=master)](https://travis-ci.com/cabify/logrusiowriter)
5+
[![Coverage Status](https://coveralls.io/repos/github/cabify/logrusiowriter/badge.svg)](https://coveralls.io/github/cabify/logrusiowriter)
6+
[![GoDoc](https://godoc.org/github.com/cabify/logrusiowriter?status.svg)](https://godoc.org/github.com/cabify/logrusiowriter)
7+
8+
# Motivation
9+
10+
Many golang libraries use the golang's `log` package to print their logs. This means that if your application
11+
uses logrus to print structured logging, those packages will print a format that is (probably) incompatible with yours,
12+
and you may end losing logs in your logs collector because they can't be parsed properly.
13+
14+
# Solution
15+
16+
Print the logs written using `log.Printf` through `logrus`, by setting `log.SetOutput` to an `io.Writer` implementation
17+
that uses `logrus` as output, i.e.:
18+
19+
```go
20+
log.SetOutput(logrusiowriter.New())
21+
```
22+
23+
See `example_*_test.go` files to find testable examples that serve as documentation.

configs.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package logrusiowriter
2+
3+
import "github.com/sirupsen/logrus"
4+
5+
// Config holds the configuration to be used with WithConfig() configurer
6+
// This struct is useful to embed into configuration structs parsed with libraries like envconfig
7+
type Config struct {
8+
Level string `default:"info"`
9+
Fields logrus.Fields `default:"logger:stdlib"`
10+
}
11+
12+
// WithLogger configures the logger with the one provided
13+
func WithLogger(logger logrus.FieldLogger) Configurer {
14+
return func(w *writer) {
15+
w.logger = logger
16+
}
17+
}
18+
19+
// WithLevel configures the level with the one provided
20+
func WithLevel(lvl logrus.Level) Configurer {
21+
return func(w *writer) {
22+
w.level = lvl
23+
}
24+
}
25+
26+
// WithFields configures the fields with the ones provided
27+
func WithFields(fields logrus.Fields) Configurer {
28+
return func(w *writer) {
29+
w.fields = fields
30+
}
31+
}
32+
33+
// WithConfig creates a configurer from the configuration provided as a struct
34+
// If it's unable to parse the Level provided as a string, it will invoke the OnLevelParseError function and set the
35+
// level returned by that function (a default value)
36+
func WithConfig(cfg Config) Configurer {
37+
return func(w *writer) {
38+
lvl, err := logrus.ParseLevel(cfg.Level)
39+
if err != nil {
40+
lvl = OnLevelParseError(err)
41+
}
42+
w.level = lvl
43+
w.fields = cfg.Fields
44+
}
45+
}
46+
47+
// WithConfigInterface creates a configurer from the configuration provided as an interface
48+
func WithConfigInterface(cfg interface {
49+
Level() logrus.Level
50+
Fields() logrus.Fields
51+
Logger() logrus.FieldLogger
52+
}) Configurer {
53+
return func(w *writer) {
54+
w.logger = cfg.Logger()
55+
w.level = cfg.Level()
56+
w.fields = cfg.Fields()
57+
}
58+
}
59+
60+
// OnLevelParseError will be invoked if logrus is unable to parse the string level provided in the configuration
61+
// The default behavior is to log it with logrus and return a default Info level,
62+
// you can change this to log in some other system or to panic
63+
// Changing this is not thread safe, so it might be a good idea to change it in a init() function
64+
var OnLevelParseError = func(err error) logrus.Level {
65+
logrus.Errorf("Can't parse level: %s", err)
66+
return logrus.InfoLevel
67+
}

configs_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package logrusiowriter
2+
3+
import (
4+
"testing"
5+
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
func TestWithConfig(t *testing.T) {
10+
t.Run("can't parse level, configures info level by default", func(t *testing.T) {
11+
expectedLevel := logrus.InfoLevel
12+
13+
cfg := Config{
14+
Level: "none",
15+
Fields: logrus.Fields{},
16+
}
17+
18+
w := New(WithConfig(cfg))
19+
20+
configuredLevel := w.(*writer).level
21+
if configuredLevel != expectedLevel {
22+
t.Errorf("Configured level should be %s, but it was %s", expectedLevel, configuredLevel)
23+
}
24+
})
25+
26+
t.Run("custom OnLevelParseError", func(t *testing.T) {
27+
originalOnLevelParseError := OnLevelParseError
28+
defer func() { OnLevelParseError = originalOnLevelParseError }()
29+
30+
expectedLevel := logrus.WarnLevel
31+
32+
cfg := Config{
33+
Level: "none",
34+
Fields: logrus.Fields{},
35+
}
36+
37+
var providedErr error
38+
OnLevelParseError = func(err error) logrus.Level {
39+
providedErr = err
40+
return expectedLevel
41+
}
42+
43+
w := New(WithConfig(cfg))
44+
45+
configuredLevel := w.(*writer).level
46+
if configuredLevel != expectedLevel {
47+
t.Errorf("Configured level should be %s, but it was %s", expectedLevel, configuredLevel)
48+
}
49+
50+
if providedErr == nil {
51+
t.Errorf("Error provided to OnLevelParseError should not be nil")
52+
}
53+
})
54+
}

example_configs_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package logrusiowriter_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/cabify/logrusiowriter"
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
func ExampleWithConfig() {
11+
removeTimestampAndSetOutputToStdout(logrus.StandardLogger())
12+
13+
config := logrusiowriter.Config{
14+
Level: "warning",
15+
Fields: map[string]interface{}{
16+
"config": "struct",
17+
},
18+
}
19+
20+
writer := logrusiowriter.New(
21+
logrusiowriter.WithConfig(config),
22+
)
23+
24+
_, _ = fmt.Fprint(writer, "Hello World!")
25+
// Output:
26+
// level=warning msg="Hello World!" config=struct
27+
}
28+
29+
func ExampleWithFields() {
30+
removeTimestampAndSetOutputToStdout(logrus.StandardLogger())
31+
32+
writer := logrusiowriter.New(
33+
logrusiowriter.WithFields(logrus.Fields{
34+
"config": "fields",
35+
"other": 288,
36+
}),
37+
)
38+
39+
_, _ = fmt.Fprint(writer, "Hello World!")
40+
// Output:
41+
// level=info msg="Hello World!" config=fields other=288
42+
}
43+
44+
func ExampleWithLevel() {
45+
removeTimestampAndSetOutputToStdout(logrus.StandardLogger())
46+
47+
writer := logrusiowriter.New(
48+
logrusiowriter.WithLevel(logrus.ErrorLevel),
49+
)
50+
51+
_, _ = fmt.Fprint(writer, "Hello World!")
52+
// Output:
53+
// level=error msg="Hello World!"
54+
}
55+
56+
func ExampleWithLogger() {
57+
logger := logrus.New()
58+
removeTimestampAndSetOutputToStdout(logger)
59+
logger.SetLevel(logrus.TraceLevel)
60+
61+
writer := logrusiowriter.New(
62+
logrusiowriter.WithLogger(logger),
63+
)
64+
65+
_, _ = fmt.Fprint(writer, "Hello World!")
66+
// Output:
67+
// level=info msg="Hello World!"
68+
}
69+
70+
func ExampleWithConfigInterface() {
71+
removeTimestampAndSetOutputToStdout(logrus.StandardLogger())
72+
73+
writer := logrusiowriter.New(
74+
logrusiowriter.WithConfigInterface(configProvider{}),
75+
)
76+
77+
_, _ = fmt.Fprint(writer, "Hello World!")
78+
// Output:
79+
// level=trace msg="Hello World!" config=interface
80+
}
81+
82+
type configProvider struct{}
83+
84+
func (configProvider) Level() logrus.Level { return logrus.TraceLevel }
85+
86+
func (configProvider) Fields() logrus.Fields { return logrus.Fields{"config": "interface"} }
87+
88+
func (configProvider) Logger() logrus.FieldLogger {
89+
logger := logrus.New()
90+
removeTimestampAndSetOutputToStdout(logger)
91+
logger.SetLevel(logrus.TraceLevel)
92+
return logger
93+
}

example_writer_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package logrusiowriter_test
2+
3+
import (
4+
"log"
5+
6+
"github.com/cabify/logrusiowriter"
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
func ExampleNew() {
11+
removeTimestampAndSetOutputToStdout(logrus.StandardLogger())
12+
13+
log.SetOutput(logrusiowriter.New())
14+
log.SetFlags(0) // no date on standard logger
15+
16+
log.Printf("Standard log")
17+
18+
// Output:
19+
// level=info msg="Standard log\n"
20+
}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/cabify/logrusiowriter
2+
3+
go 1.12
4+
5+
require (
6+
github.com/sirupsen/logrus v1.4.2
7+
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect
8+
)

0 commit comments

Comments
 (0)