Repository: go-kratos/kratos (go)

Issue:
logging.Server prints a wrong caller path
<!--
Please answer these questions before submitting your issue. Thanks!
For questions please use one of our forums: https://go-kratos.dev/docs/getting-started/faq
-->
#### What happened:

Consider this code:
```go
logger := log.With(log.NewStdLogger(os.Stdout),
	"caller", log.DefaultCaller,
)

helper := log.NewHelper(logger)
helper.Info("foo")

srv := http.NewServer(
	http.Address(":9090"),
	http.Middleware(logging.Server(logger)),
)
```

helper.Info's caller field report where the helper.Info called (e.g. main.go:10). But logger in logger.Server is not.

#### What you expected to happen:

logger.Server's caller path "level" should same as "helper.Log"

#### How to reproduce it (as minimally and precisely as possible):

Please refer this [repo](https://github.com/kvii/report_kratos_logging_issue).

#### Anything else we need to know?:

这绝对不是调一下 caller 层数或添加自定义 calller 逻辑（比如 for 循环匹配第一个不是 kratos 的目录）能解决的问题。

#### Environment:
- Kratos version (use `kratos -v`): v2.7.2
- Go version (use `go version`): 1.21.5
- OS (e.g: `cat /etc/os-release`): -
- Others: -


Pull request description:
fix: logging middleware caller issue #3133
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or Draft PR
4. Please use a semantic commits format title, such as `<type>[optional scope]: <description>`, see: https://go-kratos.dev/docs/community/contribution#type
5. at the same time, please note that similar work should be submitted in one PR as far as possible to reduce the workload of reviewers. Do not split a work into multiple PR unless it should.
-->

<!--
🎉 感谢您向 Kratos 发送 PR！以下是一些提示：
如果这是你第一次为 Kratos 贡献，请阅读我们的贡献指南：https://go-kratos.dev/en/docs/community/contribution/
2、确保您已经为您的 PR 添加或运行了适当的测试和lint，请在提交PR之前使用“make lint”和“make test”，使用“make clean”整理您的 go.mod。
3、如果 PR 未完成，您可能需要将其标记为 WIP（Work In Progress）PR 或 Draft PR
4、请使用语义提交格式标题，如“<类型>[可选范围]：<说明>`，请参阅：https://go-kratos.dev/docs/community/contribution#type
5. 同时请注意，同类的工作请尽量在一个PR中提交，以减轻 review 者的工作负担，不要把一项工作拆分成很多个PR，除非它应该这样做。
-->


#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->

Fix caller path issue in logging middleware.

#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If your PR is not fully resolved the issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->

fixes/resolves #3133

#### Other special notes for the reviewers:
<!--
* Somethings that need extra attention for the reviewers
* Some additional notes, TODO list, etc.
-->

Testing code maybe a little bit hard for understanding.


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index b28a5a28..11fb5ea9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -13,7 +13,7 @@ jobs:
   build:
     strategy:
       matrix:
-        go: [1.19,1.20.x,1.21.x]
+        go: [ 1.19.x, 1.20.x, 1.21.x, 1.22.x ]
     name: build & test
     runs-on: ubuntu-latest
     services:
@@ -54,7 +54,7 @@ jobs:
           echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
 
       - name: Module cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: |
             ~/.cache/go-build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 225b90a9..09096550 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -29,7 +29,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Lint
-        uses: golangci/golangci-lint-action@v3
+        uses: golangci/golangci-lint-action@v4
         with:
           version: v1.54.2
           working-directory: ${{ matrix.workdir }}
diff --git a/app.go b/app.go
index 2fa52ec9..1fa23132 100644
--- a/app.go
+++ b/app.go
@@ -139,6 +139,7 @@ func (a *App) Run() error {
 	if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
 		return err
 	}
+	err = nil
 	for _, fn := range a.opts.afterStop {
 		err = fn(sctx)
 	}
diff --git a/cmd/protoc-gen-go-http/httpTemplate.tpl b/cmd/protoc-gen-go-http/httpTemplate.tpl
index 8f3e87a2..ec8477f9 100644
--- a/cmd/protoc-gen-go-http/httpTemplate.tpl
+++ b/cmd/protoc-gen-go-http/httpTemplate.tpl
@@ -81,6 +81,6 @@ func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Reque
 	if err != nil {
 		return nil, err
 	}
-	return &out, err
+	return &out, nil
 }
 {{end}}
diff --git a/contrib/errortracker/sentry/README.md b/contrib/errortracker/sentry/README.md
new file mode 100644
index 00000000..edab5f57
--- /dev/null
+++ b/contrib/errortracker/sentry/README.md
@@ -0,0 +1,54 @@
+# Sentry middleware for Kratos
+This middleware helps you to catch panics and report them to [sentry](https://sentry.io/)
+
+## Quick Start
+You could check the full demo in example folder.
+```go
+// Step 1: 
+// init sentry in the entry of your application
+import "github.com/getsentry/sentry-go"
+
+sentry.Init(sentry.ClientOptions{
+		Dsn: "<your dsn>",
+		AttachStacktrace: true, // recommended
+})
+
+
+// Step 2: 
+// set middleware
+import 	ksentry "github.com/go-kratos/kratos/contrib/errortracker/sentry/v2"
+
+// for HTTP server, new HTTP server with sentry middleware options
+var opts = []http.ServerOption{
+	http.Middleware(
+		recovery.Recovery(),
+		tracing.Server(),
+		ksentry.Server(ksentry.WithTags(map[string]interface{}{
+			"tag": "some-custom-constant-tag",
+			"trace_id": tracing.TraceID(), // If you want to use the TraceID valuer, you need to place it after the A middleware.
+		})), // must after Recovery middleware, because of the exiting order will be reversed
+		
+		logging.Server(logger), 
+	),
+}
+
+// for gRPC server, new gRPC server with sentry middleware options
+var opts = []grpc.ServerOption{
+     grpc.Middleware(
+		recovery.Recovery(),
+		tracing.Server(),
+		ksentry.Server(ksentry.WithTags(map[string]interface{}{
+			"tag": "some-custom-constant-tag",
+			"trace_id": tracing.TraceID(), // If you want to use the TraceID valuer, you need to place it after the A middleware.
+		})), // must after Recovery middleware, because of the exiting order will be reversed
+		logging.Server(logger),
+     ),
+ }
+
+
+// Then, the framework will report events to Sentry when your trigger panics.
+// Or your can push events to Sentry manually
+```
+
+## Reference
+* [https://docs.sentry.io/platforms/go/](https://docs.sentry.io/platforms/go/)
diff --git a/contrib/errortracker/sentry/go.mod b/contrib/errortracker/sentry/go.mod
new file mode 100644
index 00000000..201597d8
--- /dev/null
+++ b/contrib/errortracker/sentry/go.mod
@@ -0,0 +1,28 @@
+module github.com/go-kratos/kratos/contrib/errortracker/sentry/v2
+
+go 1.19
+
+require (
+	github.com/getsentry/sentry-go v0.25.0
+	github.com/go-kratos/kratos/v2 v2.0.0-00010101000000-000000000000
+)
+
+require (
+	github.com/go-kratos/aegis v0.2.0 // indirect
+	github.com/go-playground/form/v4 v4.2.0 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/gorilla/mux v1.8.1 // indirect
+	github.com/kr/text v0.2.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
+	google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect
+	google.golang.org/grpc v1.56.3 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/errortracker/sentry/go.sum b/contrib/errortracker/sentry/go.sum
new file mode 100644
index 00000000..1d510f24
--- /dev/null
+++ b/contrib/errortracker/sentry/go.sum
@@ -0,0 +1,55 @@
+github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
+github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y=
+github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
+github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
+github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ=
+github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
+github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
+google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
+google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
+google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
+google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
+google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/contrib/errortracker/sentry/sentry.go b/contrib/errortracker/sentry/sentry.go
new file mode 100644
index 00000000..2aaba196
--- /dev/null
+++ b/contrib/errortracker/sentry/sentry.go
@@ -0,0 +1,145 @@
+package sentry
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"os"
+	"strings"
+	"time"
+
+	"github.com/getsentry/sentry-go"
+
+	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"github.com/go-kratos/kratos/v2/transport/http"
+)
+
+type ctxKey struct{}
+
+type Option func(*options)
+
+type options struct {
+	repanic         bool
+	waitForDelivery bool
+	timeout         time.Duration
+	tags            map[string]interface{}
+}
+
+// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true.
+func WithRepanic(repanic bool) Option {
+	return func(opts *options) {
+		opts.repanic = repanic
+	}
+}
+
+// WaitForDelivery configures whether you want to block the request before moving forward with the response.
+func WithWaitForDelivery(waitForDelivery bool) Option {
+	return func(opts *options) {
+		opts.waitForDelivery = waitForDelivery
+	}
+}
+
+// Timeout for the event delivery requests.
+func WithTimeout(timeout time.Duration) Option {
+	return func(opts *options) {
+		opts.timeout = timeout
+	}
+}
+
+// Global tags injection, the value type must be string or log.Valuer
+func WithTags(kvs map[string]interface{}) Option {
+	return func(opts *options) {
+		opts.tags = kvs
+	}
+}
+
+// Server returns a new server middleware for Sentry.
+func Server(opts ...Option) middleware.Middleware {
+	conf := options{repanic: true}
+	for _, o := range opts {
+		o(&conf)
+	}
+	if conf.timeout == 0 {
+		conf.timeout = 2 * time.Second
+	}
+	return func(handler middleware.Handler) middleware.Handler {
+		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+			hub := GetHubFromContext(ctx)
+			scope := hub.Scope()
+
+			for k, v := range conf.tags {
+				switch val := v.(type) {
+				case string:
+					scope.SetTag(k, val)
+				case log.Valuer:
+					scope.SetTag(k, fmt.Sprintf("%v", val(ctx)))
+				}
+			}
+
+			if tr, ok := transport.FromServerContext(ctx); ok {
+				switch tr.Kind() {
+				case transport.KindGRPC:
+					gtr := tr.(*grpc.Transport)
+					scope.SetContext("gRPC", map[string]interface{}{
+						"endpoint":  gtr.Endpoint(),
+						"operation": gtr.Operation(),
+					})
+					headers := make(map[string]interface{})
+					for _, k := range gtr.RequestHeader().Keys() {
+						headers[k] = gtr.RequestHeader().Get(k)
+					}
+					scope.SetContext("Headers", headers)
+				case transport.KindHTTP:
+					htr := tr.(*http.Transport)
+					r := htr.Request()
+					scope.SetRequest(r)
+				}
+			}
+
+			ctx = context.WithValue(ctx, ctxKey{}, hub)
+			defer recoverWithSentry(ctx, conf, hub, req)
+			return handler(ctx, req)
+		}
+	}
+}
+
+func recoverWithSentry(ctx context.Context, opts options, hub *sentry.Hub, req interface{}) {
+	if err := recover(); err != nil {
+		if !isBrokenPipeError(err) {
+			eventID := hub.RecoverWithContext(
+				context.WithValue(ctx, sentry.RequestContextKey, req),
+				err,
+			)
+			if eventID != nil && opts.waitForDelivery {
+				hub.Flush(opts.timeout)
+			}
+		}
+		if opts.repanic {
+			panic(err)
+		}
+	}
+}
+
+func isBrokenPipeError(err interface{}) bool {
+	if netErr, ok := err.(*net.OpError); ok {
+		if sysErr, ok := netErr.Err.(*os.SyscallError); ok {
+			if strings.Contains(strings.ToLower(sysErr.Error()), "broken pipe") ||
+				strings.Contains(strings.ToLower(sysErr.Error()), "connection reset by peer") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// GetHubFromContext retrieves attached *sentry.Hub instance from context or sentry.
+// You can use this hub for extra information reporting
+func GetHubFromContext(ctx context.Context) *sentry.Hub {
+	if hub, ok := ctx.Value(ctxKey{}).(*sentry.Hub); ok {
+		return hub
+	}
+	return sentry.CurrentHub().Clone()
+}
diff --git a/contrib/log/zap/zap.go b/contrib/log/zap/zap.go
index 1ad8caed..acf453fe 100644
--- a/contrib/log/zap/zap.go
+++ b/contrib/log/zap/zap.go
@@ -11,15 +11,31 @@ import (
 var _ log.Logger = (*Logger)(nil)
 
 type Logger struct {
-	log *zap.Logger
+	log    *zap.Logger
+	msgKey string
+}
+
+type Option func(*Logger)
+
+// WithMessageKey with message key.
+func WithMessageKey(key string) Option {
+	return func(l *Logger) {
+		l.msgKey = key
+	}
 }
 
 func NewLogger(zlog *zap.Logger) *Logger {
-	return &Logger{zlog}
+	return &Logger{
+		log:    zlog,
+		msgKey: log.DefaultMessageKey,
+	}
 }
 
 func (l *Logger) Log(level log.Level, keyvals ...interface{}) error {
-	keylen := len(keyvals)
+	var (
+		msg    = ""
+		keylen = len(keyvals)
+	)
 	if keylen == 0 || keylen%2 != 0 {
 		l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals))
 		return nil
@@ -27,20 +43,24 @@ func (l *Logger) Log(level log.Level, keyvals ...interface{}) error {
 
 	data := make([]zap.Field, 0, (keylen/2)+1)
 	for i := 0; i < keylen; i += 2 {
+		if keyvals[i].(string) == l.msgKey {
+			msg, _ = keyvals[i+1].(string)
+			continue
+		}
 		data = append(data, zap.Any(fmt.Sprint(keyvals[i]), keyvals[i+1]))
 	}
 
 	switch level {
 	case log.LevelDebug:
-		l.log.Debug("", data...)
+		l.log.Debug(msg, data...)
 	case log.LevelInfo:
-		l.log.Info("", data...)
+		l.log.Info(msg, data...)
 	case log.LevelWarn:
-		l.log.Warn("", data...)
+		l.log.Warn(msg, data...)
 	case log.LevelError:
-		l.log.Error("", data...)
+		l.log.Error(msg, data...)
 	case log.LevelFatal:
-		l.log.Fatal("", data...)
+		l.log.Fatal(msg, data...)
 	}
 	return nil
 }
diff --git a/contrib/opensergo/go.mod b/contrib/opensergo/go.mod
index 923f3e9c..136920cc 100644
--- a/contrib/opensergo/go.mod
+++ b/contrib/opensergo/go.mod
@@ -5,7 +5,7 @@ go 1.19
 require (
 	github.com/go-kratos/kratos/v2 v2.7.2
 	github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c
-	golang.org/x/net v0.19.0
+	golang.org/x/net v0.20.0
 	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d
 	google.golang.org/grpc v1.59.0
 	google.golang.org/protobuf v1.31.0
@@ -17,7 +17,7 @@ require (
 	github.com/google/uuid v1.3.1 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
diff --git a/contrib/opensergo/go.sum b/contrib/opensergo/go.sum
index 43215120..7abd8dde 100644
--- a/contrib/opensergo/go.sum
+++ b/contrib/opensergo/go.sum
@@ -51,7 +51,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
 github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -79,8 +79,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -93,8 +93,8 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
 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=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
diff --git a/contrib/registry/consul/client.go b/contrib/registry/consul/client.go
index ea5fe846..ab1551cd 100644
--- a/contrib/registry/consul/client.go
+++ b/contrib/registry/consul/client.go
@@ -220,7 +220,11 @@ func (c *Client) Register(_ context.Context, svc *registry.ServiceInstance, enab
 						_ = c.cli.Agent().ServiceDeregister(svc.ID)
 						return
 					}
-					err = c.cli.Agent().UpdateTTL("service:"+svc.ID, "pass", "pass")
+					err = c.cli.Agent().UpdateTTLOpts("service:"+svc.ID, "pass", "pass", new(api.QueryOptions).WithContext(c.ctx))
+					if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+						_ = c.cli.Agent().ServiceDeregister(svc.ID)
+						return
+					}
 					if err != nil {
 						log.Errorf("[Consul] update ttl heartbeat to consul failed! err=%v", err)
 						// when the previous report fails, try to re register the service
diff --git a/contrib/registry/discovery/go.mod b/contrib/registry/discovery/go.mod
index dc03d230..383562ce 100644
--- a/contrib/registry/discovery/go.mod
+++ b/contrib/registry/discovery/go.mod
@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/go-kratos/kratos/v2 v2.7.2
-	github.com/go-resty/resty/v2 v2.10.0
+	github.com/go-resty/resty/v2 v2.11.0
 	github.com/pkg/errors v0.9.1
 )
 
diff --git a/contrib/registry/discovery/go.sum b/contrib/registry/discovery/go.sum
index 0c5e1d9e..2cb8edd3 100644
--- a/contrib/registry/discovery/go.sum
+++ b/contrib/registry/discovery/go.sum
@@ -1,5 +1,5 @@
-github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
-github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
+github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
+github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
diff --git a/contrib/registry/polaris/registry.go b/contrib/registry/polaris/registry.go
index e968fe38..5ad520af 100644
--- a/contrib/registry/polaris/registry.go
+++ b/contrib/registry/polaris/registry.go
@@ -332,6 +332,7 @@ type Watcher struct {
 	Cancel           context.CancelFunc
 	Channel          <-chan model.SubScribeEvent
 	ServiceInstances []*registry.ServiceInstance
+	first            bool
 }
 
 func newWatcher(ctx context.Context, namespace string, serviceName string, consumer api.ConsumerAPI) (*Watcher, error) {
@@ -350,6 +351,7 @@ func newWatcher(ctx context.Context, namespace string, serviceName string, consu
 	w := &Watcher{
 		Namespace:        namespace,
 		ServiceName:      serviceName,
+		first:            true,
 		Channel:          watchServiceResponse.EventChannel,
 		ServiceInstances: instancesToServiceInstances(watchServiceResponse.GetAllInstancesResp.GetInstances()),
 	}
@@ -362,6 +364,10 @@ func newWatcher(ctx context.Context, namespace string, serviceName string, consu
 // 2.any service instance changes found.
 // if the above two conditions are not met, it will block until context deadline exceeded or canceled
 func (w *Watcher) Next() ([]*registry.ServiceInstance, error) {
+	if w.first {
+		w.first = false
+		return w.ServiceInstances, nil
+	}
 	select {
 	case <-w.Ctx.Done():
 		return nil, w.Ctx.Err()
diff --git a/encoding/form/proto_encode.go b/encoding/form/proto_encode.go
index 550648dc..584b1da5 100644
--- a/encoding/form/proto_encode.go
+++ b/encoding/form/proto_encode.go
@@ -111,7 +111,7 @@ func encodeRepeatedField(fieldDescriptor protoreflect.FieldDescriptor, list prot
 func encodeMapField(fieldDescriptor protoreflect.FieldDescriptor, mp protoreflect.Map) (map[string]string, error) {
 	m := make(map[string]string)
 	mp.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
-		key, err := EncodeField(fieldDescriptor.MapValue(), k.Value())
+		key, err := EncodeField(fieldDescriptor.MapKey(), k.Value())
 		if err != nil {
 			return false
 		}
diff --git a/log/helper.go b/log/helper.go
index cc0cb8c9..19fd613b 100644
--- a/log/helper.go
+++ b/log/helper.go
@@ -66,6 +66,15 @@ func (h *Helper) WithContext(ctx context.Context) *Helper {
 	}
 }
 
+// Enabled returns true if the given level above this level.
+// It delegates to the underlying *Filter.
+func (h *Helper) Enabled(level Level) bool {
+	if l, ok := h.logger.(*Filter); ok {
+		return level >= l.level
+	}
+	return true
+}
+
 // Log Print log by level and keyvals.
 func (h *Helper) Log(level Level, keyvals ...interface{}) {
 	_ = h.logger.Log(level, keyvals...)
@@ -73,11 +82,17 @@ func (h *Helper) Log(level Level, keyvals ...interface{}) {
 
 // Debug logs a message at debug level.
 func (h *Helper) Debug(a ...interface{}) {
+	if !h.Enabled(LevelDebug) {
+		return
+	}
 	_ = h.logger.Log(LevelDebug, h.msgKey, h.sprint(a...))
 }
 
 // Debugf logs a message at debug level.
 func (h *Helper) Debugf(format string, a ...interface{}) {
+	if !h.Enabled(LevelDebug) {
+		return
+	}
 	_ = h.logger.Log(LevelDebug, h.msgKey, h.sprintf(format, a...))
 }
 
@@ -88,11 +103,17 @@ func (h *Helper) Debugw(keyvals ...interface{}) {
 
 // Info logs a message at info level.
 func (h *Helper) Info(a ...interface{}) {
+	if !h.Enabled(LevelInfo) {
+		return
+	}
 	_ = h.logger.Log(LevelInfo, h.msgKey, h.sprint(a...))
 }
 
 // Infof logs a message at info level.
 func (h *Helper) Infof(format string, a ...interface{}) {
+	if !h.Enabled(LevelInfo) {
+		return
+	}
 	_ = h.logger.Log(LevelInfo, h.msgKey, h.sprintf(format, a...))
 }
 
@@ -103,11 +124,17 @@ func (h *Helper) Infow(keyvals ...interface{}) {
 
 // Warn logs a message at warn level.
 func (h *Helper) Warn(a ...interface{}) {
+	if !h.Enabled(LevelWarn) {
+		return
+	}
 	_ = h.logger.Log(LevelWarn, h.msgKey, h.sprint(a...))
 }
 
 // Warnf logs a message at warnf level.
 func (h *Helper) Warnf(format string, a ...interface{}) {
+	if !h.Enabled(LevelWarn) {
+		return
+	}
 	_ = h.logger.Log(LevelWarn, h.msgKey, h.sprintf(format, a...))
 }
 
@@ -118,11 +145,17 @@ func (h *Helper) Warnw(keyvals ...interface{}) {
 
 // Error logs a message at error level.
 func (h *Helper) Error(a ...interface{}) {
+	if !h.Enabled(LevelError) {
+		return
+	}
 	_ = h.logger.Log(LevelError, h.msgKey, h.sprint(a...))
 }
 
 // Errorf logs a message at error level.
 func (h *Helper) Errorf(format string, a ...interface{}) {
+	if !h.Enabled(LevelError) {
+		return
+	}
 	_ = h.logger.Log(LevelError, h.msgKey, h.sprintf(format, a...))
 }
 
diff --git a/middleware/logging/logging.go b/middleware/logging/logging.go
index 9573d198..8595750b 100644
--- a/middleware/logging/logging.go
+++ b/middleware/logging/logging.go
@@ -37,7 +37,7 @@ func Server(logger log.Logger) middleware.Middleware {
 				reason = se.Reason
 			}
 			level, stack := extractError(err)
-			_ = log.WithContext(ctx, logger).Log(level,
+			log.NewHelper(log.WithContext(ctx, logger)).Log(level,
 				"kind", "server",
 				"component", kind,
 				"operation", operation,
@@ -73,7 +73,7 @@ func Client(logger log.Logger) middleware.Middleware {
 				reason = se.Reason
 			}
 			level, stack := extractError(err)
-			_ = log.WithContext(ctx, logger).Log(level,
+			log.NewHelper(log.WithContext(ctx, logger)).Log(level,
 				"kind", "client",
 				"component", kind,
 				"operation", operation,
diff --git a/transport/http/server.go b/transport/http/server.go
index c967e4a1..9c5550b2 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -141,6 +141,18 @@ func PathPrefix(prefix string) ServerOption {
 	}
 }
 
+func NotFoundHandler(handler http.Handler) ServerOption {
+	return func(s *Server) {
+		s.router.NotFoundHandler = handler
+	}
+}
+
+func MethodNotAllowedHandler(handler http.Handler) ServerOption {
+	return func(s *Server) {
+		s.router.MethodNotAllowedHandler = handler
+	}
+}
+
 // Server is an HTTP server wrapper.
 type Server struct {
 	*http.Server
@@ -177,12 +189,12 @@ func NewServer(opts ...ServerOption) *Server {
 		strictSlash: true,
 		router:      mux.NewRouter(),
 	}
+	srv.router.NotFoundHandler = http.DefaultServeMux
+	srv.router.MethodNotAllowedHandler = http.DefaultServeMux
 	for _, o := range opts {
 		o(srv)
 	}
 	srv.router.StrictSlash(srv.strictSlash)
-	srv.router.NotFoundHandler = http.DefaultServeMux
-	srv.router.MethodNotAllowedHandler = http.DefaultServeMux
 	srv.router.Use(srv.filter())
 	srv.Server = &http.Server{
 		Handler:   FilterChain(srv.filters...)(srv.router),


Test patch:
diff --git a/contrib/errortracker/sentry/sentry_test.go b/contrib/errortracker/sentry/sentry_test.go
new file mode 100644
index 00000000..210c1762
--- /dev/null
+++ b/contrib/errortracker/sentry/sentry_test.go
@@ -0,0 +1,49 @@
+package sentry
+
+import (
+	"testing"
+	"time"
+)
+
+func TestWithTags(t *testing.T) {
+	opts := new(options)
+	strval := "bar"
+	kvs := map[string]interface{}{
+		"foo": strval,
+	}
+	funcTags := WithTags(kvs)
+	funcTags(opts)
+	if opts.tags["foo"].(string) != strval {
+		t.Errorf("TestWithTags() = %v, want %v", opts.tags["foo"].(string), strval)
+	}
+}
+
+func TestWithRepanic(t *testing.T) {
+	opts := new(options)
+	val := true
+	f := WithRepanic(val)
+	f(opts)
+	if opts.repanic != val {
+		t.Errorf("TestWithRepanic() = %v, want %v", opts.repanic, val)
+	}
+}
+
+func TestWithWaitForDelivery(t *testing.T) {
+	opts := new(options)
+	val := true
+	f := WithWaitForDelivery(val)
+	f(opts)
+	if opts.waitForDelivery != val {
+		t.Errorf("TestWithWaitForDelivery() = %v, want %v", opts.waitForDelivery, val)
+	}
+}
+
+func TestWithTimeout(t *testing.T) {
+	opts := new(options)
+	val := time.Second * 10
+	f := WithTimeout(val)
+	f(opts)
+	if opts.timeout != val {
+		t.Errorf("TestWithTimeout() = %v, want %v", opts.timeout, val)
+	}
+}
diff --git a/contrib/log/zap/zap_test.go b/contrib/log/zap/zap_test.go
index 393af79e..c9957300 100644
--- a/contrib/log/zap/zap_test.go
+++ b/contrib/log/zap/zap_test.go
@@ -45,6 +45,7 @@ func TestLogger(t *testing.T) {
 	zlog.Warnw("log", "warn")
 	zlog.Errorw("log", "error")
 	zlog.Errorw("log", "error", "except warn")
+	zlog.Info("hello world")
 
 	except := []string{
 		"{\"level\":\"debug\",\"msg\":\"\",\"log\":\"debug\"}\n",
@@ -52,6 +53,7 @@ func TestLogger(t *testing.T) {
 		"{\"level\":\"warn\",\"msg\":\"\",\"log\":\"warn\"}\n",
 		"{\"level\":\"error\",\"msg\":\"\",\"log\":\"error\"}\n",
 		"{\"level\":\"warn\",\"msg\":\"Keyvalues must appear in pairs: [log error except warn]\"}\n",
+		"{\"level\":\"info\",\"msg\":\"hello world\"}\n", // not {"level":"info","msg":"","msg":"hello world"}
 	}
 	for i, s := range except {
 		if s != syncer.output[i] {
diff --git a/encoding/form/form_test.go b/encoding/form/form_test.go
index 27a52c4b..484e42fa 100644
--- a/encoding/form/form_test.go
+++ b/encoding/form/form_test.go
@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"reflect"
 	"testing"
+	"time"
 
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/fieldmaskpb"
@@ -86,6 +87,10 @@ func TestFormCodecUnmarshal(t *testing.T) {
 }
 
 func TestProtoEncodeDecode(t *testing.T) {
+	loc := time.Local
+	time.Local = time.UTC
+	t.Cleanup(func() { time.Local = loc })
+
 	in := &complex.Complex{
 		Id:      2233,
 		NoOne:   "2233",
diff --git a/encoding/form/proto_encode_test.go b/encoding/form/proto_encode_test.go
index 7d0cfa9e..1b4e11e3 100644
--- a/encoding/form/proto_encode_test.go
+++ b/encoding/form/proto_encode_test.go
@@ -2,6 +2,7 @@ package form
 
 import (
 	"testing"
+	"time"
 
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/fieldmaskpb"
@@ -12,6 +13,10 @@ import (
 )
 
 func TestEncodeValues(t *testing.T) {
+	loc := time.Local
+	time.Local = time.UTC
+	t.Cleanup(func() { time.Local = loc })
+
 	in := &complex.Complex{
 		Id:      2233,
 		NoOne:   "2233",
diff --git a/log/helper_test.go b/log/helper_test.go
index c52f338b..750710c2 100644
--- a/log/helper_test.go
+++ b/log/helper_test.go
@@ -46,6 +46,13 @@ func BenchmarkHelperPrint(b *testing.B) {
 	}
 }
 
+func BenchmarkHelperPrintFilterLevel(b *testing.B) {
+	log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterLevel(LevelDebug)))
+	for i := 0; i < b.N; i++ {
+		log.Debug("test")
+	}
+}
+
 func BenchmarkHelperPrintf(b *testing.B) {
 	log := NewHelper(NewStdLogger(io.Discard))
 	for i := 0; i < b.N; i++ {
@@ -53,6 +60,13 @@ func BenchmarkHelperPrintf(b *testing.B) {
 	}
 }
 
+func BenchmarkHelperPrintfFilterLevel(b *testing.B) {
+	log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterLevel(LevelInfo)))
+	for i := 0; i < b.N; i++ {
+		log.Debugf("%s", "test")
+	}
+}
+
 func BenchmarkHelperPrintw(b *testing.B) {
 	log := NewHelper(NewStdLogger(io.Discard))
 	for i := 0; i < b.N; i++ {
diff --git a/middleware/logging/logging_test.go b/middleware/logging/logging_test.go
index 76cad19b..1e949518 100644
--- a/middleware/logging/logging_test.go
+++ b/middleware/logging/logging_test.go
@@ -177,3 +177,59 @@ func TestExtractError(t *testing.T) {
 		})
 	}
 }
+
+type extractKeyValues [][]any
+
+func (l *extractKeyValues) Log(_ log.Level, kv ...any) error { *l = append(*l, kv); return nil }
+
+func TestServer_CallerPath(t *testing.T) {
+	var a extractKeyValues
+	logger := log.With(&a, "caller", log.Caller(5)) // report where the helper was called
+
+	// make sure the caller is same
+	sameCaller := func(fn middleware.Handler) { _, _ = fn(context.Background(), nil) }
+
+	// caller: [... log inside middleware, fn(context.Background(), nil)]
+	h := func(context.Context, any) (a any, e error) { return }
+	h = Server(logger)(h)
+	sameCaller(h)
+
+	// caller: [... helper.Info("foo"), fn(context.Background(), nil)]
+	helper := log.NewHelper(logger)
+	sameCaller(func(context.Context, any) (a any, e error) { helper.Info("foo"); return })
+
+	t.Log(a[0])
+	t.Log(a[1])
+	if a[0][0] != "caller" || a[1][0] != "caller" {
+		t.Fatal("caller not found")
+	}
+	if a[0][1] != a[1][1] {
+		t.Fatalf("middleware should have the same caller as log.Helper. middleware: %s, helper: %s", a[0][1], a[1][1])
+	}
+}
+
+func TestClient_CallerPath(t *testing.T) {
+	var a extractKeyValues
+	logger := log.With(&a, "caller", log.Caller(5)) // report where the helper was called
+
+	// make sure the caller is same
+	sameCaller := func(fn middleware.Handler) { _, _ = fn(context.Background(), nil) }
+
+	// caller: [... log inside middleware, fn(context.Background(), nil)]
+	h := func(context.Context, any) (a any, e error) { return }
+	h = Client(logger)(h)
+	sameCaller(h)
+
+	// caller: [... helper.Info("foo"), fn(context.Background(), nil)]
+	helper := log.NewHelper(logger)
+	sameCaller(func(context.Context, any) (a any, e error) { helper.Info("foo"); return })
+
+	t.Log(a[0])
+	t.Log(a[1])
+	if a[0][0] != "caller" || a[1][0] != "caller" {
+		t.Fatal("caller not found")
+	}
+	if a[0][1] != a[1][1] {
+		t.Fatalf("middleware should have the same caller as log.Helper. middleware: %s, helper: %s", a[0][1], a[1][1])
+	}
+}
diff --git a/transport/http/binding/encode_test.go b/transport/http/binding/encode_test.go
index 4236abf3..066e0624 100644
--- a/transport/http/binding/encode_test.go
+++ b/transport/http/binding/encode_test.go
@@ -33,12 +33,6 @@ func TestEncodeURL(t *testing.T) {
 			needQuery:    false,
 			want:         "http://helloworld.Greeter/helloworld/{}/sub/hello",
 		},
-		{
-			pathTemplate: "http://helloworld.Greeter/helloworld/{}/sub/{sub.naming}",
-			request:      &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "hello"}},
-			needQuery:    false,
-			want:         "http://helloworld.Greeter/helloworld/{}/sub/hello",
-		},
 		{
 			pathTemplate: "http://helloworld.Greeter/helloworld/{}/sub/{sub.name.cc}",
 			request:      &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "hello"}},
@@ -69,12 +63,6 @@ func TestEncodeURL(t *testing.T) {
 			needQuery:    false,
 			want:         "http://helloworld.Greeter/helloworld/test/sub/",
 		},
-		{
-			pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}",
-			request:      &binding.HelloRequest{Name: "test"},
-			needQuery:    false,
-			want:         "http://helloworld.Greeter/helloworld/test/sub/",
-		},
 		{
 			pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub",
 			request:      &binding.HelloRequest{Name: "go", Sub: &binding.Sub{Name: "kratos"}},
@@ -132,8 +120,8 @@ func TestEncodeURL(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		if EncodeURL(test.pathTemplate, test.request, test.needQuery) != test.want {
-			t.Fatalf("want: %s, got: %s", test.want, EncodeURL(test.pathTemplate, test.request, test.needQuery))
+		if path := EncodeURL(test.pathTemplate, test.request, test.needQuery); path != test.want {
+			t.Fatalf("want: %s, got: %s", test.want, path)
 		}
 	}
 }
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index 781c06a7..44477504 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -371,3 +371,19 @@ func TestListener(t *testing.T) {
 		t.Errorf("expected not empty")
 	}
 }
+
+func TestNotFoundHandler(t *testing.T) {
+	mux := http.NewServeMux()
+	srv := NewServer(NotFoundHandler(mux))
+	if !reflect.DeepEqual(srv.router.NotFoundHandler, mux) {
+		t.Errorf("expected %v got %v", mux, srv.router.NotFoundHandler)
+	}
+}
+
+func TestMethodNotAllowedHandler(t *testing.T) {
+	mux := http.NewServeMux()
+	srv := NewServer(MethodNotAllowedHandler(mux))
+	if !reflect.DeepEqual(srv.router.MethodNotAllowedHandler, mux) {
+		t.Errorf("expected %v got %v", mux, srv.router.MethodNotAllowedHandler)
+	}
+}