Create custom error with stack trace - go

I'm trying to use go-errors to include stack traces with errors I generate. I have a custom HttpError type that I also want to include stack traces. My initial thought was to do this with an embed, but I can't seem to embed it since the name of the class (Error) is the same as the name of one of the methods.
package netutil
import (
"github.com/go-errors/errors"
)
type HttpError struct {
status int
*errors.Error
}
func (h *HttpError) Error() string {
return "Failed"
}
func NewHttpError(status int, message string) *HttpError {
return &HttpError{
status,
errors.New(message),
}
}
I receive the following error:
tmp_error.go:12: type HttpError has both field and method named Error
Any suggestions?

Why not just name this inner error with something appropriate like inner-error or stack-trace?
type HttpError struct {
status int
StackTace *errors.Error
}
Seems to be fairly common practice in classes used for error handling in other languages/frameworks like .NET and Java.
Another option would be to concatenate your custom message with the inner error using fmt.Sprintf at the time you create the error, keeping it all as one.
errors.New(fmt.Sprintf("This proprietary error happened! Stack trace: %s", message));
If you did that you wouldn't implement func (h *HttpError) Error() since you'd be relying on the embedded error for this.

Related

How to serialize and deserialize errors string in go

In the context of two go services sharing the same types, one client post a json to a server:
type Message struct {
Error string `json:"error"`
}
The client should serialize an error into a string.
The server should deserialize that string into an error on which I can use errors.As or errors.Is to check for wrapped errors and such.
How may I serialize nested errors ?
Let me share with you some thoughts that maybe can guide you in the right direction.
errors.Is and errors.As
These two operators deal with the error type. The common scenario they are used when you've to deal with an error returned by a function and, based on which type of error it is, take some actions. If you're returning just a string from a function, it's almost impossible to invoke errors.Is and errors.As unless you do some extra logic to handle the string.
A simple workaround
If you wanna preserve a hierarchical structure for your errors without having to flatten them, you could follow this approach:
package main
import (
"encoding/json"
"fmt"
)
type RecursiveErr struct {
Message string `json:"Message"`
Err error `json:"error"`
}
func (r RecursiveErr) Error() string {
return r.Message
}
func main() {
// recursive - marshal
childErr := RecursiveErr{Message: "leaf-child error"}
parentErr := RecursiveErr{Message: "root error", Err: &childErr}
data, _ := json.MarshalIndent(&parentErr, "", "\t")
fmt.Println(string(data))
// recursive - unmarshal
var parsedParentErr RecursiveErr
json.Unmarshal(data, &parsedParentErr)
fmt.Println(parsedParentErr.Message)
}
This solution has the following benefits:
You can preserve the hierarchical structure of your errors (parent-child relationship)
You can invoke the errors.Is and errors.As on the Err field.
As it's an uncommon scenario for me, I hope to not be off-topic with your request, let me know!

How to report custom Go error types to Sentry?

I want my custom error to show up in Sentry, but it just grabs the underlying errorString type.
Is there a way to show operationTimeoutError instead?
Here's what I do:
type operationTimeoutError error
var errOperationTimeout operationTimeoutError = errors.New("TIMEOUT")
func foo() {
sentry.CaptureException(errOperationTimeout)
}
sentry will call the Error() method in the error you provided, which in return prints the error message of the underlying error. you can either override the Error method for the custom error type or map it to a new error with the message you intend to see in sentry. Since its likely that you are using the error for other purposes like logging, I think mapping is the better choice.
func mapError(err error)error{
switch err.(type){
case operationTimeoutError:
return errors.New("operationTimeoutError")
default:
return err
}
}
sentry.CaptureException(mapError(errOperationTimeout))

Return error from deferred function when error is already returned

Update: I think now that there is no universal answer to this question. We can return both errors using the technique explained in the answer. I think that the most important thing here is not to forget the case when we have two errors and somehow handle it.
Notes: There are many questions on SO about how to return an error from deferred function. This is not a question here.
(In Go) What is the proper way to return an error from a deferred function when the function is already returning an error. For example
func errorMaker() (err error) {
defer func() {
err = errors.New("Deferred error")
}()
err = errors.New("Some error")
return
}
func main() {
err := errorMaker()
fmt.Printf("Error: %v\n", err)
}
In the code above the error returned by the deferred function overwrites the error returned by the function. What is the canonical way to return both errors? If another programmer uses my function what result might she expect from the function when the function returns 'two errors'?
Should I use Error wrapping for this?
Additional notes:
As #Volker says in his comment I write some application specific handling for this error. Because I know what should be done based on nature of the errors.
I think my question is - if I want to return all errors from the function what is the best way to combine them in my scenario?
Disclaimer: I don't know if the following advice can be seen as "standard" or "widely-accepted".
Should I use Error wrapping for this?
Short answer: yes (I would do so).
Go 1.12 and earlier
What I do when I need my errors to convey some specific meaning, without foregoing the error interface, I create a wrapper that implements the error interface - Error() string -. This wrapper contains all extra information I need.
If the caller is aware of the existence of those extra info, it can unwrap the error with a cast and find those info.
With the added benefit that unaware callers can just handle the error as a generic error.
type MyError struct {
DeferredError error
}
// Implements 'error' interface
func (e MyError) Error() string {
// format to string
}
func someFunc() error {
// might return an instance of MyError
}
...
// Caller code
err := someFunc()
if err != nil {
if myErr, ok := err.(*MyError); ok {
// here you can access the wrapped info
fmt.Println(myErr.DeferredError)
} else {
// otherwise handle the error generically
}
}
Go 1.13 onwards
With Go.13 you can use errors.As to unwrap an error. From the official docs:
[The method] As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.
var myErr *MyError
if errors.As(err, &myErr) {
// here you can access the wrapped info
fmt.Println(myErr.DeferredError)
} else {
// otherwise handle the error generically
}
As the docs say the myErr variable is populated as a side-effect of calling As.

