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

feat(logger): implement slog.Handler #13

Merged
merged 11 commits into from
Nov 7, 2023
3 changes: 1 addition & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ linters:
- goimports
- goprintffuncname
- gosec
- ifshort
- misspell
- nolintlint
- prealloc
Expand All @@ -32,4 +31,4 @@ linters:
- sqlclosecheck
- unconvert
- unparam
- whitespace
- whitespace
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ readable logging with batteries included.
- Leveled logging.
- Text, JSON, and Logfmt formatters.
- Store and retrieve logger in and from context.
- Slog handler.
- Standard log adapter.

## Usage
Expand Down Expand Up @@ -306,6 +307,17 @@ startOven(400) // INFO <cookies/oven.go:123> Starting oven degree=400
This will use the _caller_ function (`startOven`) line number instead of the
logging function (`log.Info`) to report the source location.

### Slog Handler

You can use Log as an [`log/slog`](https://pkg.go.dev/log/slog) handler. Just
pass a logger instance to Slog and you're good to go.

```go
handler := log.New(os.Stderr)
logger := slog.New(handler)
logger.Error("meow?")
```

### Standard Log Adapter

Some Go libraries, especially the ones in the standard library, will only accept
Expand Down
4 changes: 2 additions & 2 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package log
import (
"bytes"
"context"
"io/ioutil"
"io"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -14,7 +14,7 @@ func TestLogContext_empty(t *testing.T) {
}

func TestLogContext_simple(t *testing.T) {
l := New(ioutil.Discard)
l := New(io.Discard)
ctx := WithContext(context.Background(), l)
require.Equal(t, l, FromContext(ctx))
}
Expand Down
4 changes: 2 additions & 2 deletions formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ const (

var (
// TimestampKey is the key for the timestamp.
TimestampKey = "ts"
TimestampKey = "time"
// MessageKey is the key for the message.
MessageKey = "msg"
// LevelKey is the key for the level.
LevelKey = "lvl"
LevelKey = "level"
// CallerKey is the key for the caller.
CallerKey = "caller"
// PrefixKey is the key for the prefix.
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module github.com/charmbracelet/log

go 1.17
go 1.19

require (
github.com/charmbracelet/lipgloss v0.9.1
github.com/go-logfmt/logfmt v0.6.0
github.com/muesli/termenv v0.15.2
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
)

require (
Expand All @@ -18,6 +19,6 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
15 changes: 4 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
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/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
Expand All @@ -12,7 +11,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
Expand All @@ -24,19 +22,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
30 changes: 15 additions & 15 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestJson(t *testing.T) {
}{
{
name: "default logger info with timestamp",
expected: "{\"lvl\":\"info\",\"msg\":\"info\"}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\"}\n",
msg: "info",
kvs: nil,
f: l.Info,
Expand All @@ -38,77 +38,77 @@ func TestJson(t *testing.T) {
},
{
name: "default logger error with timestamp",
expected: "{\"lvl\":\"error\",\"msg\":\"info\"}\n",
expected: "{\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: nil,
f: l.Error,
},
{
name: "multiline message",
expected: "{\"lvl\":\"error\",\"msg\":\"info\\ninfo\"}\n",
expected: "{\"level\":\"error\",\"msg\":\"info\\ninfo\"}\n",
msg: "info\ninfo",
kvs: nil,
f: l.Error,
},
{
name: "multiline kvs",
expected: "{\"lvl\":\"error\",\"msg\":\"info\",\"multiline\":\"info\\ninfo\"}\n",
expected: "{\"level\":\"error\",\"msg\":\"info\",\"multiline\":\"info\\ninfo\"}\n",
msg: "info",
kvs: []interface{}{"multiline", "info\ninfo"},
f: l.Error,
},
{
name: "odd number of kvs",
expected: "{\"baz\":\"missing value\",\"foo\":\"bar\",\"lvl\":\"error\",\"msg\":\"info\"}\n",
expected: "{\"baz\":\"missing value\",\"foo\":\"bar\",\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"foo", "bar", "baz"},
f: l.Error,
},
{
name: "error field",
expected: "{\"error\":\"error message\",\"lvl\":\"error\",\"msg\":\"info\"}\n",
expected: "{\"error\":\"error message\",\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"error", errors.New("error message")},
f: l.Error,
},
{
name: "struct field",
expected: "{\"lvl\":\"info\",\"msg\":\"info\",\"struct\":{}}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"struct\":{}}\n",
msg: "info",
kvs: []interface{}{"struct", struct{ foo string }{foo: "bar"}},
f: l.Info,
},
{
name: "slice field",
expected: "{\"lvl\":\"info\",\"msg\":\"info\",\"slice\":[1,2,3]}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[1,2,3]}\n",
msg: "info",
kvs: []interface{}{"slice", []int{1, 2, 3}},
f: l.Info,
},
{
name: "slice of structs",
expected: "{\"lvl\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
msg: "info",
kvs: []interface{}{"slice", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
f: l.Info,
},
{
name: "slice of strings",
expected: "{\"lvl\":\"info\",\"msg\":\"info\",\"slice\":[\"foo\",\"bar\"]}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[\"foo\",\"bar\"]}\n",
msg: "info",
kvs: []interface{}{"slice", []string{"foo", "bar"}},
f: l.Info,
},
{
name: "slice of errors",
expected: "{\"lvl\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
msg: "info",
kvs: []interface{}{"slice", []error{errors.New("error message1"), errors.New("error message2")}},
f: l.Info,
},
{
name: "map of strings",
expected: "{\"lvl\":\"info\",\"map\":{\"a\":\"b\",\"foo\":\"bar\"},\"msg\":\"info\"}\n",
expected: "{\"level\":\"info\",\"map\":{\"a\":\"b\",\"foo\":\"bar\"},\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"map", map[string]string{"a": "b", "foo": "bar"}},
f: l.Info,
Expand Down Expand Up @@ -140,14 +140,14 @@ func TestJsonCaller(t *testing.T) {
}{
{
name: "simple caller",
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"lvl\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"level\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
msg: "info",
kvs: nil,
f: l.Info,
},
{
name: "nested caller",
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"lvl\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"level\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
Expand Down Expand Up @@ -177,5 +177,5 @@ func TestJsonCustomKey(t *testing.T) {
logger.SetFormatter(JSONFormatter)
logger.SetReportTimestamp(true)
logger.Info("info")
require.Equal(t, "{\"lvl\":\"info\",\"msg\":\"info\",\"time\":\"0001/01/01 00:00:00\"}\n", buf.String())
require.Equal(t, "{\"level\":\"info\",\"msg\":\"info\",\"time\":\"0002/01/01 00:00:00\"}\n", buf.String())
}
13 changes: 7 additions & 6 deletions level.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package log
import (
"errors"
"fmt"
"math"
"strings"
)

Expand All @@ -11,17 +12,17 @@ type Level int32

const (
// DebugLevel is the debug level.
DebugLevel Level = iota - 1
DebugLevel Level = -4
// InfoLevel is the info level.
InfoLevel
InfoLevel Level = 0
// WarnLevel is the warn level.
WarnLevel
WarnLevel Level = 4
// ErrorLevel is the error level.
ErrorLevel
ErrorLevel Level = 8
// FatalLevel is the fatal level.
FatalLevel
FatalLevel Level = 12
// noLevel is used with log.Print.
noLevel
noLevel Level = math.MaxInt32
)

// String returns the string representation of the level.
Expand Down
15 changes: 15 additions & 0 deletions level_121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build go1.21
// +build go1.21

package log

import "log/slog"

// fromSlogLevel converts slog.Level to log.Level.
var fromSlogLevel = map[slog.Level]Level{
slog.LevelDebug: DebugLevel,
slog.LevelInfo: InfoLevel,
slog.LevelWarn: WarnLevel,
slog.LevelError: ErrorLevel,
slog.Level(12): FatalLevel,
}
15 changes: 15 additions & 0 deletions level_no121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build !go1.21
// +build !go1.21

package log

import "golang.org/x/exp/slog"

// fromSlogLevel converts slog.Level to log.Level.
var fromSlogLevel = map[slog.Level]Level{
slog.LevelDebug: DebugLevel,
slog.LevelInfo: InfoLevel,
slog.LevelWarn: WarnLevel,
slog.LevelError: ErrorLevel,
slog.Level(12): FatalLevel,
}
Loading
Loading