Golang Errors with Stack Trace and Source Fragments
Tired of uninformative error output? Probably this will be more convenient:
Example
package main import ( "io/ioutil" "github.com/ztrue/tracerr" ) func main() { if err := read(); err != nil { tracerr.PrintSourceColor(err) } } func read() error { return readNonExistent() } func readNonExistent() error { _, err := ioutil.ReadFile("/tmp/non_existent_file") // Add stack trace to existing error, no matter if it's nil. return tracerr.Wrap(err) }
Find more executable examples in examples dir.
How to Use
Import
import "github.com/ztrue/tracerr"
Create New Error
err := tracerr.New("some error")
Or:
err := tracerr.Errorf("some error %d", num)
Add Stack Trace to Existing Error
If
err
isnil
then it still benil
with no stack trace added.
Print Error and Stack Trace
Stack trace will be printed only if
err
is of typetracerr.Error
, otherwise just error text will be shown.
This will print error message and stack trace if any:
This will add source code:
It’s able to set up number of lines of code to display for each frame, which is 6
by default:
tracerr.PrintSource(err, 9)
Or to set up number of lines before and after traced line:
tracerr.PrintSource(err, 5, 2)
The same, but with color, which is much more useful:
tracerr.PrintSourceColor(err)
tracerr.PrintSourceColor(err, 9)
tracerr.PrintSourceColor(err, 5, 2)
Save Output to Variable
It’s also able to save output to variable instead of printing it, which works the same way:
text := tracerr.Sprint(err)
text := tracerr.SprintSource(err)
text := tracerr.SprintSource(err, 9)
text := tracerr.SprintSource(err, 5, 2)
Get Stack Trace
Stack trace will be empty if
err
is not an instance oftracerr.Error
.
frames := tracerr.StackTrace(err)
Or if err
is of type tracerr.Error
:
frames := err.StackTrace()
Get Original Error
Unwrapped error will be
nil
iferr
isnil
and will be the same error iferr
is not an instance oftracerr.Error
.
err = tracerr.Unwrap(err)
Or if err
is of type tracerr.Error
:
Performance
Stack trace causes a performance overhead, depending on a stack trace depth. This can be insignificant in a number of situations (such as HTTP request handling), however, avoid of adding a stack trace for really hot spots where a high number of errors created frequently, this can be inefficient.
Benchmarks done on a MacBook Pro 2015 with go 1.11.
Benchmarks for creating a new error with a stack trace of different depth:
BenchmarkNew/5 200000 5646 ns/op 976 B/op 4 allocs/op
BenchmarkNew/10 200000 11565 ns/op 976 B/op 4 allocs/op
BenchmarkNew/20 50000 25629 ns/op 976 B/op 4 allocs/op
BenchmarkNew/40 20000 65833 ns/op 2768 B/op 5 allocs/op
A common problem that many people (including me) have faced when programming in Go, is to pin point the source of an error. Other programming languages provide you with a stack trace to tell you where the error came from, but Go does not have this behavior by default.
In this article, we will discuss how to use the fmt
, and the github.com/pkg/errors
libraries to give us better error reporting.
Consider this example:
package main
import (
"fmt"
"errors"
)
func main() {
result, err := caller1()
if err != nil {
fmt.Println("Error: ", err)
return
}
fmt.Println("Result: ", result)
}
func caller1() (int, error) {
err := caller2()
if err != nil {
return 0, err
}
return 1, nil
}
func caller2() error {
doSomething()
return caller3()
}
func caller3() error {
return errors.New("failed")
}
Try it here
Running this program would output:
Error: failed
Now, this does not really tell us much. What we really want to know is the cause of the error (which, in this case is the caller3
function), and where the error came from (which would be the stack trace leading up to caller3
)
Using the standard library
We can use the fmt.Errorf
function to wrap other errors and effectively generate a trace:
//caller1 and caller2 can be modified to wrap the errors with `fmt.Errorf` before returning them
func caller1() (int, error) {
err := caller2()
if err != nil {
return 0, fmt.Errorf("[caller1] error in calling caller2: %v", err)
}
return 1, nil
}
func caller2() error {
doSomething()
err := caller3()
if err != nil {
return fmt.Errorf("[caller2] error in calling caller 3: %v", err)
}
return nil
}
Try it here
Running this would give you:
Error: [caller1] error in calling caller2: [caller2] error in calling caller 3: failed
This time, the error is much more descriptive, and tells us the sequence of events that lead to the error. Wrapping errors in the format: “[<name of the function>] <description of error> : <actual error>
” gives us a consistent way to find its cause.
But what about custom error types?
Consider having an error type that also has an additional error code:
type CustomError struct {
Code int
}
func (c *CustomError) Error() string {
return fmt.Sprintf("Failed with code %d", c.Code)
}
If you wrap this error with fmt.Errorf
, its original type will be lost, and you won’t be able to access the Code
struct attribute, or even tell that the error is of type CustomError
The solution to this lies in the github.com/pkg/errors
libraries errors.Wrap
, and errors.Cause
functions.
import (
"fmt"
"github.com/pkg/errors"
)
func main() {
err := &CustomError{Code: 12}
// lostErr := fmt.Errorf("failed with error: %v", err)
// there is no way we can get back the `Code` attribute from `lostErr`
wrappedErr := errors.Wrap(err, "[1] failed with error:")
twiceWrappedError := errors.Wrap(wrappedErr, "[2] failed with error:")
// The `errors.Cause` function returns the originally wrapped error, which we can then type assert to its original struct type
if originalErr, ok := errors.Cause(twiceWrappedError).(*CustomError); ok {
fmt.Println("the original error coed was : ", originalErr.Code)
}
}
Adding a trail to errors in Go is almost necessary for any medium to large application if you don’t want to lose your head debugging its cause. Using the “errors” library lets you maintain the trail, while still retaining the benefits of inspecting the original error.
Содержание
- How to generate an error stack trace in Go 🥞
- Using the standard library#
- But what about custom error types?#
- ztrue/tracerr
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- Не без паники в Go
- error
- stack trace
- panic/recover
- handle/check
- How to get stacktraces from errors in Golang with go-errors/errors
- Getting the stacktrace
- Wrapping existing errors
- Recovering from panics
- Programming with errors
- Summary
How to generate an error stack trace in Go 🥞
A common problem that many people (including me) have faced when programming in Go, is to pin point the source of an error. Other programming languages provide you with a stack trace to tell you where the error came from, but Go does not have this behavior by default.
In this article, we will discuss how to use the fmt , and the github.com/pkg/errors libraries to give us better error reporting.
Consider this example:
Running this program would output:
Now, this does not really tell us much. What we really want to know is the cause of the error (which, in this case is the caller3 function), and where the error came from (which would be the stack trace leading up to caller3 )
Using the standard library#
We can use the fmt.Errorf function to wrap other errors and effectively generate a trace:
Running this would give you:
This time, the error is much more descriptive, and tells us the sequence of events that lead to the error. Wrapping errors in the format: “ [ ] : ” gives us a consistent way to find its cause.
But what about custom error types?#
Consider having an error type that also has an additional error code:
If you wrap this error with fmt.Errorf , its original type will be lost, and you won’t be able to access the Code struct attribute, or even tell that the error is of type CustomError
The solution to this lies in the github.com/pkg/errors libraries errors.Wrap , and errors.Cause functions.
Adding a trail to errors in Go is almost necessary for any medium to large application if you don’t want to lose your head debugging its cause. Using the “errors” library lets you maintain the trail, while still retaining the benefits of inspecting the original error.
Источник
ztrue/tracerr
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more.
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Golang Errors with Stack Trace and Source Fragments
Tired of uninformative error output? Probably this will be more convenient:
Find more executable examples in examples dir.
Create New Error
Add Stack Trace to Existing Error
If err is nil then it still be nil with no stack trace added.
Print Error and Stack Trace
Stack trace will be printed only if err is of type tracerr.Error , otherwise just error text will be shown.
This will print error message and stack trace if any:
This will add source code:
It’s able to set up number of lines of code to display for each frame, which is 6 by default:
Or to set up number of lines before and after traced line:
The same, but with color, which is much more useful:
Save Output to Variable
It’s also able to save output to variable instead of printing it, which works the same way:
Get Stack Trace
Stack trace will be empty if err is not an instance of tracerr.Error .
Or if err is of type tracerr.Error :
Get Original Error
Unwrapped error will be nil if err is nil and will be the same error if err is not an instance of tracerr.Error .
Or if err is of type tracerr.Error :
Stack trace causes a performance overhead, depending on a stack trace depth. This can be insignificant in a number of situations (such as HTTP request handling), however, avoid of adding a stack trace for really hot spots where a high number of errors created frequently, this can be inefficient.
Benchmarks done on a MacBook Pro 2015 with go 1.11.
Benchmarks for creating a new error with a stack trace of different depth:
Источник
Не без паники в Go
Привет, уважаемые читатели Хабрахабра. В то время, как обсуждается возможный новый дизайн обработки ошибок и ведутся споры о преимуществах явной обработки ошибок, предлагаю рассмотреть некоторые особенности ошибок, паник и их восстановления в Go, которые будут полезны на практике.
error
error это интерфейс. И как большинство интерфейсов в Go, определение error краткое и простое:
Получается любой тип у которого есть метод Error может быть использован как ошибка. Как учил Роб Пайк Ошибки это значения, а значениями можно оперировать и программировать различную логику.
В стандартной библиотеки Go имеются две функции, которые удобно использовать для создания ошибок. Функция errors.New хорошо подходит для создания простых ошибок. Функция fmt.Errorf позволяет использовать стандартное форматирования.
Обычно для работы с ошибками достаточно типа error. Но иногда может потребоваться передавать с ошибкой дополнительную информацию, в таких случаях можно добавить свой тип ошибок.
Неплохой пример это тип PathError из пакета os
Значение такой ошибки будет содержать операцию, путь и ошибку.
Инициализируются они таким образом:
Обработка может иметь стандартный вид:
А вот если есть необходимость получить дополнительную информацию, то можно распаковать error в *os.PathError:
Этот же подход можно применять если функция может вернуть несколько различных типов ошибок.
play
Объявление нескольких типов ошибок, каждая имеет свои данные:
Функция которая может вернуть эти ошибки:
Обработка ошибок через приведения типов:
В случае когда ошибкам не нужны специальные свойства, в Go хорошей практикой считается создавать переменные для хранения ошибок на уровне пакетов. Примером может служить такие ошибки как io.EOF, io.ErrNoProgress и проч.
В примере ниже, прерываем чтение и продолжаем работу приложения, когда ошибка равна io.EOF или закрываем приложения при любых других ошибках.
Это эффективно, поскольку ошибки создаются только один раз и используются многократно.
stack trace
Список функций, вызванных в момент захвата стека. Трассировка стека помогает получить более полное представление о происходящем в системе. Сохранение трассировки в логах может серьезно помочь при отладки.
Наличие этой информации в ошибке у Go часто не хватает, но к счастью получить дампа стека в Go не сложно.
Для вывода трассировки в стандартный выводов можно воспользоваться debug.PrintStack():
Как результат в Stderr будет записано такая информация:
debug.Stack() возвращает слайс байт с дампом стека, который можно в дальнейшем вывести в журнал или в другом месте.
Есть еще один момент, если мы сделаем вот так:
то на выходе получим такую информацию:
У каждой горутины отдельный стек, соответственно, мы получаем только его дамп. Кстати, о своих стеках у горутин, с этим еще связана работа recover, но об этом чуть позже.
И так, что бы увидеть информацию по всем горутинам, можно воспользоваться runtime.Stack() и передать вторым аргументом true.
Добавим в ошибку эту информацию и тем самым сильно повысим ее информативность.
Например так:
Можно добавить функцию для создания этой ошибки:
Дальше с этим уже можно работать:
Соответственно ошибку и трейс можно рзаделить:
И конечно уже есть готовые решение. Одно из них, это пакет https://github.com/pkg/errors. Он позволяет создавать новую ошибку, которая уже будет содержать стек трейс, а можно добавлять трейс и/или дополнительное сообщения к уже существующей ошибке. Плюс удобное форматирование вывода.
%v выведет только сообщения
panic/recover
Паника(aka авария, aka panic), как правило, сигнализирует о наличии неполадок, из-за которых система (или конкретная подсистема) не может продолжать функционировать. В случае вызова panic среда выполнения Go просматривает стек, пытаясь найти для нее обработчик.
Необработанные паники прекращают работу приложения. Это принципиально отличает их от ошибок, которые позволяют не обрабатывать себя.
В вызов функции panic можно передать любой аргумент.
Удобно в panic передать ошибку, того типа который упростит восстановления и поможет отладки.
Восстановление после аварии в Go основывается на отложенном вызове функций, он же defer. Такая функция гарантировано будет выполнена в момент возврата из родительской функции. Не зависимо от причины — оператор return, конец функции или паника.
А вот уже функция recover дает возможность получить информацию об аварии и остановить раскручивание стека вызовов.
Типичный пример вызова panic и обработчик:
recover возвращает interface<> (тот самый который передаем в panic) или nil, если не было вызова panic.
Рассмотрим еще один пример обработки аварийных ситуаций. У нас есть некоторая функция в которую мы передаем например ресурс и которая в теории может вызвать панику.
Во-первых, может понадобится всегда выполнять какие то действия при завершении, например очистка ресурсов, в нашем случае это закрытия файла.
Во-вторых, некорректное выполнение такой функции не должно приводить к завершению всей программы.
Такую задачу можно решить с помощью defer, recover и замыкания:
Замыкание позволяем обратится к выше объявленным переменным, благодаря этому гарантировано закрываем файл и в случае аварии, извлечь из нее ошибку и передать ее обычному механизму обработки ошибок.
Бывают обратные ситуации, когда функция c определенными аргументами всегда должна отрабатывать корректно и если этого не происходит, то что пошло совсем плохо.
В подобных случаях добавляют функцию обертку в которой вызывается целевая функция и в случае ошибки вызывается panic.
В Go обычно такие функции с префиксом Must:
Стоит помнить еще про один момент, связанный с panic и горутинами.
Часть тезисов из того что обсудили выше:
- Для каждой горутины выделяется отдельный стек.
- При вызове panic, в стеке ищется recover.
- В случае, когда recover не найдет, завершается все приложение.
Обработчик в main не перехватит панику из foo и программа аварийно завершится:
Это будет проблемой, если например вызываются обработчик для соединения на сервере. В случае паники в любом из обработчиков, весь сервер завершит выполнение. А контролировать обработку аварий в этих функциях, по какой то причине, вы не можете.
В простом случае решение может выглядит примерно так:
handle/check
Возможно в будущем нас ждут изменения в обработки ошибок. Ознакомится с ними можно по ссылкам:
go2draft
Обработка ошибок в Go 2
Источник
How to get stacktraces from errors in Golang with go-errors/errors
It’s been 6 months since we released bugsnag-go, allowing you to send Go error reports to BugSnag. It’s high time to share the technology we built to make it work: the go-errors/errors library.
By default in Go, errors don’t have a stacktrace. In fact, an error is a very simple interface:
— CODE language-go —
interface error <
В Error() string
>
In other words, the only thing you can get out of that error is the string that describes what went wrong.
This simplicity provides great power for handling expected errors, as described by Rob Pike last week in Errors are values.Unfortunately, it doesn’t provide everything you need for debugging  problems that are unexpected. This is where the go-errors/errors package comes in.
Getting the stacktrace
The most useful piece of context for tracking down an unexpected failure is the stacktrace so it’s a good idea to print it out. This lets you trace through the program and see why it ended up in the state it did.
Errors in Go are usually returned, but in order to associate a stacktrace with an error, you’ll have to do that at the point it’s created using go-errors.
func Crash() error <
В В return errors.Errorf(«this function is supposed to crash»);
>
Now any function that calls [CODE]crashy. Crash()[/CODE] can find out the stacktrace that led to the failure.
— CODE language-go —
err := crashy.Crash()
if err != nil <
В В fmt.Println(err.(*errors.Error).ErrorStack())
>
This helpfully prints out not only the [CODE]error[/CODE] message, but also the stacktrace:
— CODE language-plaintext —
*errors.errorString this function is supposed to crash
/0/go/src/github.com/go-errors/errors/crashy/crashy.go:8 (0x41280)
В В Crash: return errors.New(Crashed)
/0/go/src/github.com/go-errors/errors/example/main.go:11 (0x2026)
В В main: err := crashy.Crash()
Wrapping existing errors
Most libraries in Go already return the simple, featureless error interface. In order to add a stacktrace to those existing errors, you can Wrap them using the errors library. This’ll give you the stacktrace at the point the error made its way into your code, which is usually good enough to track down the problem.
— CODE language-go —
_, err := reader.Read(buff)
if err != nil <
В В return errors.Wrap(err, 1)
>
The second parameter to Wrap allows you to control where the stacktrace appears to start. When set to 1 it will be the line that contains [CODE]return errors. Wrap(err, 1)[/CODE].
Recovering from panics
This section assumes you know about defer, panic and recover.
When Go is recovering from a panic, it calls your deferred functions with the stacktrace still intact. This means that it’s possible to get the stacktrace exactly as it was when the [CODE]panic()[/CODE] started.
To do this you can use [CODE]errors.Wrap[/CODE] inside your deferred handler as follows:
— CODE language-go —
defer func() <
В В if err := recover(); err != nil <
В В В В fmt.Println(errors.Wrap(err, 2).ErrorStack())
В В >
>()
Programming with errors
Using the errors package does have one small downside: you can’t compare errors using equality anymore.
For example, a common pattern in Go is to return [CODE]io.EOF[/CODE] when the end of a file is reached. As errors now have stacktraces, using equality won’t work. [CODE]Anio.EOF[/CODE] error returned from one part of your code won’t be the same as [CODE]anio.EOF[/CODE] returned from another. You can work around this using [CODE]errors.Is[/CODE].
— CODE language-go —
_, err := reader.Read(buff)
if errors.Is(err, io.EOF) <
В В return nil
>
If you want your package to expose errors for common conditions, you can do that using the following pattern. First create an error template at the top of your file, then call [CODE]errors.New()[/CODE] when returning it to attach the current stacktrace:
var ExpectedCrash = errors.Errorf(«expected crash»)
func Crash() error <
В В return errors.New(ExpectedCrash)
>
Your callers can then use [CODE]errors.Is[/CODE] to detect the expected condition:
— CODE language-go —
package main
import (
В В «crashy»
В В «github.com/go-errors/errors»
)
func main() <
В В err := crashy.Crash()
В В if errors.Is(err, crashy.ExpectedError) <
В В В В return
В В >
>
Summary
I hope you agree that having stacktraces makes it much easier to track down unexpected errors, and that having errors as values makes it easy to handle expected edge cases.
go-errors/errors was designed to give you the best of both worlds. Please let me know on Twitter how it works for you.
Источник
Often when debugging Go code, errors stack trace is often built with following pattern:
if err != nil {
return fmt.Errorf("Failed to do something: %w", err)
}
One error is wrapped with another, message is concatenated, with some delimiter in this case :
. We end up with error information hard to pin to a place where it actualy happend, which results in long trail and many steps of going file by file, error to error until we actually find it. So how could we improve our error experiance ?Before we start lets rewind a little bit and answer a simple question. What is error ?
Errors are values. This is very crucial thing to understand, if you come to Go from another enviroment you have to move one from a mental object of try/catch. Once you do that handling errors becomes clear and simple.
So what does it really mean ?
Values can be programmed, and since errors are values, errors can be programmed.
Most common way of handling errors is simply comparing them with nil
value. Because errors are values.
if err != nil {
// something went wrong
}
This also means we can add any information to error, for example stack trace.
Error as value
error type is simply an interface, which means by creating our custom error type that implements that interface we could add any extra information. Lets define our error as follow:
type AppError struct {
trace string
err error
}
// Error returns the string representation of the error message.
func (e *AppError) Error() string {
return fmt.Sprintf("%sn%s", e.trace, e.err)
}
func (e *AppError) Unwrap() error {
return e.err
}
Our error will contain wrapped error, plus a stack trace where it happened. AppError type will implement two important methods: Error and Unwrap. Since Go 1.13 introduces new features to the errors and fmt standard library packages to simplify working with errors that contain other errors. It makes it even easier to handle them.
Unwrap
Unwrap method that returns its contained error:
func (e *AppError) Unwrap() error {
return e.err
}
Adding stack trace
To add stack trace to our error we will use runtime.Caller
Caller reports file and line number information about function invocations on the calling goroutine’s stack. The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.) The return values report the program counter, file name, and line number within the file of the corresponding call. The boolean ok is false if it was not possible to recover the information.
End result should look something like this:
func main() {
fmt.Printf("%s", AppenStackTrace(fmt.Errorf("internal error")))
}
AppenStackTrace
Simply wraps error
using our AppError
type and appends stack trace information:
func AppenStackTrace(err error) *AppError {
if err == nil {
panic("nil error provided")
}
var buf bytes.Buffer
frame := getFrame(2)
fmt.Fprintf(&buf, "%s", frame.File)
fmt.Fprintf(&buf, ":%d", frame.Line)
fmt.Fprintf(&buf, " %s", frame.Function)
return &AppError{
err: err,
trace: buf.String(),
}
}
func getFrame(calldepth int) *runtime.Frame {
pc, file, line, ok := runtime.Caller(calldepth)
if !ok {
return nil
}
frame := &runtime.Frame{
PC: pc,
File: file,
Line: line,
}
funcForPc := runtime.FuncForPC(pc)
if funcForPc != nil {
frame.Func = funcForPc
frame.Function = funcForPc.Name()
frame.Entry = funcForPc.Entry()
}
return frame
}
To build complete stack trace, we could create a method as follow:
func (e *AppError) StackTrace() (string, error) {
var buf bytes.Buffer
if e.trace != "" {
if _, err := fmt.Fprintf(&buf, "%s", e.trace); err != nil {
return "", err
}
}
if e.err == nil {
return buf.String(), nil
}
var next *AppError
if errors.As(e.err, &next) {
stackTrace, err := next.StackTrace()
if err != nil {
return "", err
}
buf.WriteString(fmt.Sprintf("n%s", stackTrace))
} else {
return fmt.Sprintf("%sn%s", buf.String(), e.err), nil
}
return buf.String(), nil
}
We simply build buffer with each error information and then dig dipper into our error chain. We use errors.As.
The As function tests whether an error is a specific type.
Running following example:
func main() {
err := AppenStackTrace(testOne())
fmt.Printf("%s", err)
}
func testOne() error {
return fmt.Errorf("testOne: %w", AppenStackTrace(testTwo()))
}
func testTwo() error {
return fmt.Errorf("testTwo: %w", AppenStackTrace(testThree()))
}
func testThree() error {
return AppenStackTrace(fmt.Errorf("internal error"))
}
should give us output:
/tmp/sandbox073776686/prog.go:74 main.main
testOne: /tmp/sandbox073776686/prog.go:82 main.testOne
testTwo: /tmp/sandbox073776686/prog.go:87 main.testTwo
/tmp/sandbox073776686/prog.go:91 main.testThree
internal error
Printing error uses .Error()
method from error
interface. Which under the hood unwraps each error using Unwrap
method, because we wrapped them with fmt.Errorf
and %w
print format. Lets print our stack trace using our builtin StackTrace
method:
func main() {
t, _ := err.StackTrace()
fmt.Printf("%s", t)
}
output:
/tmp/sandbox073776686/prog.go:74 main.main
/tmp/sandbox073776686/prog.go:82 main.testOne
/tmp/sandbox073776686/prog.go:87 main.testTwo
/tmp/sandbox073776686/prog.go:91 main.testThree
internal error
Conclusion
Handling errors is pretty simple, having them as values allows us to do really powerful things. We can append any information we want, for example stack trace as we did in the example above. Defining custom type for error is great way of doing that, and not necessarily has to be done one the whole scope of application. Instead of calling our type AppError
we could define many types, where each of them would hold different information. Let’s say QueryError
to handle persistence model errors with query information or HttpError
to store http response code and/or user error message (without stack trace).
As you can see simple check err != nil
is not a drop in replacement of try/catch. It’s important to change a mental object of error handling while working with Go, which is not that hard and requires only a little of time getting used to it. Please see full code snippet here.