errors

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 19, 2025 License: MIT Imports: 8 Imported by: 12

README

errors

Build Status Go Report Card GoDoc Coverage Status

The errors package provides an enterprise-grade error handling approach.

Motivation

1. Unique Error Codes for Client Support

In many corporate applications, it’s not enough for a system to return an error - it must also return a unique, user-facing error code. This code allows end users or support engineers to reference documentation or helpdesk pages that explain what the error means, under what conditions it might occur, and how to resolve it.

These codes serve as stable references and are especially valuable in systems where frontend, backend, and support operations must all stay synchronized on error semantics.

2. Centralized Error Definitions

To make error codes reliable and consistent, they must be centrally defined and maintained in advance. Without such coordination, teams risk introducing duplicate codes, inconsistent messages, or undocumented behaviors. This package was built to support structured error registration and reuse across the entire application.

3. Logging at the Top Level & Contextual Information in Errors

There is an important idiom: log errors only once, and do it as close to the top of the call stack as possible — for instance, in an HTTP controller. Lower layers (business logic, database, etc.) may wrap and propagate errors upward, but only the outermost layer should produce the log entry.

This pattern, while clean and idiomatic, introduces a challenge: how can we include rich, contextual information at the logging point, if it was only known deep inside the application?

To solve this, we need errors that are context-aware. That is, they should carry structured attributes — like the IP address of a failing server, or the input that triggered the issue — as they move up the call stack. This package provides facilities to attach such structured context to errors and extract it later during logging or formatting.

4. Stack Traces for Root Cause Analysis

When diagnosing production issues, developers need more than just error messages — they need stack traces that show where the error originated. This is especially important when multiple wrapping or rethrowing occurs. By capturing the trace at the point of error creation, this package enables faster debugging and clearer logs.

Installation

go get github.com/axkit/errors

Error Template

Predefined errors offer reusable templates for consistent error creation. Use the Template function to declare them:

import "github.com/axkit/errors"

var (
    ErrInvalidInput = errors.Template("invalid input provided").
							Code("CRM-0901").
							StatusCode(400).
							Severity(errors.Tiny)
    
	ErrServiceUnavailable = errors.Template("service unavailable").
							Code("SRV-0253").
							StatusCode(500).
							Severity(errors.Critical)

	// Predefined error gets `Tiny` severity  by default.
	ErrInvalidFilter = errors.Template("invalid filtering rule").
							Code("CRM-0042").
							StatusCode(400)

)

if request.Email == "" {
	return ErrInvalidInput.New().Msg("empty email")
}

if request.Age < 18 {
	return ErrInvalidInput.New().Set("age", request.Age).Msg("invalid age")
}

customer, err := service.CustomerByID(request.CustomerID)
if err != nil {
	return ErrServiceUnavailable.Wrap(err)
}

if customer == nil {
	return ErrInvalidInput.New().Msg("invalid customer")
}

Error Structure

The Error type is the core of this package. It encapsulates metadata, stack traces, and wrapped errors.

Attributes
Attribute Description
message Error message text
severity Severity of the error (Tiny, Medium, Critical)
statusCode HTTP status code
code Application-specific error code
fields Custom key-value pairs for additional context
stack Stack frames showing the call trace

Capturing the Stack Trace

A stack trace is automatically captured at the moment an error is created or first wrapped. This allows developers to identify where the problem originated, even if the error travels up the call stack.

A stack trace is captured when one of the following methods is called:

  • errors.TemplateError.Wrap(...)
  • errors.TemplateError.New(...)
  • errors.Wrap(...)

Rewrapping an error does not overwrite an existing stack trace. The original call site remains preserved, ensuring consistent and reliable debugging information.

Why errors.New() Is Not Provided

The axkit/errors package intentionally avoids exposing a global New() function, unlike the standard errors.New(...).

This is a deliberate design choice for two reasons:

1. Encouraging Structured, Predefined Errors

