-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
14 changed files
with
352 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.