Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit c377c6c

Browse files
authored
Merge pull request #3 from schigh/add-zerolog-shim
added zerolog shim
2 parents 578aa5e + 5065abb commit c377c6c

File tree

5 files changed

+372
-0
lines changed

5 files changed

+372
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode
2+
.idea

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,50 @@ output:
7575
time="2018-03-04T13:12:35-08:00" level=debug msg="this is a debug message"
7676
```
7777

78+
### Zerolog Logger
79+
This shim allows you to use [zerolog](https://github.com/rs/zerolog) as your logging implementation. If you pass `nil` into `New(...)`,
80+
you will get a default `zerolog.Logger` writing to `stdout` with a timestamp attached.
81+
82+
Alternatively, you can pass your own instance of `zerolog.Logger` to `New(...)`.
83+
84+
Using the `zerolog` default logger:
85+
```go
86+
import "github.com/InVisionApp/go-logger/shims/zerolog"
87+
88+
func main() {
89+
logger := zerolog.New(nil)
90+
logger.Debug("this is a debug message!")
91+
}
92+
93+
```
94+
95+
Using your own logger:
96+
```go
97+
import (
98+
"os"
99+
100+
zl "github.com/rs/zerolog"
101+
"github.com/InVisionApp/go-logger/shims/zerolog"
102+
)
103+
104+
func main() {
105+
// zerolog is a structured logger by default
106+
structuredLogger := zl.New(os.Stdout).Logger()
107+
sLogger := zerolog.New(structuredLogger)
108+
sLogger.Debug("debug message")
109+
// {"level":"debug", "message":"debug message"}
110+
111+
// If you want to use zerolog for human-readable console logging,
112+
// you create a ConsoleWriter and use it as your io.Writer implementation
113+
consoleLogger := zl.New(zl.ConsoleWriter{
114+
Out: os.Stdout,
115+
})
116+
cLogger := zerolog.New(consoleLogger)
117+
cLogger.Debug("debug message")
118+
// |DEBUG| debug message
119+
}
120+
```
121+
78122
### Test Logger
79123
The test logger is for capturing logs during the execution of a test. It writes the logs to a byte buffer which can be dumped and inspected. It also tracks a call count of the total number of times the logger has been called.
80124
**_Note:_** this logger is not meant to be used in production. It is purely designed for use in tests.

shims/zerolog/zerolog.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package zerolog
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/InVisionApp/go-logger"
8+
"github.com/rs/zerolog"
9+
)
10+
11+
type shim struct {
12+
logger *zerolog.Logger
13+
}
14+
15+
// New can be used to override the default logger.
16+
// Optionally pass in an existing zerolog logger or
17+
// pass in `nil` to use the default logger
18+
func New(logger *zerolog.Logger) log.Logger {
19+
if logger == nil {
20+
lg := zerolog.New(os.Stdout).With().Timestamp().Logger()
21+
logger = &lg
22+
}
23+
24+
return &shim{logger: logger}
25+
}
26+
27+
// this will add a space between all elements in the slice
28+
// this func is needed because fmt.Sprint will not separate
29+
// inputs by a space in all cases, which makes the resulting
30+
// output very hard to read
31+
func spaceSep(a []interface{}) []interface{} {
32+
aLen := len(a)
33+
if aLen <= 1 {
34+
return a
35+
}
36+
37+
// we only allocate enough room to add a single space between
38+
// all elements, so len(a) - 1
39+
spaceSlice := make([]interface{}, aLen-1)
40+
// add the empty space to the end of the original slice
41+
a = append(a, spaceSlice...)
42+
43+
// stagger the values. this will leave an empty slot between all
44+
// values to be filled with a space
45+
for i := aLen - 1; i > 0; i-- {
46+
a[i+i] = a[i]
47+
a[i+i-1] = " "
48+
}
49+
50+
return a
51+
}
52+
53+
func (s *shim) Debug(msg ...interface{}) {
54+
s.logger.Debug().Msg(fmt.Sprint(spaceSep(msg)...))
55+
}
56+
57+
func (s *shim) Info(msg ...interface{}) {
58+
s.logger.Info().Msg(fmt.Sprint(spaceSep(msg)...))
59+
}
60+
61+
func (s *shim) Warn(msg ...interface{}) {
62+
s.logger.Warn().Msg(fmt.Sprint(spaceSep(msg)...))
63+
}
64+
65+
func (s *shim) Error(msg ...interface{}) {
66+
s.logger.Error().Msg(fmt.Sprint(spaceSep(msg)...))
67+
}
68+
69+
/*******************************************************************
70+
*ln funcs
71+
zerolog is a json-only structured logger.
72+
To implement human-readable console logging,
73+
you must initialize the logger using zerolog.ConsoleWriter
74+
as your io.Writer. Calling a *ln func when zerolog
75+
is in structured logging mode is a no-op
76+
*******************************************************************/
77+
78+
func (s *shim) Debugln(msg ...interface{}) {
79+
msg = append(msg, "\n")
80+
s.logger.Debug().Msg(fmt.Sprint(spaceSep(msg)...))
81+
}
82+
83+
func (s *shim) Infoln(msg ...interface{}) {
84+
msg = append(msg, "\n")
85+
s.logger.Info().Msg(fmt.Sprint(spaceSep(msg)...))
86+
}
87+
88+
func (s *shim) Warnln(msg ...interface{}) {
89+
msg = append(msg, "\n")
90+
s.logger.Warn().Msg(fmt.Sprint(spaceSep(msg)...))
91+
}
92+
93+
func (s *shim) Errorln(msg ...interface{}) {
94+
msg = append(msg, "\n")
95+
s.logger.Error().Msg(fmt.Sprint(spaceSep(msg)...))
96+
}
97+
98+
func (s *shim) Debugf(format string, args ...interface{}) {
99+
s.logger.Debug().Msgf(format, args...)
100+
}
101+
102+
func (s *shim) Infof(format string, args ...interface{}) {
103+
s.logger.Info().Msgf(format, args...)
104+
}
105+
106+
func (s *shim) Warnf(format string, args ...interface{}) {
107+
s.logger.Warn().Msgf(format, args...)
108+
}
109+
110+
func (s *shim) Errorf(format string, args ...interface{}) {
111+
s.logger.Error().Msgf(format, args...)
112+
}
113+
114+
// WithFields will return a new logger derived from the original
115+
// zerolog logger, with the provided fields added to the log string,
116+
// as a key-value pair
117+
func (s *shim) WithFields(fields log.Fields) log.Logger {
118+
lg := s.logger.With().Fields(fields).Logger()
119+
s.logger = &lg
120+
121+
return s
122+
}

shims/zerolog/zerolog_suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package zerolog_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
7+
"testing"
8+
)
9+
10+
func TestZerolog(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Zerolog Suite")
13+
}

shims/zerolog/zerolog_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package zerolog
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"math/rand"
8+
9+
"github.com/InVisionApp/go-logger"
10+
"github.com/rs/zerolog"
11+
12+
. "github.com/onsi/ginkgo"
13+
. "github.com/onsi/gomega"
14+
"io"
15+
"os"
16+
)
17+
18+
var _ = Describe("satisfies interface", func() {
19+
var _ log.Logger = &shim{}
20+
})
21+
22+
var _ = Describe("zerolog logger", func() {
23+
var (
24+
newOut *bytes.Buffer
25+
l log.Logger
26+
)
27+
BeforeEach(func() {
28+
newOut = &bytes.Buffer{}
29+
zerolog.SetGlobalLevel(zerolog.DebugLevel)
30+
zl := zerolog.New(newOut).With().Timestamp().Logger()
31+
l = New(&zl)
32+
})
33+
Context("spaceSep", func() {
34+
It("works on even length slices", func() {
35+
s := []interface{}{
36+
1, "cat", errors.New("foo"), struct{ Name string }{"bar"},
37+
}
38+
s = spaceSep(s)
39+
Expect(len(s)).To(Equal(7))
40+
41+
sstr := fmt.Sprint(s...)
42+
Expect(sstr).To(Equal("1 cat foo {bar}"))
43+
})
44+
It("works on odd length slices", func() {
45+
s := []interface{}{
46+
1, "cat", errors.New("foo"), struct{ Name string }{"bar"}, []string{},
47+
}
48+
s = spaceSep(s)
49+
Expect(len(s)).To(Equal(9))
50+
51+
sstr := fmt.Sprint(s...)
52+
Expect(sstr).To(Equal("1 cat foo {bar} []"))
53+
})
54+
It("works on big slices", func() {
55+
s := make([]interface{}, 10000)
56+
for i := 0; i < 10000; i++ {
57+
s[i] = rand.Intn(10)
58+
}
59+
s = spaceSep(s)
60+
Expect(len(s)).To(Equal(19999))
61+
62+
sstr := fmt.Sprint(s...)
63+
Expect(len(sstr)).To(Equal(19999))
64+
})
65+
It("works on little slices", func() {
66+
s := []interface{}{1, 2}
67+
68+
s = spaceSep(s)
69+
Expect(len(s)).To(Equal(3))
70+
71+
sstr := fmt.Sprint(s...)
72+
Expect(sstr).To(Equal("1 2"))
73+
})
74+
It("works on really little slices", func() {
75+
s := []interface{}{1}
76+
77+
s = spaceSep(s)
78+
Expect(len(s)).To(Equal(1))
79+
80+
sstr := fmt.Sprint(s...)
81+
Expect(sstr).To(Equal("1"))
82+
})
83+
})
84+
Context("log funcs", func() {
85+
It("prints all the log levels", func() {
86+
logFuncs := map[string]func(...interface{}){
87+
"debug": l.Debug,
88+
"info": l.Info,
89+
"warn": l.Warn,
90+
"error": l.Error,
91+
}
92+
93+
for level, logFunc := range logFuncs {
94+
logFunc("hi there")
95+
96+
b := newOut.Bytes()
97+
newOut.Reset()
98+
Expect(string(b)).To(SatisfyAll(
99+
ContainSubstring("hi there"),
100+
ContainSubstring(`"level":"`+level+`"`),
101+
))
102+
}
103+
})
104+
It("prints all the log levels: *ln", func() {
105+
zl := zerolog.New(zerolog.ConsoleWriter{
106+
Out: newOut,
107+
NoColor: true,
108+
})
109+
l = New(&zl)
110+
logFuncs := map[string]func(...interface{}){
111+
"DEBUG?": l.Debugln,
112+
"INFO": l.Infoln,
113+
"WARN": l.Warnln,
114+
"ERROR?": l.Errorln,
115+
}
116+
for level, logFunc := range logFuncs {
117+
logFunc("hi", "there")
118+
119+
b := newOut.Bytes()
120+
newOut.Reset()
121+
Expect(string(b)).To(SatisfyAll(
122+
ContainSubstring("hi there"),
123+
MatchRegexp(level),
124+
))
125+
}
126+
})
127+
It("prints all the log levels: *f", func() {
128+
logFuncs := map[string]func(string, ...interface{}){
129+
"debug": l.Debugf,
130+
"info": l.Infof,
131+
"warn": l.Warnf,
132+
"error": l.Errorf,
133+
}
134+
for level, logFunc := range logFuncs {
135+
logFunc("hi %s", "there")
136+
137+
b := newOut.Bytes()
138+
newOut.Reset()
139+
Expect(string(b)).To(SatisfyAll(
140+
ContainSubstring("hi there"),
141+
ContainSubstring(`"level":"`+level+`"`),
142+
))
143+
}
144+
})
145+
It("nil logger", func() {
146+
// need to intercept stdout
147+
// https://stackoverflow.com/a/10476304
148+
old := os.Stdout
149+
150+
r, w, _ := os.Pipe()
151+
os.Stdout = w
152+
153+
l = New(nil)
154+
l.Debug("i am default")
155+
156+
outC := make(chan string)
157+
go func() {
158+
var buf bytes.Buffer
159+
io.Copy(&buf, r)
160+
outC <- buf.String()
161+
}()
162+
w.Close()
163+
os.Stdout = old
164+
165+
out := <-outC
166+
Expect(out).To(MatchRegexp(`{"level":"debug","time":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?(-\d{2}:\d{2})?","message":"i am default"}`))
167+
})
168+
})
169+
Context("fields", func() {
170+
It("", func() {
171+
l = l.WithFields(log.Fields{
172+
"foo": "bar",
173+
"tf": true,
174+
"pet": "cat",
175+
"age": 1,
176+
})
177+
178+
l.Debug("hi there")
179+
b := newOut.Bytes()
180+
181+
Expect(string(b)).To(SatisfyAll(
182+
ContainSubstring("hi there"),
183+
ContainSubstring(`"level":"debug"`),
184+
ContainSubstring(`"foo":"bar"`),
185+
ContainSubstring(`"tf":true`),
186+
ContainSubstring(`"pet":"cat"`),
187+
ContainSubstring(`"age":1`),
188+
))
189+
})
190+
})
191+
})

0 commit comments

Comments
 (0)