Skip to content

Commit

Permalink
Pull request 106: add-slogutil
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit ba43939
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 11 18:38:08 2024 +0300

    all: imp docs, errs

commit d42f5bb
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 11 18:15:15 2024 +0300

    slogutil: add more; all: imp tests
  • Loading branch information
ainar-g committed Apr 11, 2024
1 parent 89ae2d6 commit 3de5106
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 54 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ go 1.22.2

require (
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
golang.org/x/net v0.23.0
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
golang.org/x/net v0.24.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.18.0
golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
10 changes: 5 additions & 5 deletions internal/tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.19.0
github.com/uudashr/gocognit v1.1.2
golang.org/x/tools v0.19.0
golang.org/x/tools v0.20.0
golang.org/x/vuln v1.0.4
honnef.co/go/tools v0.4.7
mvdan.cc/gofumpt v0.6.0
Expand All @@ -26,9 +26,9 @@ require (
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
24 changes: 12 additions & 12 deletions internal/tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,26 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8 h1:ShhqwXlNzuDeQzaa6htzo1S333ACXZzJZgZLpKAza8E=
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20240409090435-93d18d7e34b8 h1:AhJvc/9lEtK0hdZV/K+TpY6gwkIlpBaXHsRRcHO6Ci0=
golang.org/x/exp/typeparams v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -93,8 +93,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand All @@ -107,8 +107,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I=
golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
42 changes: 42 additions & 0 deletions logutil/slogutil/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package slogutil

import (
"context"
"log/slog"

"github.com/AdguardTeam/golibs/errors"
)

// ctxKey is the type for context keys.
type ctxKey int

// Context key values.
const (
ctxKeyLogger ctxKey = iota
)

// ContextWithLogger returns a new context with the given logger.
func ContextWithLogger(parent context.Context, l *slog.Logger) (ctx context.Context) {
return context.WithValue(parent, ctxKeyLogger, l)
}

// LoggerFromContext returns a logger for this request, if any.
func LoggerFromContext(ctx context.Context) (l *slog.Logger, ok bool) {
v := ctx.Value(ctxKeyLogger)
if v == nil {
return nil, false
}

return v.(*slog.Logger), true
}

// MustLoggerFromContext returns a logger for this request and panics if there
// is no logger.
func MustLoggerFromContext(ctx context.Context) (l *slog.Logger) {
l, ok := LoggerFromContext(ctx)
if !ok {
panic(errors.Error("slogutil: no logger in context"))
}

return l
}
26 changes: 26 additions & 0 deletions logutil/slogutil/context_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package slogutil_test

import (
"context"

"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func ExampleContextWithLogger() {
handler := func(ctx context.Context) {
l := slogutil.MustLoggerFromContext(ctx)

l.Info("handling")
}

l := slogutil.New(nil)
l = l.With("request_id", 123)

ctx := context.Background()
ctx = slogutil.ContextWithLogger(ctx, l)

handler(ctx)

// Output:
// INFO handling request_id=123
}
52 changes: 52 additions & 0 deletions logutil/slogutil/defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package slogutil

import (
"context"
"io"
"log/slog"
)

// CloseAndLog is a convenient helper to log errors returned by closer. The
// point is to not lose information from deferred Close calls. The error is
// logged with the specified logging level.
//
// Instead of:
//
// defer f.Close()
//
// You can now write:
//
// defer slogutil.CloseAndLog(ctx, l, f, slog.LevelDebug)
//
// Note that if closer is nil, it is simply ignored.
func CloseAndLog(ctx context.Context, l *slog.Logger, closer io.Closer, lvl slog.Level) {
if closer == nil {
return
}

err := closer.Close()
if err == nil {
return
}

l.Log(ctx, lvl, "deferred closing", KeyError, err)
}

// RecoverAndLog is a deferred helper that recovers from a panic and logs the
// panic value into l along with the stacktrace.
func RecoverAndLog(ctx context.Context, l *slog.Logger) {
v := recover()
if v == nil {
return
}

var args []any
if err, ok := v.(error); ok {
args = []any{KeyError, err}
} else {
args = []any{"value", v}
}

l.ErrorContext(ctx, "recovered from panic", args...)
PrintStack(ctx, l, slog.LevelError)
}
55 changes: 55 additions & 0 deletions logutil/slogutil/defer_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package slogutil_test

import (
"context"
"fmt"
"log/slog"

"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil/fakeio"
)

func ExampleCloseAndLog() {
ctx := context.Background()
l := slogutil.New(&slogutil.Config{
Verbose: true,
})

func() {
defer slogutil.CloseAndLog(ctx, l, nil, slog.LevelDebug)

fmt.Println("nil closer:")
}()

c := &fakeio.Closer{
OnClose: func() (err error) {
return nil
},
}

func() {
defer slogutil.CloseAndLog(ctx, l, c, slog.LevelDebug)

fmt.Println("actual closer without error:")
}()

c = &fakeio.Closer{
OnClose: func() (err error) {
return errors.Error("close failed")
},
}

func() {
defer slogutil.CloseAndLog(ctx, l, c, slog.LevelDebug)

fmt.Println("actual closer with error:")
}()

// Output:
//
// nil closer:
// actual closer without error:
// actual closer with error:
// DEBUG deferred closing err="close failed"
}
93 changes: 93 additions & 0 deletions logutil/slogutil/defer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package slogutil_test

import (
"bytes"
"context"
"fmt"
"regexp"
"strings"
"testing"

"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRecoverAndLog(t *testing.T) {
const (
errTestMsg = "test error"
errTest errors.Error = errTestMsg
)

require.True(t, t.Run("no_panic", func(t *testing.T) {
output := &bytes.Buffer{}
l := slogutil.New(&slogutil.Config{
Output: output,
})

func() {
ctx := context.Background()

defer slogutil.RecoverAndLog(ctx, l)
}()

assert.Equal(t, 0, output.Len())
}))

require.True(t, t.Run("non_error", func(t *testing.T) {
output := &bytes.Buffer{}
l := slogutil.New(&slogutil.Config{
Output: output,
})

func() {
ctx := context.Background()

defer slogutil.RecoverAndLog(ctx, l)

panic(errTestMsg)
}()

assertPanic(t, output, fmt.Sprintf("ERROR recovered from panic value=%q", errTestMsg))
}))

require.True(t, t.Run("error", func(t *testing.T) {
output := &bytes.Buffer{}
l := slogutil.New(&slogutil.Config{
Output: output,
})

func() {
ctx := context.Background()

defer slogutil.RecoverAndLog(ctx, l)

panic(errTest)
}()

assertPanic(t, output, fmt.Sprintf("ERROR recovered from panic err=%q", errTest))
}))
}

// assertPanic is a test helper that checks the panic message.
func assertPanic(tb testing.TB, output *bytes.Buffer, wantFirstLine string) {
tb.Helper()

lines := strings.Split(output.String(), "\n")

// Require that there are at least the first line, the last empty line, and
// at least one stack trace record, which takes two lines.
require.Greater(tb, len(lines), 4)

assert.Equal(tb, wantFirstLine, lines[0])

// Remove the first line, which has already been inspected, and the last
// line, since it's likely empty.
lines = lines[1 : len(lines)-1]

wantRE := regexp.MustCompilePOSIX(`^ERROR stack i=[0-9]+ line="[^"]+"`)
for i, line := range lines {
assert.Regexpf(tb, wantRE, line, "at index %d", i)
}
}
Loading

0 comments on commit 3de5106

Please sign in to comment.