Repository: go-kratos/kratos
Pull request #3160 (2024-01-18 10:37:02)

fix: polaris registry bug: nodes is empty at startup
<!--
🎉 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
-->

北极星服务发现基本不可用,在项目初始化时如果依赖的服务节点没有变化(推送watch),那么Next()函数会阻塞,导致后启动的服务获取不到其他节点信息.

#### 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)`.
-->


#### Other special notes for the reviewers:
<!--
* Somethings that need extra attention for the reviewers
* Some additional notes, TODO list, etc.
-->


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/go-kratos/kratos/pull/3160?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`a556a2b`)](https://app.codecov.io/gh/go-kratos/kratos/commit/a556a2b531202edc57f8b877b4d4ffbcc018ee6c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) 84.62% compared to head [(`134cde6`)](https://app.codecov.io/gh/go-kratos/kratos/pull/3160?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) 84.72%.

> :exclamation: Current head 134cde6 differs from pull request most recent head 889ac7e. Consider uploading reports for the commit 889ac7e to get more accurate results


:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

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


```diff
@@            Coverage Diff             @@
##             main    #3160      +/-   ##
==========================================
+ Coverage   84.62%   84.72%   +0.10%     
==========================================
  Files          88       88              
  Lines        3993     3993              
==========================================
+ Hits         3379     3383       +4     
+ Misses        440      438       -2     
+ Partials      174      172       -2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/go-kratos/kratos/pull/3160?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index b28a5a28..f667b135 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:
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/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/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...))
 }
 


Test patch:
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/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)
 		}
 	}
 }