Repository: go-kratos/kratos
Pull request #861 (2021-04-25 12:13:02)

transport/grpc: add health check
增加grpc 健康检查


Patch:
diff --git a/errors/codes.go b/errors/codes.go
deleted file mode 100644
index 64bb1179..00000000
--- a/errors/codes.go
+++ /dev/null
@@ -1,317 +0,0 @@
-package errors
-
-import (
-	"errors"
-	"fmt"
-)
-
-// Cancelled The operation was cancelled, typically by the caller.
-// HTTP Mapping: 499 Client Closed Request
-func Cancelled(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    1,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsCancelled determines if err is an error which indicates a cancelled error.
-// It supports wrapped errors.
-func IsCancelled(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 1
-	}
-	return false
-}
-
-// Unknown error.
-// HTTP Mapping: 500 Internal Server Error
-func Unknown(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    2,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsUnknown determines if err is an error which indicates a unknown error.
-// It supports wrapped errors.
-func IsUnknown(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 2
-	}
-	return false
-}
-
-// InvalidArgument The client specified an invalid argument.
-// HTTP Mapping: 400 Bad Request
-func InvalidArgument(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    3,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsInvalidArgument determines if err is an error which indicates an invalid argument error.
-// It supports wrapped errors.
-func IsInvalidArgument(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 3
-	}
-	return false
-}
-
-// DeadlineExceeded The deadline expired before the operation could complete.
-// HTTP Mapping: 504 Gateway Timeout
-func DeadlineExceeded(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    4,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsDeadlineExceeded determines if err is an error which indicates a deadline exceeded error.
-// It supports wrapped errors.
-func IsDeadlineExceeded(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 4
-	}
-	return false
-}
-
-// NotFound Some requested entity (e.g., file or directory) was not found.
-// HTTP Mapping: 404 Not Found
-func NotFound(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    5,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsNotFound determines if err is an error which indicates a not found error.
-// It supports wrapped errors.
-func IsNotFound(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 5
-	}
-	return false
-}
-
-// AlreadyExists The entity that a client attempted to create (e.g., file or directory) already exists.
-// HTTP Mapping: 409 Conflict
-func AlreadyExists(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    6,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsAlreadyExists determines if err is an error which indicates a already exsits error.
-// It supports wrapped errors.
-func IsAlreadyExists(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 6
-	}
-	return false
-}
-
-// PermissionDenied The caller does not have permission to execute the specified operation.
-// HTTP Mapping: 403 Forbidden
-func PermissionDenied(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    7,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsPermissionDenied determines if err is an error which indicates a permission denied error.
-// It supports wrapped errors.
-func IsPermissionDenied(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 7
-	}
-	return false
-}
-
-// ResourceExhausted Some resource has been exhausted, perhaps a per-user quota, or
-// perhaps the entire file system is out of space.
-// HTTP Mapping: 429 Too Many Requests
-func ResourceExhausted(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    8,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsResourceExhausted determines if err is an error which indicates a resource exhausted error.
-// It supports wrapped errors.
-func IsResourceExhausted(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 8
-	}
-	return false
-}
-
-// FailedPrecondition The operation was rejected because the system is not in a state
-// required for the operation's execution.
-// HTTP Mapping: 400 Bad Request
-func FailedPrecondition(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    9,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsFailedPrecondition determines if err is an error which indicates a failed precondition error.
-// It supports wrapped errors.
-func IsFailedPrecondition(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 9
-	}
-	return false
-}
-
-// Aborted The operation was aborted, typically due to a concurrency issue such as
-// a sequencer check failure or transaction abort.
-// HTTP Mapping: 409 Conflict
-func Aborted(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    10,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsAborted determines if err is an error which indicates an aborted error.
-// It supports wrapped errors.
-func IsAborted(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 10
-	}
-	return false
-}
-
-// OutOfRange The operation was attempted past the valid range.  E.g., seeking or
-// reading past end-of-file.
-// HTTP Mapping: 400 Bad Request
-func OutOfRange(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    11,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsOutOfRange determines if err is an error which indicates a out of range error.
-// It supports wrapped errors.
-func IsOutOfRange(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 11
-	}
-	return false
-}
-
-// Unimplemented The operation is not implemented or is not supported/enabled in this service.
-// HTTP Mapping: 501 Not Implemented
-func Unimplemented(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    12,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsUnimplemented determines if err is an error which indicates a unimplemented error.
-// It supports wrapped errors.
-func IsUnimplemented(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 12
-	}
-	return false
-}
-
-// Internal This means that some invariants expected by the
-// underlying system have been broken.  This error code is reserved
-// for serious errors.
-//
-// HTTP Mapping: 500 Internal Server Error
-func Internal(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    13,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsInternal determines if err is an error which indicates an internal server error.
-// It supports wrapped errors.
-func IsInternal(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 13
-	}
-	return false
-}
-
-// Unavailable The service is currently unavailable.
-// HTTP Mapping: 503 Service Unavailable
-func Unavailable(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    14,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsUnavailable determines if err is an error which indicates a unavailable error.
-// It supports wrapped errors.
-func IsUnavailable(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 14
-	}
-	return false
-}
-
-// DataLoss Unrecoverable data loss or corruption.
-// HTTP Mapping: 500 Internal Server Error
-func DataLoss(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    15,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsDataLoss determines if err is an error which indicates a data loss error.
-// It supports wrapped errors.
-func IsDataLoss(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 15
-	}
-	return false
-}
-
-// Unauthorized The request does not have valid authentication credentials for the operation.
-// HTTP Mapping: 401 Unauthorized
-func Unauthorized(reason, format string, a ...interface{}) error {
-	return &StatusError{
-		Code:    16,
-		Reason:  reason,
-		Message: fmt.Sprintf(format, a...),
-	}
-}
-
-// IsUnauthorized determines if err is an error which indicates a unauthorized error.
-// It supports wrapped errors.
-func IsUnauthorized(err error) bool {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code == 16
-	}
-	return false
-}
diff --git a/errors/errors.go b/errors/errors.go
index ad991020..f5fe8a1b 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -7,110 +7,86 @@ import (
 )
 
 const (
-	// UnknownReason is unknown reason for error info.
-	UnknownReason = ""
 	// SupportPackageIsVersion1 this constant should not be referenced by any other code.
 	SupportPackageIsVersion1 = true
 )
 
