Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New API for package log (post-GopherCon) #76

Merged
merged 11 commits into from
Jul 15, 2015
32 changes: 32 additions & 0 deletions log2/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package log_test

import (
"io/ioutil"
"testing"

"github.com/go-kit/kit/log2"
)

func BenchmarkContextNoMessage(b *testing.B) {
logger := log.NewLogfmtLogger(ioutil.Discard)
ctx := log.NewContext(logger, "module", "benchmark")
for i := 0; i < b.N; i++ {
ctx.Log()
}
}

func BenchmarkContextOneMessage(b *testing.B) {
logger := log.NewLogfmtLogger(ioutil.Discard)
ctx := log.NewContext(logger, "module", "benchmark")
for i := 0; i < b.N; i++ {
ctx.Log("msg", "hello")
}
}

func BenchmarkContextWith(b *testing.B) {
logger := log.NewLogfmtLogger(ioutil.Discard)
ctx := log.NewContext(logger, "module", "benchmark")
for i := 0; i < b.N; i++ {
ctx.With("subcontext", 123).Log("msg", "goodbye")
}
}
23 changes: 23 additions & 0 deletions log2/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package log_test

import (
"os"

"github.com/go-kit/kit/log2"
)

func ExampleContext() {
logger := log.NewLogfmtLogger(os.Stdout)
logger.Log("foo", 123)
ctx := log.NewContext(logger, "level", "info")
ctx.Log()
ctx = ctx.With("msg", "hello")
ctx.Log()
ctx.With("a", 1).Log("b", 2)

// Output:
// foo=123
// level=info
// level=info msg=hello
// level=info msg=hello a=1 b=2
}
40 changes: 40 additions & 0 deletions log2/levels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package log

type Levels interface {
With(...interface{}) Levels
Debug(...interface{}) error
Info(...interface{}) error
Warn(...interface{}) error
Error(...interface{}) error
Crit(...interface{}) error
}

func NewLevels(logger Logger, keyvals ...interface{}) Levels {
return levels(NewContext(logger, keyvals...))
}

type levels Context

func (l levels) With(keyvals ...interface{}) Levels {
return levels(Context(l).With(keyvals...))
}

func (l levels) Debug(keyvals ...interface{}) error {
return NewContext(l.logger).With("level", "debug").With(l.keyvals...).Log(keyvals...)
}

func (l levels) Info(keyvals ...interface{}) error {
return NewContext(l.logger).With("level", "info").With(l.keyvals...).Log(keyvals...)
}

func (l levels) Warn(keyvals ...interface{}) error {
return NewContext(l.logger).With("level", "warn").With(l.keyvals...).Log(keyvals...)
}

func (l levels) Error(keyvals ...interface{}) error {
return NewContext(l.logger).With("level", "error").With(l.keyvals...).Log(keyvals...)
}

func (l levels) Crit(keyvals ...interface{}) error {
return NewContext(l.logger).With("level", "crit").With(l.keyvals...).Log(keyvals...)
}
25 changes: 25 additions & 0 deletions log2/levels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package log_test

import (
"io/ioutil"
"os"
"testing"

"github.com/go-kit/kit/log2"
)

func ExampleLevels() {
logger := log.NewLevels(log.NewLogfmtLogger(os.Stdout))
logger.Debug("msg", "hello")
logger.With("context", "foo").Warn("err", "error")
// Output:
// level=debug msg=hello
// level=warn context=foo err=error
}

func BenchmarkLevels(b *testing.B) {
logger := log.NewLevels(log.NewLogfmtLogger(ioutil.Discard)).With("foo", "bar")
for i := 0; i < b.N; i++ {
logger.Debug("key", "val")
}
}
39 changes: 39 additions & 0 deletions log2/logfmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package log

import (
"io"

"gopkg.in/logfmt.v0"
)

type logfmtLogger struct {
w io.Writer
}

// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
// logfmt format. The passed Writer must be safe for concurrent use by
// multiple goroutines if the returned Logger will be used concurrently.
func NewLogfmtLogger(w io.Writer) Logger {
return &logfmtLogger{w}
}

func (l logfmtLogger) Log(keyvals ...interface{}) error {
// The Logger interface requires implementations to be safe for concurrent
// use by multiple goroutines. For this implementation that means making
// only one call to l.w.Write() for each call to Log. We first collect all
// of the bytes into b, and then call l.w.Write(b).
for i := 1; i < len(keyvals); i += 2 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is 1 here ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because keyvals contains alternating keys (in even indices) and values (in odd indices). This loop only needs to operate on the values, so it can start at index 1.

if valuer, ok := keyvals[i].(Valuer); ok {
keyvals[i] = valuer.Value()
}
}
b, err := logfmt.MarshalKeyvals(keyvals...)
if err != nil {
return err
}
b = append(b, '\n')
if _, err := l.w.Write(b); err != nil {
return err
}
return nil
}
34 changes: 34 additions & 0 deletions log2/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package log

type Logger interface {
Log(keyvals ...interface{}) error
}

type Context struct {
logger Logger
keyvals []interface{}
}

func NewContext(logger Logger, keyvals ...interface{}) Context {
if len(keyvals)%2 != 0 {
panic("bad keyvals")
}
return Context{
logger: logger,
keyvals: keyvals,
}
}

func (c Context) With(keyvals ...interface{}) Context {
if len(keyvals)%2 != 0 {
panic("bad keyvals")
}
return Context{
logger: c.logger,
keyvals: append(c.keyvals, keyvals...),
}
}

func (c Context) Log(keyvals ...interface{}) error {
return c.logger.Log(append(c.keyvals, keyvals...)...)
}
20 changes: 20 additions & 0 deletions log2/value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package log

import (
"fmt"
"path/filepath"
"runtime"
)

type Valuer interface {
Value() interface{}
}

var Caller = ValuerFunc(func() interface{} {
_, file, line, _ := runtime.Caller(5)
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
})

type ValuerFunc func() interface{}

func (f ValuerFunc) Value() interface{} { return f() }