Repository: go-kratos/kratos
Pull request #3150 (2024-01-08 18:41:38)

build(deps): bump golang.org/x/sync from 0.5.0 to 0.6.0 in /contrib/registry/zookeeper
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.5.0 to 0.6.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/golang/sync/commit/59c1ca1e4661ed4452be4069ceea3c233f4deec1"><code>59c1ca1</code></a> errgroup: add reference to sync.WaitGroup</li>
<li>See full diff in <a href="https://github.com/golang/sync/compare/v0.5.0...v0.6.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sync&package-manager=go_modules&previous-version=0.5.0&new-version=0.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


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/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/metrics/prometheus/go.mod b/contrib/metrics/prometheus/go.mod
index ab3234b9..353d3153 100644
--- a/contrib/metrics/prometheus/go.mod
+++ b/contrib/metrics/prometheus/go.mod
@@ -4,18 +4,17 @@ go 1.19
 
 require (
 	github.com/go-kratos/kratos/v2 v2.7.2
-	github.com/prometheus/client_golang v1.17.0
-	github.com/prometheus/common v0.45.0
+	github.com/prometheus/client_golang v1.18.0
+	github.com/prometheus/common v0.46.0
 )
 
 require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
-	github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
-	github.com/prometheus/procfs v0.11.1 // indirect
-	golang.org/x/sys v0.13.0 // indirect
-	google.golang.org/protobuf v1.31.0 // indirect
+	github.com/prometheus/client_model v0.5.0 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/metrics/prometheus/go.sum b/contrib/metrics/prometheus/go.sum
index a15317e9..7a51e218 100644
--- a/contrib/metrics/prometheus/go.sum
+++ b/contrib/metrics/prometheus/go.sum
@@ -2,22 +2,16 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
-github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
-github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
-github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
-github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
-github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
-github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
-github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
-github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
+github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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/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/contrib/registry/zookeeper/go.mod b/contrib/registry/zookeeper/go.mod
index 09997386..8a642beb 100644
--- a/contrib/registry/zookeeper/go.mod
+++ b/contrib/registry/zookeeper/go.mod
@@ -5,7 +5,7 @@ go 1.19
 require (
 	github.com/go-kratos/kratos/v2 v2.7.2
 	github.com/go-zookeeper/zk v1.0.3
-	golang.org/x/sync v0.5.0
+	golang.org/x/sync v0.6.0
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/registry/zookeeper/go.sum b/contrib/registry/zookeeper/go.sum
index e9dcfb5b..a9c8f05b 100644
--- a/contrib/registry/zookeeper/go.sum
+++ b/contrib/registry/zookeeper/go.sum
@@ -1,4 +1,4 @@
 github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
 github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
-golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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,


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)
 		}
 	}
 }