Repository: go-kratos/kratos
Pull request #1298 (2021-08-05 04:08:23)

refactor: optimize the code and use golangci-lint to check for any errors
1. open golangci-lint check all errors
2. optimize log helper
3. replace grpc.WithBalancerName to new method
4. add more log, such as app.Stop() error
5. specification of some comments


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 73ca1662..8081ca1f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -10,4 +10,5 @@ linters:
     - staticcheck
     - govet
     - gosimple
-    - gofmt
\ No newline at end of file
+    - gofmt
+    - errcheck
\ No newline at end of file
diff --git a/README_zh.md b/README_zh.md
index dc1d7935..3b2fd176 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -31,7 +31,7 @@ Kratos 一套轻量级 Go 微服务框架，包含大量微服务相关功能及
 * 工具链：包含大量工具链，比如 cache 代码生成，lint 工具等等；
 
 ## Features
-* [APIs]((examples/helloworld/helloworld))：协议通信以 HTTP/gRPC 为基础，通过 Protobuf 进行定义；
+* [APIs](examples/helloworld/helloworld)：协议通信以 HTTP/gRPC 为基础，通过 Protobuf 进行定义；
 * [Errors](examples/errors/api)：通过 Protobuf 的 Enum 作为错误码定义，以及工具生成判定接口；
 * [Metadata](examples/metadata)：在协议通信 HTTP/gRPC 中，通过 Middleware 规范化服务元信息传递；
 * [Config](examples/config)：支持多数据源方式，进行配置合并铺平，通过 Atomic 方式支持动态配置；
diff --git a/app.go b/app.go
index 7ef71ce3..9d5657f0 100644
--- a/app.go
+++ b/app.go
@@ -38,7 +38,7 @@ type App struct {
 func New(opts ...Option) *App {
 	options := options{
 		ctx:              context.Background(),
-		logger:           log.DefaultLogger,
+		logger:           log.NewHelper(log.DefaultLogger),
 		sigs:             []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
 		registrarTimeout: 10 * time.Second,
 	}
@@ -114,7 +114,10 @@ func (a *App) Run() error {
 			case <-ctx.Done():
 				return ctx.Err()
 			case <-c:
-				a.Stop()
+				err := a.Stop()
+				if err != nil {
+					a.opts.logger.Errorf("failed to app stop: %v", err)
+				}
 			}
 		}
 	})
diff --git a/cmd/kratos/internal/proto/server/server.go b/cmd/kratos/internal/proto/server/server.go
index a30f3caa..f3970e7e 100644
--- a/cmd/kratos/internal/proto/server/server.go
+++ b/cmd/kratos/internal/proto/server/server.go
@@ -60,7 +60,8 @@ func run(cmd *cobra.Command, args []string) {
 			for _, e := range s.Elements {
 				r, ok := e.(*proto.RPC)
 				if ok {
-					cs.Methods = append(cs.Methods, &Method{Service: s.Name, Name: r.Name, Request: r.RequestType, Reply: r.ReturnsType})
+					cs.Methods = append(cs.Methods, &Method{Service: s.Name, Name: r.Name, Request: r.RequestType,
+						Reply: r.ReturnsType, Type: getMethodType(r.StreamsRequest, r.StreamsReturns)})
 				}
 			}
 			res = append(res, cs)
@@ -86,3 +87,16 @@ func run(cmd *cobra.Command, args []string) {
 		fmt.Println(to)
 	}
 }
+
+func getMethodType(streamsRequest, streamsReturns bool) MethodType {
+	if !streamsRequest && !streamsReturns {
+		return unaryType
+	} else if streamsRequest && streamsReturns {
+		return twoWayStreamsType
+	} else if streamsRequest {
+		return requestStreamsType
+	} else if streamsReturns {
+		return returnsStreamsType
+	}
+	return unaryType
+}
diff --git a/cmd/kratos/internal/proto/server/template.go b/cmd/kratos/internal/proto/server/template.go
index 343ef289..54ca8169 100644
--- a/cmd/kratos/internal/proto/server/template.go
+++ b/cmd/kratos/internal/proto/server/template.go
@@ -10,7 +10,12 @@ var serviceTemplate = `
 package service
 
 import (
+	{{- if .UseContext }}
     "context"
+	{{- end }}
+	{{- if .UseIO }}
+	"io"
+	{{- end }}
 
     pb "{{ .Package }}"
     {{- if .GoogleEmpty }}
@@ -28,18 +33,74 @@ func New{{ .Service }}Service() *{{ .Service }}Service {
 
 {{- $s1 := "google.protobuf.Empty" }}
 {{ range .Methods }}
+{{- if eq .Type 1 }}
 func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
 	return {{ if eq .Reply $s1 }}&emptypb.Empty{}{{ else }}&pb.{{ .Reply }}{}{{ end }}, nil
 }
+
+{{- else if eq .Type 2 }}
+func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+		
+		err = conn.Send(&pb.{{ .Reply }}{})
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- else if eq .Type 3 }}
+func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return conn.SendAndClose(&pb.{{ .Reply }}{})
+		}
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- else if eq .Type 4 }}
+func (s *{{ .Service }}Service) {{ .Name }}(req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}, conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		err := conn.Send(&pb.{{ .Reply }}{})
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- end }}
 {{- end }}
 `
 
+type MethodType uint8
+
+const (
+	unaryType          MethodType = 1
+	twoWayStreamsType  MethodType = 2
+	requestStreamsType MethodType = 3
+	returnsStreamsType MethodType = 4
+)
+
 // Service is a proto service.
 type Service struct {
 	Package     string
 	Service     string
 	Methods     []*Method
 	GoogleEmpty bool
+
+	UseIO      bool
+	UseContext bool
 }
 
 // Method is a proto method.