Instead of scattering ad-hoc error messages, you define reusable error templates with metadata (code, severity, status) and create instances using:

var ErrInvalidInput = errors.Template("invalid input").Code("CRM-0400").StatusCode(400)

return ErrInvalidInput.New()

This enables centralized error definitions, consistent metadata, and better observability.

2. Avoiding Ambiguity in Usage

In Go, errors.New(...) is used in two very different ways:

  • Defining a global error variable:
var ErrSomething = errors.New("something went wrong")
return ErrSomething
  • Creating and returning an error immediately:
return errors.New("invalid request")

It is impossible to implement a single New(...) function that satisfies both cases cleanly:

  • If it returns an ErrorTemplate, there’s no stack trace—good for global constants, but incomplete if returned immediately.
  • If it returns an Error, it captures a stack trace—good for immediate returns, but undesirable in global var declarations (stack is static and misleading).

In short, each use case demands a different behavior. This encourages clarity and helps avoid subtle bugs caused by unintended stack traces.

Error Logging

Effective error logging is crucial for debugging and monitoring. This package encourages logging errors at the topmost layer of the application, such as an HTTP controller, while lower layers propagate errors with additional context. This ensures that logs are concise and meaningful.

var ErrInvalidObjectID = errors.Template("inalid object id").Code("CRM-0400").StatusCode(400)

customer, err := repo.CustomerByID(customerID)
if err != nil && errors.Is(err, repo.ErrNotFound) {
    return nil, ErrInvalidObjectID.Wrap(err).Set("customerId", customerID)
}
////  
///
//
customer, err := service.CustomerByID(customerID)
if err != nil {
	buf := errors.ToJSON(err, errors.WithAttributes(errors.AddStack)))
	log.Println(string(buf))
}
Custom JSON Serialization

If you need to implement a custom JSON serializer, the errors.Serialize(err) method provides an object containing all public attributes of the error. This allows you to define your own serialization logic tailored to your application's requirements.

Alarm Notifications

Set an alarmer to notify on critical errors:

type CustomAlarmer struct{}

func (c *CustomAlarmer) Alarm(se *SerializedError) {
    fmt.Println("Critical error:", err)
}

errors.SetAlarmer(&CustomAlarmer{})

var ErrConsistencyFailed = errors.Template("data consistency failed").Severity(errors.Critical) 

// CustomAlarmer.Alarm() will be invocated automatically (severity=Critical)
return ErrDataseConnectionFailure.New()

Severity Levels

The package classifies errors into three severity levels:

  • Tiny: Minor issues, typically validation errors.
  • Medium: Regular errors that log stack traces.
  • Critical: Major issues requiring immediate attention.

When wrapping errors, the severity and statusCode attributes can be overridden. The client will always receive the latest severity and statusCode values from the outermost error. Any inner errors even with higher severity or different status codes will only be logged, ensuring that the most relevant information is presented to the client while maintaining detailed logs for debugging purposes.


Contributing

Contributions are welcome! Please submit issues or pull requests on GitHub.


License

This project is licensed under the MIT License.

Documentation

Overview

Package errors provides a structured and extensible way to create, wrap, and manage errors in Go applications. It includes support for adding contextual information, managing error hierarchies, and setting attributes such as severity, HTTP status codes, and custom error codes.

The package is designed to enhance error handling by allowing developers to attach additional metadata to errors, wrap underlying errors with more context, and facilitate debugging and logging. It also supports integration with alerting systems through the Alarm method.

Key features include: - Wrapping errors with additional context. - Setting custom attributes like severity, status codes, and business codes. - Managing error stacks and hierarchies. - Sending alerts for critical errors. - Support for custom key-value pairs to enrich error information. - Integration with predefined error types for common scenarios. - Serialization errors for easy logging.

Index

Examples

Constants

