Skip to content

Commit c8e8eaf

Browse files
committed
feat: recover log error func
Log error functions for printing panic errors with Logrus, ZeroLog, ZapLog or CharmLog.
1 parent e1c3a33 commit c8e8eaf

File tree

10 files changed

+230
-0
lines changed

10 files changed

+230
-0
lines changed

charm.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,17 @@ func CharmLogWithConfig(cfg CharmLogConfig) echo.MiddlewareFunc {
9393
}
9494
}
9595
}
96+
97+
// CharmLogRecoverFn returns a CharmLog recover log function to print panic
98+
// errors.
99+
func CharmLogRecoverFn(logger *charm.Logger) mw.LogErrorFunc {
100+
return func(ec echo.Context, err error, stack []byte) error {
101+
logger.Error(
102+
"panic recover",
103+
"stacktrace", string(stack),
104+
"error", err,
105+
)
106+
107+
return err
108+
}
109+
}

charm_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
charm "github.com/charmbracelet/log"
1515
"github.com/labstack/echo/v4"
16+
emw "github.com/labstack/echo/v4/middleware"
1617
)
1718

1819
func TestCharmLogWithConfig(t *testing.T) {
@@ -103,3 +104,31 @@ func TestCharmLogRetrievesAnError(t *testing.T) {
103104
t.Errorf("invalid log: error not found")
104105
}
105106
}
107+
108+
func TestCharmLogRecoverFn(t *testing.T) {
109+
ec := panicCtx(t)
110+
b := new(bytes.Buffer)
111+
112+
logger := charm.New(b)
113+
114+
rec := emw.RecoverWithConfig(emw.RecoverConfig{
115+
LogErrorFunc: CharmLogRecoverFn(logger),
116+
})
117+
118+
config := CharmLogConfig{
119+
Logger: logger,
120+
FieldMap: testFields,
121+
}
122+
123+
_ = CharmLogWithConfig(config)(rec(testHandler))(ec)
124+
125+
res := b.String()
126+
127+
if !strings.Contains(res, "status=500") {
128+
t.Errorf("invalid log: wrong status code")
129+
}
130+
131+
if !strings.Contains(res, `error="unable to call"`) {
132+
t.Errorf("invalid log: error not found")
133+
}
134+
}

examples_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
charm "github.com/charmbracelet/log"
1212
"github.com/labstack/echo/v4"
13+
emw "github.com/labstack/echo/v4/middleware"
1314
"github.com/rs/zerolog"
1415
"github.com/sirupsen/logrus"
1516
"go.opencensus.io/stats/view"
@@ -47,6 +48,20 @@ func ExampleZeroLogWithConfig() {
4748
e.Use(middleware.ZeroLogWithConfig(logConfig))
4849
}
4950

