Repository: go-kratos/kratos
Pull request #1053 (2021-06-14 02:28:22)

log: logger filter


Patch:
diff --git a/api/metadata/metadata_http.pb.go b/api/metadata/metadata_http.pb.go
index 8485c35b..09c21cf1 100644
--- a/api/metadata/metadata_http.pb.go
+++ b/api/metadata/metadata_http.pb.go
@@ -6,6 +6,7 @@ package metadata
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -34,7 +36,7 @@ func _Metadata_ListServices0_HTTP_Handler(srv MetadataHTTPServer) func(ctx http.
 		if err := ctx.Bind(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/kratos.api.Metadata/ListServices")
+		transport.SetOperation(ctx, "/kratos.api.Metadata/ListServices")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.ListServices(ctx, req.(*ListServicesRequest))
 		})
@@ -56,7 +58,7 @@ func _Metadata_GetServiceDesc0_HTTP_Handler(srv MetadataHTTPServer) func(ctx htt
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/kratos.api.Metadata/GetServiceDesc")
+		transport.SetOperation(ctx, "/kratos.api.Metadata/GetServiceDesc")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetServiceDesc(ctx, req.(*GetServiceDescRequest))
 		})
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index 0b18a29a..d4d3288f 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -12,6 +12,7 @@ import (
 
 const (
 	contextPackage       = protogen.GoImportPath("context")
+	transportPackage     = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport")
 	transportHTTPPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http")
 	bindingPackage       = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http/binding")
 )
@@ -43,6 +44,7 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
 	g.P("// This is a compile-time assertion to ensure that this generated file")
 	g.P("// is compatible with the kratos package it is being compiled against.")
 	g.P("var _ = new(", contextPackage.Ident("Context"), ")")
+	g.P("var _ = new(", transportPackage.Ident("Transporter"), ")")
 	g.P("var _ = ", bindingPackage.Ident("EncodeURL"))
 	g.P("const _ = ", transportHTTPPackage.Ident("SupportPackageIsVersion1"))
 	g.P()
diff --git a/cmd/protoc-gen-go-http/template.go b/cmd/protoc-gen-go-http/template.go
index 4a313099..42551999 100644
--- a/cmd/protoc-gen-go-http/template.go
+++ b/cmd/protoc-gen-go-http/template.go
@@ -34,7 +34,7 @@ func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) fu
 			return err
 		}
 		{{- end}}
-		http.SetOperation(ctx,"/{{$svrName}}/{{.Name}}")
+		transport.SetOperation(ctx,"/{{$svrName}}/{{.Name}}")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.{{.Name}}(ctx, req.(*{{.Request}}))
 		})
diff --git a/examples/blog/api/blog/v1/blog_http.pb.go b/examples/blog/api/blog/v1/blog_http.pb.go
index 3fcd414d..4aef7f9e 100644
--- a/examples/blog/api/blog/v1/blog_http.pb.go
+++ b/examples/blog/api/blog/v1/blog_http.pb.go
@@ -6,6 +6,7 @@ package v1
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -40,7 +42,7 @@ func _BlogService_CreateArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ct
 		if err := ctx.Bind(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/blog.api.v1.BlogService/CreateArticle")
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/CreateArticle")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.CreateArticle(ctx, req.(*CreateArticleRequest))
 		})
@@ -62,7 +64,7 @@ func _BlogService_UpdateArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ct
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/blog.api.v1.BlogService/UpdateArticle")
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/UpdateArticle")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.UpdateArticle(ctx, req.(*UpdateArticleRequest))
 		})
@@ -84,7 +86,7 @@ func _BlogService_DeleteArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ct
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/blog.api.v1.BlogService/DeleteArticle")
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/DeleteArticle")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.DeleteArticle(ctx, req.(*DeleteArticleRequest))
 		})
@@ -106,7 +108,7 @@ func _BlogService_GetArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx h
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/blog.api.v1.BlogService/GetArticle")
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/GetArticle")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetArticle(ctx, req.(*GetArticleRequest))
 		})
@@ -125,7 +127,7 @@ func _BlogService_ListArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx
 		if err := ctx.Bind(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/blog.api.v1.BlogService/ListArticle")
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/ListArticle")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.ListArticle(ctx, req.(*ListArticleRequest))
 		})
diff --git a/examples/helloworld/helloworld/helloworld_http.pb.go b/examples/helloworld/helloworld/helloworld_http.pb.go
index 55517338..e20130e3 100644
--- a/examples/helloworld/helloworld/helloworld_http.pb.go
+++ b/examples/helloworld/helloworld/helloworld_http.pb.go
@@ -6,6 +6,7 @@ package helloworld
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -35,7 +37,7 @@ func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Contex
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/helloworld.Greeter/SayHello")
+		transport.SetOperation(ctx, "/helloworld.Greeter/SayHello")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.SayHello(ctx, req.(*HelloRequest))
 		})
diff --git a/examples/http/middlewares/handlers.go b/examples/http/middlewares/handlers.go
index 08950ece..dce1ed2c 100644
--- a/examples/http/middlewares/handlers.go
+++ b/examples/http/middlewares/handlers.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/http"
 )
 
@@ -18,7 +19,7 @@ func sayHelloHandler(ctx http.Context) error {
 		return err
 	}
 