View Source
const (
	ServerOutputFormat      = AddProtected | AddStack | AddFields | AddWrappedErrors
	ServerDebugOutputFormat = AddProtected | AddStack | AddFields | AddWrappedErrors | IndentJSON
	ClientDebugOutputFormat = AddProtected | AddStack | AddFields | AddWrappedErrors
	ClientOutputFormat      = 0 // no fields, no stack, no wrapped errors, only message.
)

Variables

View Source
var (
	// CallerFramesFunc holds default function used by function Catch()
	// to collect call frames.
	CallerFramesFunc func(offset int) []StackFrame = DefaultCallerFrames

	// CallingStackMaxLen holds maximum elements in the call frames.
	CallingStackMaxLen int = 15
)
View Source
var ErrMarshalError = Template("error marshaling failed").Severity(Critical).StatusCode(500)

Functions

func As added in v0.0.2

func As(err error, target any) bool

As checks if the error can be cast to a target type.

func Is added in v0.0.2

func Is(err error, target error) bool

Is checks if the error is of the same type as the target error.

func SetAlarmer added in v1.0.0

func SetAlarmer(a Alarmer)

SetAlarmer sets Alarmer implementation to be used when critical error is caught.

func ToJSON added in v0.0.2

func ToJSON(err error, opts ...Option) []byte

ToJSON serializes the error to JSON format.

Example

ExampleToJSON demonstrates generating JSON output for an error.

package main

import (
	"fmt"

	"github.com/axkit/errors"
)

func main() {
	jsonErr := errors.Template("User not found").Code("E404").StatusCode(404).Severity(errors.Tiny)
	jsonOutput := errors.ToJSON(jsonErr, errors.WithAttributes(errors.AddFields))
	fmt.Println("JSON Error:", string(jsonOutput))
}
Output:

JSON Error: {"msg":"User not found","severity":"tiny","code":"E404","statusCode":404}

Types

type Alarmer added in v0.0.2

type Alarmer interface {
	Alarm(err error)
}

Alarmer is an interface wrapping a single method Alarm

Alarm is invocated automatically when critical error is caught, if alarmer is set.

Example
package main

import (
	"fmt"

	"github.com/axkit/errors"
)

type CustomAlarmer struct{}

func (c *CustomAlarmer) Alarm(err error) {
	fmt.Println("Critical error:", err)
}

func main() {

	errors.SetAlarmer(&CustomAlarmer{})
	var ErrSystemFailure = errors.Template("system failure").Severity(errors.Critical)

	ErrSystemFailure.New().Set("path", "/var/lib").Alarm()

}
Output:

Critical error: system failure

type Error added in v1.0.0

type Error struct {
	// contains filtered or unexported fields
}

Error represents a structured error with metadata, custom fields, stack trace, and optional wrapping.

func Wrap added in v0.0.6

func Wrap(err error, message string) *Error

Wrap wraps an existing error with a new message, effectively creating a new error that includes the previous error.

Example

ExampleWrap demonstrates wrapping an error.

package main

import (
	"fmt"

	"github.com/axkit/errors"
)

func main() {
	innerErr := errors.Template("Database connection failed")
	outerErr := errors.Template("Service initialization failed").Wrap(innerErr)
	fmt.Println("Wrapped Error:", outerErr.Error())
}
Output:

Wrapped Error: Service initialization failed: Database connection failed

func (*Error) Alarm added in v1.0.0

func (e *Error) Alarm()

Alarm triggers an alert for the error if an alarmer is configured.

func (*Error) Code added in v1.0.0

func (e *Error) Code(code string) *Error

Code sets a custom application-specific code for the error.

func (*Error) Error added in v1.0.0

func (e *Error) Error() string

Error returns the error message, including any wrapped error messages.

Example
package main

import (
	"fmt"

	"github.com/axkit/errors"
)