51+
// This example register the ZeroLog log error function to echo middleware
52+
// Recover.
53+
func ExampleZeroLogRecoverFn() {
54+
e := echo.New()
55+
56+
// Custom zerolog logger instance
57+
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
58+
59+
// Middleware
60+
e.Use(emw.RecoverWithConfig(emw.RecoverConfig{
61+
LogErrorFunc: middleware.ZeroLogRecoverFn(logger),
62+
}))
63+
}
64+
5065
// This example registers the Logrus middleware with default configuration.
5166
func ExampleLogrus() {
5267
e := echo.New()
@@ -76,6 +91,20 @@ func ExampleLogrusWithConfig() {
7691
e.Use(middleware.LogrusWithConfig(logConfig))
7792
}
7893

94+
// This example register the Logrus log error function to echo middleware
95+
// Recover.
96+
func ExampleLogrusRecoverFn() {
97+
e := echo.New()
98+
99+
// Custom logrus logger instance
100+
logger := logrus.New()
101+
102+
// Middleware
103+
e.Use(emw.RecoverWithConfig(emw.RecoverConfig{
104+
LogErrorFunc: middleware.LogrusRecoverFn(logger),
105+
}))
106+
}
107+
79108
// This example registers the OpenCensus middleware with default configuration.
80109
func ExampleOpenCensus() {
81110
e := echo.New()
@@ -127,6 +156,20 @@ func ExampleZapLogWithConfig() {
127156
e.Use(middleware.ZapLogWithConfig(logConfig))
128157
}
129158

159+
// This example register the ZapLog log error function to echo middleware
160+
// Recover.
161+
func ExampleZapLogRecoverFn() {
162+
e := echo.New()
163+
164+
// Custom ZapLog logger instance
165+
logger, _ := zap.NewProduction()
166+
167+
// Middleware
168+
e.Use(emw.RecoverWithConfig(emw.RecoverConfig{
169+
LogErrorFunc: middleware.ZapLogRecoverFn(logger),
170+
}))
171+
}
172+
130173
// This example registers the CharmBracelet Log middleware with default configuration.
131174
func ExampleCharmLog() {
132175
e := echo.New()
@@ -153,6 +196,17 @@ func ExampleCharmLogWithConfig() {
153196
e.Use(middleware.CharmLogWithConfig(logConfig))
154197
}
155198

199+
// This example register the CharmLog log error function to echo middleware
200+
// Recover.
201+
func ExampleCharmLogRecoverFn() {
202+
e := echo.New()
203+
204+
// Middleware
205+
e.Use(emw.RecoverWithConfig(emw.RecoverConfig{
206+
LogErrorFunc: middleware.CharmLogRecoverFn(charm.Default()),
207+
}))
208+
}
209+
156210
// This example registers the RequestID middleware with default configuration.
157211
func ExampleRequestID() {
158212
e := echo.New()

log_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func testHandler(ec echo.Context) error {
4646
return errors.New("error")
4747
}
4848

49+
if v := ec.QueryParam("panic"); v != "" {
50+
panic("unable to call")
51+
}
52+
4953
return ec.String(http.StatusOK, "test")
5054
}
5155

@@ -100,3 +104,7 @@ func postCtx(t *testing.T) echo.Context {
100104

101105
return ec
102106
}
107+
108+
func panicCtx(t *testing.T) echo.Context {
109+
return testCtx(t, "/some?panic=1")
110+
}

logrus.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,14 @@ func LogrusWithConfig(cfg LogrusConfig) echo.MiddlewareFunc {
8787
}
8888
}
8989
}
90+
91+
// LogrusRecoverFn returns a Logrus recover log function to print panic errors.
92+
func LogrusRecoverFn(logger *logrus.Logger) mw.LogErrorFunc {
93+
return func(ec echo.Context, err error, stack []byte) error {
94+
logger.WithField("stacktrace", string(stack)).
95+
WithError(err).
96+
Error("panic recover")
97+
98+
return err
99+
}
100+
}

logrus_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/labstack/echo/v4"
14+
emw "github.com/labstack/echo/v4/middleware"
1415
"github.com/sirupsen/logrus"
1516
)
1617

@@ -105,3 +106,31 @@ func TestLogrusRetrievesAnError(t *testing.T) {
105106
t.Errorf("invalid log: error not found")
106107
}
107108
}
109+
110+
func TestLogrusRecoverFn(t *testing.T) {
111+
ec := panicCtx(t)
112+
b := new(bytes.Buffer)
113+
114+
logger := logrus.StandardLogger()
115+
logger.Out = b
116+
117+
rec := emw.RecoverWithConfig(emw.RecoverConfig{
118+
LogErrorFunc: LogrusRecoverFn(logger),
119+
})
120+
121+
config := LogrusConfig{
122+
Logger: logger,
123+
}
124+
125+
_ = LogrusWithConfig(config)(rec(testHandler))(ec)
126+
127+
res := b.String()
128+
129+
if !strings.Contains(res, "status=500") {
130+
t.Errorf("invalid log: wrong status code")
131+
}
132+
133+
if !strings.Contains(res, `error="unable to call"`) {
134+
t.Errorf("invalid log: error not found")
135+
}
136+
}