-var _ error = (*StatusError)(nil)
+// Error contains an error response from the server.
+// For more details see https://github.com/go-kratos/kratos/issues/858.
+type Error struct {
+	Code     int               `json:"code"`
+	Domain   string            `json:"domain"`
+	Reason   string            `json:"reason"`
+	Message  string            `json:"message"`
+	Metadata map[string]string `json:"metadata"`
+}
 
-// StatusError contains an error response from the server.
-type StatusError = Status
+// WithMetadata with an MD formed by the mapping of key, value.
+func (e *Error) WithMetadata(md map[string]string) *Error {
+	err := *e
+	err.Metadata = md
+	return &err
+}
 
 // Is matches each error in the chain with the target value.
-func (e *StatusError) Is(target error) bool {
-	err, ok := target.(*StatusError)
-	if ok {
-		return e.Code == err.Code
+func (e *Error) Is(err error) bool {
+	if target := new(Error); errors.As(err, &target) {
+		return target.Code == e.Code &&
+			target.Domain == e.Domain &&
+			target.Reason == e.Reason
 	}
 	return false
 }
 
-// HTTPStatus returns the Status represented by se.
-func (e *StatusError) HTTPStatus() int {
-	switch e.Code {
-	case 0:
-		return http.StatusOK
-	case 1:
-		return http.StatusInternalServerError
-	case 2:
-		return http.StatusInternalServerError
-	case 3:
-		return http.StatusBadRequest
-	case 4:
-		return http.StatusRequestTimeout
-	case 5:
-		return http.StatusNotFound
-	case 6:
-		return http.StatusConflict
-	case 7:
-		return http.StatusForbidden
-	case 8:
-		return http.StatusTooManyRequests
-	case 9:
-		return http.StatusPreconditionFailed
-	case 10:
-		return http.StatusConflict
-	case 11:
-		return http.StatusBadRequest
-	case 12:
-		return http.StatusNotImplemented
-	case 13:
-		return http.StatusInternalServerError
-	case 14:
-		return http.StatusServiceUnavailable
-	case 15:
-		return http.StatusInternalServerError
-	case 16:
-		return http.StatusUnauthorized
-	default:
-		return http.StatusInternalServerError
-	}
-}
-
-func (e *StatusError) Error() string {
-	return fmt.Sprintf("error: code = %d reason = %s message = %s details = %+v", e.Code, e.Reason, e.Message, e.Details)
+func (e *Error) Error() string {
+	return fmt.Sprintf("error: code = %d domain = %s reason = %s message = %s", e.Code, e.Domain, e.Reason, e.Message)
 }
 
-// Error returns a Status representing c and msg.
-func Error(code int32, reason, message string) error {
-	return &StatusError{
+// New returns an error object for the code, message and error info.
+func New(code int, domain, reason, message string) *Error {
+	return &Error{
 		Code:    code,
+		Domain:  domain,
 		Reason:  reason,
 		Message: message,
 	}
 }
 
-// Errorf returns New(c, fmt.Sprintf(format, a...)).
-func Errorf(code int32, reason, format string, a ...interface{}) error {
-	return Error(code, reason, fmt.Sprintf(format, a...))
-}
-
-// Code returns the status code.
-func Code(err error) int32 {
+// Code returns the code for a particular error.
+// It supports wrapped errors.
+func Code(err error) int {
 	if err == nil {
-		return 0 // ok
+		return http.StatusOK
 	}
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Code
+	if target := new(Error); errors.As(err, &target) {
+		return target.Code
 	}
-	return 2 // unknown
+	return http.StatusInternalServerError
 }
 
-// Reason returns the status for a particular error.
+// Domain returns the domain for a particular error.
+// It supports wrapped errors.
+func Domain(err error) string {
+	if target := new(Error); errors.As(err, &target) {
+		return target.Domain
+	}
+	return ""
+}
+
+// Reason returns the reason for a particular error.
 // It supports wrapped errors.
 func Reason(err error) string {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se.Reason
+	if target := new(Error); errors.As(err, &target) {
+		return target.Reason
 	}
-	return UnknownReason
+	return ""
 }
 
-// FromError returns status error.
-func FromError(err error) (*StatusError, bool) {
-	if se := new(StatusError); errors.As(err, &se) {
-		return se, true
+// FromError try to convert an error to *Error.
+// It supports wrapped errors.
+func FromError(err error) *Error {
+	if target := new(Error); errors.As(err, &target) {
+		return target
 	}
-	return nil, false
+	return New(http.StatusInternalServerError, "", "", err.Error())
 }
diff --git a/errors/errors.pb.go b/errors/errors.pb.go
deleted file mode 100644
index 3b142ba3..00000000
--- a/errors/errors.pb.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.25.0
-// 	protoc        v3.13.0
-// source: errors.proto
-
-package errors
-
-import (
-	proto "github.com/golang/protobuf/proto"
-	any "github.com/golang/protobuf/ptypes/any"
-	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)
-)
-
-// This is a compile-time assertion that a sufficiently up-to-date version
-// of the legacy proto package is being used.
-const _ = proto.ProtoPackageIsVersion4
-
-type Status struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Code    int32      `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
-	Reason  string     `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"`
-	Message string     `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
-	Details []*any.Any `protobuf:"bytes,4,rep,name=details,proto3" json:"details,omitempty"`
-}
-
-func (x *Status) Reset() {
-	*x = Status{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_errors_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Status) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Status) ProtoMessage() {}
-
-func (x *Status) ProtoReflect() protoreflect.Message {
-	mi := &file_errors_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 Status.ProtoReflect.Descriptor instead.
-func (*Status) Descriptor() ([]byte, []int) {
-	return file_errors_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Status) GetCode() int32 {
-	if x != nil {
-		return x.Code
-	}
-	return 0
-}
-
-func (x *Status) GetReason() string {
-	if x != nil {
-		return x.Reason
-	}
-	return ""
-}
-
-func (x *Status) GetMessage() string {
-	if x != nil {
-		return x.Message
-	}
-	return ""
-}
-
-func (x *Status) GetDetails() []*any.Any {
-	if x != nil {
-		return x.Details
-	}
-	return nil
-}
-
-var File_errors_proto protoreflect.FileDescriptor
-
-var file_errors_proto_rawDesc = []byte{
-	0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d,
-	0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x19, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61,
-	0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
-	0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18,
-	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61,
-	0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52,
-	0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x42, 0x62, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x2e,
-	0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x0b, 0x45,
-	0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 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, 0x76, 0x32, 0x2f, 0x65, 0x72, 0x72,
-	0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x0c,
-	0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_errors_proto_rawDescOnce sync.Once
-	file_errors_proto_rawDescData = file_errors_proto_rawDesc
-)
-
-func file_errors_proto_rawDescGZIP() []byte {
-	file_errors_proto_rawDescOnce.Do(func() {
-		file_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_proto_rawDescData)
-	})
-	return file_errors_proto_rawDescData
-}
-
-var file_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
-var file_errors_proto_goTypes = []interface{}{
-	(*Status)(nil),  // 0: kratos.errors.Status
-	(*any.Any)(nil), // 1: google.protobuf.Any
-}
-var file_errors_proto_depIdxs = []int32{
-	1, // 0: kratos.errors.Status.details:type_name -> google.protobuf.Any
-	1, // [1:1] is the sub-list for method output_type
-	1, // [1:1] is the sub-list for method input_type
-	1, // [1:1] is the sub-list for extension type_name
-	1, // [1:1] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
-}
-
-func init() { file_errors_proto_init() }
-func file_errors_proto_init() {
-	if File_errors_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Status); 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_errors_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   1,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_errors_proto_goTypes,
-		DependencyIndexes: file_errors_proto_depIdxs,
-		MessageInfos:      file_errors_proto_msgTypes,
-	}.Build()
-	File_errors_proto = out.File
-	file_errors_proto_rawDesc = nil
-	file_errors_proto_goTypes = nil
-	file_errors_proto_depIdxs = nil
-}
diff --git a/errors/errors.proto b/errors/errors.proto
deleted file mode 100644
index 653bbf96..00000000
--- a/errors/errors.proto
+++ /dev/null
@@ -1,19 +0,0 @@
-syntax = "proto3";
-
-package kratos.errors;
-
-import "google/protobuf/any.proto";
-
-option cc_enable_arenas = true;
-option go_package = "github.com/go-kratos/kratos/v2/errors;errors";
-option java_multiple_files = true;
-option java_outer_classname = "ErrorsProto";
-option java_package = "com.github.kratos.errors";
-option objc_class_prefix = "KratosErrors";
-
-message Status {
-  int32 code = 1;
-  string reason = 2;
-  string message = 3;
-  repeated google.protobuf.Any details = 4;
-}
diff --git a/errors/types.go b/errors/types.go
new file mode 100644
index 00000000..fc21ad42
--- /dev/null
+++ b/errors/types.go
@@ -0,0 +1,80 @@
+package errors
+
+import "net/http"
+
+// BadRequest new BadRequest error that is mapped to a 400 response.
+func BadRequest(domain, reason, message string) *Error {
+	return New(http.StatusBadRequest, domain, reason, message)
+}
+
+// IsBadRequest determines if err is an error which indicates a BadRequest error.
+// It supports wrapped errors.
+func IsBadRequest(err error) bool {
+	return Code(err) == http.StatusBadRequest
+}
+
+// Unauthorized new Unauthorized error that is mapped to a 401 response.
+func Unauthorized(domain, reason, message string) *Error {
+	return New(http.StatusUnauthorized, domain, reason, message)
+}
+
+// IsUnauthorized determines if err is an error which indicates a Unauthorized error.
+// It supports wrapped errors.
+func IsUnauthorized(err error) bool {
+	return Code(err) == http.StatusUnauthorized
+}
+
+// Forbidden new Forbidden error that is mapped to a 403 response.
+func Forbidden(domain, reason, message string) *Error {
+	return New(http.StatusForbidden, domain, reason, message)
+}
+
+// IsForbidden determines if err is an error which indicates a Forbidden error.
+// It supports wrapped errors.
+func IsForbidden(err error) bool {
+	return Code(err) == http.StatusForbidden
+}
+
+// NotFound new NotFound error that is mapped to a 404 response.
+func NotFound(domain, reason, message string) *Error {
+	return New(http.StatusNotFound, domain, reason, message)
+}
+
+// IsNotFound determines if err is an error which indicates an NotFound error.
+// It supports wrapped errors.
+func IsNotFound(err error) bool {
+	return Code(err) == http.StatusNotFound
+}
+
+// Conflict new Conflict error that is mapped to a 409 response.
+func Conflict(domain, reason, message string) *Error {
+	return New(http.StatusConflict, domain, reason, message)
+}
+
+// IsConflict determines if err is an error which indicates a Conflict error.
+// It supports wrapped errors.
+func IsConflict(err error) bool {
+	return Code(err) == http.StatusConflict
+}
+
+// InternalServer new InternalServer error that is mapped to a 500 response.
+func InternalServer(domain, reason, message string) *Error {
+	return New(http.StatusInternalServerError, domain, reason, message)
+}
+
+// IsInternalServer determines if err is an error which indicates an InternalServer error.
+// It supports wrapped errors.
+func IsInternalServer(err error) bool {
+	return Code(err) == http.StatusInternalServerError
+}
+
+// ServiceUnavailable new ServiceUnavailable error that is mapped to a HTTP 503 response.
+func ServiceUnavailable(domain, reason, message string) *Error {
+	return New(http.StatusServiceUnavailable, domain, reason, message)
+}
+
+// IsServiceUnavailable determines if err is an error which indicates a ServiceUnavailable error.
+// It supports wrapped errors.
+func IsServiceUnavailable(err error) bool {
+	return Code(err) == http.StatusServiceUnavailable
+}
diff --git a/examples/blog/internal/server/grpc.go b/examples/blog/internal/server/grpc.go
index 0a98775e..454b02b5 100644
--- a/examples/blog/internal/server/grpc.go
+++ b/examples/blog/internal/server/grpc.go
@@ -4,6 +4,7 @@ import (
 	v1 "github.com/go-kratos/kratos/examples/blog/api/blog/v1"
 	"github.com/go-kratos/kratos/examples/blog/internal/conf"
 	"github.com/go-kratos/kratos/examples/blog/internal/service"
+	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/middleware/logging"
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
@@ -18,10 +19,10 @@ func NewGRPCServer(c *conf.Server, tracer trace.TracerProvider, blog *service.Bl
 	var opts = []grpc.ServerOption{
 		grpc.Middleware(
 			middleware.Chain(
-				recovery.Recovery(),
 				status.Server(),
 				tracing.Server(tracing.WithTracerProvider(tracer)),
-				logging.Server(),
+				logging.Server(log.DefaultLogger),
+				recovery.Recovery(),
 			),
 		),
 	}
diff --git a/examples/blog/internal/server/http.go b/examples/blog/internal/server/http.go
index 5a9a6b4d..c991bab1 100644
--- a/examples/blog/internal/server/http.go
+++ b/examples/blog/internal/server/http.go
@@ -4,6 +4,7 @@ import (
 	v1 "github.com/go-kratos/kratos/examples/blog/api/blog/v1"
 	"github.com/go-kratos/kratos/examples/blog/internal/conf"
 	"github.com/go-kratos/kratos/examples/blog/internal/service"
+	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/middleware/logging"
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
@@ -26,9 +27,9 @@ func NewHTTPServer(c *conf.Server, tracer trace.TracerProvider, blog *service.Bl
 	}
 	m := http.Middleware(
 		middleware.Chain(
-			recovery.Recovery(),
 			tracing.Server(tracing.WithTracerProvider(tracer)),
-			logging.Server(),
+			logging.Server(log.DefaultLogger),
+			recovery.Recovery(),
 		),
 	)
 	srv := http.NewServer(opts...)
diff --git a/examples/go.mod b/examples/go.mod
index c05998e9..b334d3d7 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -5,9 +5,9 @@ go 1.16
 require (
 	entgo.io/ent v0.6.0
 	github.com/gin-gonic/gin v1.6.3
-	github.com/go-kratos/consul v0.0.0-20210311161349-cfb0345e820d
+	github.com/go-kratos/consul v0.0.0-20210425141546-e047a9f6ec87
 	github.com/go-kratos/etcd v0.0.0-20210311162832-e0fdc8177742
-	github.com/go-kratos/kratos/v2 v2.0.0-20210415063033-9007abfd2888
+	github.com/go-kratos/kratos/v2 v2.0.0-20210425121923-9806191b7f32
 	github.com/go-kratos/nacos v0.0.0-20210415082641-f1b756c16257
 	github.com/go-playground/validator/v10 v10.4.1 // indirect
 	github.com/go-redis/redis/extra/redisotel v0.3.0
@@ -40,3 +40,5 @@ require (
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
+
+replace github.com/go-kratos/kratos/v2 => ../
diff --git a/examples/go.sum b/examples/go.sum
index 17fdbe79..ab1a7c31 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -125,13 +125,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kratos/consul v0.0.0-20210311161349-cfb0345e820d h1:px9mGqOsFmcXYsidSYATFoDK/R7x43WnUqiE/9Pu8x8=
-github.com/go-kratos/consul v0.0.0-20210311161349-cfb0345e820d/go.mod h1:i/8iM3Xm0YwSmiCdFFZ611y+6rk5Bi1G5UZtBG3t9rg=
+github.com/go-kratos/consul v0.0.0-20210425141546-e047a9f6ec87 h1:YKG1fpsSZivsGzeD31Mb91hsmouDFCPq8LR6Uz8SjTg=
+github.com/go-kratos/consul v0.0.0-20210425141546-e047a9f6ec87/go.mod h1:O21CidethNnnpWtHS7XcisEKl3P+f9sTZACoIyuvq4Y=
 github.com/go-kratos/etcd v0.0.0-20210311162832-e0fdc8177742 h1:gEjXnGJ30PVLE3AMgh1z48PmHgVC4E1q8ZvkdkyJyt8=
 github.com/go-kratos/etcd v0.0.0-20210311162832-e0fdc8177742/go.mod h1:+1nilFyiWLlYpAWliKnbFhIax79n4l0mOTJhyNw5EEw=
-github.com/go-kratos/kratos/v2 v2.0.0-20210311152607-a4409adf164e/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
-github.com/go-kratos/kratos/v2 v2.0.0-20210415063033-9007abfd2888 h1:GmcG4UBf9ome4mJMltFSqxRdQssIRgGBAhIvHJA9MVU=
-github.com/go-kratos/kratos/v2 v2.0.0-20210415063033-9007abfd2888/go.mod h1:hwEYWw8GFuJ8IoNt3T/3k+7kUfYt+h2fHDcyFlR1jBA=
 github.com/go-kratos/nacos v0.0.0-20210415082641-f1b756c16257 h1:mJdO5lD+C1UsxJ5NC0rQ2hPx1dA0tdXLp/3UFNWRaFc=
 github.com/go-kratos/nacos v0.0.0-20210415082641-f1b756c16257/go.mod h1:GP7wV0ohYEG+x1P/qdNCJGb7K2jtbsMop+5BDaSqBlA=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -389,7 +386,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
 github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
diff --git a/examples/helloworld/client/main.go b/examples/helloworld/client/main.go
index 2a460477..d967168c 100644
--- a/examples/helloworld/client/main.go
+++ b/examples/helloworld/client/main.go
@@ -20,7 +20,14 @@ func main() {
 }
 
 func callHTTP() {
-	client, err := transhttp.NewClient(context.Background())
+	client, err := transhttp.NewClient(
+		context.Background(),
+		transhttp.WithMiddleware(
+			middleware.Chain(
+				recovery.Recovery(),
+			),
+		),
+	)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -71,7 +78,7 @@ func callGRPC() {
 	if err != nil {
 		log.Printf("[grpc] SayHello error: %v\n", err)
 	}
-	if errors.IsInvalidArgument(err) {
+	if errors.IsBadRequest(err) {
 		log.Printf("[grpc] SayHello error is invalid argument: %v\n", err)
 	}
 }
diff --git a/examples/helloworld/server/main.go b/examples/helloworld/server/main.go
index 35ade294..c052e0be 100644
--- a/examples/helloworld/server/main.go
+++ b/examples/helloworld/server/main.go
@@ -17,6 +17,14 @@ import (
 	"github.com/go-kratos/kratos/v2/transport/http"
 )
 
+// go build -ldflags "-X main.Version=x.y.z"
+var (
+	// Name is the name of the compiled software.
+	Name = "helloworld"
+	// Version is the version of the compiled software.
+	Version = "v1.0.0"
+)
+
 // server is used to implement helloworld.GreeterServer.
 type server struct {
 	pb.UnimplementedGreeterServer
@@ -25,7 +33,7 @@ type server struct {
 // SayHello implements helloworld.GreeterServer
 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 	if in.Name == "error" {
-		return nil, errors.InvalidArgument("BadRequest", "invalid argument %s", in.Name)
+		return nil, errors.BadRequest(Name, "custom_error", fmt.Sprintf("invalid argument %s", in.Name))
 	}
 	if in.Name == "panic" {
 		panic("grpc panic")
@@ -42,8 +50,8 @@ func main() {
 		grpc.Address(":9000"),
 		grpc.Middleware(
 			middleware.Chain(
-				logging.Server(logging.WithLogger(logger)),
 				status.Server(),
+				logging.Server(logger),
 				recovery.Recovery(),
 			),
 		))
@@ -55,14 +63,14 @@ func main() {
 	httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s,
 		http.Middleware(
 			middleware.Chain(
-				logging.Server(logging.WithLogger(logger)),
+				logging.Server(logger),
 				recovery.Recovery(),
 			),
 		)),
 	)
 
 	app := kratos.New(
-		kratos.Name("helloworld"),
+		kratos.Name(Name),
 		kratos.Server(
 			httpSrv,
 			grpcSrv,
diff --git a/middleware/logging/logging.go b/middleware/logging/logging.go
index 1ad356a5..5bab4fe1 100644
--- a/middleware/logging/logging.go
+++ b/middleware/logging/logging.go
@@ -11,29 +11,9 @@ import (
 	"github.com/go-kratos/kratos/v2/transport/http"
 )
 
-// Option is HTTP logging option.
-type Option func(*options)
-
-type options struct {
-	logger log.Logger
-}
-
-// WithLogger with middleware logger.
-func WithLogger(logger log.Logger) Option {
-	return func(o *options) {
-		o.logger = logger
-	}
-}
-
 // Server is an server logging middleware.
-func Server(opts ...Option) middleware.Middleware {
-	options := options{
-		logger: log.DefaultLogger,
-	}
-	for _, o := range opts {
-		o(&options)
-	}
-	logger := log.NewHelper("middleware/logging", options.logger)
+func Server(l log.Logger) middleware.Middleware {
+	logger := log.NewHelper("middleware/logging", l)
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			var (
@@ -80,14 +60,8 @@ func Server(opts ...Option) middleware.Middleware {
 }
 
 // Client is an client logging middleware.
-func Client(opts ...Option) middleware.Middleware {
-	options := options{
-		logger: log.DefaultLogger,
-	}
-	for _, o := range opts {
-		o(&options)
-	}
-	logger := log.NewHelper("middleware/logging", options.logger)
+func Client(l log.Logger) middleware.Middleware {
+	logger := log.NewHelper("middleware/logging", l)
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			var (
diff --git a/middleware/metrics/metrics.go b/middleware/metrics/metrics.go
index d27fe7bb..bc99dc5a 100644
--- a/middleware/metrics/metrics.go
+++ b/middleware/metrics/metrics.go
@@ -48,7 +48,7 @@ func Server(opts ...Option) middleware.Middleware {
 			var (
 				method string
 				path   string
-				code   int32
+				code   int
 			)
 			if info, ok := grpc.FromServerContext(ctx); ok {
 				method = "POST"
@@ -90,7 +90,7 @@ func Client(opts ...Option) middleware.Middleware {
 			var (
 				method string
 				path   string
-				code   int32
+				code   int
 			)
 			if info, ok := grpc.FromClientContext(ctx); ok {
 				method = "POST"
diff --git a/middleware/recovery/recovery.go b/middleware/recovery/recovery.go
index f86e3fa5..33b96919 100644
--- a/middleware/recovery/recovery.go
+++ b/middleware/recovery/recovery.go
@@ -2,6 +2,7 @@ package recovery
 
 import (
 	"context"
+	"fmt"
 	"runtime"
 
 	"github.com/go-kratos/kratos/v2/errors"
@@ -39,7 +40,7 @@ func Recovery(opts ...Option) middleware.Middleware {
 	options := options{
 		logger: log.DefaultLogger,
 		handler: func(ctx context.Context, req, err interface{}) error {
-			return errors.Unknown("Unknown", "panic triggered: %v", err)
+			return errors.InternalServer("", "recovery", fmt.Sprintf("panic triggered: %v", err))
 		},
 	}
 	for _, o := range opts {
diff --git a/middleware/status/status.go b/middleware/status/status.go
index d01121ff..c55ac2d5 100644
--- a/middleware/status/status.go
+++ b/middleware/status/status.go
@@ -2,20 +2,20 @@ package status
 
 import (
 	"context"
+	"net/http"
 
 	"github.com/go-kratos/kratos/v2/errors"
 	"github.com/go-kratos/kratos/v2/middleware"
 
 	//lint:ignore SA1019 grpc
 	"github.com/golang/protobuf/proto"
-	"github.com/golang/protobuf/ptypes"
 	"google.golang.org/genproto/googleapis/rpc/errdetails"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 )
 
 // HandlerFunc is middleware error handler.
-type HandlerFunc func(error) error
+type HandlerFunc func(context.Context, error) error
 
 // Option is recovery option.
 type Option func(*options)
@@ -34,7 +34,7 @@ func WithHandler(h HandlerFunc) Option {
 // Server is an error middleware.
 func Server(opts ...Option) middleware.Middleware {
 	options := options{
-		handler: errorEncode,
+		handler: encodeErr,
 	}
 	for _, o := range opts {
 		o(&options)
@@ -43,7 +43,7 @@ func Server(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			reply, err := handler(ctx, req)
 			if err != nil {
-				return nil, options.handler(err)
+				return nil, options.handler(ctx, err)
 			}
 			return reply, nil
 		}
@@ -53,7 +53,7 @@ func Server(opts ...Option) middleware.Middleware {
 // Client is an error middleware.
 func Client(opts ...Option) middleware.Middleware {
 	options := options{
-		handler: errorDecode,
+		handler: decodeErr,
 	}
 	for _, o := range opts {
 		o(&options)
@@ -62,35 +62,23 @@ func Client(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			reply, err := handler(ctx, req)
 			if err != nil {
-				return nil, options.handler(err)
+				return nil, options.handler(ctx, err)
 			}
 			return reply, nil
 		}
 	}
 }
 
-func errorEncode(err error) error {
-	se, ok := errors.FromError(err)
-	if !ok {
-		se = &errors.StatusError{
-			Code:    2,
-			Message: err.Error(),
-		}
-	}
-	gs := status.Newf(codes.Code(se.Code), "%s: %s", se.Reason, se.Message)
+func encodeErr(ctx context.Context, err error) error {
+	se := errors.FromError(err)
+	gs := status.Newf(httpToGRPCCode(se.Code), "%s: %s", se.Reason, se.Message)
 	details := []proto.Message{
 		&errdetails.ErrorInfo{
+			Domain:   se.Domain,
 			Reason:   se.Reason,
-			Metadata: map[string]string{"message": se.Message},
+			Metadata: se.Metadata,
 		},
 	}
-	for _, any := range se.Details {
-		detail := &ptypes.DynamicAny{}
-		if err := ptypes.UnmarshalAny(any, detail); err != nil {
-			continue
-		}
-		details = append(details, detail.Message)
-	}
 	gs, err = gs.WithDetails(details...)
 	if err != nil {
 		return err
@@ -98,19 +86,60 @@ func errorEncode(err error) error {
 	return gs.Err()
 }
 
-func errorDecode(err error) error {
+func decodeErr(ctx context.Context, err error) error {
 	gs := status.Convert(err)
-	se := &errors.StatusError{
-		Code:    int32(gs.Code()),
-		Details: gs.Proto().Details,
+	se := &errors.Error{
+		Code:    grpcToHTTPCode(gs.Code()),
+		Message: gs.Message(),
 	}
 	for _, detail := range gs.Details() {
 		switch d := detail.(type) {
 		case *errdetails.ErrorInfo:
+			se.Domain = d.Domain
 			se.Reason = d.Reason
-			se.Message = d.Metadata["message"]
+			se.Metadata = d.Metadata
 			return se
 		}
 	}
 	return se
 }
+
+func httpToGRPCCode(code int) codes.Code {
+	switch code {
+	case http.StatusBadRequest:
+		return codes.InvalidArgument
+	case http.StatusUnauthorized:
+		return codes.Unauthenticated
+	case http.StatusForbidden:
+		return codes.PermissionDenied
+	case http.StatusNotFound:
+		return codes.NotFound
+	case http.StatusConflict:
+		return codes.Aborted
+	case http.StatusInternalServerError:
+		return codes.Internal
+	case http.StatusServiceUnavailable:
+		return codes.Unavailable
+	}
+	return codes.Unknown
+}
+
+func grpcToHTTPCode(code codes.Code) int {
+	switch code {
+	case codes.InvalidArgument:
+		return http.StatusBadRequest
+	case codes.Unauthenticated:
+		return http.StatusUnauthorized
+	case codes.PermissionDenied:
+		return http.StatusForbidden
+	case codes.NotFound:
+		return http.StatusNotFound
+	case codes.Aborted:
+		return http.StatusConflict
+	case codes.Internal:
+		return http.StatusInternalServerError
+	case codes.Unavailable:
+		return http.StatusServiceUnavailable
+	}
+	return http.StatusInternalServerError
+}
diff --git a/middleware/validate/validate.go b/middleware/validate/validate.go
index 884bb396..bc823bfe 100644
--- a/middleware/validate/validate.go
+++ b/middleware/validate/validate.go
@@ -12,12 +12,12 @@ type validator interface {
 }
 
 // Validator is a validator middleware.
-func Validator() middleware.Middleware {
+func Validator(domain string) middleware.Middleware {
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if v, ok := req.(validator); ok {
 				if err := v.Validate(); err != nil {
-					return nil, errors.InvalidArgument("Validator", err.Error())
+					return nil, errors.BadRequest(domain, "validator", err.Error())
 				}
 			}
 			return handler(ctx, req)
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index 6b13b95e..2e93104d 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
-	"github.com/go-kratos/kratos/v2/middleware/status"
 	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/grpc/resolver/discovery"
@@ -77,7 +76,6 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 		timeout: 500 * time.Millisecond,
 		middleware: middleware.Chain(
 			recovery.Recovery(),
-			status.Client(),
 		),
 	}
 	for _, o := range opts {
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 191db352..ccc1f158 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -10,10 +10,10 @@ import (
 	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
-	"github.com/go-kratos/kratos/v2/middleware/status"
 	"github.com/go-kratos/kratos/v2/transport"
-
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/health"
+	healthpb "google.golang.org/grpc/health/grpc_health_v1"
 )
 
 const loggerName = "transport/grpc"
@@ -75,6 +75,7 @@ type Server struct {
 	log        *log.Helper
 	middleware middleware.Middleware
 	grpcOpts   []grpc.ServerOption
+	health     *health.Server
 }
 
 // NewServer creates a gRPC server by options.
@@ -86,8 +87,8 @@ func NewServer(opts ...ServerOption) *Server {
 		log:     log.NewHelper(loggerName, log.DefaultLogger),
 		middleware: middleware.Chain(
 			recovery.Recovery(),
-			status.Server(),
 		),
+		health: health.NewServer(),
 	}
 	for _, o := range opts {
 		o(srv)
@@ -101,6 +102,8 @@ func NewServer(opts ...ServerOption) *Server {
 		grpcOpts = append(grpcOpts, srv.grpcOpts...)
 	}
 	srv.Server = grpc.NewServer(grpcOpts...)
+	// grpc health register
+	healthpb.RegisterHealthServer(srv.Server, srv.health)
 	return srv
 }
 
@@ -123,12 +126,14 @@ func (s *Server) Start() error {
 	}
 	s.lis = lis
 	s.log.Infof("[gRPC] server listening on: %s", lis.Addr().String())
+	s.health.Resume()
 	return s.Serve(lis)
 }
 
 // Stop stop the gRPC server.
 func (s *Server) Stop() error {
 	s.GracefulStop()
+	s.health.Shutdown()
 	s.log.Info("[gRPC] server stopping")
 	return nil
 }
diff --git a/transport/http/client.go b/transport/http/client.go
index 38e0fb4a..95b4e1bb 100644
--- a/transport/http/client.go
+++ b/transport/http/client.go
@@ -117,7 +117,7 @@ func Do(client *http.Client, req *http.Request, target interface{}) error {
 	}
 	defer res.Body.Close()
 	if res.StatusCode < 200 || res.StatusCode > 299 {
-		se := &errors.StatusError{Code: 2}
+		se := &errors.Error{Code: 500}
 		if err := decodeResponse(res, se); err != nil {
 			return err
 		}
diff --git a/transport/http/handle.go b/transport/http/handle.go
index b9630146..1bce7e9a 100644
--- a/transport/http/handle.go
+++ b/transport/http/handle.go
@@ -108,17 +108,11 @@ func encodeResponse(w http.ResponseWriter, r *http.Request, v interface{}) error
 
 // encodeError encodes the error to the HTTP response.
 func encodeError(w http.ResponseWriter, r *http.Request, err error) {
-	se, ok := errors.FromError(err)
-	if !ok {
-		se = &errors.StatusError{
-			Code:    2,
-			Message: err.Error(),
-		}
-	}
+	se := errors.FromError(err)
 	codec := codecForRequest(r)
 	data, _ := codec.Marshal(se)
 	w.Header().Set(contentTypeHeader, contentType(codec.Name()))
-	w.WriteHeader(se.HTTPStatus())
+	w.WriteHeader(se.Code)
 	_, _ = w.Write(data)
 }
 


Test patch:
diff --git a/errors/errors_test.go b/errors/errors_test.go
index 4c66b293..80d64260 100644
--- a/errors/errors_test.go
+++ b/errors/errors_test.go
@@ -6,49 +6,45 @@ import (
 	"testing"
 )
 
-func TestErrorsMatch(t *testing.T) {
-	s := &StatusError{Code: 1}
-	st := &StatusError{Code: 2}
-
-	if errors.Is(s, st) {
-		t.Errorf("error is not match: %+v -> %+v", s, st)
+func TestError(t *testing.T) {
+	var (
+		base *Error
+	)
+	err := New(400, "domain", "reason", "message")
+	err2 := New(400, "domain", "reason", "message")
+	err3 := err.WithMetadata(map[string]string{
+		"foo": "bar",
+	})
+	werr := fmt.Errorf("wrap %w", err)
+
+	if errors.Is(err, new(Error)) {
+		t.Errorf("should not be equal: %v", err)
 	}
-
-	s.Code = 1
-	st.Code = 1
-	if !errors.Is(s, st) {
-		t.Errorf("error is not match: %+v -> %+v", s, st)
+	if !errors.Is(werr, err) {
+		t.Errorf("should be equal: %v", err)
 	}
-
-	s.Reason = "test_reason"
-	s.Reason = "test_reason"
-
-	if !errors.Is(s, st) {
-		t.Errorf("error is not match: %+v -> %+v", s, st)
+	if !errors.Is(werr, err2) {
+		t.Errorf("should be equal: %v", err)
 	}
 
-	if Reason(s) != "test_reason" {
-		t.Errorf("error is not match: %+v -> %+v", s, st)
+	if !errors.As(err, &base) {
+		t.Errorf("should be matchs: %v", err)
 	}
-}
-
-func TestErrorIs(t *testing.T) {
-	err1 := &StatusError{Code: 1}
-	t.Log(err1)
-	err2 := fmt.Errorf("wrap : %w", err1)
-	t.Log(err2)
-
-	if !(errors.Is(err2, err1)) {
-		t.Errorf("error is not match: a: %v b: %v ", err2, err1)
+	if !IsBadRequest(err) {
+		t.Errorf("should be matchs: %v", err)
 	}
-}
 
-func TestErrorAs(t *testing.T) {
-	err1 := &StatusError{Code: 1}
-	err2 := fmt.Errorf("wrap : %w", err1)
+	if code := Code(err); code != err2.Code {
+		t.Errorf("got %d want: %s", code, err)
+	}
+	if domain := Domain(err); domain != err2.Domain {
+		t.Errorf("got %s want: %s", domain, err)
+	}
+	if reason := Reason(err); reason != err3.Reason {
+		t.Errorf("got %s want: %s", reason, err)
+	}
 
-	err3 := new(StatusError)
-	if !errors.As(err2, &err3) {
-		t.Errorf("error is not match: %v", err2)
+	if err3.Metadata["foo"] != "bar" {
+		t.Error("not expected metadata")
 	}
 }
diff --git a/errors/types_test.go b/errors/types_test.go
new file mode 100644
index 00000000..828981fd
--- /dev/null
+++ b/errors/types_test.go
@@ -0,0 +1,32 @@
+package errors
+
+import "testing"
+
+func TestTypes(t *testing.T) {
+	var (
+		input = []*Error{
+			BadRequest("domain_400", "reason_400", "message_400"),
+			Unauthorized("domain_401", "reason_401", "message_401"),
+			Forbidden("domain_403", "reason_403", "message_403"),
+			NotFound("domain_404", "reason_404", "message_404"),
+			Conflict("domain_409", "reason_409", "message_409"),
+			InternalServer("domain_500", "reason_500", "message_500"),
+			ServiceUnavailable("domain_503", "reason_503", "message_503"),
+		}
+		output = []func(error) bool{
+			IsBadRequest,
+			IsUnauthorized,
+			IsForbidden,
+			IsNotFound,
+			IsConflict,
+			IsInternalServer,
+			IsServiceUnavailable,
+		}
+	)
+
+	for i, in := range input {
+		if !output[i](in) {
+			t.Errorf("not expect: %v", in)
+		}
+	}
+}
diff --git a/middleware/status/status_test.go b/middleware/status/status_test.go
index f6636bba..279d68d3 100644
--- a/middleware/status/status_test.go
+++ b/middleware/status/status_test.go
@@ -1,6 +1,7 @@
 package status
 
 import (
+	"context"
 	"testing"
 
 	"github.com/go-kratos/kratos/v2/errors"
@@ -9,13 +10,13 @@ import (
 )
 
 func TestErrEncoder(t *testing.T) {
-	err := errors.InvalidArgument("InvalidArgument", "format")
-	en := errorEncode(err)
+	err := errors.BadRequest("test", "invalid_argument", "format")
+	en := encodeErr(context.Background(), err)
 	if code := status.Code(en); code != codes.InvalidArgument {
 		t.Errorf("expected %d got %d", codes.InvalidArgument, code)
 	}
-	de := errorDecode(en)
-	if !errors.IsInvalidArgument(de) {
+	de := decodeErr(context.Background(), en)
+	if !errors.IsBadRequest(de) {
 		t.Errorf("expected %v got %v", err, de)
 	}
 }