func main() {

	type Input struct {
		ID        int    `json:"id"`
		FirstName string `json:"firstName"`
		LastName  string `json:"lastName"`
	}

	var ErrEmptyAttribute = errors.Template("empty attribute value").Code("CMN-0400")
	var ErrInvalidInput = errors.Template("invalid input").Code("CMN-0400")

	validateInput := func(inp *Input) error {
		if inp.ID == 0 {
			return ErrEmptyAttribute.New().Set("emptyFields", []string{"id"})
		}
		return nil
	}

	if err := validateInput(&Input{}); err != nil {
		returnErr := ErrInvalidInput.Wrap(err)
		fmt.Println(returnErr.Error())

	}
}
Output:

invalid input: empty attribute value

func (*Error) Msg added in v1.0.0

func (e *Error) Msg(s string) *Error

Msg sets the error message and marks the error as not being a pure wrapper.

func (*Error) Protected added in v1.0.0

func (e *Error) Protected(protected bool) *Error

Protected marks the error as protected to prevent certain modifications or exposure.

func (*Error) Set added in v1.0.0

func (e *Error) Set(key string, value any) *Error

Set adds or updates a custom key-value pair in the error's fields.

func (*Error) Severity added in v1.0.0

func (e *Error) Severity(severity SeverityLevel) *Error

Severity sets the severity level for the error.

func (*Error) StatusCode added in v1.0.0

func (e *Error) StatusCode(statusCode int) *Error

StatusCode sets the associated HTTP status code for the error.

func (*Error) Wrap added in v1.0.0

func (e *Error) Wrap(err error) *Error

Wrap returns a new Error that wraps the given error while retaining the current error's metadata and fields. It also preserves the stack trace of the wrapped error if available. The new error is marked as a pure wrapper if the original error is of type ErrorTemplate or Error. If the original error is nil, it returns the current error. This method is useful for chaining errors and maintaining context. It supports wrapping both ErrorTemplate and Error types, preserving their fields and stack trace.

func (*Error) WrappedErrors added in v1.0.0

func (err *Error) WrappedErrors() []Error

WrappedErrors returns a slice of all wrapped errors, including the current one if it's not a pure wrapper.

type ErrorFormattingOptions added in v1.0.0

type ErrorFormattingOptions struct {
	// contains filtered or unexported fields
}

type ErrorSerializationRule added in v1.0.0

type ErrorSerializationRule uint8
const (

	// AddStack - add stack in the JSON.
	AddStack ErrorSerializationRule = 1 << iota

	// AddProtected - add protected errors in the JSON.
	AddProtected

	// AddFields - add fields in the JSON.
	AddFields

	// AddWrappedErrors - add previous errors in the JSON.
	AddWrappedErrors

	IndentJSON
)

type ErrorTemplate added in v1.0.0

type ErrorTemplate struct {
	// contains filtered or unexported fields
}

ErrorTemplate defines a reusable error blueprint that includes metadata and custom key-value fields. It is designed for creating structured errors with consistent attributes such as severity and HTTP status code.

Example

ExamplePredefinedErrors demonstrates using predefined errors.

package main

import (
	"fmt"

	"github.com/axkit/errors"
)

func main() {
	var ErrDatabaseDown = errors.Template("Database is unreachable").
		Code("DB-500").
		StatusCode(500).
		Severity(errors.Critical)

	if err := openDatabase("pg:5432"); err != nil {
		fmt.Println("Error:", ErrDatabaseDown.Wrap(err).Error())

	}
}

func openDatabase(connStr string) error {
	var dbErr error
	return errors.Wrap(dbErr, "unable to connect to database").Set("connectionString", connStr)
}
Output:

Error: Database is unreachable: unable to connect to database

func Template added in v1.0.0

func Template(msg string) *ErrorTemplate

Template returns a new ErrorTemplate initialized with the given message. It can be extended with additional attributes and reused to create multiple error instances.

func (*ErrorTemplate) Code added in v1.0.0

func (et *ErrorTemplate) Code(code string) *ErrorTemplate

Code sets an application-specific error code on the template.

func (*ErrorTemplate) Error added in v1.0.0

func (et *ErrorTemplate) Error() string

Error returns the error message from the template.

func (*ErrorTemplate) New added in v1.0.0

