Golang use of Zerolog and raising errors - go

I'm using zerolog to do logging in my Go app, and I'm trying to follow best practices for only erroring out at the top of the stack. However, I'm not sure exactly how to write both to my error log and pass the error up the chain:
func VeryDeepFunction() (int, error) {
err := doSomethingThatCouldCauseError()
if err != nil {
errMsg := fmt.Sprintf("something bad happened: %+v", err)
log.Error().Msgf(errMsg)
return 0, fmt.Errorf(errMsg)
}
return 1, nil
}
This feels redundant to me - formatting the string, filing with zerolog, and then wrapping the error again. Is there a more correct way to do this?

It is possible to chain errors
package main
import (
"log"
"github.com/pkg/errors"
)
var someError = errors.New("something is wrong")
func main() {
// processing error on top level
if err := someFunc(); errors.Is(err, someError) {
log.Println(err)
}
}
func someFunc() error {
if err := alwaysError(); err != nil {
return errors.Wrap(err, "someFunc")
}
return nil
}
func alwaysError() error {
return someError
}
Output will be
2022/09/01 10:00:46 someFunc: something is wrong
See errors package for details

Related

How to handle error of singleton only once

How to handle error of singleton only once?
I have a singleton service which could generate error only at first call and then it returns already created instance.
Service looks like below:
package data
import (
"sync"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
databaseSingleton *gorm.DB
once sync.Once
)
func NewDatabase() (*gorm.DB, error) {
once.Do(func() {
// ...
databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
if err != nil {
return nil, err
}
})
return databaseSingleton, nil
}
The problem is multiple services which uses databaseSingleton above handle error which can occurs only once.
Services which uses databaseSingleton looks like below:
func NewServiceOne() (ServiceOne, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceOne{database}, nil
}
func NewServiceTwo() (ServiceTwo, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceTwo{database}, nil
}
func NewServiceThree() (ServiceThree, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceThree{database}, nil
}
If there any way to omit this error handling because err could be generated only once?
If the error occurs (only once), your databaseSingleton will not be setup. You should return the error in all cases.
Although this isn't something you can do anything about (since the attempt to initialize databaseSingleton will not be repeated due to the use of sync.Once), you could as well halt the app.
In fact, there is no point deferring this initialization, you could just do it during package init, and terminate if it fails. And if it succeeds, you could use databaseSingleton without having to check error of the initialization.
So simply do it like this:
var databaseSingleton *gorm.DB
func init() {
var err error
databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
if err != nil {
log.Fatalf("Failed to connect to DB: %v", err)
}
}
NewServiceOne() could look like this:
func NewServiceOne() ServiceOne {
return &serviceOne{databaseSingleton}
}

How can I use errors.Is on an error created by errors.Wrap?