Call function of specific type in Go

I'm a complete Go newbie, so sorry for the question in advance.
I'm trying to work with a so-defined interface to connect to a message broker:
// Broker is an interface used for asynchronous messaging.
type Broker interface {
Options() Options
Address() string
Connect() error
Disconnect() error
Init(...Option) error
Publish(string, *Message, ...PublishOption) error
Subscribe(string, Handler, ...SubscribeOption) (Subscriber, error)
String() string
}
// Handler is used to process messages via a subscription of a topic.
// The handler is passed a publication interface which contains the
// message and optional Ack method to acknowledge receipt of the message.
type Handler func(Publication) error
// Publication is given to a subscription handler for processing
type Publication interface {
Topic() string
Message() *Message
Ack() error
}
I'm trying to use the Subscribe-function to subscribe to a channel and thats the point where I'm struggeling right now.
My current approach is the following one:
natsBroker.Subscribe(
"QueueName",
func(p broker.Publication) {
fmt.Printf(p.Message)
},
)
The error output is cannot use func literal (type func(broker.Publication)) as type broker.Handler in argument to natsBroker.Subscribe.
But how do I ensure that the function type actually is a broker.Handler?
Thx for your time in advance!
Update
In case anybody is interested, the error return type was missing which caused the error, so it should look similar to that:
natsBroker.Subscribe(
"QueueName",
broker.Handler(func(p broker.Publication) error {
fmt.Printf(p.Topic())
return nil
}),
)
As the error indicates, the parameter and what you're passing don't match:
type Handler func(Publication) error
func(p broker.Publication)
You have no return value. If you add a return value (even if you always return nil), it will work fine.
If your signature of your anonymous function matched that of the handler type declaration (Adrian correctly points out you're missing the error return), you should be able to just do a type conversion:
package main
import "fmt"
type Handler func(int) error
var a Handler
func main() {
a = Handler(func(i int) error {
return nil
})
fmt.Println(isHandler(a))
}
func isHandler(h Handler) bool {
return true
}
Since the the compiler knows at compiler-time that the types match, there's no need to do additional checking, like you might in the case of, say, a type assertion.

Cannot use implementation of interface as argument to func that wants interface

I'm getting the following error:
./main.go:31: cannot use telegramService (type messaging.TelegramService) as type mypackage.MessagingService in argument to mypackage.RegisterMessagingService:
messaging.TelegramService does not implement mypackage.MessagingService (wrong type for HandleIncomingMessage method)
have HandleIncomingMessage(telegram.Message) error
want HandleIncomingMessage(mypackage.IncomingMessage) error
I have an interface that describes a messaging service like Telegram or WhatsApp, and an interface that describes an incoming message from one of those services:
// IncomingMessage is a message that comes in on a messaging service
type IncomingMessage interface {
Send() error
}
// MessagingService is a service on which messages can be send (like Telegram or FB Messenger)
type MessagingService interface {
Start()
HandleIncomingMessage(IncomingMessage) error
GetHTTPHandler() http.HandlerFunc
GetCommands() []MessagingCommand
}
The first implementation of MessagingService is for Telegram. The issue is the HandleIncomingMessage function, which currently doesn't really do anything and just looks like this:
// HandleIncomingMessage will take an incoming message and repond to it
func (s TelegramService) HandleIncomingMessage(msg *telegram.Message) error {
return nil
}
The issue is that this function accepts a telegram.Message, which the compiler says doesn't comply with the interface. The thing is, that telegram.Message is an implementation of IncomingMessage:
// Message is a Telegram message
type Message struct {
// Added the line below at some point, but it didn't work without it either
mypackage.IncomingMessage
MessageID uint64 `json:"message_id"`
FirstName string `json:"first_name"`
Username string `json:"username"`
Date uint64 `json:"date"`
Text string `json:"text"`
Chat Chat `json:"chat"`
From User `json:"from"`
}
// Send will take m and send it
func (m Message) Send() error {
// Do stuff
return nil
}
Initially IncomingMessage was an empty interface, which is where I first noticed the issue. I tried adding the function Send() which I was going to add anyway, as I thought maybe just giving it any struct wouldnt't work. However, I'm still getting this error.
I don't see any reason why telegram.Message doesn't implement the interface, it's pretty straight forward.
Can anyone explain why this doesn't work?
PS: My package isn't actually called mypackage, changed for clarity
HandleIncomingMessage must take an IncomingMessage argument since that's the way the interface is defined. You can't define an implementation of HandleIncomingMessage that takes some other type as the argument, even if that type implements IncomingMessage. You can define your function to take IncomingMessage and convert that to *telegram.Message using a type assertion:
func (s TelegramService) HandleIncomingMessage(im IncomingMessage) error {
msg := im.(*telegram.Message)
return nil
}
I'm assuming you actually want to be using a pointer to telegram.Message. If so, you need to change the definition of the Send method to take a pointer receiver.

Resources