Repository: cloudwego/hertz
Pull request #304 (2022-10-17 14:13:06)

feat: support errors format for the recovery middleware
#### What type of PR is this?

feat

#### Check the PR title.
- [x] This PR title match the format: \<type\>(optional scope): \<description\>
- [x] The description of this PR title is user-oriented and clear enough for others to understand.

#### (Optional) Translate the PR title into Chinese.

为 recovery 中间件支持用户自定义错误输出格式


codecov[bot]: # [Codecov](https://codecov.io/gh/cloudwego/hertz/pull/304?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Base: **64.46**% // Head: **64.51**% // Increases project coverage by **`+0.05%`** :tada:
> Coverage data is based on head [(`2099bb4`)](https://codecov.io/gh/cloudwego/hertz/pull/304?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) compared to base [(`b4ba993`)](https://codecov.io/gh/cloudwego/hertz/commit/b4ba9939024483a5c0da34fdbc22235cdaceb846?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop     #304      +/-   ##
===========================================
+ Coverage    64.46%   64.51%   +0.05%     
===========================================
  Files           84       85       +1     
  Lines         8288     8300      +12     
===========================================
+ Hits          5343     5355      +12     
  Misses        2583     2583              
  Partials       362      362              
```


| [Impacted Files](https://codecov.io/gh/cloudwego/hertz/pull/304?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) | Coverage Δ | |
|---|---|---|
| [pkg/app/middlewares/server/recovery/option.go](https://codecov.io/gh/cloudwego/hertz/pull/304/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2FwcC9taWRkbGV3YXJlcy9zZXJ2ZXIvcmVjb3Zlcnkvb3B0aW9uLmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/app/middlewares/server/recovery/recovery.go](https://codecov.io/gh/cloudwego/hertz/pull/304/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2FwcC9taWRkbGV3YXJlcy9zZXJ2ZXIvcmVjb3ZlcnkvcmVjb3ZlcnkuZ28=) | `86.36% <100.00%> (-0.31%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/cloudwego/hertz/pull/304?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).


Patch:
diff --git a/README.md b/README.md
index 8ee7496..50757fc 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,33 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
 - [Netpoll](https://github.com/cloudwego/netpoll): A high-performance network library. Hertz integrated by default.
 - [Hertz-Contrib](https://github.com/hertz-contrib): A partial extension library of Hertz, which users can integrate into Hertz through options according to their needs.
 - [Example](https://github.com/cloudwego/hertz-examples): Use examples of Hertz.
+## Extensions
+
+| Extensions                                                   | Description                                                  |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [Websocket](https://github.com/hertz-contrib/websocket)      | Enable Hertz to support the Websocket protocol.              |
+| [Pprof](https://github.com/hertz-contrib/pprof)              | Extension for Hertz integration with Pprof.                  |
+| [Sessions](https://github.com/hertz-contrib/sessions)        | Session middleware with multi-state store support.           |
+| [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry) | Hertz's Opentelemetry extension that supports Metric, Logger, Tracing and works out of the box. |
+| [Registry](https://github.com/hertz-contrib/registry)        | Provides service registry and discovery functions. So far, the supported service discovery extensions are nacos, consul, etcd, eureka, polaris, servicecomb, zookeeper. |
+| [Keyauth](https://github.com/hertz-contrib/keyauth)          | Provides token-based authentication.                         |
+| [Secure](https://github.com/hertz-contrib/secure)            | Secure middleware with multiple configuration items.         |
+| [Sentry](https://github.com/hertz-contrib/hertzsentry)       | Sentry extension provides some unified interfaces to help users perform real-time error monitoring. |
+| [Requestid](https://github.com/hertz-contrib/requestid)      | Add request id in response.                                  |
+| [Limiter](https://github.com/hertz-contrib/limiter)          | Provides a current limiter based on the bbr algorithm.       |
+| [Jwt](https://github.com/hertz-contrib/jwt)                  | Jwt extension.                                               |
+| [Autotls](https://github.com/hertz-contrib/autotls)          | Make Hertz support Let's Encrypt.                            |
+| [Monitor-prometheus](https://github.com/hertz-contrib/monitor-prometheus) | Provides service monitoring based on Prometheus.             |
+| [I18n](https://github.com/hertz-contrib/i18n)                | Helps translate Hertz programs into multi programming languages. |
+| [Reverseproxy](https://github.com/hertz-contrib/reverseproxy) | Implement a reverse proxy.                                   |
+| [Opensergo](https://github.com/hertz-contrib/opensergo)      | The Opensergo extension.                                     |
+| [Gzip](https://github.com/hertz-contrib/gzip)                | A Gzip extension with multiple options.                      |
+| [Cors](https://github.com/hertz-contrib/cors)                | Provides cross-domain resource sharing support.              |
+| [Swagger](https://github.com/hertz-contrib/swagger)          | Automatically generate RESTful API documentation with Swagger 2.0. |
+| [Tracer](https://github.com/hertz-contrib/tracer)            | Link tracing based on Opentracing.                           |
+| [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery) | Recovery middleware for Hertz.                               |
+| [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth middleware can provide HTTP basic authentication.  |
+
 ## Blogs
 - [ByteDance Practice on Go Network Library](https://www.cloudwego.io/blog/2021/10/09/bytedance-practices-on-go-network-library/)
 ## Contributing
diff --git a/README_cn.md b/README_cn.md
index 9079a48..4fd84d3 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -52,6 +52,33 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
 - [Netpoll](https://github.com/cloudwego/netpoll): 自研高性能网络库，Hertz 默认集成
 - [Hertz-Contrib](https://github.com/hertz-contrib): Hertz 扩展仓库，提供中间件、tracer 等能力
 - [Example](https://github.com/cloudwego/hertz-examples): Hertz 使用例子
+## 相关拓展
+
+| 拓展                                                         | 描述                                                         |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [Websocket](https://github.com/hertz-contrib/websocket)      | 使 Hertz 支持 Websocket 协议。                               |
+| [Pprof](https://github.com/hertz-contrib/pprof)              | Hertz 集成 Pprof 的扩展。                                    |
+| [Sessions](https://github.com/hertz-contrib/sessions)        | 具有多状态存储支持的 Session 中间件。                        |
+| [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry) | Hertz 的 Opentelemetry 扩展，支持 Metric、Logger、Tracing并且达到开箱即用。 |
+| [Registry](https://github.com/hertz-contrib/registry)        | 提供服务注册与发现功能。到现在为止，支持的服务发现拓展有 nacos， consul， etcd， eureka， polaris， servicecomb， zookeeper。 |
+| [Keyauth](https://github.com/hertz-contrib/keyauth)          | 提供基于 token 的身份验证。                                  |
+| [Secure](https://github.com/hertz-contrib/secure)            | 具有多配置项的 Secure 中间件。                               |
+| [Sentry](https://github.com/hertz-contrib/hertzsentry)       | Sentry 拓展提供了一些统一的接口来帮助用户进行实时的错误监控。 |
+| [Requestid](https://github.com/hertz-contrib/requestid)      | 在 response 中添加 request id。                              |
+| [Limiter](https://github.com/hertz-contrib/limiter)          | 提供了基于 bbr 算法的限流器。                                |
+| [Jwt](https://github.com/hertz-contrib/jwt)                  | Jwt 拓展。                                                   |
+| [Autotls](https://github.com/hertz-contrib/autotls)          | 为 Hertz 支持 Let's Encrypt 。                               |
+| [Monitor-prometheus](https://github.com/hertz-contrib/monitor-prometheus) | 提供基于 Prometheus 服务监控功能。                           |
+| [I18n](https://github.com/hertz-contrib/i18n)                | 可帮助将 Hertz 程序翻译成多种语言。                          |
+| [Reverseproxy](https://github.com/hertz-contrib/reverseproxy) | 实现反向代理。                                               |
+| [Opensergo](https://github.com/hertz-contrib/opensergo)      | Opensergo 扩展。                                             |
+| [Gzip](https://github.com/hertz-contrib/gzip)                | 含多个可选项的 Gzip 拓展。                                   |
+| [Cors](https://github.com/hertz-contrib/cors)                | 提供跨域资源共享支持。                                       |
+| [Swagger](https://github.com/hertz-contrib/swagger)          | 使用 Swagger 2.0 自动生成 RESTful API 文档。                 |
+| [Tracer](https://github.com/hertz-contrib/tracer)            | 基于 Opentracing 的链路追踪。                                |
+| [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery) | Hertz 的异常恢复中间件。                                     |
+| [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth 中间件能够提供 HTTP 基本身份验证。                 |
+
 ## 相关文章
 - [字节跳动在 Go 网络库上的实践](https://www.cloudwego.io/blog/2021/10/09/bytedance-practices-on-go-network-library/)
 ## 贡献代码
diff --git a/pkg/app/middlewares/server/recovery/option.go b/pkg/app/middlewares/server/recovery/option.go
new file mode 100644
index 0000000..d7632ca
--- /dev/null
+++ b/pkg/app/middlewares/server/recovery/option.go
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 CloudWeGo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package recovery
+
+import (
+	"context"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/app"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
+	"github.com/cloudwego/hertz/pkg/protocol/consts"
+)
+
+type (
+	options struct {
+		recoveryHandler func(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte)
+	}
+
+	Option func(o *options)
+)
+
+func defaultRecoveryHandler(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {
+	hlog.SystemLogger().CtxErrorf(c, "[Recovery] %s panic recovered:\n%s\n%s\n",
+		timeFormat(time.Now()), err, stack)
+	ctx.AbortWithStatus(consts.StatusInternalServerError)
+}
+
+func newOptions(opts ...Option) *options {
+	cfg := &options{
+		recoveryHandler: defaultRecoveryHandler,
+	}
+
+	for _, opt := range opts {
+		opt(cfg)
+	}
+
+	return cfg
+}
+
+func WithRecoveryHandler(f func(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte)) Option {
+	return func(o *options) {
+		o.recoveryHandler = f
+	}
+}
diff --git a/pkg/app/middlewares/server/recovery/recovery.go b/pkg/app/middlewares/server/recovery/recovery.go
index 2dbb5cc..dc9e434 100644
--- a/pkg/app/middlewares/server/recovery/recovery.go
+++ b/pkg/app/middlewares/server/recovery/recovery.go
@@ -25,8 +25,6 @@ import (
 	"time"
 
 	"github.com/cloudwego/hertz/pkg/app"
-	"github.com/cloudwego/hertz/pkg/common/hlog"
-	"github.com/cloudwego/hertz/pkg/protocol/consts"
 )
 
 var (
@@ -36,16 +34,18 @@ var (
 	slash     = []byte("/")
 )
 
-// Recovery returns a middleware that recovers from any panic and writes a 500 if there was one.
-func Recovery() app.HandlerFunc {
+// Recovery returns a middleware that recovers from any panic.
+// By default, it will print the time, content, and stack information of the error and write a 500.
+// Overriding the Config configuration, you can customize the error printing logic.
+func Recovery(opts ...Option) app.HandlerFunc {
+	cfg := newOptions(opts...)
+
 	return func(c context.Context, ctx *app.RequestContext) {
 		defer func() {
 			if err := recover(); err != nil {
 				stack := stack(3)
 
-				hlog.SystemLogger().CtxErrorf(c, "[Recovery] %s panic recovered:\n%s\n%s\n",
-					timeFormat(time.Now()), err, stack)
-				ctx.AbortWithStatus(consts.StatusInternalServerError)
+				cfg.recoveryHandler(c, ctx, err, stack)
 			}
 		}()
 		ctx.Next(c)
diff --git a/pkg/app/server/binding/binding.go b/pkg/app/server/binding/binding.go
index ac5aee5..1d72981 100644
--- a/pkg/app/server/binding/binding.go
+++ b/pkg/app/server/binding/binding.go
@@ -23,13 +23,13 @@ import (
 	"github.com/bytedance/go-tagexpr/v2/binding"
 	"github.com/bytedance/go-tagexpr/v2/binding/gjson"
 	"github.com/bytedance/go-tagexpr/v2/validator"
-	"github.com/bytedance/sonic"
+	hjson "github.com/cloudwego/hertz/pkg/common/json"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/route/param"
 )
 
 func init() {
-	binding.ResetJSONUnmarshaler(sonic.Unmarshal)
+	binding.ResetJSONUnmarshaler(hjson.Unmarshal)
 }
 
 var defaultBinder = binding.Default()
diff --git a/pkg/app/server/render/json.go b/pkg/app/server/render/json.go
index 69aa2f9..b206776 100644
--- a/pkg/app/server/render/json.go
+++ b/pkg/app/server/render/json.go
@@ -45,7 +45,7 @@ import (
 	"bytes"
 	"encoding/json"
 
-	"github.com/bytedance/sonic"
+	hjson "github.com/cloudwego/hertz/pkg/common/json"
 	"github.com/cloudwego/hertz/pkg/protocol"
 )
 
@@ -55,7 +55,7 @@ type JSONMarshaler func(v interface{}) ([]byte, error)
 var jsonMarshalFunc JSONMarshaler
 
 func init() {
-	ResetJSONMarshal(sonic.Marshal)
+	ResetJSONMarshal(hjson.Marshal)
 }
 
 func ResetJSONMarshal(fn JSONMarshaler) {
diff --git a/pkg/common/json/sonic.go b/pkg/common/json/sonic.go
new file mode 100644
index 0000000..2176733
--- /dev/null
+++ b/pkg/common/json/sonic.go
@@ -0,0 +1,40 @@
+//
+// Copyright 2022 CloudWeGo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//go:build (linux || windows || darwin) && amd64
+// +build linux windows darwin
+// +build amd64
+
+package json
+
+import "github.com/bytedance/sonic"
+
+// Name is the name of the effective json package.
+const Name = "sonic"
+
+var (
+	json = sonic.ConfigStd
+	// Marshal is sonic implementation exported by hertz which is used by rendering.
+	Marshal = json.Marshal
+	// Unmarshal is sonic implementation exported by hertz which is used by binding.
+	Unmarshal = json.Unmarshal
+	// MarshalIndent is sonic implementation exported by hertz.
+	MarshalIndent = json.MarshalIndent
+	// NewDecoder is sonic implementation exported by hertz.
+	NewDecoder = json.NewDecoder
+	// NewEncoder is sonic implementation exported by hertz.
+	NewEncoder = json.NewEncoder
+)
diff --git a/pkg/common/json/std.go b/pkg/common/json/std.go
new file mode 100644
index 0000000..2acde86
--- /dev/null
+++ b/pkg/common/json/std.go
@@ -0,0 +1,38 @@
+//
+// Copyright 2022 CloudWeGo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//go:build stdjson || !(amd64 && (linux || windows || darwin))
+// +build stdjson !amd64 !linux,!windows,!darwin
+
+package json
+
+import "encoding/json"
+
+// Name is the name of the effective json package.
+const Name = "encoding/json"
+
+var (
+	// Marshal is standard implementation exported by hertz which is used by rendering.
+	Marshal = json.Marshal
+	// Unmarshal is standard implementation exported by hertz which is used by binding.
+	Unmarshal = json.Unmarshal
+	// MarshalIndent is standard implementation exported by hertz.
+	MarshalIndent = json.MarshalIndent
+	// NewDecoder is standard implementation exported by hertz.
+	NewDecoder = json.NewDecoder
+	// NewEncoder is standard implementation exported by hertz.
+	NewEncoder = json.NewEncoder
+)
diff --git a/pkg/network/netpoll/dial.go b/pkg/network/netpoll/dial.go
index 9c6d7b3..da55ee0 100644
--- a/pkg/network/netpoll/dial.go
+++ b/pkg/network/netpoll/dial.go
@@ -1,22 +1,22 @@
+//
+// Copyright 2022 CloudWeGo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 //go:build !windows
 // +build !windows
 
-/*
- * Copyright 2022 CloudWeGo Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package netpoll
 
 import (
diff --git a/pkg/route/default.go b/pkg/route/default.go
index f0e4153..01029af 100644
--- a/pkg/route/default.go
+++ b/pkg/route/default.go
@@ -1,22 +1,22 @@
+//
+// Copyright 2022 CloudWeGo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 //go:build !windows
 // +build !windows
 
-/*
- * Copyright 2022 CloudWeGo Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package route
 
 import (


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7f46f75..ba2af0a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -19,15 +19,13 @@ jobs:
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-
-
       - name: Lint
         run: |
           go vet -stdmethods=false $(go list ./...)
           go install mvdan.cc/gofumpt@v0.2.0
-          test -z "$(gofumpt -l -extra .)" 
-
+          test -z "$(gofumpt -l -extra .)"
       - name: Unit Test
         run: go test -race -covermode=atomic -coverprofile=coverage.txt ./...
 
       - name: Codecov
-        run: bash <(curl -s https://codecov.io/bash)
+        run: bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
diff --git a/pkg/app/middlewares/server/recovery/option_test.go b/pkg/app/middlewares/server/recovery/option_test.go
new file mode 100644
index 0000000..b5c6c27
--- /dev/null
+++ b/pkg/app/middlewares/server/recovery/option_test.go
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 CloudWeGo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package recovery
+
+import (
+	"context"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/app"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/common/utils"
+)
+
+func TestDefaultOption(t *testing.T) {
+	opts := newOptions()
+	assert.DeepEqual(t, fmt.Sprintf("%p", defaultRecoveryHandler), fmt.Sprintf("%p", opts.recoveryHandler))
+}
+
+func newRecoveryHandler(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {
+	hlog.SystemLogger().CtxErrorf(c, "[New Recovery] %s panic recovered:\n%s\n%s\n",
+		timeFormat(time.Now()), err, stack)
+	ctx.JSON(501, utils.H{"msg": err.(string)})
+}
+
+func TestOption(t *testing.T) {
+	opts := newOptions(WithRecoveryHandler(newRecoveryHandler))
+	assert.DeepEqual(t, fmt.Sprintf("%p", newRecoveryHandler), fmt.Sprintf("%p", opts.recoveryHandler))
+}
diff --git a/pkg/app/middlewares/server/recovery/recovery_test.go b/pkg/app/middlewares/server/recovery/recovery_test.go
index 79fe4e4..ffaa1a3 100644
--- a/pkg/app/middlewares/server/recovery/recovery_test.go
+++ b/pkg/app/middlewares/server/recovery/recovery_test.go
@@ -22,6 +22,7 @@ import (
 	"testing"
 
 	"github.com/cloudwego/hertz/pkg/app"
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
 )
 
 func TestRecovery(t *testing.T) {
@@ -39,3 +40,20 @@ func TestRecovery(t *testing.T) {
 		t.Fatalf("unexpected %v. Expecting %v", ctx.Response.StatusCode(), 500)
 	}
 }
+
+func TestWithRecoveryHandler(t *testing.T) {
+	ctx := app.NewContext(0)
+	var hc app.HandlersChain
+	hc = append(hc, func(c context.Context, ctx *app.RequestContext) {
+		fmt.Println("this is test")
+		panic("test")
+	})
+	ctx.SetHandlers(hc)
+
+	Recovery(WithRecoveryHandler(newRecoveryHandler))(context.Background(), ctx)
+
+	if ctx.Response.StatusCode() != 501 {
+		t.Fatalf("unexpected %v. Expecting %v", ctx.Response.StatusCode(), 501)
+	}
+	assert.DeepEqual(t, "{\"msg\":\"test\"}", string(ctx.Response.Body()))
+}