-	http.SetOperation(ctx, "/helloworld.Greeter/SayHello")
+	transport.SetOperation(ctx, "/helloworld.Greeter/SayHello")
 	h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 		return &helloworld.HelloReply{Message: "test:" + req.(*helloworld.HelloRequest).Name}, nil
 	})
diff --git a/examples/metadata/client/main.go b/examples/metadata/client/main.go
index d3248b81..2499c432 100644
--- a/examples/metadata/client/main.go
+++ b/examples/metadata/client/main.go
@@ -6,9 +6,11 @@ import (
 
 	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
 	"github.com/go-kratos/kratos/v2/metadata"
-	mmd "github.com/go-kratos/kratos/v2/middleware/metadata"
+	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
+	grpcmd "google.golang.org/grpc/metadata"
 )
 
 func main() {
@@ -20,7 +22,7 @@ func callHTTP() {
 	conn, err := http.NewClient(
 		context.Background(),
 		http.WithMiddleware(
-			mmd.Client(),
+			recovery.Recovery(),
 		),
 		http.WithEndpoint("127.0.0.1:8000"),
 	)
@@ -28,9 +30,12 @@ func callHTTP() {
 		panic(err)
 	}
 	client := helloworld.NewGreeterHTTPClient(conn)
-	ctx := context.Background()
-	ctx = metadata.AppendToClientContext(ctx, "x-md-global-extra", "2233")
-	reply, err := client.SayHello(ctx, &helloworld.HelloRequest{Name: "kratos"})
+	md := metadata.Metadata{"kratos-extra": "2233"}
+	reply, err := client.SayHello(context.Background(),
+		&helloworld.HelloRequest{Name: "kratos"},
+		// call options
+		http.Metadata(md),
+	)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -42,15 +47,16 @@ func callGRPC() {
 		context.Background(),
 		grpc.WithEndpoint("127.0.0.1:9000"),
 		grpc.WithMiddleware(
-			mmd.Client(),
+			middleware.Chain(
+				recovery.Recovery(),
+			),
 		),
 	)
 	if err != nil {
 		log.Fatal(err)
 	}
 	client := helloworld.NewGreeterClient(conn)
-	ctx := context.Background()
-	ctx = metadata.AppendToClientContext(ctx, "x-md-global-extra", "2233")
+	ctx := grpcmd.AppendToOutgoingContext(context.Background(), "kratos-extra", "2233")
 	reply, err := client.SayHello(ctx, &helloworld.HelloRequest{Name: "kratos"})
 	if err != nil {
 		log.Fatal(err)
diff --git a/examples/metadata/server/main.go b/examples/metadata/server/main.go
index e7cab1c4..67e35e0a 100644
--- a/examples/metadata/server/main.go
+++ b/examples/metadata/server/main.go
@@ -7,8 +7,8 @@ import (
 
 	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
 	"github.com/go-kratos/kratos/v2"
-	"github.com/go-kratos/kratos/v2/metadata"
-	mmd "github.com/go-kratos/kratos/v2/middleware/metadata"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
 )
@@ -29,8 +29,8 @@ type server struct {
 // SayHello implements helloworld.GreeterServer
 func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
 	var extra string
-	if md, ok := metadata.FromServerContext(ctx); ok {
-		extra = md.Get("x-md-global-extra")
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		extra = tr.Metadata().Get("kratos-extra")
 	}
 	return &helloworld.HelloReply{Message: fmt.Sprintf("Hello %s and %s", in.Name, extra)}, nil
 }
@@ -39,12 +39,12 @@ func main() {
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
 		grpc.Middleware(
-			mmd.Server(),
+			recovery.Recovery(),
 		))
 	httpSrv := http.NewServer(
 		http.Address(":8000"),
 		http.Middleware(
-			mmd.Server(),
+			recovery.Recovery(),
 		),
 	)
 
diff --git a/examples/traces/api/message/message_http.pb.go b/examples/traces/api/message/message_http.pb.go
index bfa19083..40ad223d 100644
--- a/examples/traces/api/message/message_http.pb.go
+++ b/examples/traces/api/message/message_http.pb.go
@@ -6,6 +6,7 @@ package v1
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -35,7 +37,7 @@ func _MessageService_GetUserMessage0_HTTP_Handler(srv MessageServiceHTTPServer)
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/api.message.v1.MessageService/GetUserMessage")
+		transport.SetOperation(ctx, "/api.message.v1.MessageService/GetUserMessage")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetUserMessage(ctx, req.(*GetUserMessageRequest))
 		})
diff --git a/examples/traces/api/user/user_http.pb.go b/examples/traces/api/user/user_http.pb.go
index 758b4560..cf26c3f2 100644
--- a/examples/traces/api/user/user_http.pb.go
+++ b/examples/traces/api/user/user_http.pb.go
@@ -6,6 +6,7 @@ package v1
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -35,7 +37,7 @@ func _User_GetMyMessages0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/api.user.v1.User/GetMyMessages")
+		transport.SetOperation(ctx, "/api.user.v1.User/GetMyMessages")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetMyMessages(ctx, req.(*GetMyMessagesRequest))
 		})