func (et *ErrorTemplate) New() *Error

New creates a new Error instance using the template's metadata and fields. A new stack trace is captured at the point of the call.

func (*ErrorTemplate) Protected added in v1.0.0

func (et *ErrorTemplate) Protected(protected bool) *ErrorTemplate

Protected marks the error as protected, indicating it should not be exposed externally.

func (*ErrorTemplate) Set added in v1.0.0

func (et *ErrorTemplate) Set(key string, value any) *ErrorTemplate

Set adds a custom key-value pair to the template's fields.

func (*ErrorTemplate) Severity added in v1.0.0

func (et *ErrorTemplate) Severity(severity SeverityLevel) *ErrorTemplate

Severity sets the severity level for the error template.

func (*ErrorTemplate) StatusCode added in v1.0.0

func (et *ErrorTemplate) StatusCode(statusCode int) *ErrorTemplate

StatusCode sets the HTTP status code associated with the error.

func (*ErrorTemplate) Wrap added in v1.0.0

func (et *ErrorTemplate) Wrap(err error) *Error

Wrap wraps an existing error with the ErrorTemplate's metadata and fields. It supports wrapping both ErrorTemplate and Error types, preserving their fields and stack trace.

type Option added in v1.0.0

type Option func(*ErrorFormattingOptions)

func WithAttributes added in v1.0.0

func WithAttributes(rule ErrorSerializationRule) Option

func WithRootLevelFields added in v1.0.0

func WithRootLevelFields(fields []string) Option

func WithStopStackOn added in v1.0.0

func WithStopStackOn(stopOnFuncContains string) Option

WithStopStackOn sets the function name to stop the adding stack frames. As instance: WithStopStackOn("fasthttp") will stop adding stack frames when the function name contains "fasthttp". It's useful to avoid adding stack frames of the libraries which are not interesting for the user.

type SerializedError added in v1.0.0

type SerializedError struct {
	Message    string         `json:"msg"`
	Severity   string         `json:"severity,omitempty"`
	Code       string         `json:"code,omitempty"`
	StatusCode int            `json:"statusCode,omitempty"`
	Fields     map[string]any `json:"fields,omitempty"`
	Wrapped    []Error        `json:"wrapped,omitempty"`
	Stack      []StackFrame   `json:"stack,omitempty"`
}

SerializedError is serialization ready error.

func Serialize added in v1.0.0

func Serialize(err error, opts ...Option) *SerializedError

Serialize serializes the error to a SerializedError struct.

type SeverityLevel

type SeverityLevel int

SeverityLevel describes error severity levels.

const (
	// Tiny classifies expected, managed errors that do not require administrator attention.
	// Writing a call stack to the journal file is not recommended.
	//
	// Example: error related to validation of entered form fields.
	Tiny SeverityLevel = iota

	// Medium classifies a regular error. A call stack is written to the log.
	Medium

	// Critical classifies a significant error, requiring immediate attention.
	// The occurrence of the error should be communicated to the administrator
	// through all available channels. A call stack is written to the log.
	// If Alarmer is set, it will be called.
	Critical
)

func (SeverityLevel) MarshalJSON

func (sl SeverityLevel) MarshalJSON() ([]byte, error)

MarshalJSON implements json/Marshaller interface.

func (SeverityLevel) String

func (sl SeverityLevel) String() string

String returns severity level string representation.

func (*SeverityLevel) UnmarshalJSON added in v1.0.0

func (sl *SeverityLevel) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json/Unmarshaller interface.

type StackFrame added in v1.0.0

type StackFrame struct {
	Function string `json:"func"`
	File     string `json:"file"`
	Line     int    `json:"line"`
}

StackFrame describes content of a single stack frame stored with error.

func DefaultCallerFrames added in v0.0.2

func DefaultCallerFrames(offset int) []StackFrame

DefaultCallerFrames returns default implementation of call frames collector.

func (StackFrame) String added in v1.0.0

func (s StackFrame) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL