Как gRPC работает с кастомными ошибками?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка кастомных ошибок в gRPC
gRPC использует строго типизированную систему ошибок, основанную на кодах статусов из пакета google.golang.org/grpc/codes. Однако для передачи дополнительной структурированной информации вместе с ошибками существует механизм кастомных (пользовательских) ошибок, который реализуется через Status Details.
Основной механизм: Status с Details
Ключевая идея заключается в том, что любой gRPC-ответ содержит не только код ошибки и сообщение, но и опциональное поле details, которое может содержать сериализованные protobuf-сообщения любой структуры:
// Пример определения кастомной ошибки в .proto файле
syntax = "proto3";
package example;
import "google/rpc/error_details.proto";
import "google/rpc/status.proto";
message CustomErrorDetail {
string error_code = 1;
string domain = 2;
map<string, string> metadata = 3;
int64 timestamp = 4;
}
Практическая реализация в Go
1. Создание кастомной ошибки на сервере
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
)
func (s *Server) MyMethod(ctx context.Context, req *MyRequest) (*MyResponse, error) {
// Бизнес-логика, которая может вернуть ошибку
if err := validateRequest(req); err != nil {
// Создаем кастомную деталь ошибки
customErr := &CustomErrorDetail{
ErrorCode: "VALIDATION_FAILED",
Domain: "auth",
Metadata: map[string]string{"field": "email"},
Timestamp: time.Now().Unix(),
}
// Преобразуем в Any
anyDetail, _ := anypb.New(customErr)
// Создаем статус с деталями
st := status.New(codes.InvalidArgument, "Invalid request parameters")
st, _ = st.WithDetails(anyDetail)
return nil, st.Err()
}
// Можно добавлять несколько деталей
if isRateLimited(req) {
err1 := &CustomErrorDetail{ErrorCode: "RATE_LIMIT"}
err2 := &CustomErrorDetail{ErrorCode: "SUGGEST_RETRY_AFTER"}
any1, _ := anypb.New(err1)
any2, _ := anypb.New(err2)
st := status.New(codes.ResourceExhausted, "Too many requests")
st, _ = st.WithDetails(any1, any2)
return nil, st.Err()
}
return &MyResponse{Success: true}, nil
}
2. Обработка кастомных ошибок на клиенте
import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
func callGRPCMethod() error {
resp, err := client.MyMethod(ctx, &MyRequest{})
if err != nil {
// Получаем статус из ошибки
st, ok := status.FromError(err)
if !ok {
// Это не gRPC ошибка
return err
}
// Извлекаем детали
for _, detail := range st.Details() {
switch d := detail.(type) {
case *CustomErrorDetail:
fmt.Printf("Custom error: Code=%s, Domain=%s\n",
d.ErrorCode, d.Domain)
// Обрабатываем структурированную информацию
if d.Metadata != nil {
for k, v := range d.Metadata {
fmt.Printf(" %s: %s\n", k, v)
}
}
case *errdetails.BadRequest:
// Можно использовать стандартные детали Google
for _, violation := range d.GetFieldViolations() {
fmt.Printf("Field violation: %s - %s\n",
violation.GetField(),
violation.GetDescription())
}
}
}
return fmt.Errorf("gRPC error: %v", st.Message())
}
// Обработка успешного ответа
return nil
}
Стандартные типы ошибок от Google
gRPC рекомендует использовать стандартные типы из пакета google.golang.org/genproto/googleapis/rpc/errdetails:
import "google.golang.org/genproto/googleapis/rpc/errdetails"
// Пример использования стандартных деталей
func createStandardError() error {
st := status.New(codes.FailedPrecondition, "Precondition check failed")
// Добавляем информацию о поле
badRequest := &errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{Field: "email", Description: "Invalid email format"},
},
}
// Добавляем информацию для отладки
debugInfo := &errdetails.DebugInfo{
Detail: "Stack trace or debug details",
StackEntries: []string{"func1:line10", "func2:line25"},
}
st, _ = st.WithDetails(badRequest, debugInfo)
return st.Err()
}
Best Practices и важные аспекты
-
Согласованность схемы: Прототипы кастомных ошибок должны быть согласованы между клиентом и сервером через общие
.protoфайлы. -
Серийная совместимость: При изменении структуры
CustomErrorDetailсоблюдайте правила обратной совместимости protobuf. -
Лимитирование размера: Помните, что детали ошибок передаются в метаданных, которые имеют ограничения по размеру (обычно 8-16KB).
-
Безопасность: Не включайте чувствительную информацию (пароли, токены) в детали ошибок.
-
Множественные детали: Можно передавать несколько деталей разных типов в одной ошибке для разносторонней информации.
-
Интернационализация: Для мультиязычных систем используйте коды ошибок вместо человекочитаемых сообщений в деталях.
Альтернативные подходы
// 1. Использование богатых ошибок (более сложный подход)
type RichError struct {
Code string
Message string
Details map[string]interface{}
GRPCCode codes.Code
}
// 2. Встраивание информации в основное сообщение (простой способ)
func simpleCustomError() error {
return status.Errorf(codes.NotFound,
"resource not found. ErrorID: %s, Context: %s",
generateErrorID(), getRequestContext())
}
Кастомные ошибки в gRPC предоставляют мощный механизм для структурированной передачи информации об ошибках между микросервисами, что особенно важно в распределенных системах, где стандартных кодов статусов часто недостаточно для полноценной диагностики проблем.