diff --git a/log/filter.go b/log/filter.go
new file mode 100644
index 00000000..de1ebf55
--- /dev/null
+++ b/log/filter.go
@@ -0,0 +1,81 @@
+package log
+
+// FilterOption is filter option.
+type FilterOption func(*Filter)
+
+// FilterLevel with filter level.
+func FilterLevel(level Level) FilterOption {
+	return func(opts *Filter) {
+		opts.level = level
+	}
+}
+
+// FilterKey with filter key.
+func FilterKey(key ...string) FilterOption {
+	return func(o *Filter) {
+		for _, v := range key {
+			o.key[v] = struct{}{}
+		}
+	}
+}
+
+// FilterValue with filter value.
+func FilterValue(value ...string) FilterOption {
+	return func(o *Filter) {
+		for _, v := range value {
+			o.value[v] = struct{}{}
+		}
+	}
+}
+
+// FilterFunc with filter func.
+func FilterFunc(f func(level Level, keyvals ...interface{}) bool) FilterOption {
+	return func(o *Filter) {
+		o.filter = f
+	}
+}
+
+// Filter is a logger filter.
+type Filter struct {
+	logger Logger
+	level  Level
+	key    map[interface{}]struct{}
+	value  map[interface{}]struct{}
+	filter func(level Level, keyvals ...interface{}) bool
+}
+
+// NewFilter new a logger filter.
+func NewFilter(logger Logger, opts ...FilterOption) *Filter {
+	options := Filter{
+		logger: logger,
+		key:    make(map[interface{}]struct{}),
+		value:  make(map[interface{}]struct{}),
+	}
+	for _, o := range opts {
+		o(&options)
+	}
+	return &options
+}
+
+// Log Print log by level and keyvals.
+func (f *Filter) Log(level Level, keyvals ...interface{}) error {
+	if f.level > level {
+		return nil
+	}
+	if f.filter != nil && f.filter(level, keyvals...) {
+		return nil
+	}
+	for i := 0; i < len(keyvals); i += 2 {
+		if _, ok := f.key[keyvals[i]]; ok {
+			keyvals[i+1] = "***"
+		}
+		vi := i + 1
+		if vi >= len(keyvals) {
+			continue
+		}
+		if _, ok := f.value[keyvals[vi]]; ok {
+			keyvals[i+1] = "***"
+		}
+	}
+	return f.logger.Log(level, keyvals...)
+}
diff --git a/log/helper.go b/log/helper.go
index 2a2aceb4..7e6a25d1 100644
--- a/log/helper.go
+++ b/log/helper.go
@@ -25,7 +25,7 @@ func (h *Helper) WithContext(ctx context.Context) *Helper {
 	}
 }
 
-// Log .
+// Log Print log by level and keyvals.
 func (h *Helper) Log(level Level, keyvals ...interface{}) {
 	h.logger.Log(level, keyvals...)
 }