I like to do this:
var ErrMyCustomError = errors.New("something went wrong")
func doAThing(input string) error {
if input == "bad input" {
return ErrMyCustomError
}
return nil
}
And then in my tests:
func TestFailCase() {
err := doAThing("bad input")
require.True(errors.Is(err, ErrMyCustomError)
}
My question is how do I do all that but have return ErrMyCustomError instead wrap an error I got from somewhere else in an ErrMyCustomError (so that the errors.Is still works).
func doAThing(input string) error {
err := doSomething(input)
if err != nil {
return errors.Wrap(err, "some context message") // <-- this line here needs to return an ErrMyCustomError somehow but also wrap err
}
}
// Imagine this is in some third party lib and I can't alter it.
func doSomething(input string) error {
if input == "bad input" {
return errors.New("some error I will later wrap")
}
return nil
}
func TestFailCase() {
err := doAThing("bad input")
require.True(errors.Is(err, ErrMyCustomError) // <-- this is no longer true but I want it to be
}
This is the best I have been able to come up with. I'd prefer to just var MyCustomErr = errors.New("something went wrong") but then I have no way to get it into the error chain. I'd prefer to errors.Is rather than having to declare a receiver pointer to check errors.As but so far I haven't found a way to do that either.
package main
import (
"errors"
"fmt"
)
type MyCustomError struct {
Err error
}
func (e MyCustomError) Error() string {
return "something went wrong: " + e.Err.Error()
}
func main() {
err := myWrapper()
ptr := &MyCustomError{}
print(errors.As(err, ptr)) // prints "true"
}
func myWrapper() error {
err := thirdPartyFunc()
if err != nil {
return fmt.Errorf("some additional context: %w", MyCustomError{Err: err})
}
return nil
}
func thirdPartyFunc() error {
return errors.New("this is the error I want to wrap")
}

How to test error condition for this function?

func getHostname() (string, error) {
host, err := os.Hostname()
if err != nil {
// Untested code-block
return "", err
}
// Don't judge the example. This is just an example.
// Basically, I want to know how to induce error in os.Hostname()
return host, nil
}
The corresponding test for this function is:-
import "testing"
func TestGetHostname(t *testing.T) {
host, err := getHostname()
if err != nil {
t.Errorf("Error executing getHostname(): %s", err)
}
if len(host) < 1 {
t.Errorf("Hostname returned is not proper")
}
}
If I were to make the 100% coverage for this function, I would like induce error in os.Hostname() so that I can run that if block too. How can I achieve that?
Is creating an interface and passing it as an argument to function the only way?
You can make the code testable by using dependency injection like this:
// Override for testing
var osHostname = os.Hostname
func getHostname() (string, error) {
host, err := osHostname()
if err != nil {
return "", err
}
return host, nil
}
In your test, you can replace the stub with an error-producing version.
func TestGetHostnameFails(t *testing.T) {
defer func() { osHostname = os.Hostname }()
osHostname = func()(string, error) { return "", errors.New("fail") }
got, err := getHostname()
if err == nil {
t.Errorf("getHostname() = (%v, nil), want error", got)
}
}
There's advantages and disadvantages to using package globals to do dependency injection. The advantages are that it's very simple, and most importantly to me doesn't mess the production code up too much. For those reasons it's my choice when you want to test code in isolation like this. The disadvantages are that you may forget to reset the state in your test, and it doesn't give a good testing api -- so if there's lots of tests that are using this stub (or worse, tests in another package), you may prefer to make the configuration explicit by putting it in a struct, and making getHostname a method of that struct.
This is just an example, but I feel compelled to provide a cautionary message about getting 100% test coverage. os.Hostname() is very unlikely to fail in practice, and it's also very unlikely that your error handling when it fails is wrong. It's much more likely that any dependency injection introduces a bug than it is that this test identifies any real bug.
what happens if your function under test panics?
add panic-checking to your testing code:
package hostname
import "testing"
func TestGetHostname(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("The code did panic")
}
}()
name, err := getHostname()
if err != nil {
t.Errorf("err: %v\n", err)
}
if len(name) == 0 {
t.Errorf("Hostname is empty")
}
}
old:
look inside os.hostname():
in case of err, it will returns:
return "", NewSyscallError("...", err)
your getHostname() repeats this again.
let me do it again (just to clarify what i'm talking about):
func getHostname2() (string, error) {
host, err := getHostname() // your getHostname !
if err != nil {
return "", err
}
return host, nil
}
this is extra superfluous, isn't it?
so i think this is enough:
package main
import "os"
import "fmt"
func main() {
name, err := os.Hostname()
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(name)
}
so simple error checking is enough (like this):
package main
import "os"
import "fmt"
func work() {
if name, err := os.Hostname(); err != nil {
fmt.Printf("err: %v\n", err)
return
} else {
// do some job ...
fmt.Println(name)
}
}
func main() {
work()
}
i hope this helps.

rpc.ServerCodec Still Serving?

I was performing some RPC tests, and stumbled across a problem I can't seem to solve. In my testing I create three separate RPC servers, all of which I try to close and shutdown. However upon performing my last test (TestRpcCodecServerClientComm), it seems my client connection is connecting to the first RPC server I started (I know this because I at some point attached IDs to the RPCHandlers), even though I attempted everything I could to make sure it was shutdown. Though the code is not there I have attempted to inspect every single error I could, but that did not bring about anything.
rpc.go
package rbot
import (
"io"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func RpcCodecClientWithPort(port string) (rpc.ClientCodec, error) {
conn, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
return nil, err
}
return jsonrpc.NewClientCodec(conn), nil
}
func RpcCodecServer(conn io.ReadWriteCloser) rpc.ServerCodec {
return jsonrpc.NewServerCodec(conn)
}
rpc_test.go
package rbot
import (
"errors"
"fmt"
"net"
"net/rpc"
"testing"
)
type RPCHandler struct {
RPCServer net.Listener
conn rpc.ServerCodec
done chan bool
TestPort string
stop bool
GotRPC bool
}
func (r *RPCHandler) SetupTest() {
r.stop = false
r.GotRPC = false
r.done = make(chan bool)
r.TestPort = "5556"
}
// TODO: Create separate function to handle erroring
func (r *RPCHandler) CreateRPCServer() error {
rpc.RegisterName("TestMaster", TestAPI{r})
var err error
r.RPCServer, err = net.Listen("tcp", ":"+r.TestPort)
if err != nil {
return err
}
go func() {
for {
conn, err := r.RPCServer.Accept()
if err != nil || r.stop {
r.done <- true
return
}
r.conn = RpcCodecServer(conn)
rpc.ServeCodec(r.conn)
}
}()
return nil
}
func (r *RPCHandler) CloseRPCServer() error {
r.stop = true
if r.conn != nil {
err := r.conn.Close()
if err != nil {
fmt.Println(err)
}
}
err := r.RPCServer.Close()
<-r.done
return err
}
type TestAPI struct {
t *RPCHandler
}
func (tapi TestAPI) Send(msg string, result *string) error {
if msg == "Got RPC?" {
tapi.t.GotRPC = true
return nil
}
return errors.New("Didn't receive right message")
}
// Check if we can create and close an RPC server successfully using the RPC server codec.
func TestRpcCodecServer(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
err := r.CreateRPCServer()
if err != nil {
t.Fatalf("Could not create rpc server! %s:", err.Error())
}
err = r.CloseRPCServer()
if err != nil {
t.Fatalf("Could not close RPC server! %s:", err.Error())
}
}
// Check if we can create a client without erroring.
func TestRpcCodecClientWithPortt(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCClient, err := RpcCodecClientWithPort(r.TestPort)
defer RPCClient.Close()
if err != nil {
t.Fatalf("Could not create an RPC client! %s:", err.Error())
}
}
// Let's double check and make sure our server and client can speak to each other
func TestRpcCodecServerClientComm(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCCodec, _ := RpcCodecClientWithPort(r.TestPort)
RPCClient := rpc.NewClientWithCodec(RPCCodec)
defer RPCClient.Close()
var result string
err := RPCClient.Call("TestMaster.Send", "Got RPC?", &result)
if err != nil {
t.Fatalf("Error while trying to send RPC message: %s", err.Error())
}
if !r.GotRPC {
t.Fatalf("Could not send correct message over RPC")
}
}
Not sure if I'm just mishandling the connection or something of the like, any help would be much appreciated.
For the Record The RPC api does receive the correct string message
While not the source of your problems, your test configuration has a few race conditions which you should take care of before they cause problems. Always check for issues with the -race option. You should also let the OS allocate the port so you don't run into conflicts. See for example how httptest.Server works.
Your failure here is that you're not creating a new rpc.Server for each test, you're reusing the rpc.DefaultServer. The first call to CreateRPCServer registers a TestAPI under the name TestMaster. Each subsequent call uses the already registered instance.
If you create a new rpc.Server each time you setup the test and register a new TestAPI, the final test will pass.
srv := rpc.NewServer()
srv.RegisterName("TestMaster", testAPI)
...
// and then use srv to handle the new connection
srv.ServeCodec(RpcCodecServer(conn))

Whats the correct Go way to do handle errors

This seems a bit stupid, surely theres a better way?
err = SendMessageAndWait(db, "this is a test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = DoSomething(db, "this is a test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = CheckSomething(db, "this is another test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = SendMessageAndWait(db, "this is a third test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
... x10 ...
Update: For the record, 5 years on from when I wrote this, I am now persuaded that this is a completely sufficient, and perhaps even better, way to handle errors clearly. Not saying its pretty though.
Sadly that's the way it is in Go, however in a way you can make it cleaner:
func isError(err error, pre string) error {
if err != nil {
log.Printf("%v: %v", pre, err)
}
return err
}
func isErrorBool(err error, pre string) (b bool) {
if err != nil {
log.Printf("%v: %v", pre, err)
b = true
}
return
}
func checkSomething() error {
return nil
}
func main() {
if err := isError(checkSomething(), "something failed"); err != nil {
return /* err */
}
//if you don't want to return the error, just check it and die.
if isErrorBool(checkSomething(), "something else failed") {
return
}
}
I would not just print an error and return nothing: the idea is to act on the error and return it (if no decisive action was taken, like a simple log).
Simply calling return is like ignoring the error completely as far as the rest of the application is concerned.
See "Best Practices for Errors in Go", which includes advices as:
Predefine errors
Given a small set of errors, the best way to handle this is to predefine each error publicly at the package level.
Provide information
custom error type is the best solution to this problem. Go's implicit interfaces make creating one easy
Provide stack traces
package errgo provides the functionality of wrapping an error into another one that records where the error happened.
(You have the same features in dropbox/godropbox/errors/errors.go)
In Go, always check for errors. For example,
package main
import "fmt"
func doStuff() error {
err := SendMessageAndWait(db, "this is a test")
if err != nil {
return err
}
err = DoSomething(db, "this is a test")
if err != nil {
return err
}
err = CheckSomething(db, "this is another test")
if err != nil {
return err
}
err = SendMessageAndWait(db, "this is a third test")
if err != nil {
return err
}
return nil
}
func main() {
err := doStuff()
if err != nil {
fmt.Println("Error sending message", err)
}
}
Given your lack of context, I can only assume you're returning from func main().
http://play.golang.org/p/pgcwMb647A
package main
import (
"fmt"
"log"
"errors"
)
func foo(x int) error {
if x == 3 {
return errors.New("x == 3")
}
fmt.Println(x)
return nil
}
func main() {
check := func(err error) {
if err != nil {
log.Fatal(err)
}
}
check(foo(1))
check(foo(2))
check(foo(3))
check(foo(4))
}
Generally, explicit handling is the way to go, but there's a variety of things you can do depending on the context.
At the risk of turning this into code golf, Go supports single line if statements with assignments in them:
if err := SendMessageAndWait(db, "this is a test"); err != nil {
return err
}
The downside is that all return values assigned are scoped to the corresponding if/else if/else block, so if you actually need a different returned value outside that block, you have to go with something closer to PeterSO's answer.

Resources