Description
This example shows how to create a base class *** that provides consistency in error logging.
(***) Go uses structs and associated methods which provides the same functionality as a classical "class" with it's internal methods in Object Oriented (OO) programing.
You can then create error subclasses that have custom behaviors, e.g., emailing admins or sending a text message to a manager.
Having consistent error logging patterns become valuable when you use log analysis tools like Splunk.
Golang Features
This golang code sample demonstrates the following go language features:
- switch statement
- classes (struct + methods)
- variadic function
- include timestamp in error message
- custom error type
- inherit behavior from base class using embedded struct
Code Example
package main
import (
"fmt"
"time"
)
type Err struct {
errNo int
when time.Time
msg string
}
func (e *Err) Error() string {
return fmt.Sprintf("%v [%d] %s", e.when, e.errNo, e.msg)
}
func (err Err) errorNumber() int {
return err.errNo
}
type ErrWidget_A struct {
Err // Err is an embedded struct - ErrWidget_A inherits it's data and behavior
}
// a behavior only available for the ErrWidget_A
func (e ErrWidget_A) Error() string {
fmt.Println("do special ErrWidget_A thing...")
return fmt.Sprintf("%s [%d] %s", e.when, e.errNo, e.msg)
}
// a behavior only available for the ErrWidget_A
func (e ErrWidget_A) optionalErrHandlingOperation() {
fmt.Println("Email the admins...\n")
}
type ErrWidget_B struct {
Err // Err is an embedded struct - ErrWidget_B inherits it's data and behavior
}
// a behavior only available for the Widget_B
func (e ErrWidget_B) Error() string {
fmt.Println("do special Widget_B thing...")
return fmt.Sprintf("%s [%d] %s", e.when, e.errNo, e.msg)
}
// a behavior only available for the Widget_B
func (e ErrWidget_B) optionalErrHandlingOperation() {
fmt.Println("SMS operations manager...\n")
}
func run() error {
return &Err{
8001,
time.Now(),
"generic error occurred\n",
}
}
func run2() *ErrWidget_B {
errB := new(ErrWidget_B)
errB.errNo = 6001
errB.when = time.Now()
errB.msg = "Widget_B error occurred"
return errB
}
func RunWidget(modelNo int) (string, error) {
// Run valid widgets
switch modelNo {
case 1:
return fmt.Sprintf("run widget model %d", modelNo), nil
case 2:
return fmt.Sprintf("run widget model %d", modelNo), nil
default:
// Error condition - unknown widget model number
errA := new(ErrWidget_A)
errA.errNo = 5002
errA.when = time.Now()
errA.msg = "Widget_A error occurred"
return fmt.Sprintf("unable to run unknown model %d", modelNo), errA
}
}
// Split multiple (variadic) return values into a slice of values
// in this case, where [0] = value and [1] = the error message
func split(args ...interface{}) []interface{} {
return args
}
func main() {
// Execute RunWidget function and handle error if necessary
msg := ""
// RunWidget(1) succeeds
x := split(RunWidget(1))
msg = "\n\n"; if x[1] != nil {msg = fmt.Sprintf(", err(%v)\n\n", x[1])}
fmt.Printf("RunWidget(1) => result(%s)" + msg, x[0])
// RunWidget(2) succeeds
x = split(RunWidget(2))
msg = "\n\n"; if x[1] != nil {msg = fmt.Sprintf(", err(%v)\n\n", x[1])}
fmt.Printf("RunWidget(2) => result(%s)" + msg, x[0])
// RunWidget(666) fails -
x = split(RunWidget(666))
msg = "\n\n"; if x[1] != nil {msg = fmt.Sprintf(", err(%v)\n\n", x[1])}
fmt.Printf("RunWidget(666) => result(%s)" + msg, x[0])
// Throw generic custom error type and handle it
if err := run(); err != nil { fmt.Println(err) }
// Throw ErrWidget_B error and handle it by printing and running optional custom behavior
widget_B_error := run2(); if widget_B_error.errNo != 0 {
fmt.Println(widget_B_error)
}
fmt.Println("")
timeNow := time.Now()
// Create and print ErrWidget_A, then call custom behavior
a := ErrWidget_A {Err{5001, timeNow, "test"}}
fmt.Println(a) // fmt will execute Error() method that can have special behavior
fmt.Println("A ErrWidget_A has this error number: ", a.errorNumber())
a.optionalErrHandlingOperation() // Widget_A emails admins
// Create ErrWidget_B, then call custom behavior
b := ErrWidget_B {Err{6001, timeNow, "test"}}
fmt.Println("A ErrWidget_B has this error number: ", b.errorNumber())
b.optionalErrHandlingOperation() // Widget_B sends SMS message to managers
// Since b was not printed by fmt, the special ErrWidget_B behavior is not triggered
}
Output
RunWidget(1) => result(run widget model 1)
RunWidget(2) => result(run widget model 2)
do special ErrWidget_A thing...
RunWidget(666) => result(unable to run unknown model 666), err(2014-07-19 14:59:19.94525681 -0400 EDT [5002] Widget_A error occurred)
2014-07-19 14:59:19.945377402 -0400 EDT [8001] generic error occurred
do special Widget_B thing...
2014-07-19 14:59:19.945384269 -0400 EDT [6001] Widget_B error occurred
do special ErrWidget_A thing...
2014-07-19 14:59:19.945391131 -0400 EDT [5001] test
A ErrWidget_A has this error number: 5001
Email the admins...
A ErrWidget_B has this error number: 6001
SMS operations manager...
Process finished with exit code 0
Notes
In Go, an error is something that implements an Error() function that returns a string. Here's the predefined, built-in interface type error:
type error interface {
Error() string
}
The fmt package functions automatically call the Error() function when asked to print an error.
Basic Error Handling
The Error function from a new errors object will return the error string:
package main
import (
"fmt"
"errors"
)
func main() {
test_error := errors.New("ERROR - just testing!")
fmt.Println(test_error.Error())
}