Как правильно использовать CockroachDB/errors

Я использую Go и наткнулся на Cockroachdb/errors для обработки ошибок. Я понимаю, что errors.Wrap можно использовать для добавления дополнительного контекста и трассировки стека к ошибкам, например:

func DoSomething() error {
    err := os.WriteFile(...)
    if err != nil {
        return errors.Wrap(err, "something happened here in DoSomething")
    }
}

func CallSomething() error {
    err := DoSomething()
    if err != nil {
        return errors.WithDetail(err, "here is some additional info")
    }
}

Предполагая, что приведенное выше использование правильно, я не знаю, как обрабатывать ошибки сторонних библиотек. Учтите следующее:

func DoSomething() error {
    err := some.ThirdPartyLibraryCode(...)
    if err != nil {
        return errors.Wrap(err, "something happened here in DoSomething")
    }
}

Если сторонняя библиотека также использует cockroachdb/errors, обертывание их ошибок в errors.Wrap может привести к вложенным ошибкам с несколькими трассировками стека, что может привести к путанице в выводе:

(1) attached stack trace
  -- stack trace:
  | graph-editor/nodes.(*Graph).ExecuteImpl
  |     /Users/daniel/git/graph-main/[email protected]:52
  | [...repeated from below...]  👈👈👈👈 ??
Wraps: (2) failed to execute  

Как мне обрабатывать ошибки сторонних библиотек, которые, возможно, уже используют cockroachdb/errors (или нет), чтобы избежать дублирования трассировки стека и сохранить чистоту вывода ошибок?

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
0
117
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Согласно документации , создание error с семейством New() предлагает следующие варианты печати соответствующей информации:

// - message via `Error()` and formatting using `%v`/`%s`/`%q`.
// - everything when formatting with `%+v`.
// - stack trace and message via `errors.GetSafeDetails()`.
// - stack trace and message in Sentry reports.

Вы не рассказываете нам, как печатаете информацию, которая вас сбивает с толку. Поигравшись немного с библиотекой, я думаю, вы используете префикс + для глагола v, например. с

fmt.Printf("%+v", myCockroachDBErr)

(Честно говоря, я не знаю, почему это приводит к выводу, подобному тому, который вы публикуете, поскольку согласно документации fmt это просто «добавляет имена полей». Go magixx.)

Итак, с помощью %+v вы выбираете вывод «все», что, вероятно, редко является тем, что вам нужно (кроме сеансов отладки). Вы увидите более или менее всю информацию, содержащуюся в структуре error, в более или менее понятном виде. Если вы просто хотите распечатать простой стек, используйте

fmt.Printf("%v", errors.GetSafeDetails(myCockroachDBErr))

Тем не менее, похоже, что функции Wrap() ведут себя примерно так же, как и функции New(). Это означает, что Wrap() создает новый error и сохраняет завернутый для дальнейшего использования. Хотя, согласно документации Wrap(), он «сохраняет» старый стек error's, он, очевидно, не считается частью нового стека error's, который начинается прямо там, где вызывается Wrap(). Если вы хотите вывести стопку завернутых error, вам нужно сначала ее развернуть:

import (
    "fmt"
    "github.com/cockroachdb/errors"
)

