diff --git a/internal/apiresp/resp.go b/internal/apiresp/resp.go index 5d208d0d3..57f95619c 100644 --- a/internal/apiresp/resp.go +++ b/internal/apiresp/resp.go @@ -19,12 +19,8 @@ func apiSuccess(data any) *apiResponse { func apiError(err error) *apiResponse { unwrap := errs.Unwrap(err) - var dlt string - if unwrap != err { - dlt = err.Error() - } if codeErr, ok := unwrap.(errs.CodeError); ok { - return &apiResponse{ErrCode: codeErr.Code(), ErrMsg: codeErr.Msg(), ErrDlt: dlt} + return &apiResponse{ErrCode: codeErr.Code(), ErrMsg: codeErr.Msg(), ErrDlt: codeErr.Detail()} } - return &apiResponse{ErrCode: errs.ServerInternalError, ErrMsg: err.Error(), ErrDlt: dlt} + return &apiResponse{ErrCode: errs.ServerInternalError, ErrMsg: err.Error()} } diff --git a/pkg/common/mw/rpc_client_interceptor.go b/pkg/common/mw/rpc_client_interceptor.go index 02d44da9b..5d649d79b 100644 --- a/pkg/common/mw/rpc_client_interceptor.go +++ b/pkg/common/mw/rpc_client_interceptor.go @@ -6,10 +6,11 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/constant" "github.com/OpenIMSDK/Open-IM-Server/pkg/common/log" "github.com/OpenIMSDK/Open-IM-Server/pkg/errs" + "github.com/OpenIMSDK/Open-IM-Server/pkg/proto/errinfo" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/wrapperspb" + "strings" ) func GrpcClient() grpc.DialOption { @@ -46,8 +47,10 @@ func rpcClientInterceptor(ctx context.Context, method string, req, resp interfac return errs.NewCodeError(errs.ServerInternalError, err.Error()).Wrap() } if details := sta.Details(); len(details) > 0 { - if v, ok := details[0].(*wrapperspb.StringValue); ok { - return errs.NewCodeError(int(sta.Code()), sta.Message()).Wrap(v.String()) + errInfo, ok := details[0].(*errinfo.ErrorInfo) + if ok { + s := strings.Join(errInfo.Warp, "->") + errInfo.Cause + return errs.NewCodeError(int(sta.Code()), sta.Message()).WithDetail(s).Wrap() } } return errs.NewCodeError(int(sta.Code()), sta.Message()).Wrap() diff --git a/pkg/common/mw/rpc_server_interceptor.go b/pkg/common/mw/rpc_server_interceptor.go index 09645d08e..0c72f473d 100644 --- a/pkg/common/mw/rpc_server_interceptor.go +++ b/pkg/common/mw/rpc_server_interceptor.go @@ -6,13 +6,16 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/log" "github.com/OpenIMSDK/Open-IM-Server/pkg/common/mw/specialerror" "github.com/OpenIMSDK/Open-IM-Server/pkg/errs" - "github.com/OpenIMSDK/Open-IM-Server/pkg/proto/wrapperspb" + "github.com/OpenIMSDK/Open-IM-Server/pkg/proto/errinfo" + "github.com/pkg/errors" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "math" + "runtime" "runtime/debug" + "strings" ) const OperationID = "operationID" @@ -31,7 +34,22 @@ func rpcServerInterceptor(ctx context.Context, req interface{}, info *grpc.Unary if r := recover(); r != nil { log.ZError(ctx, "rpc panic", nil, "FullMethod", info.FullMethod, "type:", fmt.Sprintf("%T", r), "panic:", r) fmt.Println("stack info:", string(debug.Stack())) - err = errs.ErrInternalServer + pc, file, line, ok := runtime.Caller(4) + if ok { + panic("get runtime.Caller failed") + } + errInfo := &errinfo.ErrorInfo{ + Path: file, + Line: uint32(line), + Name: runtime.FuncForPC(pc).Name(), + Cause: fmt.Sprintf("%s", r), + Warp: nil, + } + sta, err := status.New(codes.Code(errs.ErrInternalServer.Code()), errs.ErrInternalServer.Msg()).WithDetails(errInfo) + if err != nil { + panic(err) + } + err = sta.Err() } }() funcName := info.FullMethod @@ -68,14 +86,37 @@ func rpcServerInterceptor(ctx context.Context, req interface{}, info *grpc.Unary code = errs.ServerInternalError } grpcStatus := status.New(codes.Code(code), codeErr.Msg()) + var errInfo *errinfo.ErrorInfo if unwrap != err { - stack := fmt.Sprintf("%+v", err) - if details, err := grpcStatus.WithDetails(wrapperspb.String(stack)); err == nil { - grpcStatus = details + sti, ok := err.(interface{ StackTrace() errors.StackTrace }) + if ok { + log.ZWarn(ctx, "rpc server resp", err, "funcName", funcName, "unwrap", unwrap.Error(), "stack", fmt.Sprintf("%+v", err)) + if fs := sti.StackTrace(); len(fs) > 0 { + pc := uintptr(fs[0]) + fn := runtime.FuncForPC(pc) + file, line := fn.FileLine(pc) + errInfo = &errinfo.ErrorInfo{ + Path: file, + Line: uint32(line), + Name: fn.Name(), + Cause: unwrap.Error(), + Warp: nil, + } + if arr := strings.Split(err.Error(), ": "); len(arr) > 1 { + errInfo.Warp = arr[:len(arr)-1] + } + } } } + if errInfo == nil { + errInfo = &errinfo.ErrorInfo{Cause: err.Error()} + } + details, err := grpcStatus.WithDetails(errInfo) + if err != nil { + panic(err) + } log.ZError(ctx, "rpc server resp", err, "funcName", funcName) - return nil, grpcStatus.Err() + return nil, details.Err() } func GrpcServer() grpc.ServerOption { diff --git a/pkg/errs/coderr.go b/pkg/errs/coderr.go index ba0c45c79..838073bc9 100644 --- a/pkg/errs/coderr.go +++ b/pkg/errs/coderr.go @@ -9,6 +9,8 @@ import ( type CodeError interface { Code() int Msg() string + Detail() string + WithDetail(detail string) CodeError Is(err error) bool Wrap(msg ...string) error error @@ -35,6 +37,24 @@ func (e *codeError) Msg() string { return e.msg } +func (e *codeError) Detail() string { + return e.detail +} + +func (e *codeError) WithDetail(detail string) CodeError { + var d string + if e.detail == "" { + d = detail + } else { + d = e.detail + ", " + detail + } + return &codeError{ + code: e.code, + msg: e.msg, + detail: d, + } +} + func (e *codeError) Wrap(w ...string) error { return errors.Wrap(e, strings.Join(w, ", ")) } diff --git a/pkg/proto/errinfo/errinfo.pb.go b/pkg/proto/errinfo/errinfo.pb.go new file mode 100644 index 000000000..4db1d6d95 --- /dev/null +++ b/pkg/proto/errinfo/errinfo.pb.go @@ -0,0 +1,183 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.29.1 +// protoc v4.22.0 +// source: errinfo/errinfo.proto + +package errinfo + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ErrorInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path"` + Line uint32 `protobuf:"varint,2,opt,name=line,proto3" json:"line"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name"` + Cause string `protobuf:"bytes,4,opt,name=cause,proto3" json:"cause"` + Warp []string `protobuf:"bytes,5,rep,name=warp,proto3" json:"warp"` +} + +func (x *ErrorInfo) Reset() { + *x = ErrorInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_errinfo_errinfo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ErrorInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ErrorInfo) ProtoMessage() {} + +func (x *ErrorInfo) ProtoReflect() protoreflect.Message { + mi := &file_errinfo_errinfo_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 ErrorInfo.ProtoReflect.Descriptor instead. +func (*ErrorInfo) Descriptor() ([]byte, []int) { + return file_errinfo_errinfo_proto_rawDescGZIP(), []int{0} +} + +func (x *ErrorInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *ErrorInfo) GetLine() uint32 { + if x != nil { + return x.Line + } + return 0 +} + +func (x *ErrorInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ErrorInfo) GetCause() string { + if x != nil { + return x.Cause + } + return "" +} + +func (x *ErrorInfo) GetWarp() []string { + if x != nil { + return x.Warp + } + return nil +} + +var File_errinfo_errinfo_proto protoreflect.FileDescriptor + +var file_errinfo_errinfo_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x65, 0x72, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x65, 0x72, 0x72, 0x69, 0x6e, 0x66, + 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x4d, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x71, + 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, + 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x77, 0x61, 0x72, 0x70, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x77, 0x61, 0x72, + 0x70, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x4d, 0x53, 0x44, 0x4b, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x2d, 0x49, + 0x4d, 0x2d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x65, 0x72, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_errinfo_errinfo_proto_rawDescOnce sync.Once + file_errinfo_errinfo_proto_rawDescData = file_errinfo_errinfo_proto_rawDesc +) + +func file_errinfo_errinfo_proto_rawDescGZIP() []byte { + file_errinfo_errinfo_proto_rawDescOnce.Do(func() { + file_errinfo_errinfo_proto_rawDescData = protoimpl.X.CompressGZIP(file_errinfo_errinfo_proto_rawDescData) + }) + return file_errinfo_errinfo_proto_rawDescData +} + +var file_errinfo_errinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_errinfo_errinfo_proto_goTypes = []interface{}{ + (*ErrorInfo)(nil), // 0: OpenIMServer.protobuf.ErrorInfo +} +var file_errinfo_errinfo_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_errinfo_errinfo_proto_init() } +func file_errinfo_errinfo_proto_init() { + if File_errinfo_errinfo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_errinfo_errinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ErrorInfo); 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_errinfo_errinfo_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_errinfo_errinfo_proto_goTypes, + DependencyIndexes: file_errinfo_errinfo_proto_depIdxs, + MessageInfos: file_errinfo_errinfo_proto_msgTypes, + }.Build() + File_errinfo_errinfo_proto = out.File + file_errinfo_errinfo_proto_rawDesc = nil + file_errinfo_errinfo_proto_goTypes = nil + file_errinfo_errinfo_proto_depIdxs = nil +} diff --git a/pkg/proto/errinfo/errinfo.proto b/pkg/proto/errinfo/errinfo.proto new file mode 100644 index 000000000..77182d1d5 --- /dev/null +++ b/pkg/proto/errinfo/errinfo.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package OpenIMServer.protobuf; + +option go_package = "github.com/OpenIMSDK/Open-IM-Server/pkg/proto/errinfo"; + +message ErrorInfo { + string path = 1; + uint32 line = 2; + string name = 3; + string cause = 4; + repeated string warp = 5; +} \ No newline at end of file diff --git a/pkg/proto/gen.cmd b/pkg/proto/gen.cmd index 95f0b2f79..16c7a43f7 100644 --- a/pkg/proto/gen.cmd +++ b/pkg/proto/gen.cmd @@ -1,5 +1,6 @@ protoc --go_out=plugins=grpc:./auth --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/auth auth/auth.proto protoc --go_out=plugins=grpc:./conversation --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/conversation conversation/conversation.proto +protoc --go_out=plugins=grpc:./errinfo --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/errinfo errinfo/errinfo.proto protoc --go_out=plugins=grpc:./friend --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/friend friend/friend.proto protoc --go_out=plugins=grpc:./group --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/group group/group.proto protoc --go_out=plugins=grpc:./msg --go_opt=module=github.com/OpenIMSDK/Open-IM-Server/pkg/proto/msg msg/msg.proto