diff --git a/log/value.go b/log/value.go
index ea6338eb..a472d77a 100644
--- a/log/value.go
+++ b/log/value.go
@@ -33,8 +33,13 @@ func Value(ctx context.Context, v interface{}) interface{} {
 func Caller(depth int) Valuer {
 	return func(context.Context) interface{} {
 		_, file, line, _ := runtime.Caller(depth)
+		if strings.LastIndex(file, "/log/filter.go") > 0 {
+			depth++
+			_, file, line, _ = runtime.Caller(depth)
+		}
 		if strings.LastIndex(file, "/log/helper.go") > 0 {
-			_, file, line, _ = runtime.Caller(depth + 1)
+			depth++
+			_, file, line, _ = runtime.Caller(depth)
 		}
 		idx := strings.LastIndexByte(file, '/')
 		return file[idx+1:] + ":" + strconv.Itoa(line)
diff --git a/metadata/context.go b/metadata/context.go
deleted file mode 100644
index e05a3775..00000000
--- a/metadata/context.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package metadata
-
-import (
-	"context"
-	"fmt"
-)
-
-type serverMetadataKey struct{}
-
-// NewServerContext creates a new context with client md attached.
-func NewServerContext(ctx context.Context, md Metadata) context.Context {
-	return context.WithValue(ctx, serverMetadataKey{}, md)
-}
-
-// FromServerContext returns the server metadata in ctx if it exists.
-func FromServerContext(ctx context.Context) (Metadata, bool) {
-	md, ok := ctx.Value(serverMetadataKey{}).(Metadata)
-	return md, ok
-}
-
-type clientMetadataKey struct{}
-
-// NewClientContext creates a new context with client md attached.
-func NewClientContext(ctx context.Context, md Metadata) context.Context {
-	return context.WithValue(ctx, clientMetadataKey{}, md)
-}
-
-// FromClientContext returns the client metadata in ctx if it exists.
-func FromClientContext(ctx context.Context) (Metadata, bool) {
-	md, ok := ctx.Value(clientMetadataKey{}).(Metadata)
-	return md, ok
-}
-
-// AppendToClientContext returns a new context with the provided kv merged
-// with any existing metadata in the context.
-func AppendToClientContext(ctx context.Context, kv ...string) context.Context {
-	if len(kv)%2 == 1 {
-		panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
-	}
-	md, _ := FromClientContext(ctx)
-	md = md.Clone()
-	for i := 0; i < len(kv); i += 2 {
-		md.Set(kv[i], kv[i+1])
-	}
-	return NewClientContext(ctx, md)
-}
diff --git a/metadata/metadata.go b/metadata/metadata.go
index 7cc225f2..445886fb 100644
--- a/metadata/metadata.go
+++ b/metadata/metadata.go
@@ -10,17 +10,15 @@ import (
 type Metadata map[string]string
 
 // New creates an MD from a given key-values map.
-func New(mds ...map[string]string) Metadata {
+func New(m map[string][]string) Metadata {
 	md := Metadata{}
-	for _, m := range mds {
-		for k, v := range m {
-			if k == "" {
-				continue
-			}
-			key := strings.ToLower(k)
-			if len(v) > 0 && v != "" {
-				md[key] = v
-			}
+	for k, v := range m {
+		if k == "" {
+			continue
+		}
+		key := strings.ToLower(k)
+		if len(v) > 0 && v[0] != "" {
+			md[key] = v[0]
 		}
 	}
 	return md
@@ -41,6 +39,24 @@ func (m Metadata) Set(key string, value string) {
 	m[k] = value
 }
 
+// Keys lists the keys stored in this carrier.
+func (m Metadata) Keys() []string {
+	keys := make([]string, 0, len(m))
+	for k := range m {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+// Pairs returns all metadata to key/value pairs.
+func (m Metadata) Pairs() []string {
+	var kvs = make([]string, 0, len(m)*2)
+	for k, v := range m {
+		kvs = append(kvs, k, v)
+	}
+	return kvs
+}
+
 // Clone returns a deep copy of Metadata
 func (m Metadata) Clone() Metadata {
 	md := Metadata{}
diff --git a/middleware/metadata/metadata.go b/middleware/metadata/metadata.go
deleted file mode 100644
index ac1e76ed..00000000
--- a/middleware/metadata/metadata.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package metadata
-
-import (
-	"context"
-	"strings"
-
-	"github.com/go-kratos/kratos/v2/metadata"
-	"github.com/go-kratos/kratos/v2/middleware"
-	"github.com/go-kratos/kratos/v2/transport"
-)
-
-// Option is metadata option.
-type Option func(*options)
-
-type options struct {
-	prefix []string
-	md     metadata.Metadata
-}
-
-// WithConstants is option with constant metadata key value.
-func WithConstants(md metadata.Metadata) Option {
-	return func(o *options) {
-		o.md = md
-	}
-}
-
-// WithPropagatedPrefix is option with global propagated key prefix.
-func WithPropagatedPrefix(prefix ...string) Option {
-	return func(o *options) {
-		o.prefix = prefix
-	}
-}
-
-// Server is middleware client-side metadata.
-func Server(opts ...Option) middleware.Middleware {
-	options := options{
-		prefix: []string{"x-md-global-", "x-md-local-"},
-	}
-	for _, o := range opts {
-		o(&options)
-	}
-	return func(handler middleware.Handler) middleware.Handler {
-		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
-			if tr, ok := transport.FromServerContext(ctx); ok {
-				md := metadata.Metadata{}
-				for _, k := range tr.Header().Keys() {
-					key := strings.ToLower(k)
-					for _, prefix := range options.prefix {
-						if strings.HasPrefix(key, prefix) {
-							md.Set(k, tr.Header().Get(k))
-							break
-						}
-					}
-				}
-				ctx = metadata.NewServerContext(ctx, md)
-			}
-			return handler(ctx, req)
-		}
-	}
-}
-
-// Client is middleware client-side metadata.
-func Client(opts ...Option) middleware.Middleware {
-	options := options{
-		prefix: []string{"x-md-global-"},
-	}
-	for _, o := range opts {
-		o(&options)
-	}
-	return func(handler middleware.Handler) middleware.Handler {
-		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
-			if tr, ok := transport.FromClientContext(ctx); ok {
-				for k, v := range options.md {
-					tr.Header().Set(k, v)
-				}
-				if md, ok := metadata.FromClientContext(ctx); ok {
-					for k, v := range md {
-						tr.Header().Set(k, v)
-					}
-				}
-				if md, ok := metadata.FromServerContext(ctx); ok {
-					for k, v := range md {
-						for _, prefix := range options.prefix {
-							if strings.HasPrefix(k, prefix) {
-								tr.Header().Set(k, v)
-								break
-							}
-						}
-					}
-				}
-			}
-			return handler(ctx, req)
-		}
-	}
-}
diff --git a/middleware/tracing/tracing.go b/middleware/tracing/tracing.go
index bc5e70db..2b3f385d 100644
--- a/middleware/tracing/tracing.go
+++ b/middleware/tracing/tracing.go
@@ -38,7 +38,7 @@ 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(), tr.Operation(), tr.Header())
+				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Operation(), tr.Metadata())
 				defer func() { tracer.End(ctx, span, err) }()
 			}
 			return handler(ctx, req)
@@ -53,7 +53,7 @@ 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(), tr.Operation(), tr.Header())
+				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Operation(), tr.Metadata())
 				defer func() { tracer.End(ctx, span, err) }()
 			}
 			return handler(ctx, req)
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index 58cbe950..55d6e7f2 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/go-kratos/kratos/v2/transport"
@@ -116,7 +117,7 @@ func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration) g
 		ctx = transport.NewClientContext(ctx, &Transport{
 			endpoint:  cc.Target(),
 			operation: method,
-			header:    headerCarrier{},
+			metadata:  metadata.Metadata{},
 		})
 		if timeout > 0 {
 			var cancel context.CancelFunc
@@ -125,12 +126,7 @@ func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration) g
 		}
 		h := func(ctx context.Context, req interface{}) (interface{}, error) {
 			if tr, ok := transport.FromClientContext(ctx); ok {
-				keys := tr.Header().Keys()
-				keyvals := make([]string, 0, len(keys))
-				for _, k := range keys {
-					keyvals = append(keyvals, k, tr.Header().Get(k))
-				}
-				ctx = grpcmd.AppendToOutgoingContext(ctx, keyvals...)
+				ctx = grpcmd.AppendToOutgoingContext(ctx, tr.Metadata().Pairs()...)
 			}
 			return reply, invoker(ctx, method, req, reply, cc, opts...)
 		}
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 5710c824..e6c6bf3a 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -11,6 +11,7 @@ import (
 	ic "github.com/go-kratos/kratos/v2/internal/context"
 	"github.com/go-kratos/kratos/v2/internal/host"
 	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/transport"
 
@@ -180,7 +181,7 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
 		ctx = transport.NewServerContext(ctx, &Transport{
 			endpoint:  s.endpoint.String(),
 			operation: info.FullMethod,
-			header:    headerCarrier(md),
+			metadata:  metadata.New(md),
 		})
 		if s.timeout > 0 {
 			var cancel context.CancelFunc
diff --git a/transport/grpc/transport.go b/transport/grpc/transport.go
index 80814f58..89bed23b 100644
--- a/transport/grpc/transport.go
+++ b/transport/grpc/transport.go
@@ -1,8 +1,8 @@
 package grpc
 
 import (
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/transport"
-	"google.golang.org/grpc/metadata"
 )
 
 var (
@@ -13,7 +13,7 @@ var (
 type Transport struct {
 	endpoint  string
 	operation string
-	header    headerCarrier
+	metadata  metadata.Metadata
 }
 
 // Kind returns the transport kind.
@@ -31,32 +31,19 @@ func (tr *Transport) Operation() string {
 	return tr.operation
 }
 
-// Header returns the transport header.
-func (tr *Transport) Header() transport.Header {
-	return tr.header
+// SetOperation sets the transport operation.
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
 }
 
-type headerCarrier metadata.MD
-
-// Get returns the value associated with the passed key.
-func (mc headerCarrier) Get(key string) string {
-	vals := metadata.MD(mc).Get(key)
-	if len(vals) > 0 {
-		return vals[0]
-	}
-	return ""
-}
-
-// Set stores the key-value pair.
-func (mc headerCarrier) Set(key string, value string) {
-	metadata.MD(mc).Set(key, value)
+// Metadata returns the transport metadata.
+func (tr *Transport) Metadata() metadata.Metadata {
+	return tr.metadata
 }
 
-// Keys lists the keys stored in this carrier.
-func (mc headerCarrier) Keys() []string {
-	keys := make([]string, 0, len(mc))
-	for k := range metadata.MD(mc) {
-		keys = append(keys, k)
+// WithMetadata with a metadata into transport md.
+func (tr *Transport) WithMetadata(md metadata.Metadata) {
+	for k, v := range md {
+		tr.metadata.Set(k, v)
 	}
-	return keys
 }
diff --git a/transport/http/calloption.go b/transport/http/calloption.go
index cd2cd72c..b2f6b043 100644
--- a/transport/http/calloption.go
+++ b/transport/http/calloption.go
@@ -1,5 +1,7 @@
 package http
 
+import "github.com/go-kratos/kratos/v2/metadata"
+
 // CallOption configures a Call before it starts or extracts information from
 // a Call after it completes.
 type CallOption interface {
@@ -15,6 +17,7 @@ type CallOption interface {
 type callInfo struct {
 	contentType string
 	operation   string
+	metatada    metadata.Metadata
 }
 
 // EmptyCallOption does not alter the Call configuration.
@@ -47,6 +50,7 @@ func defaultCallInfo(path string) callInfo {
 	return callInfo{
 		contentType: "application/json",
 		operation:   path,
+		metatada:    metadata.Metadata{},
 	}
 }
 
@@ -65,3 +69,19 @@ func (o OperationCallOption) before(c *callInfo) error {
 	c.operation = o.Operation
 	return nil
 }
+
+// Metadata is Metadata call option
+func Metadata(metatada metadata.Metadata) CallOption {
+	return MetadataCallOption{Metatada: metatada}
+}
+
+// MetadataCallOption is set Metadata for client call
+type MetadataCallOption struct {
+	EmptyCallOption
+	Metatada metadata.Metadata
+}
+
+func (o MetadataCallOption) before(c *callInfo) error {
+	c.metatada = o.Metatada
+	return nil
+}
diff --git a/transport/http/client.go b/transport/http/client.go
index 5d5a13e4..436859d5 100644
--- a/transport/http/client.go
+++ b/transport/http/client.go
@@ -12,6 +12,7 @@ import (
 	"github.com/go-kratos/kratos/v2/encoding"
 	"github.com/go-kratos/kratos/v2/errors"
 	"github.com/go-kratos/kratos/v2/internal/httputil"
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/go-kratos/kratos/v2/transport"
@@ -196,9 +197,12 @@ func (client *Client) Invoke(ctx context.Context, method, path string, args inte
 	if client.opts.userAgent != "" {
 		req.Header.Set("User-Agent", client.opts.userAgent)
 	}
+	if c.metatada == nil {
+		c.metatada = metadata.Metadata{}
+	}
 	ctx = transport.NewClientContext(ctx, &Transport{
 		endpoint:  client.opts.endpoint,
-		header:    headerCarrier(req.Header),
+		metadata:  c.metatada,
 		path:      path,
 		method:    method,
 		operation: c.operation,
@@ -226,6 +230,11 @@ func (client *Client) invoke(ctx context.Context, req *http.Request, args interf
 			req.URL.Scheme = scheme
 			req.URL.Host = addr
 		}
+		if tr, ok := transport.FromClientContext(ctx); ok {
+			for _, key := range tr.Metadata().Keys() {
+				req.Header.Set(key, tr.Metadata().Get(key))
+			}
+		}
 		res, err := client.do(ctx, req, c)
 		if done != nil {
 			done(ctx, balancer.DoneInfo{Err: err})
diff --git a/transport/http/server.go b/transport/http/server.go
index 99266ef4..12f7664c 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -12,6 +12,7 @@ import (
 	ic "github.com/go-kratos/kratos/v2/internal/context"
 	"github.com/go-kratos/kratos/v2/internal/host"
 	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/transport"
 
@@ -167,7 +168,7 @@ func (s *Server) filter() mux.MiddlewareFunc {
 				path:      req.RequestURI,
 				method:    req.Method,
 				operation: req.RequestURI,
-				header:    headerCarrier(req.Header),
+				metadata:  metadata.New(req.Header),
 			}
 			if r := mux.CurrentRoute(req); r != nil {
 				if path, err := r.GetPathTemplate(); err == nil {
diff --git a/transport/http/transport.go b/transport/http/transport.go
index 07a42741..8a9eb9bb 100644
--- a/transport/http/transport.go
+++ b/transport/http/transport.go
@@ -2,8 +2,8 @@ package http
 
 import (
 	"context"
-	"net/http"
 
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/transport"
 )
 
@@ -17,7 +17,7 @@ type Transport struct {
 	path      string
 	method    string
 	operation string
-	header    headerCarrier
+	metadata  metadata.Metadata
 }
 
 // Kind returns the transport kind.
@@ -35,9 +35,21 @@ func (tr *Transport) Operation() string {
 	return tr.operation
 }
 
-// Header returns the transport header.
-func (tr *Transport) Header() transport.Header {
-	return tr.header
+// SetOperation sets the transport operation.
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
+}
+
+// Metadata returns the transport metadata.
+func (tr *Transport) Metadata() metadata.Metadata {
+	return tr.metadata
+}
+
+// WithMetadata with a metadata into transport md.
+func (tr *Transport) WithMetadata(md metadata.Metadata) {
+	for k, v := range md {
+		tr.metadata.Set(k, v)
+	}
 }
 
 // Path returns the Transport path from server context.
@@ -59,33 +71,3 @@ func Method(ctx context.Context) string {
 	}
 	return ""
 }
-
-// SetOperation sets the transport operation.
-func SetOperation(ctx context.Context, op string) {
-	if tr, ok := transport.FromServerContext(ctx); ok {
-		if tr, ok := tr.(*Transport); ok {
-			tr.operation = op
-		}
-	}
-}
-
-type headerCarrier http.Header
-
-// Get returns the value associated with the passed key.
-func (hc headerCarrier) Get(key string) string {
-	return http.Header(hc).Get(key)
-}
-
-// Set stores the key-value pair.
-func (hc headerCarrier) Set(key string, value string) {
-	http.Header(hc).Set(key, value)
-}
-
-// Keys lists the keys stored in this carrier.
-func (hc headerCarrier) Keys() []string {
-	keys := make([]string, 0, len(hc))
-	for k := range http.Header(hc) {
-		keys = append(keys, k)
-	}
-	return keys
-}
diff --git a/transport/transport.go b/transport/transport.go
index fa814d5f..3d3ac113 100644
--- a/transport/transport.go
+++ b/transport/transport.go
@@ -9,6 +9,7 @@ import (
 	_ "github.com/go-kratos/kratos/v2/encoding/proto"
 	_ "github.com/go-kratos/kratos/v2/encoding/xml"
 	_ "github.com/go-kratos/kratos/v2/encoding/yaml"
+	"github.com/go-kratos/kratos/v2/metadata"
 )
 
 // Server is transport server.
@@ -22,19 +23,18 @@ type Endpointer interface {
 	Endpoint() (*url.URL, error)
 }
 
-// Header is the storage medium used by a Header.
-type Header interface {
-	Get(key string) string
-	Set(key string, value string)
-	Keys() []string
-}
-
 // Transporter is transport context value interface.
 type Transporter interface {
 	Kind() string
 	Endpoint() string
+
 	Operation() string
-	Header() Header
+	SetOperation(string)
+
+	Metadata() metadata.Metadata
+	// WithMetadata merge new metadata into transport,
+	// it will override old metadata key value if key exists
+	WithMetadata(metadata.Metadata)
 }
 
 type serverTransportKey struct{}
@@ -61,3 +61,26 @@ func FromClientContext(ctx context.Context) (tr Transporter, ok bool) {
 	tr, ok = ctx.Value(clientTransportKey{}).(Transporter)
 	return
 }
+
+// SetOperation set operation into context transport.
+func SetOperation(ctx context.Context, method string) {
+	if tr, ok := FromServerContext(ctx); ok {
+		tr.SetOperation(method)
+	}
+}
+
+// Operation returns the Transport operation from server context.
+func Operation(ctx context.Context) string {
+	if tr, ok := FromServerContext(ctx); ok {
+		return tr.Operation()
+	}
+	return ""
+}
+
+// Metadata returns incoming metadata from server transport.
+func Metadata(ctx context.Context) metadata.Metadata {
+	if tr, ok := FromServerContext(ctx); ok {
+		return tr.Metadata()
+	}
+	return metadata.Metadata{}
+}


Test patch:
diff --git a/internal/testproto/echo_service_http.pb.go b/internal/testproto/echo_service_http.pb.go
index 819db2c8..feca7ea2 100644
--- a/internal/testproto/echo_service_http.pb.go
+++ b/internal/testproto/echo_service_http.pb.go
@@ -6,6 +6,7 @@ package testproto
 
 import (
 	context "context"
+	transport "github.com/go-kratos/kratos/v2/transport"
 	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
 )
@@ -13,6 +14,7 @@ import (
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
 var _ = new(context.Context)
+var _ = new(transport.Transporter)
 var _ = binding.EncodeURL
 
 const _ = http.SupportPackageIsVersion1
@@ -47,7 +49,7 @@ func _EchoService_Echo0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Co
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/Echo")
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.Echo(ctx, req.(*SimpleMessage))
 		})
@@ -69,7 +71,7 @@ func _EchoService_Echo1_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Co
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/Echo")
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.Echo(ctx, req.(*SimpleMessage))
 		})
@@ -91,7 +93,7 @@ func _EchoService_Echo2_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Co
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/Echo")
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.Echo(ctx, req.(*SimpleMessage))
 		})
@@ -113,7 +115,7 @@ func _EchoService_Echo3_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Co
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/Echo")
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.Echo(ctx, req.(*SimpleMessage))
 		})
@@ -135,7 +137,7 @@ func _EchoService_Echo4_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Co
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/Echo")
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.Echo(ctx, req.(*SimpleMessage))
 		})
@@ -154,7 +156,7 @@ func _EchoService_EchoBody0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx htt
 		if err := ctx.Bind(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/EchoBody")
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoBody")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.EchoBody(ctx, req.(*SimpleMessage))
 		})
@@ -173,7 +175,7 @@ func _EchoService_EchoResponseBody0_HTTP_Handler(srv EchoServiceHTTPServer) func
 		if err := ctx.Bind(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/EchoResponseBody")
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoResponseBody")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.EchoResponseBody(ctx, req.(*DynamicMessageUpdate))
 		})
@@ -195,7 +197,7 @@ func _EchoService_EchoDelete0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx h
 		if err := ctx.BindVars(&in); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/EchoDelete")
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoDelete")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.EchoDelete(ctx, req.(*SimpleMessage))
 		})
@@ -214,7 +216,7 @@ func _EchoService_EchoPatch0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx ht
 		if err := ctx.Bind(&in.Body); err != nil {
 			return err
 		}
-		http.SetOperation(ctx, "/testproto.EchoService/EchoPatch")
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoPatch")
 		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.EchoPatch(ctx, req.(*DynamicMessageUpdate))
 		})
diff --git a/internal/testproto/echo_service_test.go b/internal/testproto/echo_service_test.go
index 373c7619..3621cfc3 100644
--- a/internal/testproto/echo_service_test.go
+++ b/internal/testproto/echo_service_test.go
@@ -9,22 +9,23 @@ import (
 
 	"github.com/go-kratos/kratos/v2/encoding"
 	"github.com/go-kratos/kratos/v2/metadata"
-	mmd "github.com/go-kratos/kratos/v2/middleware/metadata"
+	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
 
 	_struct "github.com/golang/protobuf/ptypes/struct"
+	grpcmd "google.golang.org/grpc/metadata"
 )
 
-var md = metadata.Metadata{"x-md-global-test": "test_value"}
+var md = metadata.Metadata{"test_key": "test_value"}
 
 type echoService struct {
 	UnimplementedEchoServiceServer
 }
 
 func (s *echoService) Echo(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) {
-	md, _ := metadata.FromServerContext(ctx)
-	if v := md.Get("x-md-global-test"); v != "test_value" {
+	md := transport.Metadata(ctx)
+	if v := md.Get("test_key"); v != "test_value" {
 		return nil, errors.New("md not match" + v)
 	}
 	return m, nil
@@ -52,7 +53,7 @@ type echoClient struct {
 
 // post: /v1/example/echo/{id}
 func (c *echoClient) Echo(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) {
-	return c.client.Echo(ctx, in)
+	return c.client.Echo(ctx, in, http.Metadata(md))
 }
 
 // post: /v1/example/echo_body
@@ -103,10 +104,7 @@ func TestJSON(t *testing.T) {
 func TestEchoHTTPServer(t *testing.T) {
 	echo := &echoService{}
 	ctx := context.Background()
-	srv := http.NewServer(
-		http.Address(":2333"),
-		http.Middleware(mmd.Server()),
-	)
+	srv := http.NewServer(http.Address(":2333"))
 	RegisterEchoServiceHTTPServer(srv, echo)
 	go func() {
 		if err := srv.Start(ctx); err != nil {
@@ -129,16 +127,11 @@ func testEchoHTTPClient(t *testing.T, addr string) {
 			t.Errorf("[%s] expected %v got %v", name, in, out)
 		}
 	}
-	cc, _ := http.NewClient(context.Background(),
-		http.WithEndpoint(addr),
-		http.WithMiddleware(mmd.Client()),
-	)
+	cc, _ := http.NewClient(context.Background(), http.WithEndpoint(addr))
 
 	cli := &echoClient{client: NewEchoServiceHTTPClient(cc)}
 
-	ctx := context.Background()
-	ctx = metadata.NewClientContext(ctx, md)
-	if out, err = cli.Echo(ctx, in); err != nil {
+	if out, err = cli.Echo(context.Background(), in); err != nil {
 		t.Fatal(err)
 	}
 	check("echo", &SimpleMessage{Id: "test_id"}, out)
@@ -180,10 +173,7 @@ func testEchoHTTPClient(t *testing.T, addr string) {
 func TestEchoGRPCServer(t *testing.T) {
 	echo := &echoService{}
 	ctx := context.Background()
-	srv := grpc.NewServer(
-		grpc.Address(":2233"),
-		grpc.Middleware(mmd.Server()),
-	)
+	srv := grpc.NewServer(grpc.Address(":2233"))
 	RegisterEchoServiceServer(srv, echo)
 	go func() {
 		if err := srv.Start(ctx); err != nil {
@@ -196,11 +186,8 @@ func TestEchoGRPCServer(t *testing.T) {
 }
 
 func testEchoGRPCClient(t *testing.T, addr string) {
-	cc, err := grpc.DialInsecure(
-		context.Background(),
-		grpc.WithEndpoint(addr),
-		grpc.WithMiddleware(mmd.Client()),
-	)
+	ctx := context.Background()
+	cc, err := grpc.DialInsecure(ctx, grpc.WithEndpoint(addr))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -209,8 +196,7 @@ func testEchoGRPCClient(t *testing.T, addr string) {
 		out = &SimpleMessage{}
 	)
 	client := NewEchoServiceClient(cc)
-	ctx := context.Background()
-	ctx = metadata.NewClientContext(ctx, md)
+	ctx = grpcmd.NewOutgoingContext(ctx, grpcmd.New(md))
 	if out, err = client.Echo(ctx, in); err != nil {
 		t.Fatal(err)
 	}
diff --git a/log/filter_test.go b/log/filter_test.go
new file mode 100644
index 00000000..386ee81c
--- /dev/null
+++ b/log/filter_test.go
@@ -0,0 +1,90 @@
+package log
+
+import (
+	"io/ioutil"
+	"testing"
+)
+
+func TestFilterAll(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewHelper(NewFilter(logger,
+		FilterLevel(LevelDebug),
+		FilterKey("username"),
+		FilterValue("hello"),
+		FilterFunc(testFilterFunc),
+	))
+	log.Log(LevelDebug, "msg", "test debug")
+	log.Info("hello")
+	log.Infow("password", "123456")
+	log.Infow("username", "kratos")
+	log.Warn("warn log")
+}
+func TestFilterLevel(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewHelper(NewFilter(NewFilter(logger, FilterLevel(LevelWarn))))
+	log.Log(LevelDebug, "msg1", "te1st debug")
+	log.Debug("test debug")
+	log.Debugf("test %s", "debug")
+	log.Debugw("log", "test debug")
+	log.Warn("warn log")
+}
+
+func TestFilterCaller(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewFilter(logger)
+	log.Log(LevelDebug, "msg1", "te1st debug")
+	logHelper := NewHelper(NewFilter(logger))
+	logHelper.Log(LevelDebug, "msg1", "te1st debug")
+}
+
+func TestFilterKey(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewHelper(NewFilter(logger, FilterKey("password")))
+	log.Debugw("password", "123456")
+}
+
+func TestFilterValue(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewHelper(NewFilter(logger, FilterValue("debug")))
+	log.Debugf("test %s", "debug")
+}
+
+func TestFilterFunc(t *testing.T) {
+	logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller)
+	log := NewHelper(NewFilter(logger, FilterFunc(testFilterFunc)))
+	log.Debug("debug level")
+	log.Infow("password", "123456")
+}
+
+func BenchmarkFilterKey(b *testing.B) {
+	log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterKey("password")))
+	for i := 0; i < b.N; i++ {
+		log.Infow("password", "123456")
+	}
+}
+
+func BenchmarkFilterValue(b *testing.B) {
+	log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterValue("password")))
+	for i := 0; i < b.N; i++ {
+		log.Infow("password")
+	}
+}
+
+func BenchmarkFilterFunc(b *testing.B) {
+	log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterFunc(testFilterFunc)))
+	for i := 0; i < b.N; i++ {
+		log.Info("password", "123456")
+	}
+}
+
+func testFilterFunc(level Level, keyvals ...interface{}) bool {
+	if level == LevelWarn {
+		return true
+	}
+	for i := 0; i < len(keyvals); i++ {
+		if keyvals[i] == "password" {
+			keyvals[i+1] = "***"
+		}
+	}
+	return false
+}
diff --git a/middleware/logging/logging_test.go b/middleware/logging/logging_test.go
index e08d9db5..104c6570 100644
--- a/middleware/logging/logging_test.go
+++ b/middleware/logging/logging_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/transport"
 )
@@ -33,10 +34,18 @@ func (tr *Transport) Operation() string {
 	return tr.operation
 }
 
-func (tr *Transport) Header() transport.Header {
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
+}
+
+func (tr *Transport) Metadata() metadata.Metadata {
 	return nil
 }
 
+func (tr *Transport) WithMetadata(md metadata.Metadata) {
+
+}
+
 func TestHTTP(t *testing.T) {
 	var err = errors.New("reply.error")
 	var bf = bytes.NewBuffer(nil)