func main() {
    wrappingErr := someFkt1()
    fmt.Printf("wrappingErr: %v\n", errors.GetSafeDetails(wrappingErr))
    unwrappedErr := errors.UnwrapOnce(errors.UnwrapOnce(wrappingErr)
    fmt.Printf("got2: %v\n", errors.GetSafeDetails(unwrappedErr))
    return
}


func someFkt1() error {
    return someFkt2()
}
func someFkt2() error {
    return errors.Wrapf(someFkt3(), "outer error")
}

func someFkt3() error {
    return errors.New("inner error")
}

Для лучшего понимания можно еще раз вывести «все»:

fmt.Printf("everything: %+v\n\n", err)

Который будет регистрировать что-то вроде:

everything: inner error
(1) attached stack trace
  -- stack trace:
  | main.someFkt2
  |     /myPath/main.go:31
  | [...repeated from below...] 👈 i.e. /myPath/main.go:28, /myPath/main.go:11, proc.go:267, asm_amd64.s:1650
Wraps: (2) attached stack trace 👈 the one we unwrap in my example
  -- stack trace:
  | main.someFkt3
  |     /myPath/main.go:35
  | main.someFkt2
  |     /myPath/main.go:31
  | main.someFkt1
  |     /myPath/main.go:28
  | main.cockroachDBErrors
  |     /myPath/main.go:11
  | runtime.main
  |     /usr/local/go/src/runtime/proc.go:267
  | runtime.goexit
  |     /usr/local/go/src/runtime/asm_amd64.s:1650
Wraps: (3) inner error 👈 looks like even New() counts as wrapping
Error types: (1) *withstack.withStack (2) *withstack.withStack (3) *errutil.leafError 👈 a helpful legend

Я полагаю, что вы могли бы создать разворачивающийся цикл errors непосредственно перед тем, как достигнете самого внутреннего, и вывести эту error's трассировку стека, чтобы увидеть все это с самого начала. Однако я не вижу более простого способа - errors.UnwrapAll() возвращает leafError, который сам по себе не несет следов.

Это было идеальное резюме, очень полезное! Очень признателен!

HelloWorld 03.06.2024 04:48

Во-первых, вы должны понимать, что cockroachdb/errors действительно старый, он был разработан до расширенной обработки ошибок в Go 1.13 . См. также « Значения ошибок: часто задаваемые вопросы».

cockroachdb/errors предоставляет некоторые функции (личную информацию и отчеты sendry.io), которые могут быть важны для вас, хотя я думаю, что они не должны быть в пакете ошибок, и мне также не нравится зависимость от gogo/protobuf, но это может быть просто мне.

Еще одна вещь, которую он делает, — это обрабатывает ошибки не как данные, а как исключения. Я написал простой пример программы, чтобы проверить ее полезность:

package main

import (
    "errors"
    "fmt"

    cerr "github.com/cockroachdb/errors"
    "golang.org/x/sync/errgroup"
)

func main() {
    err1 := func1()
    fmt.Printf("func1: %+v, expected: %v\n", err1, errors.Is(err1, ErrOne))

    err2 := func3()
    fmt.Printf("func3: %+v, expected: %v\n", err2, errors.Is(err2, ErrTwo))
}

func func1() error {
    var g errgroup.Group

    g.Go(func2)

    if err := g.Wait(); err != nil {
        return fmt.Errorf("func1 failed: %w", err)
    }

    return nil
}

var ErrOne = errors.New("error one")

func func2() error {
    return ErrOne
}

func func3() error {
    var g errgroup.Group

    g.Go(func4)

    if err := g.Wait(); err != nil {
        return cerr.Wrap(err, "func3 failed")
    }

    return nil
}

var ErrTwo = cerr.New("error two")

func func4() error {
    return ErrTwo
}

К сожалению, он не компилируется на Go Playground, время ожидания истекает из-за зависимостей cockroachdb/errors. Когда я запускаю его локально, я получаю:

func1: func1 failed: error one, expected: true

для первого ожидаемого вызова: я определяю ErrOne, обозначающий некоторую ошибку, и обогащаю его при возврате из func1. Для этого не обязательно использовать имя функции, достаточно того, что можно легко найти с помощью текстового поиска. Это дает мне представление о том, где произошла ошибка. Я бы также добавил параметры, чтобы понять, какой вызов вызвал эту ошибку. Также я программно проверяю, получу ли я ErrOne.

Вторая строка:

func3: func3 failed: error two
(1) attached stack trace
  -- stack trace:
  | main.func3
  |     [...]/main.go:43
  | main.main
  |     [...]/main.go:15
  | [...repeated from below...]
Wraps: (2) func3 failed
Wraps: (3) attached stack trace
  -- stack trace:
  | main.init
  |     [...]/main.go:49
  | runtime.doInit1
  |     [...]/go/src/runtime/proc.go:7176
  | runtime.doInit
  |     [...]/go/src/runtime/proc.go:7143
  | runtime.main
  |     [...]/go/src/runtime/proc.go:253
  | runtime.goexit
  |     [...]/go/src/runtime/asm_amd64.s:1695
Wraps: (4) error two
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *withstack.withStack (4) *errutil.leafError, expected: true

который — я не знаю, многие вещи написаны даже не мной — это код инициализации пакета. Кроме того, трассировки стека не пересекаются, поскольку я использую предопределенное определение ошибки (чтобы иметь возможность ее проверять, а также не тратить ресурсы на создание одной и той же ошибки снова и снова) и горутины, что не должно быть редкостью в программах на Go.

Вы уверены, что отправка данных на sendry.io и обработка ошибок как исключений того стоят? Если да, я предполагаю, что несколько трассировок стека должны быть в порядке, поскольку вы не можете знать, возникла ли исходная ошибка из вашего стека или из другой горутины. Трассировки стека стали популяризироваться языками с преимущественно линейным стеком и отсутствием возможностей для эффективного расширения сообщений об ошибках.

Если вы ищете более мощные методы отладки, могу ли я предложить вместо этого изучить запись полета?

Супер полезно, очень ценно! Особенно тот факт, что пакет старше, чем некоторые средства обработки ошибок Go, что объясняет несколько сообщений в блоге, которые я не мог понять.

HelloWorld 03.06.2024 04:50

@HelloWorld Да, изменения, внесенные в Go 1.13, изначально планировались для Go 2, поэтому в старых сообщениях это называется «обработкой ошибок Go 2».

eik 03.06.2024 12:16

Другие вопросы по теме