zap.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,15 @@ func ZapLogWithConfig(cfg ZapLogConfig) echo.MiddlewareFunc {
9797
}
9898
}
9999
}
100+
101+
// ZapLogRecoverFn returns a ZapLog recover log function to print panic errors.
102+
func ZapLogRecoverFn(logger *zap.Logger) mw.LogErrorFunc {
103+
return func(ec echo.Context, err error, stack []byte) error {
104+
logger.With(
105+
zap.ByteString("stacktrace", stack),
106+
zap.Error(err),
107+
).Error("panic recover")
108+
109+
return err
110+
}
111+
}

zap_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/labstack/echo/v4"
14+
emw "github.com/labstack/echo/v4/middleware"
1415
"go.uber.org/zap"
1516
"go.uber.org/zap/zaptest/observer"
1617
)
@@ -105,3 +106,33 @@ func TestZapLogRetrievesAnError(t *testing.T) {
105106
t.Errorf("invalid log: error not found")
106107
}
107108
}
109+
110+
func TestZapLogRecoverFn(t *testing.T) {
111+
ec := panicCtx(t)
112+
obsLog, logs := observer.New(zap.InfoLevel)
113+
logger := zap.New(obsLog)
114+
115+
rec := emw.RecoverWithConfig(emw.RecoverConfig{
116+
LogErrorFunc: ZapLogRecoverFn(logger),
117+
})
118+
119+
config := ZapLogConfig{
120+
Logger: logger,
121+
}
122+
123+
_ = ZapLogWithConfig(config)(rec(testHandler))(ec)
124+
125+
entry := logs.All()[0]
126+
ectx := entry.ContextMap()
127+
128+
if _, ok := ectx["error"]; !ok {
129+
t.Errorf("invalid log: error not found")
130+
}
131+
132+
entry = logs.All()[1]
133+
ectx = entry.ContextMap()
134+
135+
if ectx["status"] != int64(http.StatusInternalServerError) {
136+
t.Errorf("invalid log: wrong status code")
137+
}
138+
}

zerolog.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,18 @@ func ZeroLogWithConfig(cfg ZeroLogConfig) echo.MiddlewareFunc {
8787
}
8888
}
8989
}
90+
91+
// ZeroLogRecoverFn returns a ZeroLog recover log function to print panic
92+
// errors.
93+
func ZeroLogRecoverFn(logger zerolog.Logger) mw.LogErrorFunc {
94+
return func(ec echo.Context, err error, stack []byte) error {
95+
logger.Error().
96+
Err(err).
97+
Fields(map[string]interface{}{
98+
"stacktrace": stack,
99+
}).
100+
Msg("panic recover")
101+
102+
return err
103+
}
104+
}

zerolog_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/labstack/echo/v4"
14+
emw "github.com/labstack/echo/v4/middleware"
1415
"github.com/rs/zerolog"
1516
"github.com/rs/zerolog/log"
1617
)
@@ -102,3 +103,29 @@ func TestZeroLogRetrievesAnError(t *testing.T) {
102103
t.Errorf("invalid log: error not found")
103104
}
104105
}
106+
107+
func TestZeroLogRecoverFn(t *testing.T) {
108+
ec := panicCtx(t)
109+
b := new(bytes.Buffer)
110+
logger := log.Output(zerolog.ConsoleWriter{Out: b, NoColor: true})
111+
112+
rec := emw.RecoverWithConfig(emw.RecoverConfig{
113+
LogErrorFunc: ZeroLogRecoverFn(logger),
114+
})
115+
116+
config := ZeroLogConfig{
117+
Logger: logger,
118+
}
119+
120+
_ = ZeroLogWithConfig(config)(rec(testHandler))(ec)
121+
122+
res := b.String()
123+
124+
if !strings.Contains(res, "status=500") {
125+
t.Errorf("invalid log: wrong status code")
126+
}
127+
128+
if !strings.Contains(res, `error="unable to call"`) {
129+
t.Errorf("invalid log: error not found")
130+
}
131+
}

0 commit comments

Comments
 (0)