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

1st pass slog support #52

Merged
merged 21 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.18.x]
go-version: [1.18.x, 1.21.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -32,7 +32,7 @@ jobs:
run: go test -race -covermode=atomic -coverprofile="profile.cov" ./...

- name: Send Coverage
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x'
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.21.x'
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: profile.cov
Expand All @@ -43,9 +43,9 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.21.x
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.46.2
version: latest
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [8.1.0] - 2023-08-16
### Added
- log.G as shorthand for adding a set of Grouped fields. This ability has always been present but is now fully supported in the default logger and with helper function for ease of use.
- slog support added in Go 1.21+ both to use as an slog.Handler or redirect.

### Fixed
- errors.Chain handling from default withErrorFn handler after dep upgrade.

## [8.0.2] - 2023-06-22
### Fixed
- Corrected removal of default logger upon registering a custom one.

## [8.0.1] - 2022-06-23
### Fixed
- Handling un-hashable tag values during dedupe process.
Expand All @@ -30,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed ability to remove individual log levels externally; RemoveHandler+AddHandler can do the same.


[Unreleased]: https://github.com/go-playground/log/compare/v8.0.1...HEAD
[Unreleased]: https://github.com/go-playground/log/compare/v8.1.0...HEAD
[8.1.0]: https://github.com/go-playground/log/compare/v8.0.2...v8.1.0
[8.0.2]: https://github.com/go-playground/log/compare/v8.0.1...v8.0.2
[8.0.1]: https://github.com/go-playground/log/compare/v8.0.0...v8.0.1
[8.0.0]: https://github.com/go-playground/log/compare/v7.0.2...v8.0.0
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## log
<img align="center" src="https://raw.githubusercontent.com/go-playground/log/master/logo.png">![Project status](https://img.shields.io/badge/version-8.0.1-green.svg)
<img align="center" src="https://raw.githubusercontent.com/go-playground/log/master/logo.png">![Project status](https://img.shields.io/badge/version-8.1.0-green.svg)
[![Test](https://github.com/go-playground/log/actions/workflows/go.yml/badge.svg)](https://github.com/go-playground/log/actions/workflows/go.yml)
[![Coverage Status](https://coveralls.io/repos/github/go-playground/log/badge.svg?branch=master)](https://coveralls.io/github/go-playground/log?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/log)](https://goreportcard.com/report/github.com/go-playground/log)
Expand Down Expand Up @@ -152,9 +152,19 @@ func main() {
// logging with fields can be used with any of the above
log.WithField("key", "value").Info("test info")
}

```

#### Go 1.21+ slog compatibility

There is a compatibility layer for slog, which allows redirecting slog to this logger and ability to output to an slog.Handler+.

| type | Definition |
|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Handler](_examples/slog/hanlder/main.go) | This example demonstrates how to redirect the std log and slog to this logger by using it as an slog.Handler. |
| [Redirect](_examples/slog/redirect/main.go) | This example demonstrates how to redirect the std log and slog to this logger and output back out to any slog.Handler, as well as any other handler(s) registered with this logger. |

```go

Log Level Definitions
---------------------

Expand Down
20 changes: 20 additions & 0 deletions _examples/slog/handler/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build go1.21
// +build go1.21

package main

import (
"github.com/go-playground/log/v8"
stdlog "log"
"log/slog"
)

func main() {

// This example demonstrates how to redirect the std log and slog to this logger by using it as
// an slog.Handler.
log.RedirectGoStdLog(true)
log.WithFields(log.G("grouped", log.F("key", "value"))).Debug("test")
stdlog.Println("test stdlog")
slog.Info("test slog", slog.Group("group", "key", "value"))
}
24 changes: 24 additions & 0 deletions _examples/slog/redirect/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build go1.21
// +build go1.21

package main

import (
"github.com/go-playground/log/v8"
slogredirect "github.com/go-playground/log/v8/handlers/slog"
stdlog "log"
"log/slog"
"os"
)

func main() {

// This example demonstrates how to redirect the std log and slog to this logger and output back out to any
// slog.Handler, as well as any other handler(s) registered with this logger.
log.AddHandler(slogredirect.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: slogredirect.ReplaceAttrFn, // for custom log level output
})), log.AllLevels...)
log.WithFields(log.G("grouped", log.F("key", "value"))).Debug("test")
stdlog.Println("test stdlog")
slog.Info("test slog", slog.Group("group", "key", "value"))
}
2 changes: 1 addition & 1 deletion benchmarks/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"testing"
"time"

"github.com/go-playground/log/v8"
log "github.com/go-playground/log/v8"
"github.com/go-playground/log/v8/handlers/json"
)

Expand Down
44 changes: 34 additions & 10 deletions default_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,49 +72,73 @@ func (c *Logger) Log(e Entry) {
buff.B = append(buff.B, space)
buff.B = append(buff.B, e.Message...)

for _, f := range e.Fields {
buff.B = append(buff.B, space)
buff.B = append(buff.B, f.Key...)
buff.B = append(buff.B, equals)
c.addFields("", buff, e.Fields)
buff.B = append(buff.B, newLine)

c.m.Lock()
_, _ = c.writer.Write(buff.B)
c.m.Unlock()

BytePool().Put(buff)
}

func (c *Logger) addFields(prefix string, buff *Buffer, fields []Field) {
for _, f := range fields {

switch t := f.Value.(type) {
case string:
printKey(buff, prefix+f.Key)
buff.B = append(buff.B, t...)
case int:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendInt(buff.B, int64(t), base10)
case int8:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendInt(buff.B, int64(t), base10)
case int16:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendInt(buff.B, int64(t), base10)
case int32:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendInt(buff.B, int64(t), base10)
case int64:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendInt(buff.B, t, base10)
case uint:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendUint(buff.B, uint64(t), base10)
case uint8:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendUint(buff.B, uint64(t), base10)
case uint16:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendUint(buff.B, uint64(t), base10)
case uint32:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendUint(buff.B, uint64(t), base10)
case uint64:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendUint(buff.B, t, base10)
case float32:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendFloat(buff.B, float64(t), 'f', -1, 32)
case float64:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendFloat(buff.B, t, 'f', -1, 64)
case bool:
printKey(buff, prefix+f.Key)
buff.B = strconv.AppendBool(buff.B, t)
case []Field:
c.addFields(prefix+f.Key+".", buff, t)
default:
printKey(buff, prefix+f.Key)
buff.B = append(buff.B, fmt.Sprintf(v, f.Value)...)
}
}
buff.B = append(buff.B, newLine)

c.m.Lock()
_, _ = c.writer.Write(buff.B)
c.m.Unlock()
}

BytePool().Put(buff)
func printKey(buff *Buffer, key string) {
buff.B = append(buff.B, space)
buff.B = append(buff.B, key...)
buff.B = append(buff.B, equals)
}
6 changes: 2 additions & 4 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,11 @@ func formatLink(l *errors.Link, b []byte) []byte {
b = extractSource(b, l.Source)
if l.Prefix != "" {
b = append(b, l.Prefix...)
}

if _, ok := l.Err.(errors.Chain); !ok {
if l.Prefix != "" {
if l.Err != nil {
b = append(b, ": "...)
b = append(b, l.Err.Error()...)
}
b = append(b, l.Err.Error()...)
}
return b
}
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/go-playground/log/v8
go 1.18

require (
github.com/go-playground/errors/v5 v5.2.3
github.com/go-playground/pkg/v5 v5.6.0
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
github.com/go-playground/errors/v5 v5.3.1
github.com/go-playground/pkg/v5 v5.21.2
golang.org/x/term v0.11.0
)

require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
require golang.org/x/sys v0.11.0 // indirect
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/go-playground/errors/v5 v5.2.3 h1:RPxaFHgJZjgk/OFkUcfytJgRQKRINLtueVxgOdnfPpg=
github.com/go-playground/errors/v5 v5.2.3/go.mod h1:DincxRGwraWmq39TZDqtnOtHGOJ+AbNbO0OmBzX6MLw=
github.com/go-playground/pkg/v5 v5.6.0 h1:97YpRFzIcS5NFP2Uzxj8cPYz4zTuwweyXADYcA1KfUQ=
github.com/go-playground/pkg/v5 v5.6.0/go.mod h1:TvZ2nNtNh6VfoNteY9ApA2BXt1ZwJliFZ4hzPAwLS9Y=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
github.com/go-playground/errors/v5 v5.3.1 h1:J2qU+9Whg863g3SATXKSJyFA91Zz85pYD3+obr5Oodk=
github.com/go-playground/errors/v5 v5.3.1/go.mod h1:LcLhmzQ/RuEntAs9r38NSV+xtbHffhMx/1yuuEroc7M=
github.com/go-playground/pkg/v5 v5.21.2 h1:DgVr88oMI3pfMFkEN9E6hp9YGG8NHc+019LRJfnUOfU=
github.com/go-playground/pkg/v5 v5.21.2/go.mod h1:UgHNntEQnMJSygw2O2RQ3LAB0tprx81K90c/pOKh7cU=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
2 changes: 1 addition & 1 deletion handlers/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"io"
"sync"

"github.com/go-playground/log/v8"
log "github.com/go-playground/log/v8"
)

// Handler implementation.
Expand Down
2 changes: 1 addition & 1 deletion handlers/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"testing"

"github.com/go-playground/log/v8"
log "github.com/go-playground/log/v8"
)

func TestJSONLogger(t *testing.T) {
Expand Down
59 changes: 59 additions & 0 deletions handlers/slog/slog_redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build go1.21
// +build go1.21

package slog

import (
"context"
log "github.com/go-playground/log/v8"
"log/slog"
)

// Handler implementation.
type Handler struct {
handler slog.Handler
}

// New handler wraps an slog.Handler for log output.
//
// Calling this function automatically calls the slog.RedirectGoStdLog function in order to intercept and forward
// the Go standard library log output to this handler.
func New(handler slog.Handler) *Handler {
log.RedirectGoStdLog(true)
return &Handler{handler: handler}
}

// Log handles the log entry
func (h *Handler) Log(e log.Entry) {
r := slog.NewRecord(e.Timestamp, slog.Level(e.Level), e.Message, 0)
r.AddAttrs(h.convertFields(e.Fields)...)
_ = h.handler.Handle(context.Background(), r)
}

func (h *Handler) convertFields(fields []log.Field) []slog.Attr {
attrs := make([]slog.Attr, 0, len(fields))
for _, f := range fields {
switch t := f.Value.(type) {
case []log.Field:
a := h.convertFields(t)
arr := make([]any, 0, len(a))
for _, v := range a {
arr = append(arr, v)
}
attrs = append(attrs, slog.Group(f.Key, arr...))
default:
attrs = append(attrs, slog.Any(f.Key, f.Value))
}
}
return attrs
}

// ReplaceAttrFn can be used with slog.HandlerOptions to replace attributes.
// This function replaces the "level" attribute to get the custom log levels of this package.
var ReplaceAttrFn = func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.LevelKey {
level := log.Level(a.Value.Any().(slog.Level))
a.Value = slog.StringValue(level.String())
}
return a
}
46 changes: 46 additions & 0 deletions handlers/slog/slog_redirect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//go:build go1.21
// +build go1.21

package slog

import (
"bytes"
"encoding/json"
"github.com/go-playground/log/v8"
"log/slog"
"strings"
"testing"
"testing/slogtest"
)

func TestSlogRedirect(t *testing.T) {
var buff bytes.Buffer
log.AddHandler(New(slog.NewJSONHandler(&buff, &slog.HandlerOptions{
ReplaceAttr: ReplaceAttrFn, // for custom log level output
})), log.AllLevels...)
h := slog.Default().Handler()

results := func() []map[string]any {
var ms []map[string]any
for _, line := range bytes.Split(buff.Bytes(), []byte{'\n'}) {
if len(line) == 0 {
continue
}
var m map[string]any
if err := json.Unmarshal(line, &m); err != nil {
panic(err) // In a real test, use t.Fatal.
}
ms = append(ms, m)
}
return ms
}
err := slogtest.TestHandler(h, results)
if err != nil {
// if a single error and is time key errors, is ok this logger always sets that.
// sad this its the only way to hook into these errors because none of concrete and
// Joined errors has no way to reach into them when not.
if strings.Count(err.Error(), "\n") != 0 || !strings.Contains(err.Error(), "unexpected key \"time\": a Handler should ignore a zero Record.Time") {
t.Fatal(err)
}
}
}
Loading