@@ -48,14 +109,24 @@ type Method struct {
 	Name    string
 	Request string
 	Reply   string
+
+	// type: unary or stream
+	Type MethodType
 }
 
 func (s *Service) execute() ([]byte, error) {
 	buf := new(bytes.Buffer)
 	for _, method := range s.Methods {
-		if method.Request == "google.protobuf.Empty" || method.Reply == "google.protobuf.Empty" {
+		if (method.Type == unaryType && (method.Request == "google.protobuf.Empty" || method.Reply == "google.protobuf.Empty")) ||
+			(method.Type == returnsStreamsType && method.Request == "google.protobuf.Empty") {
 			s.GoogleEmpty = true
 		}
+		if method.Type == twoWayStreamsType || method.Type == returnsStreamsType {
+			s.UseIO = true
+		}
+		if method.Type == unaryType {
+			s.UseContext = true
+		}
 	}
 	tmpl, err := template.New("service").Parse(serviceTemplate)
 	if err != nil {
diff --git a/cmd/kratos/internal/run/run.go b/cmd/kratos/internal/run/run.go
index 88c1f575..8025e3c9 100644
--- a/cmd/kratos/internal/run/run.go
+++ b/cmd/kratos/internal/run/run.go
@@ -34,7 +34,7 @@ func Run(cmd *cobra.Command, args []string) {
 	}
 	if dir == "" {
 		// find the directory containing the cmd/*
-		cmdDir, cmdPath, err := findCMD(base)
+		cmdPath, err := findCMD(base)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err)
 			return
@@ -43,17 +43,23 @@ func Run(cmd *cobra.Command, args []string) {
 			fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", "The cmd directory cannot be found in the current directory")
 			return
 		} else if len(cmdPath) == 1 {
-			dir = path.Join(cmdDir, cmdPath[0])
+			for k, v := range cmdPath {
+				dir = path.Join(v, k)
+			}
 		} else {
+			var cmdPaths []string
+			for k, _ := range cmdPath {
+				cmdPaths = append(cmdPaths, k)
+			}
 			prompt := &survey.Select{
 				Message: "Which directory do you want to run?",
-				Options: cmdPath,
+				Options: cmdPaths,
 			}
 			survey.AskOne(prompt, &dir)
 			if dir == "" {
 				return
 			}
-			dir = path.Join(cmdDir, dir)
+			dir = path.Join(cmdPath[dir], dir)
 		}
 	}
 	fd := exec.Command("go", "run", ".")
@@ -67,15 +73,12 @@ func Run(cmd *cobra.Command, args []string) {
 	return
 }
 
-func findCMD(base string) (string, []string, error) {
+func findCMD(base string) (map[string]string, error) {
 	var root bool
-	next := func(dir string) (string, []string, error) {
-		var (
-			cmdDir  string
-			cmdPath []string
-		)
+	next := func(dir string) (map[string]string, error) {
+		cmdPath := make(map[string]string)
 		err := filepath.Walk(dir, func(walkPath string, info os.FileInfo, err error) error {
-			// multi level directory is not allowed under the cmd directory, so it is judged that the path ends with cmd.
+			// multi level directory is not allowed under the cmdPath directory, so it is judged that the path ends with cmdPath.
 			if strings.HasSuffix(walkPath, "cmd") {
 				paths, err := ioutil.ReadDir(walkPath)
 				if err != nil {
@@ -83,10 +86,9 @@ func findCMD(base string) (string, []string, error) {
 				}
 				for _, fileInfo := range paths {
 					if fileInfo.IsDir() {
-						cmdPath = append(cmdPath, path.Join("cmd", fileInfo.Name()))
+						cmdPath[path.Join("cmd", fileInfo.Name())] = filepath.Join(walkPath, "..")
 					}
 				}
-				cmdDir = filepath.Join(walkPath, "..")
 				return nil
 			}
 			if info.Name() == "go.mod" {
@@ -94,20 +96,21 @@ func findCMD(base string) (string, []string, error) {
 			}
 			return nil
 		})
-		return cmdDir, cmdPath, err
+		return cmdPath, err
 	}
 	for i := 0; i < 5; i++ {
-		cmdDir, res, err := next(base)
+		tmp := base
+		cmd, err := next(tmp)
 		if err != nil {
-			return "", nil, err
+			return nil, err
 		}
-		if len(res) > 0 {
-			return cmdDir, res, nil
+		if len(cmd) > 0 {
+			return cmd, nil
 		}
 		if root {
 			break
 		}
-		base = filepath.Join(base, "..")
+		tmp = filepath.Join(base, "..")
 	}
-	return "", []string{base}, nil
+	return map[string]string{"": base}, nil
 }
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index 202e7a5b..fe0303e6 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"os"
 	"strings"
 
 	"github.com/go-kratos/kratos/v2"
@@ -129,7 +130,10 @@ func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotati
 	body = rule.Body
 	responseBody = rule.ResponseBody
 	md := buildMethodDesc(g, m, method, path)
-	if method == "GET" {
+	if method == "GET" || method == "DELETE" {
+		if body != "" {
+			_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s body should not be declared.\n", method, path)
+		}
 		md.HasBody = false
 	} else if body == "*" {
 		md.HasBody = true
@@ -139,6 +143,7 @@ func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotati
 		md.Body = "." + camelCaseVars(body)
 	} else {
 		md.HasBody = false
+		_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s is does not declare a body.\n", method, path)
 	}
 	if responseBody == "*" {
 		md.ResponseBody = ""
diff --git a/config/config.go b/config/config.go
index 9c1fccb0..7f4aa53b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,6 +1,7 @@
 package config
 
 import (
+	"context"
 	"errors"
 	"reflect"
 	"sync"
@@ -65,6 +66,10 @@ func New(opts ...Option) Config {
 func (c *config) watch(w Watcher) {
 	for {
 		kvs, err := w.Next()
+		if errors.Is(err, context.Canceled) {
+			c.log.Infof("watcher's ctx cancel : %v", err)
+			return
+		}
 		if err != nil {
 			time.Sleep(time.Second)
 			c.log.Errorf("failed to watch next config: %v", err)
@@ -107,6 +112,7 @@ func (c *config) Load() error {
 			c.log.Errorf("failed to watch config source: %v", err)
 			return err
 		}
+		c.watchers = append(c.watchers, w)
 		go c.watch(w)
 	}
 	if err := c.reader.Resolve(); err != nil {
diff --git a/config/env/watcher.go b/config/env/watcher.go
index 15a3f8ce..edcaf3e9 100644
--- a/config/env/watcher.go
+++ b/config/env/watcher.go
@@ -1,24 +1,32 @@
 package env
 
 import (
+	"context"
+
 	"github.com/go-kratos/kratos/v2/config"
 )
 
 type watcher struct {
-	exit chan struct{}
+	ctx    context.Context
+	cancel context.CancelFunc
 }
 
+var _ config.Watcher = (*watcher)(nil)
+
 func NewWatcher() (config.Watcher, error) {
-	return &watcher{exit: make(chan struct{})}, nil
+	ctx, cancel := context.WithCancel(context.Background())
+	return &watcher{ctx: ctx, cancel: cancel}, nil
 }
 
 // Next will be blocked until the Stop method is called
 func (w *watcher) Next() ([]*config.KeyValue, error) {
-	<-w.exit
-	return nil, nil
+	select {
+	case <-w.ctx.Done():
+		return nil, w.ctx.Err()
+	}
 }
 
 func (w *watcher) Stop() error {
-	close(w.exit)
+	w.cancel()
 	return nil
 }
diff --git a/config/file/watcher.go b/config/file/watcher.go
index fcb3de59..4c00b046 100644
--- a/config/file/watcher.go
+++ b/config/file/watcher.go
@@ -1,6 +1,7 @@
 package file
 
 import (
+	"context"
 	"os"
 	"path/filepath"
 
@@ -11,8 +12,13 @@ import (
 type watcher struct {
 	f  *file
 	fw *fsnotify.Watcher
+
+	ctx    context.Context
+	cancel context.CancelFunc
 }
 
+var _ config.Watcher = (*watcher)(nil)
+
 func newWatcher(f *file) (config.Watcher, error) {
 	fw, err := fsnotify.NewWatcher()
 	if err != nil {
@@ -21,11 +27,14 @@ func newWatcher(f *file) (config.Watcher, error) {
 	if err := fw.Add(f.path); err != nil {
 		return nil, err
 	}
-	return &watcher{f: f, fw: fw}, nil
+	ctx, cancel := context.WithCancel(context.Background())
+	return &watcher{f: f, fw: fw, ctx: ctx, cancel: cancel}, nil
 }
 
 func (w *watcher) Next() ([]*config.KeyValue, error) {
 	select {
+	case <-w.ctx.Done():
+		return nil, w.ctx.Err()
 	case event := <-w.fw.Events:
 		if event.Op == fsnotify.Rename {
 			if _, err := os.Stat(event.Name); err == nil || os.IsExist(err) {
@@ -53,5 +62,6 @@ func (w *watcher) Next() ([]*config.KeyValue, error) {
 }
 
 func (w *watcher) Stop() error {
+	w.cancel()
 	return w.fw.Close()
 }
diff --git a/config/options.go b/config/options.go
index 3574e974..a96d8939 100644
--- a/config/options.go
+++ b/config/options.go
@@ -84,7 +84,7 @@ func defaultDecoder(src *KeyValue, target map[string]interface{}) error {
 // placeholder format in ${key:default} or $key.
 func defaultResolver(input map[string]interface{}) error {
 	mapper := func(name string) string {
-		args := strings.Split(strings.TrimSpace(name), ":")
+		args := strings.SplitN(strings.TrimSpace(name), ":", 2)
 		if v, has := readValue(input, args[0]); has {
 			s, _ := v.String()
 			return s
diff --git a/config/reader.go b/config/reader.go
index c18b91d3..1410780e 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"strings"
+	"sync"
 
 	"github.com/imdario/mergo"
 	"google.golang.org/protobuf/encoding/protojson"
@@ -23,17 +24,21 @@ type Reader interface {
 type reader struct {
 	opts   options
 	values map[string]interface{}
+	lock   sync.Mutex
 }
 
 func newReader(opts options) Reader {
 	return &reader{
 		opts:   opts,
 		values: make(map[string]interface{}),
+		lock:   sync.Mutex{},
 	}
 }
 
 func (r *reader) Merge(kvs ...*KeyValue) error {
+	r.lock.Lock()
 	merged, err := cloneMap(r.values)
+	r.lock.Unlock()
 	if err != nil {
 		return err
 	}
@@ -46,19 +51,27 @@ func (r *reader) Merge(kvs ...*KeyValue) error {
 			return err
 		}
 	}
+	r.lock.Lock()
 	r.values = merged
+	r.lock.Unlock()
 	return nil
 }
 
 func (r *reader) Value(path string) (Value, bool) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
 	return readValue(r.values, path)
 }
 
 func (r *reader) Source() ([]byte, error) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
 	return marshalJSON(convertMap(r.values))
 }
 
 func (r *reader) Resolve() error {
+	r.lock.Lock()
+	defer r.lock.Unlock()
 	return r.opts.resolver(r.values)
 }
 
diff --git a/config/value.go b/config/value.go
index db122b09..8c6b50f8 100644
--- a/config/value.go
+++ b/config/value.go
@@ -49,7 +49,7 @@ func (v *atomicValue) Int() (int64, error) {
 	case int32:
 		return int64(val), nil
 	case int64:
-		return int64(val), nil
+		return val, nil
 	case float64:
 		return int64(val), nil
 	case string:
@@ -60,7 +60,7 @@ func (v *atomicValue) Int() (int64, error) {
 func (v *atomicValue) Float() (float64, error) {
 	switch val := v.Load().(type) {
 	case float64:
-		return float64(val), nil
+		return val, nil
 	case int:
 		return float64(val), nil
 	case int32:
diff --git a/examples/go.mod b/examples/go.mod
index 6cdfc075..64048ffd 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -3,20 +3,20 @@ module github.com/go-kratos/kratos/examples
 go 1.16
 
 require (
-	entgo.io/ent v0.8.0
+	entgo.io/ent v0.9.0
 	github.com/BurntSushi/toml v0.3.1
 	github.com/envoyproxy/protoc-gen-validate v0.6.1
-	github.com/gin-gonic/gin v1.7.2
+	github.com/gin-gonic/gin v1.7.3
 	github.com/go-kratos/consul v0.1.4
-	github.com/go-kratos/etcd v0.1.0
+	github.com/go-kratos/etcd v0.1.3
 	github.com/go-kratos/gin v0.1.0
 	github.com/go-kratos/kratos/v2 v2.0.3
-	github.com/go-kratos/nacos v0.1.0
+	github.com/go-kratos/nacos v0.1.1
 	github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47
 	github.com/go-kratos/swagger-api v1.0.0
 	github.com/go-kratos/zookeeper v0.0.0-20210721021028-9e7498619b89
 	github.com/go-redis/redis/extra/redisotel v0.3.0
-	github.com/go-redis/redis/v8 v8.9.0
+	github.com/go-redis/redis/v8 v8.11.2
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/google/wire v0.5.0
 	github.com/gorilla/handlers v1.5.1
@@ -24,22 +24,22 @@ require (
 	github.com/gorilla/websocket v1.4.2
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
 	github.com/hashicorp/consul/api v1.9.1
-	github.com/labstack/echo/v4 v4.3.0
-	github.com/nacos-group/nacos-sdk-go v1.0.7
+	github.com/labstack/echo/v4 v4.5.0
+	github.com/nacos-group/nacos-sdk-go v1.0.8
 	github.com/nicksnyder/go-i18n/v2 v2.1.2
 	github.com/prometheus/client_golang v1.11.0
 	github.com/segmentio/kafka-go v0.4.17
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	go.etcd.io/etcd/client/v3 v3.5.0
-	go.opentelemetry.io/otel v1.0.0-RC1
-	go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1
-	go.opentelemetry.io/otel/sdk v1.0.0-RC1
-	go.opentelemetry.io/otel/trace v1.0.0-RC1
-	go.uber.org/zap v1.18.1
+	go.opentelemetry.io/otel v1.0.0-RC2
+	go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2
+	go.opentelemetry.io/otel/sdk v1.0.0-RC2
+	go.opentelemetry.io/otel/trace v1.0.0-RC2
+	go.uber.org/zap v1.19.0
 	golang.org/x/text v0.3.6
-	google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9
-	google.golang.org/grpc v1.39.0
+	google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67
+	google.golang.org/grpc v1.39.1
 	google.golang.org/protobuf v1.27.1
 )
 
diff --git a/examples/go.sum b/examples/go.sum
index 7e1bc575..3b3b8bea 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -32,8 +32,9 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-entgo.io/ent v0.8.0 h1:xirrW//1oda7pp0bz+XssSOv4/C3nmgYQOxjIfljFt8=
 entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc=
+entgo.io/ent v0.9.0 h1:2S1zfpMMW6p+wctj6kcYUprNPNjLWFW06T5MdyAfmWc=
+entgo.io/ent v0.9.0/go.mod h1:6NUeTfUN5mp5YN+5tgoH1SlakSvYPTBOYotSOvaI4ak=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -159,8 +160,9 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
 github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
+github.com/gin-gonic/gin v1.7.3 h1:aMBzLJ/GMEYmv1UWs2FFTcPISLrQH2mRgL9Glz8xows=
+github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-errors/errors v1.4.0 h1:2OA7MFw38+e9na72T1xgkomPb6GzZzzxvJ5U630FoRM=
@@ -172,22 +174,22 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-kratos/consul v0.1.2 h1:vryYF2o3wWQzFyzqKKKc1+GX/FY9T7DSkM3nc/Hy6/s=
 github.com/go-kratos/consul v0.1.2/go.mod h1:FhxdCfkqaqIYfgpGbzGb8ywM/zV3jk+EBS+WSy1FzQQ=
 github.com/go-kratos/consul v0.1.4 h1:qOOQE/FYpbWLV4cipC+jPrrc/gQY1DdfnX0IJvQleXE=
 github.com/go-kratos/consul v0.1.4/go.mod h1:egsB9apqg+4BMYVnY/VuQCeC3aAmd7mpYfIMhs4ga4U=
-github.com/go-kratos/etcd v0.1.0 h1:ewAfloZcpcg2L1QB1VLXYUMdvfUU0i3kAlToHrxTv2g=
 github.com/go-kratos/etcd v0.1.0/go.mod h1:RzWYbka3LjS0v2RbCtADN7d2Ng8eH774ehHlTGgbma4=
+github.com/go-kratos/etcd v0.1.3 h1:ARz1j4nZGgpeSw8WYau5y8LiKbsty/tMASQCaiOGZnM=
+github.com/go-kratos/etcd v0.1.3/go.mod h1:4UAwbmm/PIjUS9KwhqmfsBi9pksuvwc3ae+xaKF0Lwk=
 github.com/go-kratos/gin v0.1.0 h1:yq5GfZnSNo8cOIqxqPE0FVNQ8fm++oKQBd3/rTTp4oI=
 github.com/go-kratos/gin v0.1.0/go.mod h1:KzE88+mV1QJdmiT+sU2D560ucOXnWZx+4m0OnVcSyzQ=
 github.com/go-kratos/grpc-gateway/v2 v2.0.0-20210804092615-bdc92eb5ce83 h1:YN1KsqX3mB8hkSrOLZv3xZaZh+lZ/L7QC00RATFLJSg=
 github.com/go-kratos/grpc-gateway/v2 v2.0.0-20210804092615-bdc92eb5ce83/go.mod h1:qV5/s5A7wfY7GkNzptcUFRGAG4Y8H4mlXpSrOkDEIk0=
 github.com/go-kratos/kratos/examples v0.0.0-20210718121552-f0d077a3286f/go.mod h1:R5ZvhHSeFgk+JynHp3yMSkjaATRA3FRatecP4kQibsg=
-github.com/go-kratos/nacos v0.1.0 h1:sUvTsgZ4u88YaKF/jAi2Rdr+hzPCfQtmNjBpiyEbbJ0=
 github.com/go-kratos/nacos v0.1.0/go.mod h1:qhVsyp6RyDDEKLtT0/iTMaN3D7SheRMrYb2bV0jYqjw=
+github.com/go-kratos/nacos v0.1.1 h1:0d3+yucPGIin+RSw+EDnbJAzJCOZn6Ci3b5Pn5SghTY=
+github.com/go-kratos/nacos v0.1.1/go.mod h1:qhVsyp6RyDDEKLtT0/iTMaN3D7SheRMrYb2bV0jYqjw=
 github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47 h1:uJennLdg0rwhrIl0bjsObwub+I3U0vJGAhpK6CYASrM=
 github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47/go.mod h1:e3D0pnFDX5SG+ZGmGONd83WvrbmFWCdx5l4Hkl3I1eY=
-github.com/go-kratos/swagger-api v0.1.8 h1:4afwLcDwMut7LyS5b0iarB5p6XTbaJngRvAqDb4Skig=
 github.com/go-kratos/swagger-api v0.1.8/go.mod h1:HMI1NY/OWbjbkZF2z//tEKoL1XpZOzyxOedtBddEyZE=
 github.com/go-kratos/swagger-api v1.0.0 h1:R1JcAqLSvfDzqHoDnpK3Jt1MEJAXGNifV7AnBEhP3PE=
 github.com/go-kratos/swagger-api v1.0.0/go.mod h1:BPeS1OUN4jtMitb99ptZAagAgyQ51S2F2vd9dT/J3eM=
@@ -214,8 +216,9 @@ github.com/go-redis/redis/extra/redisotel v0.3.0 h1:8rrizwFAUUeMgmelyiQi9KeFwmpQ
 github.com/go-redis/redis/extra/redisotel v0.3.0/go.mod h1:sGV3dQnPMBUuqzowEP2nZPhLXCMeh83nY64yaju249c=
 github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
 github.com/go-redis/redis/v8 v8.5.0/go.mod h1:YmEcgBDttjnkbMzDAhDtQxY9yVA7jMN6PCR5HeMvqFE=
-github.com/go-redis/redis/v8 v8.9.0 h1:FTTbB7WqlXfVNdVv0SsxA+oVi0bAwit6bMe3IUucq2o=
 github.com/go-redis/redis/v8 v8.9.0/go.mod h1:ik7vb7+gm8Izylxu6kf6wG26/t2VljgCfSQ1DM4O1uU=
+github.com/go-redis/redis/v8 v8.11.2 h1:WqlSpAwz8mxDSMCvbyz1Mkiqe0LE5OY4j3lgkvu1Ts0=
+github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.1-0.20200311113236-681ffa848bae/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@@ -233,6 +236,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU=
 github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -431,8 +435,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.3.0 h1:DCP6cbtT+Zu++K6evHOJzSgA2115cPMuCx0xg55q1EQ=
 github.com/labstack/echo/v4 v4.3.0/go.mod h1:PvmtTvhVqKDzDQy4d3bWzPjZLzom4iQbAZy2sgZ/qI8=
+github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -445,9 +450,9 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod
 github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
 github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
 github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/longXboy/grpc-gateway/v2 v2.0.0-20210707031540-bd2d73d86cee h1:lmk9VXnkwZiLgbQdlM4EfJBZAaTZTfex/hZ9ByQrzqo=
 github.com/longXboy/grpc-gateway/v2 v2.0.0-20210707031540-bd2d73d86cee/go.mod h1:upmbHHZp+gtRNfSk94XgTH+14LqZxzmvgnrp9KEMKRg=
 github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@@ -470,6 +475,7 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -497,8 +503,9 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nacos-group/nacos-sdk-go v1.0.7 h1:Am1tJFe7GUTNCREKsZ5ok0H2OspHDRmRcsxn7DiSwhA=
 github.com/nacos-group/nacos-sdk-go v1.0.7/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
+github.com/nacos-group/nacos-sdk-go v1.0.8 h1:8pEm05Cdav9sQgJSv5kyvlgfz0SzFUUGI3pWX6SiSnM=
+github.com/nacos-group/nacos-sdk-go v1.0.8/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
 github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
 github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
@@ -685,13 +692,16 @@ go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw=
 go.etcd.io/etcd/api/v3 v3.5.0-beta.4/go.mod h1:yF0YUmBghT48aC0/eTFrhULo+uKQAr5spQQ6sRhPauE=
+go.etcd.io/etcd/api/v3 v3.5.0-rc.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0-beta.4/go.mod h1:a+pbz+UrcOpvve1Qxf6tGovi15PjgtRhi0QTO2Nlc4U=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0-rc.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8=
 go.etcd.io/etcd/client/v3 v3.5.0-beta.4/go.mod h1:0L1RulN1QSXq6uKPMUSX+OTAYyFkapMK7iUHXXIH/1E=
+go.etcd.io/etcd/client/v3 v3.5.0-rc.1/go.mod h1:2N1F3ShRL8f2OhE5R4JvjHicW6XDgYMZMH9JES76bOQ=
 go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
 go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
 go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY=
@@ -707,33 +717,35 @@ go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/
 go.opentelemetry.io/otel v0.16.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA=
 go.opentelemetry.io/otel v0.17.0/go.mod h1:Oqtdxmf7UtEvL037ohlgnaYa1h7GtMh0NcSd9eqkC9s=
 go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
-go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
 go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
-go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1 h1:tVhw2BMSAk248rhdeirOe9hlXKwGHDvVtF7P8F+H2DU=
+go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg=
+go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1/go.mod h1:FXJnjGCoTQL6nQ8OpFJ0JI1DrdOvMoVx49ic0Hg4+D4=
-go.opentelemetry.io/otel/internal/metric v0.21.0 h1:gZlIBo5O51hZOOZz8vEcuRx/l5dnADadKfpT70AELoo=
+go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2 h1:RF0nWsIDpDBe+s06lkLxUw9CWQUAhO6hBSxxB7dz45s=
+go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2/go.mod h1:sZZqN3Vb0iT+NE6mZ1S7sNyH3t4PFk6ElK5TLGFBZ7E=
 go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo=
 go.opentelemetry.io/otel/metric v0.17.0/go.mod h1:hUz9lH1rNXyEwWAhIWCMFWKhYtpASgSnObJFnU26dJ0=
 go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
-go.opentelemetry.io/otel/metric v0.21.0 h1:ZtcJlHqVE4l8Su0WOLOd9fEPheJuYEiQ0wr9wv2p25I=
 go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc=
 go.opentelemetry.io/otel/oteltest v0.17.0/go.mod h1:JT/LGFxPwpN+nlsTiinSYjdIx3hZIGqHCpChcIZmdoE=
 go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
-go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk=
 go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
-go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q=
 go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2 h1:ROuteeSCBaZNjiT9JcFzZepmInDvLktR28Y6qKo8bCs=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw=
 go.opentelemetry.io/otel/trace v0.17.0/go.mod h1:bIujpqg6ZL6xUTubIUgziI1jSaUPthmabA/ygf/6Cfg=
 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
-go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
 go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w=
+go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
+go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -749,8 +761,9 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
 go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 go.uber.org/zap v1.16.1-0.20210329175301-c23abee72d19/go.mod h1:aMfIlz3TDBfB0BwTCKFU1XbEmj9zevr5S5LcBr85MXw=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
 go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
+go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
+go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -797,7 +810,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -841,8 +853,10 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -922,10 +936,14 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -998,8 +1016,9 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1061,10 +1080,11 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210611144927-798beca9d670/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
-google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
-google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9 h1:HBPuvo39L0DgfVn9eHR3ki/RjZoUFWa+em77e7KFDfs=
 google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 h1:VmMSf20ssFK0+u1dscyTH9bU4/M4y+X/xNfkvD6kGtM=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -1088,8 +1108,9 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1 h1:f37vZbBVTiJ6jKG5mWz8ySOBxNqy6ViPgyhSdVnxF3E=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/examples/registry/etcd/client/main.go b/examples/registry/etcd/client/main.go
index d7d02c22..00653466 100644
--- a/examples/registry/etcd/client/main.go
+++ b/examples/registry/etcd/client/main.go
@@ -10,6 +10,7 @@ import (
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
 	clientv3 "go.etcd.io/etcd/client/v3"
+	srcgrpc "google.golang.org/grpc"
 )
 
 func main() {
@@ -20,15 +21,8 @@ func main() {
 		panic(err)
 	}
 	r := registry.New(cli)
-	for {
-		callHTTP(r)
-		callGRPC(r)
-		time.Sleep(time.Second)
-	}
-}
 
-func callGRPC(r *registry.Registry) {
-	conn, err := grpc.DialInsecure(
+	connGrpc, err := grpc.DialInsecure(
 		context.Background(),
 		grpc.WithEndpoint("discovery:///helloworld"),
 		grpc.WithDiscovery(r),
@@ -36,17 +30,9 @@ func callGRPC(r *registry.Registry) {
 	if err != nil {
 		log.Fatal(err)
 	}
-	defer conn.Close()
-	client := helloworld.NewGreeterClient(conn)
-	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
-	if err != nil {
-		log.Fatal(err)
-	}
-	log.Printf("[grpc] SayHello %+v\n", reply)
-}
+	defer connGrpc.Close()
 
-func callHTTP(r *registry.Registry) {
-	conn, err := http.NewClient(
+	connHttp, err := http.NewClient(
 		context.Background(),
 		http.WithEndpoint("discovery:///helloworld"),
 		http.WithDiscovery(r),
@@ -55,7 +41,25 @@ func callHTTP(r *registry.Registry) {
 	if err != nil {
 		log.Fatal(err)
 	}
-	defer conn.Close()
+	defer connHttp.Close()
+
+	for {
+		callHTTP(r, connHttp)
+		callGRPC(r, connGrpc)
+		time.Sleep(time.Second)
+	}
+}
+
+func callGRPC(r *registry.Registry, conn *srcgrpc.ClientConn) {
+	client := helloworld.NewGreeterClient(conn)
+	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("[grpc] SayHello %+v\n", reply)
+}
+
+func callHTTP(r *registry.Registry,conn *http.Client) {
 	client := helloworld.NewGreeterHTTPClient(conn)
 	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
 	if err != nil {
diff --git a/examples/stream/client/main.go b/examples/stream/client/main.go
new file mode 100644
index 00000000..5086ae7e
--- /dev/null
+++ b/examples/stream/client/main.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/go-kratos/kratos/examples/stream/hello"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
+)
+
+var wg = sync.WaitGroup{}
+
+func main() {
+	conn, err := transgrpc.DialInsecure(
+		context.Background(),
+		transgrpc.WithEndpoint("127.0.0.1:9001"),
+		transgrpc.WithMiddleware(
+			recovery.Recovery(),
+		),
+	)
+	if err != nil {
+		panic(err)
+	}
+	defer conn.Close()
+
+	client := hello.NewHelloClient(conn)
+
+	wg.Add(3)
+
+	go getNumber(client)
+	go uploadLog(client)
+	go chat(client)
+
+	wg.Wait()
+}
+
+func getNumber(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.GetNumber(context.Background(), &hello.GetNumberRequest{Data: "2021/08/01"})
+	if err != nil {
+		log.Fatal(err)
+	}
+	for {
+		res, err := stream.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		// 打印返回值
+		log.Println(res.Number)
+	}
+}
+
+func uploadLog(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.UploadLog(context.Background())
+	if err != nil {
+		log.Fatal(err)
+	}
+	var number int
+	for {
+		err := stream.Send(&hello.UploadLogRequest{Log: "log:" + strconv.Itoa(number)})
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		time.Sleep(time.Millisecond * 50)
+		number++
+	}
+	return
+}
+
+func chat(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.Chat(context.Background())
+	if err != nil {
+		log.Fatal(err)
+	}
+	var number int
+	for {
+		err = stream.Send(&hello.ChatRequest{UpMsg: "kratos:" + strconv.Itoa(number)})
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		res, err := stream.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		fmt.Println(res.DownMsg)
+		time.Sleep(time.Millisecond * 50)
+		number++
+	}
+}
diff --git a/examples/stream/hello/hello.pb.go b/examples/stream/hello/hello.pb.go
new file mode 100644
index 00000000..b1b00122
--- /dev/null
+++ b/examples/stream/hello/hello.pb.go
@@ -0,0 +1,479 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.17.3
+// source: stream/hello/hello.proto
+
+package hello
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GetNumberRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *GetNumberRequest) Reset() {
+	*x = GetNumberRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetNumberRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetNumberRequest) ProtoMessage() {}
+
+func (x *GetNumberRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetNumberRequest.ProtoReflect.Descriptor instead.
+func (*GetNumberRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetNumberRequest) GetData() string {
+	if x != nil {
+		return x.Data
+	}
+	return ""
+}
+
+type GetNumberReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *GetNumberReply) Reset() {
+	*x = GetNumberReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetNumberReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetNumberReply) ProtoMessage() {}
+
+func (x *GetNumberReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetNumberReply.ProtoReflect.Descriptor instead.
+func (*GetNumberReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetNumberReply) GetNumber() int64 {
+	if x != nil {
+		return x.Number
+	}
+	return 0
+}
+
+type UploadLogRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Log string `protobuf:"bytes,1,opt,name=log,proto3" json:"log,omitempty"`
+}
+
+func (x *UploadLogRequest) Reset() {
+	*x = UploadLogRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UploadLogRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UploadLogRequest) ProtoMessage() {}
+
+func (x *UploadLogRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UploadLogRequest.ProtoReflect.Descriptor instead.
+func (*UploadLogRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *UploadLogRequest) GetLog() string {
+	if x != nil {
+		return x.Log
+	}
+	return ""
+}
+
+type UploadLogReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Res string `protobuf:"bytes,1,opt,name=res,proto3" json:"res,omitempty"`
+}
+
+func (x *UploadLogReply) Reset() {
+	*x = UploadLogReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UploadLogReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UploadLogReply) ProtoMessage() {}
+
+func (x *UploadLogReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UploadLogReply.ProtoReflect.Descriptor instead.
+func (*UploadLogReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *UploadLogReply) GetRes() string {
+	if x != nil {
+		return x.Res
+	}
+	return ""
+}
+
+type ChatRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	UpMsg string `protobuf:"bytes,1,opt,name=up_msg,json=upMsg,proto3" json:"up_msg,omitempty"`
+}
+
+func (x *ChatRequest) Reset() {
+	*x = ChatRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ChatRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatRequest) ProtoMessage() {}
+
+func (x *ChatRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatRequest.ProtoReflect.Descriptor instead.
+func (*ChatRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ChatRequest) GetUpMsg() string {
+	if x != nil {
+		return x.UpMsg
+	}
+	return ""
+}
+
+type ChatReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	DownMsg string `protobuf:"bytes,1,opt,name=down_msg,json=downMsg,proto3" json:"down_msg,omitempty"`
+}
+
+func (x *ChatReply) Reset() {
+	*x = ChatReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ChatReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatReply) ProtoMessage() {}
+
+func (x *ChatReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatReply.ProtoReflect.Descriptor instead.
+func (*ChatReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ChatReply) GetDownMsg() string {
+	if x != nil {
+		return x.DownMsg
+	}
+	return ""
+}
+
+var File_stream_hello_hello_proto protoreflect.FileDescriptor
+
+var file_stream_hello_hello_proto_rawDesc = []byte{
+	0x0a, 0x18, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, 0x68,
+	0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x26, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4e,
+	0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
+	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
+	0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x24, 0x0a, 0x10, 0x55, 0x70,
+	0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10,
+	0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6c, 0x6f, 0x67,
+	0x22, 0x22, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x03, 0x72, 0x65, 0x73, 0x22, 0x24, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x70, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x70, 0x4d, 0x73, 0x67, 0x22, 0x26, 0x0a, 0x09, 0x43, 0x68,
+	0x61, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x5f,
+	0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x77, 0x6e, 0x4d,
+	0x73, 0x67, 0x32, 0xe1, 0x01, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x4b, 0x0a, 0x09,
+	0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x09, 0x55, 0x70, 0x6c,
+	0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e,
+	0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e,
+	0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52,
+	0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x12, 0x3e, 0x0a, 0x04, 0x43, 0x68, 0x61, 0x74, 0x12, 0x19,
+	0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x43, 0x68,
+	0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x28, 0x01, 0x30, 0x01, 0x42, 0x49, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d,
+	0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b,
+	0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x73,
+	0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3b, 0x68, 0x65, 0x6c, 0x6c,
+	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_stream_hello_hello_proto_rawDescOnce sync.Once
+	file_stream_hello_hello_proto_rawDescData = file_stream_hello_hello_proto_rawDesc
+)
+
+func file_stream_hello_hello_proto_rawDescGZIP() []byte {
+	file_stream_hello_hello_proto_rawDescOnce.Do(func() {
+		file_stream_hello_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_stream_hello_hello_proto_rawDescData)
+	})
+	return file_stream_hello_hello_proto_rawDescData
+}
+
+var file_stream_hello_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_stream_hello_hello_proto_goTypes = []interface{}{
+	(*GetNumberRequest)(nil), // 0: stream.hello.GetNumberRequest
+	(*GetNumberReply)(nil),   // 1: stream.hello.GetNumberReply
+	(*UploadLogRequest)(nil), // 2: stream.hello.UploadLogRequest
+	(*UploadLogReply)(nil),   // 3: stream.hello.UploadLogReply
+	(*ChatRequest)(nil),      // 4: stream.hello.ChatRequest
+	(*ChatReply)(nil),        // 5: stream.hello.ChatReply
+}
+var file_stream_hello_hello_proto_depIdxs = []int32{
+	0, // 0: stream.hello.Hello.GetNumber:input_type -> stream.hello.GetNumberRequest
+	2, // 1: stream.hello.Hello.UploadLog:input_type -> stream.hello.UploadLogRequest
+	4, // 2: stream.hello.Hello.Chat:input_type -> stream.hello.ChatRequest
+	1, // 3: stream.hello.Hello.GetNumber:output_type -> stream.hello.GetNumberReply
+	3, // 4: stream.hello.Hello.UploadLog:output_type -> stream.hello.UploadLogReply
+	5, // 5: stream.hello.Hello.Chat:output_type -> stream.hello.ChatReply
+	3, // [3:6] is the sub-list for method output_type
+	0, // [0:3] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_stream_hello_hello_proto_init() }
+func file_stream_hello_hello_proto_init() {
+	if File_stream_hello_hello_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_stream_hello_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetNumberRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetNumberReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UploadLogRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UploadLogReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ChatRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ChatReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_stream_hello_hello_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_stream_hello_hello_proto_goTypes,
+		DependencyIndexes: file_stream_hello_hello_proto_depIdxs,
+		MessageInfos:      file_stream_hello_hello_proto_msgTypes,
+	}.Build()
+	File_stream_hello_hello_proto = out.File
+	file_stream_hello_hello_proto_rawDesc = nil
+	file_stream_hello_hello_proto_goTypes = nil
+	file_stream_hello_hello_proto_depIdxs = nil
+}
diff --git a/examples/stream/hello/hello_grpc.pb.go b/examples/stream/hello/hello_grpc.pb.go
new file mode 100644
index 00000000..11590f88
--- /dev/null
+++ b/examples/stream/hello/hello_grpc.pb.go
@@ -0,0 +1,266 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package hello
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// HelloClient is the client API for Hello service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type HelloClient interface {
+	GetNumber(ctx context.Context, in *GetNumberRequest, opts ...grpc.CallOption) (Hello_GetNumberClient, error)
+	UploadLog(ctx context.Context, opts ...grpc.CallOption) (Hello_UploadLogClient, error)
+	Chat(ctx context.Context, opts ...grpc.CallOption) (Hello_ChatClient, error)
+}
+
+type helloClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewHelloClient(cc grpc.ClientConnInterface) HelloClient {
+	return &helloClient{cc}
+}
+
+func (c *helloClient) GetNumber(ctx context.Context, in *GetNumberRequest, opts ...grpc.CallOption) (Hello_GetNumberClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[0], "/stream.hello.Hello/GetNumber", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloGetNumberClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type Hello_GetNumberClient interface {
+	Recv() (*GetNumberReply, error)
+	grpc.ClientStream
+}
+
+type helloGetNumberClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloGetNumberClient) Recv() (*GetNumberReply, error) {
+	m := new(GetNumberReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *helloClient) UploadLog(ctx context.Context, opts ...grpc.CallOption) (Hello_UploadLogClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[1], "/stream.hello.Hello/UploadLog", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloUploadLogClient{stream}
+	return x, nil
+}
+
+type Hello_UploadLogClient interface {
+	Send(*UploadLogRequest) error
+	CloseAndRecv() (*UploadLogReply, error)
+	grpc.ClientStream
+}
+
+type helloUploadLogClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloUploadLogClient) Send(m *UploadLogRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *helloUploadLogClient) CloseAndRecv() (*UploadLogReply, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(UploadLogReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *helloClient) Chat(ctx context.Context, opts ...grpc.CallOption) (Hello_ChatClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[2], "/stream.hello.Hello/Chat", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloChatClient{stream}
+	return x, nil
+}
+
+type Hello_ChatClient interface {
+	Send(*ChatRequest) error
+	Recv() (*ChatReply, error)
+	grpc.ClientStream
+}
+
+type helloChatClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloChatClient) Send(m *ChatRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *helloChatClient) Recv() (*ChatReply, error) {
+	m := new(ChatReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// HelloServer is the service API for Hello service.
+// All implementations must embed UnimplementedHelloServer
+// for forward compatibility
+type HelloServer interface {
+	GetNumber(*GetNumberRequest, Hello_GetNumberServer) error
+	UploadLog(Hello_UploadLogServer) error
+	Chat(Hello_ChatServer) error
+	mustEmbedUnimplementedHelloServer()
+}
+
+// UnimplementedHelloServer must be embedded to have forward compatible implementations.
+type UnimplementedHelloServer struct {
+}
+
+func (UnimplementedHelloServer) GetNumber(*GetNumberRequest, Hello_GetNumberServer) error {
+	return status.Errorf(codes.Unimplemented, "method GetNumber not implemented")
+}
+func (UnimplementedHelloServer) UploadLog(Hello_UploadLogServer) error {
+	return status.Errorf(codes.Unimplemented, "method UploadLog not implemented")
+}
+func (UnimplementedHelloServer) Chat(Hello_ChatServer) error {
+	return status.Errorf(codes.Unimplemented, "method Chat not implemented")
+}
+func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {}
+
+// UnsafeHelloServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to HelloServer will
+// result in compilation errors.
+type UnsafeHelloServer interface {
+	mustEmbedUnimplementedHelloServer()
+}
+
+func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) {
+	s.RegisterService(&Hello_ServiceDesc, srv)
+}
+
+func _Hello_GetNumber_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(GetNumberRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(HelloServer).GetNumber(m, &helloGetNumberServer{stream})
+}
+
+type Hello_GetNumberServer interface {
+	Send(*GetNumberReply) error
+	grpc.ServerStream
+}
+
+type helloGetNumberServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloGetNumberServer) Send(m *GetNumberReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _Hello_UploadLog_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(HelloServer).UploadLog(&helloUploadLogServer{stream})
+}
+
+type Hello_UploadLogServer interface {
+	SendAndClose(*UploadLogReply) error
+	Recv() (*UploadLogRequest, error)
+	grpc.ServerStream
+}
+
+type helloUploadLogServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloUploadLogServer) SendAndClose(m *UploadLogReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *helloUploadLogServer) Recv() (*UploadLogRequest, error) {
+	m := new(UploadLogRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func _Hello_Chat_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(HelloServer).Chat(&helloChatServer{stream})
+}
+
+type Hello_ChatServer interface {
+	Send(*ChatReply) error
+	Recv() (*ChatRequest, error)
+	grpc.ServerStream
+}
+
+type helloChatServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloChatServer) Send(m *ChatReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *helloChatServer) Recv() (*ChatRequest, error) {
+	m := new(ChatRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// Hello_ServiceDesc is the grpc.ServiceDesc for Hello service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Hello_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "stream.hello.Hello",
+	HandlerType: (*HelloServer)(nil),
+	Methods:     []grpc.MethodDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "GetNumber",
+			Handler:       _Hello_GetNumber_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "UploadLog",
+			Handler:       _Hello_UploadLog_Handler,
+			ClientStreams: true,
+		},
+		{
+			StreamName:    "Chat",
+			Handler:       _Hello_Chat_Handler,
+			ServerStreams: true,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "stream/hello/hello.proto",
+}
diff --git a/examples/stream/server/main.go b/examples/stream/server/main.go
new file mode 100644
index 00000000..b2561217
--- /dev/null
+++ b/examples/stream/server/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"log"
+
+	"github.com/go-kratos/kratos/examples/stream/hello"
+	"github.com/go-kratos/kratos/examples/stream/service"
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+)
+
+func main() {
+	grpcSrv := grpc.NewServer(
+		grpc.Address(":9001"),
+		grpc.Middleware(
+			recovery.Recovery(),
+		),
+	)
+	hello.RegisterHelloServer(grpcSrv, service.NewHelloService())
+
+	app := kratos.New(
+		kratos.Name("hello"),
+		kratos.Server(
+			grpcSrv,
+		),
+	)
+	if err := app.Run(); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/examples/stream/service/hello.go b/examples/stream/service/hello.go
new file mode 100644
index 00000000..b0b531ae
--- /dev/null
+++ b/examples/stream/service/hello.go
@@ -0,0 +1,58 @@
+package service
+
+import (
+	"fmt"
+	"io"
+	"time"
+
+	pb "github.com/go-kratos/kratos/examples/stream/hello"
+)
+
+type HelloService struct {
+	pb.UnimplementedHelloServer
+}
+
+func NewHelloService() *HelloService {
+	return &HelloService{}
+}
+
+func (s *HelloService) GetNumber(req *pb.GetNumberRequest, conn pb.Hello_GetNumberServer) error {
+	var number int64
+	for {
+		fmt.Println(req.Data)
+		err := conn.Send(&pb.GetNumberReply{Number: number})
+		if err != nil {
+			return err
+		}
+		number++
+		time.Sleep(time.Second)
+	}
+}
+func (s *HelloService) UploadLog(conn pb.Hello_UploadLogServer) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return conn.SendAndClose(&pb.UploadLogReply{Res: "ok"})
+		}
+		if err != nil {
+			return err
+		}
+		fmt.Println(req.Log)
+	}
+}
+func (s *HelloService) Chat(conn pb.Hello_ChatServer) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+
+		err = conn.Send(&pb.ChatReply{DownMsg: "hello " + req.UpMsg})
+		if err != nil {
+			return err
+		}
+	}
+}
diff --git a/examples/traces/app/message/main.go b/examples/traces/app/message/main.go
index 4f81b025..a882d835 100644
--- a/examples/traces/app/message/main.go
+++ b/examples/traces/app/message/main.go
@@ -12,12 +12,12 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
 	"github.com/go-kratos/kratos/v2/middleware/tracing"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/exporters/jaeger"
 	"go.opentelemetry.io/otel/sdk/resource"
 	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
-	"go.opentelemetry.io/otel/trace"
 )
 
 // go build -ldflags "-X main.Version=x.y.z"
@@ -31,28 +31,28 @@ var (
 // server is used to implement helloworld.GreeterServer.
 type server struct {
 	v1.UnimplementedMessageServiceServer
-	tracer trace.TracerProvider
 }
 
-// Get trace provider
-func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
+// set trace provider
+func setTracerProvider(url string) error {
 	// Create the Jaeger exporter
 	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
 	if err != nil {
-		return nil, err
+		return err
 	}
 	tp := tracesdk.NewTracerProvider(
-		tracesdk.WithSampler(tracesdk.AlwaysSample()),
+		// Set the sampling rate based on the parent span to 100%
+		tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0))),
 		// Always be sure to batch in production.
 		tracesdk.WithBatcher(exp),
 		// Record information about this application in an Resource.
 		tracesdk.WithResource(resource.NewSchemaless(
-			semconv.ServiceNameKey.String(v1.MessageService_ServiceDesc.ServiceName),
-			attribute.String("environment", "development"),
-			attribute.Int64("ID", 1),
+			semconv.ServiceNameKey.String(Name),
+			attribute.String("env", "dev"),
 		)),
 	)
-	return tp, nil
+	otel.SetTracerProvider(tp)
+	return nil
 }
 
 func (s *server) GetUserMessage(ctx context.Context, request *v1.GetUserMessageRequest) (*v1.GetUserMessageReply, error) {
@@ -69,12 +69,16 @@ func main() {
 	logger = log.With(logger, "span_id", log.SpanID())
 	log := log.NewHelper(logger)
 
-	tp, err := tracerProvider("http://jaeger:14268/api/traces")
+	url := "http://jaeger:14268/api/traces"
+	if os.Getenv("jaeger_url") != "" {
+		url = os.Getenv("jaeger_url")
+	}
+	err := setTracerProvider(url)
 	if err != nil {
 		log.Error(err)
 	}
 
-	s := &server{tracer: tp}
+	s := &server{}
 	// grpc server
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
@@ -82,9 +86,7 @@ func main() {
 			middleware.Chain(
 				recovery.Recovery(),
 				// Configuring tracing Middleware
-				tracing.Server(
-					tracing.WithTracerProvider(tp),
-				),
+				tracing.Server(),
 				logging.Server(logger),
 			),
 		))
diff --git a/examples/traces/app/user/main.go b/examples/traces/app/user/main.go
index 48b838ac..0ca46932 100644
--- a/examples/traces/app/user/main.go
+++ b/examples/traces/app/user/main.go
@@ -14,12 +14,14 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware/tracing"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
+
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/exporters/jaeger"
 	"go.opentelemetry.io/otel/sdk/resource"
 	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
-	"go.opentelemetry.io/otel/trace"
+	grpcx "google.golang.org/grpc"
 )
 
 // go build -ldflags "-X main.Version=x.y.z"
@@ -33,41 +35,42 @@ var (
 // server is used to implement helloworld.GreeterServer.
 type server struct {
 	v1.UnimplementedUserServer
-	tracer trace.TracerProvider
 }
 
-// Get trace provider
-func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
+// Set global trace provider
+func setTracerProvider(url string) error {
 	// Create the Jaeger exporter
 	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
 	if err != nil {
-		return nil, err
+		return err
 	}
 	tp := tracesdk.NewTracerProvider(
-		tracesdk.WithSampler(tracesdk.AlwaysSample()),
+		// Set the sampling rate based on the parent span to 100%
+		tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0))),
 		// Always be sure to batch in production.
 		tracesdk.WithBatcher(exp),
 		// Record information about this application in an Resource.
 		tracesdk.WithResource(resource.NewSchemaless(
-			semconv.ServiceNameKey.String(v1.User_ServiceDesc.ServiceName),
-			attribute.String("environment", "development"),
-			attribute.Int64("ID", 1),
+			semconv.ServiceNameKey.String(Name),
+			attribute.String("env", "dev"),
 		)),
 	)
-	return tp, nil
+	otel.SetTracerProvider(tp)
+	return nil
 }
 
 func (s *server) GetMyMessages(ctx context.Context, in *v1.GetMyMessagesRequest) (*v1.GetMyMessagesReply, error) {
 	// create grpc conn
+	// only for demo, use single instance in production env
 	conn, err := grpc.DialInsecure(ctx,
 		grpc.WithEndpoint("127.0.0.1:9000"),
 		grpc.WithMiddleware(
 			recovery.Recovery(),
-			tracing.Client(
-				tracing.WithTracerProvider(s.tracer),
-			),
+			tracing.Client(),
 		),
 		grpc.WithTimeout(2*time.Second),
+		// for tracing remote ip recording
+		grpc.WithOptions(grpcx.WithStatsHandler(&tracing.ClientHandler{})),
 	)
 	if err != nil {
 		return nil, err
@@ -91,7 +94,11 @@ func main() {
 	logger = log.With(logger, "span_id", log.SpanID())
 	log := log.NewHelper(logger)
 
-	tp, err := tracerProvider("http://jaeger:14268/api/traces")
+	url := "http://jaeger:14268/api/traces"
+	if os.Getenv("jaeger_url") != "" {
+		url = os.Getenv("jaeger_url")
+	}
+	err := setTracerProvider(url)
 	if err != nil {
 		log.Error(err)
 	}
@@ -101,13 +108,11 @@ func main() {
 		http.Middleware(
 			recovery.Recovery(),
 			// Configuring tracing middleware
-			tracing.Server(
-				tracing.WithTracerProvider(tp),
-			),
+			tracing.Server(),
 			logging.Server(logger),
 		),
 	)
-	s := &server{tracer: tp}
+	s := &server{}
 	v1.RegisterUserHTTPServer(httpSrv, s)
 
 	app := kratos.New(
diff --git a/go.mod b/go.mod
index 4705ecef..2d6cd62a 100644
--- a/go.mod
+++ b/go.mod
@@ -9,12 +9,14 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/imdario/mergo v0.3.12
 	github.com/stretchr/testify v1.7.0
-	go.opentelemetry.io/otel v1.0.0-RC1
-	go.opentelemetry.io/otel/sdk v1.0.0-RC1
-	go.opentelemetry.io/otel/trace v1.0.0-RC1
+	go.opentelemetry.io/otel v1.0.0-RC2
+	go.opentelemetry.io/otel/sdk v1.0.0-RC2
+	go.opentelemetry.io/otel/trace v1.0.0-RC2
+	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151
-	google.golang.org/grpc v1.39.0
+	golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
+	google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67
+	google.golang.org/grpc v1.39.1
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index 22827354..26a9bc78 100644
--- a/go.sum
+++ b/go.sum
@@ -64,14 +64,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
-go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
-go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk=
-go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
-go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q=
-go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
-go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
-go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg=
+go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2 h1:ROuteeSCBaZNjiT9JcFzZepmInDvLktR28Y6qKo8bCs=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw=
+go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w=
+go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -93,6 +91,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 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=
@@ -108,13 +108,19 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -123,6 +129,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -136,6 +143,8 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151 h1:H/uPzsolsGjhl3CVT6Wb7bK+mf+hmkEvUVu+FBKyNlc=
 google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 h1:VmMSf20ssFK0+u1dscyTH9bU4/M4y+X/xNfkvD6kGtM=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -145,6 +154,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1 h1:f37vZbBVTiJ6jKG5mWz8ySOBxNqy6ViPgyhSdVnxF3E=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/internal/context/context.go b/internal/context/context.go
index 0b2c3388..3c15f7a6 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -29,9 +29,9 @@ func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFun
 	}
 	select {
 	case <-parent1.Done():
-		mc.finish(parent1.Err())
+		_ = mc.finish(parent1.Err())
 	case <-parent2.Done():
-		mc.finish(parent2.Err())
+		_ = mc.finish(parent2.Err())
 	default:
 		go mc.wait()
 	}
@@ -57,7 +57,7 @@ func (mc *mergeCtx) wait() {
 	case <-mc.cancelCh:
 		err = context.Canceled
 	}
-	mc.finish(err)
+	_ = mc.finish(err)
 }
 
 func (mc *mergeCtx) cancel() {
diff --git a/log/helper.go b/log/helper.go
index dea23c76..6c7f3a57 100644
--- a/log/helper.go
+++ b/log/helper.go
@@ -28,83 +28,83 @@ func (h *Helper) WithContext(ctx context.Context) *Helper {
 
 // Log Print log by level and keyvals.
 func (h *Helper) Log(level Level, keyvals ...interface{}) {
-	h.logger.Log(level, keyvals...)
+	_ = h.logger.Log(level, keyvals...)
 }
 
 // Debug logs a message at debug level.
 func (h *Helper) Debug(a ...interface{}) {
-	h.logger.Log(LevelDebug, "msg", fmt.Sprint(a...))
+	h.Log(LevelDebug, "msg", fmt.Sprint(a...))
 }
 
 // Debugf logs a message at debug level.
 func (h *Helper) Debugf(format string, a ...interface{}) {
-	h.logger.Log(LevelDebug, "msg", fmt.Sprintf(format, a...))
+	h.Log(LevelDebug, "msg", fmt.Sprintf(format, a...))
 }
 
 // Debugw logs a message at debug level.
 func (h *Helper) Debugw(keyvals ...interface{}) {
-	h.logger.Log(LevelDebug, keyvals...)
+	h.Log(LevelDebug, keyvals...)
 }
 
 // Info logs a message at info level.
 func (h *Helper) Info(a ...interface{}) {
-	h.logger.Log(LevelInfo, "msg", fmt.Sprint(a...))
+	h.Log(LevelInfo, "msg", fmt.Sprint(a...))
 }
 
 // Infof logs a message at info level.
 func (h *Helper) Infof(format string, a ...interface{}) {
-	h.logger.Log(LevelInfo, "msg", fmt.Sprintf(format, a...))
+	h.Log(LevelInfo, "msg", fmt.Sprintf(format, a...))
 }
 
 // Infow logs a message at info level.
 func (h *Helper) Infow(keyvals ...interface{}) {
-	h.logger.Log(LevelInfo, keyvals...)
+	h.Log(LevelInfo, keyvals...)
 }
 
 // Warn logs a message at warn level.
 func (h *Helper) Warn(a ...interface{}) {
-	h.logger.Log(LevelWarn, "msg", fmt.Sprint(a...))
+	h.Log(LevelWarn, "msg", fmt.Sprint(a...))
 }
 
 // Warnf logs a message at warnf level.
 func (h *Helper) Warnf(format string, a ...interface{}) {
-	h.logger.Log(LevelWarn, "msg", fmt.Sprintf(format, a...))
+	h.Log(LevelWarn, "msg", fmt.Sprintf(format, a...))
 }
 
 // Warnw logs a message at warnf level.
 func (h *Helper) Warnw(keyvals ...interface{}) {
-	h.logger.Log(LevelWarn, keyvals...)
+	h.Log(LevelWarn, keyvals...)
 }
 
 // Error logs a message at error level.
 func (h *Helper) Error(a ...interface{}) {
-	h.logger.Log(LevelError, "msg", fmt.Sprint(a...))
+	h.Log(LevelError, "msg", fmt.Sprint(a...))
 }
 
 // Errorf logs a message at error level.
 func (h *Helper) Errorf(format string, a ...interface{}) {
-	h.logger.Log(LevelError, "msg", fmt.Sprintf(format, a...))
+	h.Log(LevelError, "msg", fmt.Sprintf(format, a...))
 }
 
 // Errorw logs a message at error level.
 func (h *Helper) Errorw(keyvals ...interface{}) {
-	h.logger.Log(LevelError, keyvals...)
+	h.Log(LevelError, keyvals...)
 }
 
 // Fatal logs a message at fatal level.
 func (h *Helper) Fatal(a ...interface{}) {
-	h.logger.Log(LevelFatal, "msg", fmt.Sprint(a...))
+	h.Log(LevelFatal, "msg", fmt.Sprint(a...))
 	os.Exit(1)
 }
 
 // Fatalf logs a message at fatal level.
 func (h *Helper) Fatalf(format string, a ...interface{}) {
-	h.logger.Log(LevelFatal, "msg", fmt.Sprintf(format, a...))
+	h.Log(LevelFatal, "msg", fmt.Sprintf(format, a...))
 	os.Exit(1)
 }
 
 // Fatalw logs a message at fatal level.
 func (h *Helper) Fatalw(keyvals ...interface{}) {
-	h.logger.Log(LevelFatal, keyvals...)
+	h.Log(LevelFatal, keyvals...)
 	os.Exit(1)
 }
diff --git a/log/std.go b/log/std.go
index 1aab1929..a26258d0 100644
--- a/log/std.go
+++ b/log/std.go
@@ -38,9 +38,9 @@ func (l *stdLogger) Log(level Level, keyvals ...interface{}) error {
 	buf := l.pool.Get().(*bytes.Buffer)
 	buf.WriteString(level.String())
 	for i := 0; i < len(keyvals); i += 2 {
-		fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1])
+		_, _ = fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1])
 	}
-	l.log.Output(4, buf.String())
+	_ = l.log.Output(4, buf.String())
 	buf.Reset()
 	l.pool.Put(buf)
 	return nil
diff --git a/middleware/logging/logging.go b/middleware/logging/logging.go
index d985e9bb..d050da9a 100644
--- a/middleware/logging/logging.go
+++ b/middleware/logging/logging.go
@@ -32,7 +32,7 @@ func Server(logger log.Logger) middleware.Middleware {
 				reason = se.Reason
 			}
 			level, stack := extractError(err)
-			log.WithContext(ctx, logger).Log(level,
+			_ = log.WithContext(ctx, logger).Log(level,
 				"kind", "server",
 				"component", kind,
 				"operation", operation,
@@ -68,7 +68,7 @@ func Client(logger log.Logger) middleware.Middleware {
 				reason = se.Reason
 			}
 			level, stack := extractError(err)
-			log.WithContext(ctx, logger).Log(level,
+			_ = log.WithContext(ctx, logger).Log(level,
 				"kind", "client",
 				"component", kind,
 				"operation", operation,
diff --git a/middleware/tracing/metadata.go b/middleware/tracing/metadata.go
new file mode 100644
index 00000000..7e8cbe95
--- /dev/null
+++ b/middleware/tracing/metadata.go
@@ -0,0 +1,45 @@
+package tracing
+
+import (
+	"context"
+
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/metadata"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+const serviceHeader = "x-md-service-name"
+
+// Metadata is tracing metadata propagator
+type Metadata struct{}
+
+var _ propagation.TextMapPropagator = Metadata{}
+
+// Inject sets metadata key-values from ctx into the carrier.
+func (b Metadata) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
+	app, ok := kratos.FromContext(ctx)
+	if ok {
+		carrier.Set(serviceHeader, app.Name())
+	}
+}
+
+// Extract returns a copy of parent with the metadata from the carrier added.
+func (b Metadata) Extract(parent context.Context, carrier propagation.TextMapCarrier) context.Context {
+	name := carrier.Get(serviceHeader)
+	if name != "" {
+		if md, ok := metadata.FromServerContext(parent); ok {
+			md.Set(serviceHeader, name)
+		} else {
+			md := metadata.New()
+			md.Set(serviceHeader, name)
+			parent = metadata.NewServerContext(parent, md)
+		}
+	}
+
+	return parent
+}
+
+// Fields returns the keys who's values are set with Inject.
+func (b Metadata) Fields() []string {
+	return []string{serviceHeader}
+}
diff --git a/middleware/tracing/span.go b/middleware/tracing/span.go
new file mode 100644
index 00000000..b8acf370
--- /dev/null
+++ b/middleware/tracing/span.go
@@ -0,0 +1,144 @@
+package tracing
+
+import (
+	"context"
+	"net"
+	"net/url"
+	"strings"
+
+	"github.com/go-kratos/kratos/v2/metadata"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/go-kratos/kratos/v2/transport/http"
+
+	"go.opentelemetry.io/otel/attribute"
+	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
+	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/protobuf/proto"
+)
+
+func setClientSpan(ctx context.Context, span trace.Span, m interface{}) {
+	attrs := []attribute.KeyValue{}
+	var remote string
+	var operation string
+	var rpcKind string
+	if tr, ok := transport.FromClientContext(ctx); ok {
+		operation = tr.Operation()
+		rpcKind = tr.Kind().String()
+		if tr.Kind() == transport.KindHTTP {
+			if ht, ok := tr.(*http.Transport); ok {
+				method := ht.Request().Method
+				route := ht.PathTemplate()
+				path := ht.Request().URL.Path
+				attrs = append(attrs, semconv.HTTPMethodKey.String(method))
+				attrs = append(attrs, semconv.HTTPRouteKey.String(route))
+				attrs = append(attrs, semconv.HTTPTargetKey.String(path))
+				remote = ht.Request().Host
+			}
+		} else if tr.Kind() == transport.KindGRPC {
+			remote, _ = parseTarget(tr.Endpoint())
+		}
+	}
+	attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
+	_, mAttrs := parseFullMethod(operation)
+	attrs = append(attrs, mAttrs...)
+	if remote != "" {
+		attrs = append(attrs, peerAttr(remote)...)
+	}
+	if p, ok := m.(proto.Message); ok {
+		attrs = append(attrs, attribute.Key("send_msg.size").Int(proto.Size(p)))
+	}
+
+	span.SetAttributes(attrs...)
+}
+
+func setServerSpan(ctx context.Context, span trace.Span, m interface{}) {
+	attrs := []attribute.KeyValue{}
+	var remote string
+	var operation string
+	var rpcKind string
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		operation = tr.Operation()
+		rpcKind = tr.Kind().String()
+		if tr.Kind() == transport.KindHTTP {
+			if ht, ok := tr.(*http.Transport); ok {
+				method := ht.Request().Method
+				route := ht.PathTemplate()
+				path := ht.Request().URL.Path
+				attrs = append(attrs, semconv.HTTPMethodKey.String(method))
+				attrs = append(attrs, semconv.HTTPRouteKey.String(route))
+				attrs = append(attrs, semconv.HTTPTargetKey.String(path))
+				remote = ht.Request().RemoteAddr
+			}
+		} else if tr.Kind() == transport.KindGRPC {
+			if p, ok := peer.FromContext(ctx); ok {
+				remote = p.Addr.String()
+			}
+		}
+	}
+	attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
+	_, mAttrs := parseFullMethod(operation)
+	attrs = append(attrs, mAttrs...)
+	attrs = append(attrs, peerAttr(remote)...)
+	if p, ok := m.(proto.Message); ok {
+		attrs = append(attrs, attribute.Key("recv_msg.size").Int(proto.Size(p)))
+	}
+	if md, ok := metadata.FromServerContext(ctx); ok {
+		attrs = append(attrs, semconv.PeerServiceKey.String(md.Get(serviceHeader)))
+	}
+
+	span.SetAttributes(attrs...)
+}
+
+// parseFullMethod returns a span name following the OpenTelemetry semantic
+// conventions as well as all applicable span attribute.KeyValue attributes based
+// on a gRPC's FullMethod.
+func parseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
+	name := strings.TrimLeft(fullMethod, "/")
+	parts := strings.SplitN(name, "/", 2)
+	if len(parts) != 2 {
+		// Invalid format, does not follow `/package.service/method`.
+		return name, []attribute.KeyValue{attribute.Key("rpc.operation").String(fullMethod)}
+	}
+
+	var attrs []attribute.KeyValue
+	if service := parts[0]; service != "" {
+		attrs = append(attrs, semconv.RPCServiceKey.String(service))
+	}
+	if method := parts[1]; method != "" {
+		attrs = append(attrs, semconv.RPCMethodKey.String(method))
+	}
+	return name, attrs
+}
+
+// peerAttr returns attributes about the peer address.
+func peerAttr(addr string) []attribute.KeyValue {
+	host, port, err := net.SplitHostPort(addr)
+	if err != nil {
+		return []attribute.KeyValue(nil)
+	}
+
+	if host == "" {
+		host = "127.0.0.1"
+	}
+
+	return []attribute.KeyValue{
+		semconv.NetPeerIPKey.String(host),
+		semconv.NetPeerPortKey.String(port),
+	}
+}
+
+func parseTarget(endpoint string) (address string, err error) {
+	var u *url.URL
+	u, err = url.Parse(endpoint)
+	if err != nil {
+		if u, err = url.Parse("http://" + endpoint); err != nil {
+			return "", err
+		}
+		return u.Host, nil
+	}
+	if len(u.Path) > 1 {
+		return u.Path[1:], nil
+	}
+	return endpoint, nil
+}
diff --git a/middleware/tracing/statsHandler.go b/middleware/tracing/statsHandler.go
new file mode 100644
index 00000000..70c9efdc
--- /dev/null
+++ b/middleware/tracing/statsHandler.go
@@ -0,0 +1,39 @@
+package tracing
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/grpc/stats"
+)
+
+// ClientHandler is tracing ClientHandler
+type ClientHandler struct {
+}
+
+// HandleConn exists to satisfy gRPC stats.Handler.
+func (c *ClientHandler) HandleConn(ctx context.Context, cs stats.ConnStats) {
+}
+
+// TagConn exists to satisfy gRPC stats.Handler.
+func (c *ClientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context {
+	return ctx
+}
+
+// HandleRPC implements per-RPC tracing and stats instrumentation.
+func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
+	if _, ok := rs.(*stats.OutHeader); ok {
+		if p, ok := peer.FromContext(ctx); ok {
+			remoteAddr := p.Addr.String()
+			if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
+				span.SetAttributes(peerAttr(remoteAddr)...)
+			}
+		}
+	}
+}
+
+// TagRPC implements per-RPC context management.
+func (c *ClientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
+	return ctx
+}
diff --git a/middleware/tracing/tracer.go b/middleware/tracing/tracer.go
index e3ce2af4..8bad3987 100644
--- a/middleware/tracing/tracer.go
+++ b/middleware/tracing/tracer.go
@@ -4,70 +4,77 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/go-kratos/kratos/v2/errors"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/codes"
 	"go.opentelemetry.io/otel/propagation"
 	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/protobuf/proto"
 )
 
 // Tracer is otel span tracer
 type Tracer struct {
 	tracer trace.Tracer
 	kind   trace.SpanKind
+	opt    *options
 }
 
 // NewTracer create tracer instance
 func NewTracer(kind trace.SpanKind, opts ...Option) *Tracer {
-	options := options{}
+	options := options{
+		propagator: propagation.NewCompositeTextMapPropagator(Metadata{}, propagation.Baggage{}, propagation.TraceContext{}),
+	}
 	for _, o := range opts {
 		o(&options)
 	}
-	if options.TracerProvider != nil {
-		otel.SetTracerProvider(options.TracerProvider)
-	}
-	if options.Propagator != nil {
-		otel.SetTextMapPropagator(options.Propagator)
-	} else {
-		otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}))
+	if options.tracerProvider != nil {
+		otel.SetTracerProvider(options.tracerProvider)
 	}
+
 	switch kind {
 	case trace.SpanKindClient:
-		return &Tracer{tracer: otel.Tracer("client"), kind: kind}
+		return &Tracer{tracer: otel.Tracer("kartos"), kind: kind, opt: &options}
 	case trace.SpanKindServer:
-		return &Tracer{tracer: otel.Tracer("server"), kind: kind}
+		return &Tracer{tracer: otel.Tracer("kartos"), kind: kind, opt: &options}
 	default:
 		panic(fmt.Sprintf("unsupported span kind: %v", kind))
 	}
 }
 
 // Start start tracing span
-func (t *Tracer) Start(ctx context.Context, component string, operation string, carrier propagation.TextMapCarrier) (context.Context, trace.Span) {
+func (t *Tracer) Start(ctx context.Context, operation string, carrier propagation.TextMapCarrier) (context.Context, trace.Span) {
 	if t.kind == trace.SpanKindServer {
-		ctx = otel.GetTextMapPropagator().Extract(ctx, carrier)
+		ctx = t.opt.propagator.Extract(ctx, carrier)
 	}
 	ctx, span := t.tracer.Start(ctx,
 		operation,
-		trace.WithAttributes(attribute.String("component", component)),
 		trace.WithSpanKind(t.kind),
 	)
 	if t.kind == trace.SpanKindClient {
-		otel.GetTextMapPropagator().Inject(ctx, carrier)
+		t.opt.propagator.Inject(ctx, carrier)
 	}
 	return ctx, span
 }
 
 // End finish tracing span
-func (t *Tracer) End(ctx context.Context, span trace.Span, err error) {
+func (t *Tracer) End(ctx context.Context, span trace.Span, m interface{}, err error) {
 	if err != nil {
 		span.RecordError(err)
-		span.SetAttributes(
-			attribute.String("event", "error"),
-			attribute.String("message", err.Error()),
-		)
+		if e := errors.FromError(err); e != nil {
+			span.SetAttributes(attribute.Key("rpc.status_code").Int64(int64(e.Code)))
+		}
 		span.SetStatus(codes.Error, err.Error())
 	} else {
 		span.SetStatus(codes.Ok, "OK")
 	}
+
+	if p, ok := m.(proto.Message); ok {
+		if t.kind == trace.SpanKindServer {
+			span.SetAttributes(attribute.Key("send_msg.size").Int(proto.Size(p)))
+		} else {
+			span.SetAttributes(attribute.Key("recv_msg.size").Int(proto.Size(p)))
+		}
+	}
 	span.End()
 }
diff --git a/middleware/tracing/tracing.go b/middleware/tracing/tracing.go
index 5f968724..3e03c157 100644
--- a/middleware/tracing/tracing.go
+++ b/middleware/tracing/tracing.go
@@ -13,21 +13,22 @@ import (
 type Option func(*options)
 
 type options struct {
-	TracerProvider trace.TracerProvider
-	Propagator     propagation.TextMapPropagator
+	tracerProvider trace.TracerProvider
+	propagator     propagation.TextMapPropagator
 }
 
 // WithPropagator with tracer propagator.
 func WithPropagator(propagator propagation.TextMapPropagator) Option {
 	return func(opts *options) {
-		opts.Propagator = propagator
+		opts.propagator = propagator
 	}
 }
 
 // WithTracerProvider with tracer provider.
+// Deprecated: use otel.SetTracerProvider(provider) instead.
 func WithTracerProvider(provider trace.TracerProvider) Option {
 	return func(opts *options) {
-		opts.TracerProvider = provider
+		opts.tracerProvider = provider
 	}
 }
 
@@ -38,8 +39,9 @@ func Server(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromServerContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind().String(), tr.Operation(), tr.RequestHeader())
-				defer func() { tracer.End(ctx, span, err) }()
+				ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader())
+				setServerSpan(ctx, span, req)
+				defer func() { tracer.End(ctx, span, reply, err) }()
 			}
 			return handler(ctx, req)
 		}
@@ -53,8 +55,9 @@ func Client(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromClientContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind().String(), tr.Operation(), tr.RequestHeader())
-				defer func() { tracer.End(ctx, span, err) }()
+				ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader())
+				setClientSpan(ctx, span, req)
+				defer func() { tracer.End(ctx, span, reply, err) }()
 			}
 			return handler(ctx, req)
 		}
diff --git a/options.go b/options.go
index 39047d13..29349f60 100644
--- a/options.go
+++ b/options.go
@@ -25,7 +25,7 @@ type options struct {
 	ctx  context.Context
 	sigs []os.Signal
 
-	logger           log.Logger
+	logger           *log.Helper
 	registrar        registry.Registrar
 	registrarTimeout time.Duration
 	servers          []transport.Server
@@ -63,7 +63,9 @@ func Context(ctx context.Context) Option {
 
 // Logger with service logger.
 func Logger(logger log.Logger) Option {
-	return func(o *options) { o.logger = logger }
+	return func(o *options) {
+		o.logger = log.NewHelper(logger)
+	}
 }
 
 // Server with transport servers.
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index c4746a7a..f2495e80 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -3,6 +3,7 @@ package grpc
 import (
 	"context"
 	"crypto/tls"
+	"fmt"
 	"time"
 
 	"github.com/go-kratos/kratos/v2/middleware"
@@ -106,8 +107,7 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 		ints = append(ints, options.ints...)
 	}
 	var grpcOpts = []grpc.DialOption{
-		//todo: grpc.WithBalancerName is deprecated.
-		grpc.WithBalancerName(roundrobin.Name), //nolint:staticcheck
+		grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)),
 		grpc.WithChainUnaryInterceptor(ints...),
 	}
 	if options.discovery != nil {
diff --git a/transport/grpc/resolver/direct/builder.go b/transport/grpc/resolver/direct/builder.go
index fefbe747..dd1ba114 100644
--- a/transport/grpc/resolver/direct/builder.go
+++ b/transport/grpc/resolver/direct/builder.go
@@ -24,9 +24,12 @@ func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, op
 	for _, addr := range strings.Split(target.Endpoint, ",") {
 		addrs = append(addrs, resolver.Address{Addr: addr})
 	}
-	cc.UpdateState(resolver.State{
+	err := cc.UpdateState(resolver.State{
 		Addresses: addrs,
 	})
+	if err != nil {
+		return nil, err
+	}
 	return newDirectResolver(), nil
 }
 
diff --git a/transport/grpc/resolver/discovery/resolver.go b/transport/grpc/resolver/discovery/resolver.go
index 76fd812a..c7eeb32e 100644
--- a/transport/grpc/resolver/discovery/resolver.go
+++ b/transport/grpc/resolver/discovery/resolver.go
@@ -36,7 +36,7 @@ func (r *discoveryResolver) watch() {
 			if errors.Is(err, context.Canceled) {
 				return
 			}
-			r.log.Errorf("[resovler] Failed to watch discovery endpoint: %v", err)
+			r.log.Errorf("[resolver] Failed to watch discovery endpoint: %v", err)
 			time.Sleep(time.Second)
 			continue
 		}
@@ -49,7 +49,7 @@ func (r *discoveryResolver) update(ins []*registry.ServiceInstance) {
 	for _, in := range ins {
 		endpoint, err := endpoint.ParseEndpoint(in.Endpoints, "grpc", !r.insecure)
 		if err != nil {
-			r.log.Errorf("[resovler] Failed to parse discovery endpoint: %v", err)
+			r.log.Errorf("[resolver] Failed to parse discovery endpoint: %v", err)
 			continue
 		}
 		if endpoint == "" {
@@ -63,17 +63,23 @@ func (r *discoveryResolver) update(ins []*registry.ServiceInstance) {
 		addrs = append(addrs, addr)
 	}
 	if len(addrs) == 0 {
-		r.log.Warnf("[resovler] Zero endpoint found,refused to write, instances: %v", ins)
+		r.log.Warnf("[resolver] Zero endpoint found,refused to write, instances: %v", ins)
 		return
 	}
-	r.cc.UpdateState(resolver.State{Addresses: addrs})
+	err := r.cc.UpdateState(resolver.State{Addresses: addrs})
+	if err != nil {
+		r.log.Errorf("[resolver] failed to update state: %s", err)
+	}
 	b, _ := json.Marshal(ins)
 	r.log.Infof("[resolver] update instances: %s", b)
 }
 
 func (r *discoveryResolver) Close() {
 	r.cancel()
-	r.w.Stop()
+	err := r.w.Stop()
+	if err != nil {
+		r.log.Errorf("[resolver] failed to watch top: %s", err)
+	}
 }
 
 func (r *discoveryResolver) ResolveNow(options resolver.ResolveNowOptions) {}
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index f94c9dfc..c2aaf2fb 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -155,7 +155,7 @@ func (s *Server) Endpoint() (*url.URL, error) {
 		}
 		addr, err := host.Extract(s.address, lis)
 		if err != nil {
-			lis.Close()
+			_ = lis.Close()
 			s.err = err
 			return
 		}
@@ -211,7 +211,7 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
 		}
 		reply, err := h(ctx, req)
 		if len(replyHeader) > 0 {
-			grpc.SetHeader(ctx, replyHeader)
+			_ = grpc.SetHeader(ctx, replyHeader)
 		}
 		return reply, err
 	}
diff --git a/transport/http/codec.go b/transport/http/codec.go
index 58db62e1..7eb62872 100644
--- a/transport/http/codec.go
+++ b/transport/http/codec.go
@@ -45,7 +45,10 @@ func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{
 		return err
 	}
 	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
-	w.Write(data)
+	_, err = w.Write(data)
+	if err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -60,7 +63,7 @@ func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
 	}
 	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
 	w.WriteHeader(int(se.Code))
-	w.Write(body)
+	_, _ = w.Write(body)
 }
 
 // CodecForRequest get encoding.Codec via http.Request
diff --git a/transport/http/context.go b/transport/http/context.go
index 5e2079f8..0c68329c 100644
--- a/transport/http/context.go
+++ b/transport/http/context.go
@@ -120,14 +120,20 @@ func (c *wrapper) XML(code int, v interface{}) error {
 func (c *wrapper) String(code int, text string) error {
 	c.res.Header().Set("Content-Type", "text/plain")
 	c.res.WriteHeader(code)
-	c.res.Write([]byte(text))
+	_, err := c.res.Write([]byte(text))
+	if err != nil {
+		return err
+	}
 	return nil
 }
 
 func (c *wrapper) Blob(code int, contentType string, data []byte) error {
 	c.res.Header().Set("Content-Type", contentType)
 	c.res.WriteHeader(code)
-	c.res.Write(data)
+	_, err := c.res.Write(data)
+	if err != nil {
+		return err
+	}
 	return nil
 }
 
diff --git a/transport/http/resolver.go b/transport/http/resolver.go
index 1e553fa1..ebb42f72 100644
--- a/transport/http/resolver.go
+++ b/transport/http/resolver.go
@@ -87,12 +87,18 @@ func newResolver(ctx context.Context, discovery registry.Discovery, target *Targ
 		select {
 		case err := <-done:
 			if err != nil {
-				watcher.Stop()
+				err := watcher.Stop()
+				if err != nil {
+					r.logger.Errorf("failed to http client watch stop: %v", target)
+				}
 				return nil, err
 			}
 		case <-ctx.Done():
 			r.logger.Errorf("http client watch service %v reaching context deadline!", target)
-			watcher.Stop()
+			err := watcher.Stop()
+			if err != nil {
+				r.logger.Errorf("failed to http client watch stop: %v", target)
+			}
 			return nil, ctx.Err()
 		}
 	}
@@ -116,12 +122,12 @@ func newResolver(ctx context.Context, discovery registry.Discovery, target *Targ
 func (r *resolver) update(services []*registry.ServiceInstance) {
 	var nodes []*registry.ServiceInstance
 	for _, in := range services {
-		endpoint, err := endpoint.ParseEndpoint(in.Endpoints, "http", !r.insecure)
+		ept, err := endpoint.ParseEndpoint(in.Endpoints, "http", !r.insecure)
 		if err != nil {
 			r.logger.Errorf("Failed to parse (%v) discovery endpoint: %v error %v", r.target, in.Endpoints, err)
 			continue
 		}
-		if endpoint == "" {
+		if ept == "" {
 			continue
 		}
 		nodes = append(nodes, in)
diff --git a/transport/transport.go b/transport/transport.go
index 2c06efff..45f2f549 100644
--- a/transport/transport.go
+++ b/transport/transport.go
@@ -32,20 +32,22 @@ type Header interface {
 
 // Transporter is transport context value interface.
 type Transporter interface {
+	// Kind transporter
 	// grpc
 	// http
 	Kind() Kind
+	// Endpoint return server or client endpoint
 	// Server Transport: grpc://127.0.0.1:9000
 	// Client Transport: discovery:///provider-demo
 	Endpoint() string
-	// Service full method selector generated by protobuf
+	// Operation Service full method selector generated by protobuf
 	// example: /helloworld.Greeter/SayHello
 	Operation() string
-	// request header
+	// RequestHeader return transport request header
 	// http: http.Header
 	// grpc: metadata.MD
 	RequestHeader() Header
-	// reply header
+	// ReplyHeader return transport reply/response header
 	// only valid for server transport
 	// http: http.Header
 	// grpc: metadata.MD
diff --git a/version.go b/version.go
index a68cd985..103678f1 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
 package kratos
 
 // Release is the current kratos version.
-const Release = "v2.0.3"
+const Release = "v2.0.4"


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 7bdacd8b..e18bb685 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -183,6 +183,7 @@ func TestDefaultResolver(t *testing.T) {
 				"enable":   "${ENABLE:false}",
 				"rate":     "${RATE}",
 				"empty":    "${EMPTY:foobar}",
+				"url":      "${URL:http://example.com}",
 				"array": []interface{}{
 					"${PORT}",
 					map[string]interface{}{"foobar": "${NOTEXIST:8081}"},
@@ -237,6 +238,11 @@ func TestDefaultResolver(t *testing.T) {
 			path:   "foo.bar.empty",
 			expect: "",
 		},
+		{
+			name:   "test url with default",
+			path:   "foo.bar.url",
+			expect: "http://example.com",
+		},
 		{
 			name:   "test array",
 			path:   "foo.bar.array",
diff --git a/config/env/watcher_test.go b/config/env/watcher_test.go
new file mode 100644
index 00000000..bea14947
--- /dev/null
+++ b/config/env/watcher_test.go
@@ -0,0 +1,29 @@
+package env
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_watcher_next(t *testing.T) {
+	t.Run("next after stop should return err", func(t *testing.T) {
+		w, err := NewWatcher()
+		require.NoError(t, err)
+
+		_ = w.Stop()
+		_, err = w.Next()
+		assert.Error(t, err)
+	})
+}
+
+func Test_watcher_stop(t *testing.T) {
+	t.Run("stop multiple times should not panic", func(t *testing.T) {
+		w, err := NewWatcher()
+		require.NoError(t, err)
+
+		_ = w.Stop()
+		_ = w.Stop()
+	})
+}
diff --git a/config/file/file_test.go b/config/file/file_test.go
index 57fae145..5f9621fa 100644
--- a/config/file/file_test.go
+++ b/config/file/file_test.go
@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"sync"
 	"testing"
 	"time"
 
@@ -68,14 +69,14 @@ const (
 	}
 }`
 
-//	_testYaml = `
-//Foo:
-//    bar :
-//        - {name: nihao,age: 1}
-//        - {name: nihao,age: 1}
-//
-//
-//`
+	//	_testYaml = `
+	//Foo:
+	//    bar :
+	//        - {name: nihao,age: 1}
+	//        - {name: nihao,age: 1}
+	//
+	//
+	//`
 )
 
 //func TestScan(t *testing.T) {
@@ -288,3 +289,35 @@ func testScan(t *testing.T, c config.Config) {
 	}
 	t.Log(conf)
 }
+
+func TestMergeDataRace(t *testing.T) {
+	path := filepath.Join(t.TempDir(), "test_config.json")
+	defer os.Remove(path)
+	if err := ioutil.WriteFile(path, []byte(_testJSON), 0666); err != nil {
+		t.Error(err)
+	}
+	c := config.New(config.WithSource(
+		NewSource(path),
+	))
+	wg := &sync.WaitGroup{}
+	wg.Add(2)
+	go func() {
+		defer wg.Done()
+		for i := 0; i < 100; i++ {
+			var conf struct{}
+			if err := c.Scan(&conf); err != nil {
+				t.Error(err)
+			}
+		}
+	}()
+
+	go func() {
+		defer wg.Done()
+		for i := 0; i < 100; i++ {
+			if err := c.Load(); err != nil {
+				t.Error(err)
+			}
+		}
+	}()
+	wg.Wait()
+}
diff --git a/internal/context/context_test.go b/internal/context/context_test.go
new file mode 100644
index 00000000..563c8c69
--- /dev/null
+++ b/internal/context/context_test.go
@@ -0,0 +1,98 @@
+package context
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestContext(t *testing.T) {
+	ctx1 := context.WithValue(context.Background(), "go-kratos", "https://github.com/go-kratos/")
+	ctx2 := context.WithValue(context.Background(), "kratos", "https://go-kratos.dev/")
+
+	ctx, cancel := Merge(ctx1, ctx2)
+	defer cancel()
+
+	got := ctx.Value("go-kratos")
+	value1, ok := got.(string)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, value1, "https://github.com/go-kratos/")
+	//
+	got2 := ctx.Value("kratos")
+	value2, ok := got2.(string)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, value2, "https://go-kratos.dev/")
+
+	t.Log(value1)
+	t.Log(value2)
+}
+
+func TestErr(t *testing.T) {
+	ctx1, cancel := context.WithTimeout(context.Background(), time.Microsecond)
+	defer cancel()
+	time.Sleep(time.Millisecond)
+
+	ctx, cancel := Merge(ctx1, context.Background())
+	defer cancel()
+
+	assert.Equal(t, ctx.Err(), context.DeadlineExceeded)
+}
+
+func TestDone(t *testing.T) {
+	ctx1, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	ctx, cancel := Merge(ctx1, context.Background())
+	go func() {
+		time.Sleep(time.Millisecond * 50)
+		cancel()
+	}()
+
+	assert.Equal(t, <-ctx.Done(), struct{}{})
+}
+
+func TestFinish(t *testing.T) {
+	mc := &mergeCtx{
+		parent1:  context.Background(),
+		parent2:  context.Background(),
+		done:     make(chan struct{}),
+		cancelCh: make(chan struct{}),
+	}
+	err := mc.finish(context.DeadlineExceeded)
+	assert.Equal(t, err, context.DeadlineExceeded)
+	assert.Equal(t, mc.doneMark, uint32(1))
+	assert.Equal(t, <-mc.done, struct{}{})
+}
+
+func TestWait(t *testing.T) {
+	ctx1, cancel := context.WithCancel(context.Background())
+
+	mc := &mergeCtx{
+		parent1:  ctx1,
+		parent2:  context.Background(),
+		done:     make(chan struct{}),
+		cancelCh: make(chan struct{}),
+	}
+	go func() {
+		time.Sleep(time.Millisecond * 50)
+		cancel()
+	}()
+
+	mc.wait()
+	t.Log(mc.doneErr)
+	assert.Equal(t, mc.doneErr, context.Canceled)
+}
+
+func TestCancel(t *testing.T) {
+	mc := &mergeCtx{
+		parent1:  context.Background(),
+		parent2:  context.Background(),
+		done:     make(chan struct{}),
+		cancelCh: make(chan struct{}),
+	}
+	mc.cancel()
+
+	assert.Equal(t, <-mc.cancelCh, struct{}{})
+}
diff --git a/internal/endpoint/endpoint_test.go b/internal/endpoint/endpoint_test.go
index 7b9519ba..db089288 100644
--- a/internal/endpoint/endpoint_test.go
+++ b/internal/endpoint/endpoint_test.go
@@ -40,3 +40,73 @@ func TestEndPoint(t *testing.T) {
 		})
 	}
 }
+
+func TestNewEndpoint(t *testing.T) {
+	type args struct {
+		scheme   string
+		host     string
+		isSecure bool
+	}
+	tests := []struct {
+		name string
+		args args
+		want *url.URL
+	}{
+		{
+			name: "https://github.com/go-kratos/kratos/",
+			args: args{"https", "github.com/go-kratos/kratos/", false},
+			want: &url.URL{Scheme: "https", Host: "github.com/go-kratos/kratos/"},
+		},
+		{
+			name: "https://go-kratos.dev/",
+			args: args{"https", "go-kratos.dev/", true},
+			want: &url.URL{Scheme: "https", Host: "go-kratos.dev/", RawQuery: "isSecure=true"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := NewEndpoint(tt.args.scheme, tt.args.host, tt.args.isSecure); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewEndpoint() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestParseEndpoint(t *testing.T) {
+	type args struct {
+		endpoints []string
+		scheme    string
+		isSecure  bool
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr bool
+	}{
+		{
+			name:    "kratos",
+			args:    args{endpoints: []string{"https://github.com/go-kratos/kratos?isSecure=true"}, scheme: "https", isSecure: true},
+			want:    "github.com",
+			wantErr: false,
+		},
+		{
+			name:    "test",
+			args:    args{endpoints: []string{"https://go-kratos.dev/"}, scheme: "http", isSecure: true},
+			want:    "",
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme, tt.args.isSecure)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ParseEndpoint() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("ParseEndpoint() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/internal/httputil/http_test.go b/internal/httputil/http_test.go
index e8058b2c..f6b7ba3e 100644
--- a/internal/httputil/http_test.go
+++ b/internal/httputil/http_test.go
@@ -1,6 +1,10 @@
 package httputil
 
-import "testing"
+import (
+	"google.golang.org/grpc/codes"
+	"net/http"
+	"testing"
+)
 
 func TestContentSubtype(t *testing.T) {
 	tests := []struct {
@@ -25,3 +29,86 @@ func TestContentSubtype(t *testing.T) {
 		})
 	}
 }
+
+func TestGRPCCodeFromStatus(t *testing.T) {
+
+	tests := []struct {
+		name string
+		code int
+		want codes.Code
+	}{
+		{"http.StatusOK", http.StatusOK, codes.OK},
+		{"http.StatusBadRequest", http.StatusBadRequest, codes.InvalidArgument},
+		{"http.StatusUnauthorized", http.StatusUnauthorized, codes.Unauthenticated},
+		{"http.StatusForbidden", http.StatusForbidden, codes.PermissionDenied},
+		{"http.StatusNotFound", http.StatusNotFound, codes.NotFound},
+		{"http.StatusConflict", http.StatusConflict, codes.Aborted},
+		{"http.StatusTooManyRequests", http.StatusTooManyRequests, codes.ResourceExhausted},
+		{"http.StatusInternalServerError", http.StatusInternalServerError, codes.Internal},
+		{"http.StatusNotImplemented", http.StatusNotImplemented, codes.Unimplemented},
+		{"http.StatusServiceUnavailable", http.StatusServiceUnavailable, codes.Unavailable},
+		{"http.StatusGatewayTimeout", http.StatusGatewayTimeout, codes.DeadlineExceeded},
+		{"StatusClientClosed", StatusClientClosed, codes.Canceled},
+		{"else", 100000, codes.Unknown},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GRPCCodeFromStatus(tt.code); got != tt.want {
+				t.Errorf("GRPCCodeFromStatus() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestStatusFromGRPCCode(t *testing.T) {
+	tests := []struct {
+		name string
+		code codes.Code
+		want int
+	}{
+		{"codes.OK", codes.OK, http.StatusOK},
+		{"codes.Canceled", codes.Canceled, StatusClientClosed},
+		{"codes.Unknown", codes.Unknown, http.StatusInternalServerError},
+		{"codes.InvalidArgument", codes.InvalidArgument, http.StatusBadRequest},
+		{"codes.DeadlineExceeded", codes.DeadlineExceeded, http.StatusGatewayTimeout},
+		{"codes.NotFound", codes.NotFound, http.StatusNotFound},
+		{"codes.AlreadyExists", codes.AlreadyExists, http.StatusConflict},
+		{"codes.PermissionDenied", codes.PermissionDenied, http.StatusForbidden},
+		{"codes.Unauthenticated", codes.Unauthenticated, http.StatusUnauthorized},
+		{"codes.ResourceExhausted", codes.ResourceExhausted, http.StatusTooManyRequests},
+		{"codes.FailedPrecondition", codes.FailedPrecondition, http.StatusBadRequest},
+		{"codes.Aborted", codes.Aborted, http.StatusConflict},
+		{"codes.OutOfRange", codes.OutOfRange, http.StatusBadRequest},
+		{"codes.Unimplemented", codes.Unimplemented, http.StatusNotImplemented},
+		{"codes.Internal", codes.Internal, http.StatusInternalServerError},
+		{"codes.Unavailable", codes.Unavailable, http.StatusServiceUnavailable},
+		{"codes.DataLoss", codes.DataLoss, http.StatusInternalServerError},
+		{"else", codes.Code(10000), http.StatusInternalServerError},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := StatusFromGRPCCode(tt.code); got != tt.want {
+				t.Errorf("StatusFromGRPCCode() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestContentType(t *testing.T) {
+
+	tests := []struct {
+		name string
+		subtype string
+		want string
+	}{
+		{"kratos","kratos","application/kratos"},
+		{"json","json","application/json"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := ContentType(tt.subtype); got != tt.want {
+				t.Errorf("ContentType() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
\ No newline at end of file
diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go
new file mode 100644
index 00000000..55439b16
--- /dev/null
+++ b/metadata/metadata_test.go
@@ -0,0 +1,285 @@
+package metadata
+
+import (
+	"context"
+	"reflect"
+	"testing"
+)
+
+func TestNew(t *testing.T) {
+	type args struct {
+		mds []map[string]string
+	}
+	tests := []struct {
+		name string
+		args args
+		want Metadata
+	}{
+		{
+			name: "hello",
+			args: args{[]map[string]string{{"hello": "kratos"}, {"hello2": "go-kratos"}}},
+			want: Metadata{"hello": "kratos", "hello2": "go-kratos"},
+		},
+		{
+			name: "hi",
+			args: args{[]map[string]string{{"hi": "kratos"}, {"hi2": "go-kratos"}}},
+			want: Metadata{"hi": "kratos", "hi2": "go-kratos"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := New(tt.args.mds...); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("New() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestMetadata_Get(t *testing.T) {
+	type args struct {
+		key string
+	}
+	tests := []struct {
+		name string
+		m    Metadata
+		args args
+		want string
+	}{
+		{
+			name: "kratos",
+			m:    Metadata{"kratos": "value", "env": "dev"},
+			args: args{key: "kratos"},
+			want: "value",
+		},
+		{
+			name: "env",
+			m:    Metadata{"kratos": "value", "env": "dev"},
+			args: args{key: "env"},
+			want: "dev",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.m.Get(tt.args.key); got != tt.want {
+				t.Errorf("Get() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestMetadata_Set(t *testing.T) {
+	type args struct {
+		key   string
+		value string
+	}
+	tests := []struct {
+		name string
+		m    Metadata
+		args args
+		want Metadata
+	}{
+		{
+			name: "kratos",
+			m:    Metadata{},
+			args: args{key: "hello", value: "kratos"},
+			want: Metadata{"hello": "kratos"},
+		},
+		{
+			name: "env",
+			m:    Metadata{"hello": "kratos"},
+			args: args{key: "env", value: "pro"},
+			want: Metadata{"hello": "kratos", "env": "pro"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.m.Set(tt.args.key, tt.args.value)
+			if !reflect.DeepEqual(tt.m, tt.want) {
+				t.Errorf("Set() = %v, want %v", tt.m, tt.want)
+			}
+		})
+	}
+}
+
+func TestClientContext(t *testing.T) {
+	type args struct {
+		ctx context.Context
+		md  Metadata
+	}
+	tests := []struct {
+		name string
+		args args
+	}{
+		{
+			name: "kratos",
+			args: args{context.Background(), Metadata{"hello": "kratos", "kratos": "https://go-kratos.dev"}},
+		},
+		{
+			name: "hello",
+			args: args{context.Background(), Metadata{"hello": "kratos", "hello2": "https://go-kratos.dev"}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := NewClientContext(tt.args.ctx, tt.args.md)
+			m, ok := FromClientContext(ctx)
+			if !ok {
+				t.Errorf("FromClientContext() = %v, want %v", ok, true)
+			}
+
+			if !reflect.DeepEqual(m, tt.args.md) {
+				t.Errorf("meta = %v, want %v", m, tt.args.md)
+			}
+		})
+	}
+}
+
+func TestServerContext(t *testing.T) {
+	type args struct {
+		ctx context.Context
+		md  Metadata
+	}
+	tests := []struct {
+		name string
+		args args
+	}{
+		{
+			name: "kratos",
+			args: args{context.Background(), Metadata{"hello": "kratos", "kratos": "https://go-kratos.dev"}},
+		},
+		{
+			name: "hello",
+			args: args{context.Background(), Metadata{"hello": "kratos", "hello2": "https://go-kratos.dev"}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := NewServerContext(tt.args.ctx, tt.args.md)
+			m, ok := FromServerContext(ctx)
+			if !ok {
+				t.Errorf("FromServerContext() = %v, want %v", ok, true)
+			}
+
+			if !reflect.DeepEqual(m, tt.args.md) {
+				t.Errorf("meta = %v, want %v", m, tt.args.md)
+			}
+		})
+	}
+}
+
+func TestAppendToClientContext(t *testing.T) {
+	type args struct {
+		md Metadata
+		kv []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want Metadata
+	}{
+		{
+			name: "kratos",
+			args: args{Metadata{}, []string{"hello", "kratos", "env", "dev"}},
+			want: Metadata{"hello": "kratos", "env": "dev"},
+		},
+		{
+			name: "hello",
+			args: args{Metadata{"hi": "https://go-kratos.dev/"}, []string{"hello", "kratos", "env", "dev"}},
+			want: Metadata{"hello": "kratos", "env": "dev", "hi": "https://go-kratos.dev/"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := NewClientContext(context.Background(), tt.args.md)
+			ctx = AppendToClientContext(ctx, tt.args.kv...)
+			md, ok := FromClientContext(ctx)
+			if !ok {
+				t.Errorf("FromServerContext() = %v, want %v", ok, true)
+			}
+			if !reflect.DeepEqual(md, tt.want) {
+				t.Errorf("metadata = %v, want %v", md, tt.want)
+			}
+		})
+	}
+}
+
+func TestMergeToClientContext(t *testing.T) {
+	type args struct {
+		md       Metadata
+		appendMd Metadata
+	}
+	tests := []struct {
+		name string
+		args args
+		want Metadata
+	}{
+		{
+			name: "kratos",
+			args: args{Metadata{}, Metadata{"hello": "kratos", "env": "dev"}},
+			want: Metadata{"hello": "kratos", "env": "dev"},
+		},
+		{
+			name: "hello",
+			args: args{Metadata{"hi": "https://go-kratos.dev/"}, Metadata{"hello": "kratos", "env": "dev"}},
+			want: Metadata{"hello": "kratos", "env": "dev", "hi": "https://go-kratos.dev/"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := NewClientContext(context.Background(), tt.args.md)
+			ctx = MergeToClientContext(ctx, tt.args.appendMd)
+			md, ok := FromClientContext(ctx)
+			if !ok {
+				t.Errorf("FromServerContext() = %v, want %v", ok, true)
+			}
+			if !reflect.DeepEqual(md, tt.want) {
+				t.Errorf("metadata = %v, want %v", md, tt.want)
+			}
+		})
+	}
+}
+
+func TestMetadata_Range(t *testing.T) {
+	md := Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"}
+	var tmp = Metadata{}
+	md.Range(func(k, v string) bool {
+		if k == "https://go-kratos.dev/" || k == "kratos" {
+			tmp[k] = v
+		}
+		return true
+	})
+	if !reflect.DeepEqual(tmp, Metadata{"https://go-kratos.dev/": "https://go-kratos.dev/", "kratos": "kratos"}) {
+		t.Errorf("metadata = %v, want %v", tmp, Metadata{"kratos": "kratos"})
+	}
+}
+
+func TestMetadata_Clone(t *testing.T) {
+	tests := []struct {
+		name string
+		m    Metadata
+		want Metadata
+	}{
+		{
+			name: "kratos",
+			m:    Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"},
+			want: Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"},
+		},
+		{
+			name: "go",
+			m:    Metadata{"language": "golang"},
+			want: Metadata{"language": "golang"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.m.Clone()
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Clone() = %v, want %v", got, tt.want)
+			}
+			got["kratos"] = "go"
+			if reflect.DeepEqual(got, tt.want) {
+				t.Errorf("want got != want got %v want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go
new file mode 100644
index 00000000..ca5ff4c5
--- /dev/null
+++ b/middleware/middleware_test.go
@@ -0,0 +1,54 @@
+package middleware
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var i int
+
+func TestChain(t *testing.T) {
+	next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		t.Log(req)
+		i += 10
+		return "reply", nil
+	}
+
+	got, err := Chain(test1Middleware, test2Middleware, test3Middleware)(next)(context.Background(), "hello kratos!")
+	assert.Nil(t, err)
+	assert.Equal(t, got, "reply")
+	assert.Equal(t, i, 16)
+}
+
+func test1Middleware(handler Handler) Handler {
+	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+		fmt.Println("test1 before")
+		i++
+		reply, err = handler(ctx, req)
+		fmt.Println("test1 after")
+		return
+	}
+}
+
+func test2Middleware(handler Handler) Handler {
+	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+		fmt.Println("test2 before")
+		i += 2
+		reply, err = handler(ctx, req)
+		fmt.Println("test2 after")
+		return
+	}
+}
+
+func test3Middleware(handler Handler) Handler {
+	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+		fmt.Println("test3 before")
+		i += 3
+		reply, err = handler(ctx, req)
+		fmt.Println("test3 after")
+		return
+	}
+}
diff --git a/middleware/tracing/tracing_test.go b/middleware/tracing/tracing_test.go
index 10d69880..7725c030 100644
--- a/middleware/tracing/tracing_test.go
+++ b/middleware/tracing/tracing_test.go
@@ -57,15 +57,15 @@ func TestTracing(t *testing.T) {
 	tracer := NewTracer(trace.SpanKindClient, WithTracerProvider(tp), WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})))
 	ts := &Transport{kind: transport.KindHTTP, header: carrier}
 
-	ctx, aboveSpan := tracer.Start(transport.NewClientContext(context.Background(), ts), ts.Kind().String(), ts.Operation(), ts.RequestHeader())
-	defer tracer.End(ctx, aboveSpan, nil)
+	ctx, aboveSpan := tracer.Start(transport.NewClientContext(context.Background(), ts), ts.Operation(), ts.RequestHeader())
+	defer tracer.End(ctx, aboveSpan, nil, nil)
 
 	// server use Extract fetch traceInfo from carrier
 	tracer = NewTracer(trace.SpanKindServer, WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})))
 	ts = &Transport{kind: transport.KindHTTP, header: carrier}
 
-	ctx, span := tracer.Start(transport.NewServerContext(ctx, ts), ts.Kind().String(), ts.Operation(), ts.RequestHeader())
-	defer tracer.End(ctx, span, nil)
+	ctx, span := tracer.Start(transport.NewServerContext(ctx, ts), ts.Operation(), ts.RequestHeader())
+	defer tracer.End(ctx, span, nil, nil)
 
 	if aboveSpan.SpanContext().TraceID() != span.SpanContext().TraceID() {
 		t.Fatalf("TraceID failed to deliver")
diff --git a/options_test.go b/options_test.go
index 34bce67c..c7f34287 100644
--- a/options_test.go
+++ b/options_test.go
@@ -66,7 +66,7 @@ func TestLogger(t *testing.T) {
 	o := &options{}
 	v := xlog.NewStdLogger(log.Writer())
 	Logger(v)(o)
-	assert.Equal(t, v, o.logger)
+	assert.Equal(t, xlog.NewHelper(v), o.logger)
 }
 
 type mockServer struct{}
diff --git a/transport/grpc/client_test.go b/transport/grpc/client_test.go
index 9349a16b..89122cbf 100644
--- a/transport/grpc/client_test.go
+++ b/transport/grpc/client_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"crypto/tls"
 	"testing"
+	"time"
 
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
@@ -18,6 +19,13 @@ func TestWithEndpoint(t *testing.T) {
 	assert.Equal(t, v, o.endpoint)
 }
 
+func TestWithTimeout(t *testing.T) {
+	o := &clientOptions{}
+	v := time.Duration(123)
+	WithTimeout(v)(o)
+	assert.Equal(t, v, o.timeout)
+}
+
 func TestWithMiddleware(t *testing.T) {
 	o := &clientOptions{}
 	v := []middleware.Middleware{
@@ -50,6 +58,25 @@ func TestWithTLSConfig(t *testing.T) {
 	assert.Equal(t, v, o.tlsConf)
 }
 
+func EmptyMiddleware() middleware.Middleware {
+	return func(handler middleware.Handler) middleware.Handler {
+		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+			return handler(ctx, req)
+		}
+	}
+}
+
+func TestUnaryClientInterceptor(t *testing.T) {
+	f := unaryClientInterceptor([]middleware.Middleware{EmptyMiddleware()}, time.Duration(100))
+	req := &struct{}{}
+	resp := &struct{}{}
+
+	err := f(context.TODO(), "hello", req, resp, &grpc.ClientConn{}, func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
+		return nil
+	})
+	assert.NoError(t, err)
+}
+
 func TestWithUnaryInterceptor(t *testing.T) {
 	o := &clientOptions{}
 	v := []grpc.UnaryClientInterceptor{
@@ -74,3 +101,12 @@ func TestWithOptions(t *testing.T) {
 	WithOptions(v...)(o)
 	assert.Equal(t, v, o.grpcOpts)
 }
+
+func TestDial(t *testing.T) {
+	o := &clientOptions{}
+	v := []grpc.DialOption{
+		grpc.EmptyDialOption{},
+	}
+	WithOptions(v...)(o)
+	assert.Equal(t, v, o.grpcOpts)
+}
diff --git a/transport/grpc/resolver/direct/builder_test.go b/transport/grpc/resolver/direct/builder_test.go
new file mode 100644
index 00000000..8db9cddd
--- /dev/null
+++ b/transport/grpc/resolver/direct/builder_test.go
@@ -0,0 +1,37 @@
+package direct
+
+import (
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc/resolver"
+	"google.golang.org/grpc/serviceconfig"
+	"testing"
+)
+
+func TestDirectBuilder_Scheme(t *testing.T) {
+	b := NewBuilder()
+	assert.Equal(t, "direct", b.Scheme())
+}
+
+type mockConn struct {
+}
+
+func (m *mockConn) UpdateState(resolver.State) error {
+	return nil
+}
+
+func (m *mockConn) ReportError(error) {}
+
+func (m *mockConn) NewAddress(addresses []resolver.Address) {}
+
+func (m *mockConn) NewServiceConfig(serviceConfig string) {}
+
+func (m *mockConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
+	return nil
+}
+
+func TestDirectBuilder_Build(t *testing.T) {
+	b := NewBuilder()
+	r, err := b.Build(resolver.Target{}, &mockConn{}, resolver.BuildOptions{})
+	assert.NoError(t, err)
+	r.ResolveNow(resolver.ResolveNowOptions{})
+}
diff --git a/transport/grpc/resolver/direct/resolver_test.go b/transport/grpc/resolver/direct/resolver_test.go
new file mode 100644
index 00000000..47717734
--- /dev/null
+++ b/transport/grpc/resolver/direct/resolver_test.go
@@ -0,0 +1,1 @@
+package direct
diff --git a/transport/grpc/resolver/discovery/builder_test.go b/transport/grpc/resolver/discovery/builder_test.go
new file mode 100644
index 00000000..c68120c5
--- /dev/null
+++ b/transport/grpc/resolver/discovery/builder_test.go
@@ -0,0 +1,81 @@
+package discovery
+
+import (
+	"context"
+	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc/resolver"
+	"google.golang.org/grpc/serviceconfig"
+	"testing"
+	"time"
+)
+
+type mockLogger struct {
+	level log.Level
+	key   string
+	val   string
+}
+
+func (l *mockLogger) Log(level log.Level, keyvals ...interface{}) error {
+	l.level = level
+	l.key = keyvals[0].(string)
+	l.val = keyvals[1].(string)
+	return nil
+}
+
+func TestWithLogger(t *testing.T) {
+	b := &builder{}
+	WithLogger(&mockLogger{})(b)
+}
+
+func TestWithInsecure(t *testing.T) {
+	b := &builder{}
+	WithInsecure(true)(b)
+	assert.True(t, b.insecure)
+}
+
+func TestWithTimeout(t *testing.T) {
+	o := &builder{}
+	v := time.Duration(123)
+	WithTimeout(v)(o)
+	assert.Equal(t, v, o.timeout)
+}
+
+type mockDiscovery struct {
+}
+
+func (m *mockDiscovery) GetService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
+	return nil, nil
+}
+func (m *mockDiscovery) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) {
+	return &testWatch{}, nil
+}
+
+func TestBuilder_Scheme(t *testing.T) {
+	b := NewBuilder(&mockDiscovery{})
+	assert.Equal(t, "discovery", b.Scheme())
+}
+
+type mockConn struct {
+}
+
+func (m *mockConn) UpdateState(resolver.State) error {
+	return nil
+}
+
+func (m *mockConn) ReportError(error) {}
+
+func (m *mockConn) NewAddress(addresses []resolver.Address) {}
+
+func (m *mockConn) NewServiceConfig(serviceConfig string) {}
+
+func (m *mockConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
+	return nil
+}
+
+func TestBuilder_Build(t *testing.T) {
+	b := NewBuilder(&mockDiscovery{})
+	_, err := b.Build(resolver.Target{Scheme: resolver.GetDefaultScheme(), Endpoint: "gprc://authority/endpoint"}, &mockConn{}, resolver.BuildOptions{})
+	assert.NoError(t, err)
+}
diff --git a/transport/grpc/resolver/discovery/resolver_test.go b/transport/grpc/resolver/discovery/resolver_test.go
index 06e7a09f..2492b177 100644
--- a/transport/grpc/resolver/discovery/resolver_test.go
+++ b/transport/grpc/resolver/discovery/resolver_test.go
@@ -2,11 +2,13 @@ package discovery
 
 import (
 	"context"
+	"errors"
 	"testing"
 	"time"
 
 	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/stretchr/testify/assert"
 	"google.golang.org/grpc/resolver"
 )
 
@@ -21,30 +23,57 @@ func (t *testClientConn) UpdateState(s resolver.State) error {
 }
 
 type testWatch struct {
+	err error
 }
 
 func (m *testWatch) Next() ([]*registry.ServiceInstance, error) {
 	time.Sleep(time.Millisecond * 200)
 	ins := []*registry.ServiceInstance{
 		{
-			ID:      "mock_ID",
-			Name:    "mock_Name",
-			Version: "mock_Version",
+			ID:        "mock_ID",
+			Name:      "mock_Name",
+			Version:   "mock_Version",
+			Endpoints: []string{"grpc://127.0.0.1?isSecure=true"},
+		},
+		{
+			ID:        "mock_ID2",
+			Name:      "mock_Name2",
+			Version:   "mock_Version2",
+			Endpoints: []string{""},
 		},
 	}
-	return ins, nil
+	return ins, m.err
 }
 
 // Watch creates a watcher according to the service name.
 func (m *testWatch) Stop() error {
-	return nil
+	return m.err
 }
 
 func TestWatch(t *testing.T) {
 	ctx, cancel := context.WithCancel(context.Background())
 
 	r := &discoveryResolver{
-		w:      &testWatch{},
+		w:        &testWatch{},
+		cc:       &testClientConn{te: t},
+		log:      log.NewHelper(log.DefaultLogger),
+		ctx:      ctx,
+		cancel:   cancel,
+		insecure: false,
+	}
+	go func() {
+		time.Sleep(time.Second * 2)
+		r.Close()
+	}()
+	r.watch()
+	t.Log("watch goroutine exited after 2 second")
+}
+
+func TestWatchError(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+
+	r := &discoveryResolver{
+		w:      &testWatch{err: errors.New("bad")},
 		cc:     &testClientConn{te: t},
 		log:    log.NewHelper(log.DefaultLogger),
 		ctx:    ctx,
@@ -55,6 +84,31 @@ func TestWatch(t *testing.T) {
 		r.Close()
 	}()
 	r.watch()
+	t.Log("watch goroutine exited after 2 second")
+}
+
+func TestWatchContextCancel(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
 
+	r := &discoveryResolver{
+		w:      &testWatch{err: context.Canceled},
+		cc:     &testClientConn{te: t},
+		log:    log.NewHelper(log.DefaultLogger),
+		ctx:    ctx,
+		cancel: cancel,
+	}
+	go func() {
+		time.Sleep(time.Second * 2)
+		r.Close()
+	}()
+	r.watch()
 	t.Log("watch goroutine exited after 2 second")
 }
+
+func TestParseAttributes(t *testing.T) {
+	a := parseAttributes(map[string]string{"a": "b"})
+	assert.Equal(t, "b", a.Value("a").(string))
+	x := a.WithValues("qq", "ww")
+	assert.Equal(t, "ww", x.Value("qq").(string))
+	assert.Nil(t, x.Value("notfound"))
+}
diff --git a/transport/grpc/server_test.go b/transport/grpc/server_test.go
index 5dd287ca..1e9d6c32 100644
--- a/transport/grpc/server_test.go
+++ b/transport/grpc/server_test.go
@@ -3,8 +3,10 @@ package grpc
 import (
 	"context"
 	"crypto/tls"
+	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"google.golang.org/grpc"
+	"net/url"
 	"strings"
 	"testing"
 	"time"
@@ -17,7 +19,9 @@ type testKey struct{}
 func TestServer(t *testing.T) {
 	ctx := context.Background()
 	ctx = context.WithValue(ctx, testKey{}, "test")
-	srv := NewServer()
+	srv := NewServer(Middleware([]middleware.Middleware{
+		func(middleware.Handler) middleware.Handler { return nil },
+	}...))
 
 	if e, err := srv.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") {
 		t.Fatal(e, err)
@@ -69,16 +73,35 @@ func TestTimeout(t *testing.T) {
 }
 
 func TestMiddleware(t *testing.T) {
-	o := &clientOptions{}
+	o := &Server{}
 	v := []middleware.Middleware{
 		func(middleware.Handler) middleware.Handler { return nil },
 	}
-	WithMiddleware(v...)(o)
+	Middleware(v...)(o)
 	assert.Equal(t, v, o.middleware)
 }
 
+type mockLogger struct {
+	level log.Level
+	key   string
+	val   string
+}
+
+func (l *mockLogger) Log(level log.Level, keyvals ...interface{}) error {
+	l.level = level
+	l.key = keyvals[0].(string)
+	l.val = keyvals[1].(string)
+	return nil
+}
+
 func TestLogger(t *testing.T) {
-	//todo
+	o := &Server{}
+	v := &mockLogger{}
+	Logger(v)(o)
+	o.log.Log(log.LevelWarn, "foo", "bar")
+	assert.Equal(t, "foo", v.key)
+	assert.Equal(t, "bar", v.val)
+	assert.Equal(t, log.LevelWarn, v.level)
 }
 
 func TestTLSConfig(t *testing.T) {
@@ -110,3 +133,23 @@ func TestOptions(t *testing.T) {
 	Options(v...)(o)
 	assert.Equal(t, v, o.grpcOpts)
 }
+
+type testResp struct {
+	Data string
+}
+
+func TestServer_unaryServerInterceptor(t *testing.T) {
+	u, err := url.Parse("grpc://hello/world")
+	assert.NoError(t, err)
+	srv := &Server{ctx: context.Background(),
+		endpoint:   u,
+		middleware: []middleware.Middleware{EmptyMiddleware()},
+		timeout:    time.Duration(10),
+	}
+	req := &struct{}{}
+	rv, err := srv.unaryServerInterceptor()(context.TODO(), req, &grpc.UnaryServerInfo{}, func(ctx context.Context, req interface{}) (i interface{}, e error) {
+		return &testResp{Data: "hi"}, nil
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, "hi", rv.(*testResp).Data)
+}
diff --git a/transport/grpc/transport_test.go b/transport/grpc/transport_test.go
index 5d19ff51..6cbb4c26 100644
--- a/transport/grpc/transport_test.go
+++ b/transport/grpc/transport_test.go
@@ -29,6 +29,8 @@ func TestTransport_RequestHeader(t *testing.T) {
 	v.Set("a", "1")
 	o := &Transport{reqHeader: v}
 	assert.Equal(t, "1", o.RequestHeader().Get("a"))
+	assert.Equal(t, "", o.RequestHeader().Get("notfound"))
+
 }
 
 func TestTransport_ReplyHeader(t *testing.T) {
diff --git a/transport/http/calloption_test.go b/transport/http/calloption_test.go
new file mode 100644
index 00000000..fa81e5ae
--- /dev/null
+++ b/transport/http/calloption_test.go
@@ -0,0 +1,68 @@
+package http
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEmptyCallOptions(t *testing.T) {
+	assert.NoError(t, EmptyCallOption{}.before(&callInfo{}))
+	EmptyCallOption{}.after(&callInfo{}, &csAttempt{})
+}
+
+func TestContentType(t *testing.T) {
+	assert.Equal(t, "aaa", ContentType("aaa").(ContentTypeCallOption).ContentType)
+}
+
+func TestContentTypeCallOption_before(t *testing.T) {
+	c := &callInfo{}
+	err := ContentType("aaa").before(c)
+	assert.NoError(t, err)
+	assert.Equal(t, "aaa", c.contentType)
+}
+
+func TestDefaultCallInfo(t *testing.T) {
+	path := "hi"
+	rv := defaultCallInfo(path)
+	assert.Equal(t, path, rv.pathTemplate)
+	assert.Equal(t, path, rv.operation)
+	assert.Equal(t, "application/json", rv.contentType)
+}
+
+func TestOperation(t *testing.T) {
+	assert.Equal(t, "aaa", Operation("aaa").(OperationCallOption).Operation)
+}
+
+func TestOperationCallOption_before(t *testing.T) {
+	c := &callInfo{}
+	err := Operation("aaa").before(c)
+	assert.NoError(t, err)
+	assert.Equal(t, "aaa", c.operation)
+}
+
+func TestPathTemplate(t *testing.T) {
+	assert.Equal(t, "aaa", PathTemplate("aaa").(PathTemplateCallOption).Pattern)
+}
+
+func TestPathTemplateCallOption_before(t *testing.T) {
+	c := &callInfo{}
+	err := PathTemplate("aaa").before(c)
+	assert.NoError(t, err)
+	assert.Equal(t, "aaa", c.pathTemplate)
+}
+
+func TestHeader(t *testing.T) {
+	h := http.Header{"A": []string{"123"}}
+	assert.Equal(t, "123", Header(&h).(HeaderCallOption).header.Get("A"))
+}
+
+func TestHeaderCallOption_after(t *testing.T) {
+	h := http.Header{"A": []string{"123"}}
+	c := &callInfo{}
+	cs := &csAttempt{res: &http.Response{Header: h}}
+	o := Header(&h)
+	o.after(c, cs)
+	assert.Equal(t, &h, o.(HeaderCallOption).header)
+}
diff --git a/transport/http/client_test.go b/transport/http/client_test.go
index f95d42fc..8aaee2b5 100644
--- a/transport/http/client_test.go
+++ b/transport/http/client_test.go
@@ -3,6 +3,7 @@ package http
 import (
 	"bytes"
 	"context"
+	"crypto/tls"
 	"encoding/json"
 	"io/ioutil"
 	nethttp "net/http"
@@ -38,10 +39,25 @@ func TestWithTimeout(t *testing.T) {
 	assert.Equal(t, co.timeout, ov)
 }
 
+func TestWithBlock(t *testing.T) {
+	o := WithBlock()
+	co := &clientOptions{}
+	o(co)
+	assert.True(t, co.block)
+}
+
 func TestWithBalancer(t *testing.T) {
 
 }
 
+func TestWithTLSConfig(t *testing.T) {
+	ov := &tls.Config{}
+	o := WithTLSConfig(ov)
+	co := &clientOptions{}
+	o(co)
+	assert.Same(t, ov, co.tlsConf)
+}
+
 func TestWithUserAgent(t *testing.T) {
 	ov := "kratos"
 	o := WithUserAgent(ov)
diff --git a/transport/http/context_test.go b/transport/http/context_test.go
new file mode 100644
index 00000000..d02cfda6
--- /dev/null
+++ b/transport/http/context_test.go
@@ -0,0 +1,1 @@
+package http
diff --git a/transport/http/router_test.go b/transport/http/router_test.go
index 074df92e..b2b50f65 100644
--- a/transport/http/router_test.go
+++ b/transport/http/router_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"github.com/stretchr/testify/assert"
 	"log"
 	"net/http"
 	"strings"
@@ -166,3 +167,17 @@ func testRoute(t *testing.T, srv *Server) {
 		t.Fatal("cors failed")
 	}
 }
+
+func TestRouter_Group(t *testing.T) {
+	r := &Router{}
+	rr := r.Group("a", func(http.Handler) http.Handler { return nil })
+	assert.Equal(t, "a", rr.prefix)
+}
+
+func TestHandle(t *testing.T) {
+	r := newRouter("/", NewServer())
+	h := func(i Context) error {
+		return nil
+	}
+	r.GET("/get", h)
+}
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index 9c726cf5..2fa62c28 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"strings"
 	"testing"
 	"time"
@@ -181,13 +182,23 @@ func TestLogger(t *testing.T) {
 	//todo
 }
 
+func TestEndpoint(t *testing.T) {
+	u, err := url.Parse("http://hello/world")
+	assert.NoError(t, err)
+	o := &Server{}
+	Endpoint(u)(o)
+	assert.Equal(t, "hello", o.endpoint.Host)
+	assert.Equal(t, "http", o.endpoint.Scheme)
+
+}
+
 func TestMiddleware(t *testing.T) {
-	o := &clientOptions{}
+	o := &Server{}
 	v := []middleware.Middleware{
 		func(middleware.Handler) middleware.Handler { return nil },
 	}
-	WithMiddleware(v...)(o)
-	assert.Equal(t, v, o.middleware)
+	Middleware(v...)(o)
+	assert.Equal(t, v, o.ms)
 }
 
 func TestRequestDecoder(t *testing.T) {