errors

package module
v0.0.0-...-5b1f793 Latest Latest
Warning

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

Go to latest
Published: Apr 7, 2026 License: MIT Imports: 6 Imported by: 0

README

Application Errors with Integrated Logging

English | 中文文档

errors is a Go package providing a structured approach to application-specific errors, incorporating integer error codes, error chaining, and seamless integration with the zeralog structured logging library.

It aims to standardize error handling across your application, making it easier to:

  • Identify specific error conditions using predefined codes.
  • Maintain context and cause using error wrapping.
  • Log errors consistently with relevant structured data (like the error code and chain).
  • Unify successful and erroneous API responses using a common structure.

features

  • Custom Error Type (AppError)
  • Defines a standard structure for application errors.
  • Integer Error Codes:
    • Each AppError includes a distinct integer code, useful for API responses or internal logic branches.
  • (Error Chaining:
    • Supports wrapping underlying errors (WithCause) and adding contextual messages (With), preserving the error chain compatible with errors.Is and errors.As.
  • zevalog Integration:
    • Provides convenience methods (LogError, LogWarn, LogInfo, etc.) to log AppError instances directly, automatically including the code and the full error chain.
  • Predefined Common Errors:
    • Includes a set of standard error codes and messages for common scenarios.
  • Unified Response Structure:
    • The Error() method is formatted to be suitable for direct use in API response messages alongside the code. Special handling for the Success code ensures a clean output.

Installation

Install the package using go get:

go get github.com/gaoxing520/errors
go mod tidy

Usage

Basic Error Creation

Create application-specific errors with integer codes:

package main

import (
	"fmt"
	"github.com/gaoxing520/errors"
)

func main() {
	// Create a custom error with code and message
	err := errors.NewAppError(1001, "user not found")
	fmt.Printf("Error: %s, Code: %d\n", err.Error(), err.Code())
	// Output: Error: user not found, code=1001, Code: 1001
}
Using Predefined Errors

The library provides several predefined error types:

// Success case (code: 0)
successErr := errors.Success
fmt.Println(successErr.Error()) // Output: "" (empty string for success)

// Unknown error (code: -1)
unknownErr := errors.Unknown
fmt.Println(unknownErr.Error()) // Output: unknown error, code=-1

// System error (code: 999999)
systemErr := errors.ErrSystem
fmt.Println(systemErr.Error()) // Output: system error, code=999999
Error Chaining and Context

Add context and wrap underlying errors:

// Add context to an error
err := errors.NewAppError(1002, "database operation failed")
contextErr := err.With("failed to update user %s", "john_doe")
fmt.Println(contextErr.Error())
// Output: failed to update user john_doe: database operation failed, code=1002

// Wrap an underlying error
originalErr := fmt.Errorf("connection timeout")
wrappedErr := errors.NewAppError(1003, "service unavailable").WithCause(originalErr)
fmt.Println(wrappedErr.Error())
// Output: service unavailable: connection timeout, code=1003
Logging Integration

The library integrates seamlessly with zerolog for structured logging:

// Error logging at different levels
err := errors.NewAppError(1004, "validation failed")

// Log at different levels
err.LogInfo() // Log at INFO level
err.LogWarn()  // Log at WARN level
err.LogError() // Log at ERROR level
err.LogDebug() // Log at DEBUG level
err.LogTrace() // Log at TRACE level

// Fatal and panic logging
err.LogFatal() // Logs and exits the application
err.LogPanic() // Logs and panics

Note about nil receivers

To make the logging helpers safe to call even when an AppCommonError pointer is nil, the Log* methods omit the code field when the receiver is nil. This prevents nil-pointer dereference panics in call sites that may have an absent error value. For example:

var e *errors.AppCommonError = nil
e.LogInfo() // safe: will log an info-level event but will not include a `code` field

This design choice favors robustness and avoids introducing a magic numeric default in logs. If you prefer a different behavior (for example: always including a default code), consider wrapping the call site or constructing a non-nil AppCommonError with the desired default code.

Chaining Operations

You can chain error operations for concise code:

err := errors.NewAppError(1005, "authentication failed").
With("user attempted login with invalid credentials").
WithCause(fmt.Errorf("password mismatch")).
LogWarn() // Chain logging

// The error is logged and can still be used
return err
Error Comparison

Use standard Go error comparison methods:

err1 := errors.NewAppError(1006, "not found")
err2 := errors.NewAppError(1006, "not found")

// Compare errors by code
if errors.Is(err1, err2) {
fmt.Println("Errors have the same code")
}

// Check specific error types
var appErr *errors.AppCommonError
if errors.As(err1, &appErr) {
fmt.Printf("Error code: %d\n", appErr.Code())
}
Custom Logger Configuration

Configure the default logger used by the error logging methods:

import (
"os"
"github.com/rs/zerolog"
"github.com/gaoxing520/errors"
)

// Set custom output
errors.DefaultLogOutput = os.Stdout

// Configure the default logger
errors.DefaultLogger = zerolog.New(os.Stdout).
With().
Timestamp().
Caller().
Logger().
Level(zerolog.InfoLevel)
Complete Example

Here's a complete example showing typical usage in an application:

package main

import (
	"fmt"
	"github.com/gaoxing520/errors"
)

// Define application-specific errors
var (
	ErrUserNotFound    = errors.NewAppError(2001, "user not found")
	ErrInvalidPassword = errors.NewAppError(2002, "invalid password")
	ErrDatabaseError   = errors.NewAppError(2003, "database error")
)

func authenticateUser(username, password string) error {
	// Simulate user lookup
	if username == "" {
		return ErrUserNotFound.With("username is empty").LogWarn()
	}

	// Simulate password validation
	if password != "secret" {
		return ErrInvalidPassword.
			With("authentication failed for user %s", username).
			LogError()
	}

	// Simulate database error
	if username == "dbfail" {
		dbErr := fmt.Errorf("connection lost")
		return ErrDatabaseError.
			WithCause(dbErr).
			With("failed to verify user %s", username).
			LogError()
	}

	// Success case
	errors.Success.With("user %s authenticated successfully", username).LogInfo()
	return nil
}

func main() {
	// Test different scenarios
	scenarios := []struct {
		username, password string
	}{
		{"", "secret"},       // Empty username
		{"john", "wrong"},    // Wrong password
		{"dbfail", "secret"}, // Database error
		{"john", "secret"},   // Success
	}

	for _, scenario := range scenarios {
		err := authenticateUser(scenario.username, scenario.password)
		if err != nil {
			fmt.Printf("Authentication failed: %s\n", err.Error())
		} else {
			fmt.Println("Authentication successful")
		}
	}
}

Development

Running Tests
# Run all tests
make test

# Run tests with coverage
make cover

# Run benchmarks
make bench

# Run all checks (format, vet, test, bench)
make all
Project Structure
├── error.go           # Core error types and functions
├── logger.go          # Logging integration
├── *_test.go         # Test files
├── docs/             # Documentation
│   └── README_zh.md  # Chinese documentation
└── .github/workflows/ # CI/CD workflows

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for your changes
  5. Run the test suite (make all)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

License

This project is licensed under the [MIT LICENSE] - see the LICENSE file for details.

Documentation

Overview

Package errors provides structured application-specific error handling with integer error codes, error chaining, and seamless integration with zerolog structured logging.

Example (Chaining)
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	// Simulate a complex error scenario
	dbErr := fmt.Errorf("connection lost")

	err := errors.NewAppError(2001, "user operation failed").
		WithCause(dbErr).
		With("failed to update profile for user %s", "alice")

	fmt.Printf("Final error: %s\n", err.Error())
	fmt.Printf("Error code: %d\n", err.Code())
}
Output:
Final error: failed to update profile for user alice: user operation failed: connection lost, code=2001
Error code: 2001
Example (PredefinedErrors)
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	// Using predefined errors
	fmt.Printf("Success: '%s' (code: %d)\n", errors.Success.Error(), errors.Success.Code())
	fmt.Printf("Not Found: '%s' (code: %d)\n", errors.ErrNotFound.Error(), errors.ErrNotFound.Code())
	fmt.Printf("Unauthorized: '%s' (code: %d)\n", errors.ErrUnauthorized.Error(), errors.ErrUnauthorized.Code())
}
Output:
Success: '' (code: 0)
Not Found: 'not found, code=404' (code: 404)
Unauthorized: 'unauthorized, code=401' (code: 401)

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	Unknown   = NewAppError(-1, "unknown error")
	ErrSystem = NewAppError(999999, "system error")

	// Common application errors
	ErrInvalidInput       = NewAppError(400, "invalid input")
	ErrUnauthorized       = NewAppError(401, "unauthorized")
	ErrForbidden          = NewAppError(403, "forbidden")
	ErrNotFound           = NewAppError(404, "not found")
	ErrConflict           = NewAppError(409, "conflict")
	ErrInternalError      = NewAppError(500, "internal server error")
	ErrServiceUnavailable = NewAppError(503, "service unavailable")
)
View Source
var (
	// DefaultLogger is the global zerolog logger instance used by this package's logging functions.
	DefaultLogger *zerolog.Logger

	// DefaultLogOutput is the default output writer for the logger if not set externally.
	DefaultLogOutput io.Writer = os.Stderr

	// DefaultLogLevel is the default log level for the logger if not set externally.
	DefaultLogLevel = zerolog.InfoLevel
)
View Source
var (
	Success = &AppCommonError{code: 0, err: nil}
)

Predefined common errors

Functions

func Debug

func Debug() *zerolog.Event

Debug returns a debug level logger event.

func Error

func Error() *zerolog.Event

Error returns an error level logger event.

func Fatal

func Fatal() *zerolog.Event

Fatal returns a fatal level logger event, which will exit the application after logging.

func GetErrorCode

func GetErrorCode(err error) int

GetErrorCode extracts the error code from an error, returns -1 if not an AppError

Example
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	appErr := errors.NewAppError(1005, "application error")
	stdErr := fmt.Errorf("standard error")

	fmt.Printf("AppError code: %d\n", errors.GetErrorCode(appErr))
	fmt.Printf("Standard error code: %d\n", errors.GetErrorCode(stdErr))
}
Output:
AppError code: 1005
Standard error code: -1

func Info

func Info() *zerolog.Event

Info returns an info level logger event.

func Panic

func Panic() *zerolog.Event

Panic returns a panic level logger event, which will panic after logging.

func SetLogLevel

func SetLogLevel(level zerolog.Level)

SetLogLevel sets the log level for the default logger

func SetLogOutput

func SetLogOutput(output io.Writer)

SetLogOutput sets the output writer for the default logger

func SetLogger

func SetLogger(logger *zerolog.Logger)

SetLogger allows users to set a custom logger

func Trace

func Trace() *zerolog.Event

Trace returns a trace level logger event.

func Warn

func Warn() *zerolog.Event

Warn returns a warn level logger event.

Types

type AppCommonError

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

AppCommonError represents an error with a specific code and an underlying cause.

func IsAppError

func IsAppError(err error) (*AppCommonError, bool)

IsAppError checks if an error is an AppError and returns it

Example
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	appErr := errors.NewAppError(1004, "application error")
	stdErr := fmt.Errorf("standard error")

	if extracted, ok := errors.IsAppError(appErr); ok {
		fmt.Printf("AppError with code: %d\n", extracted.Code())
	}

	if _, ok := errors.IsAppError(stdErr); !ok {
		fmt.Println("Not an AppError")
	}
}
Output:
AppError with code: 1004
Not an AppError

func (*AppCommonError) Code

func (e *AppCommonError) Code() int

Code returns the application-specific error code.

func (*AppCommonError) Error

func (e *AppCommonError) Error() string

Error implements the error interface.

func (*AppCommonError) Is

func (e *AppCommonError) Is(target error) bool

Is reports whether this AppError matches the target AppError code.

func (*AppCommonError) LogDebug

func (e *AppCommonError) LogDebug() AppError

LogDebug logs the AppError at debug level using the default logger.

func (*AppCommonError) LogError

func (e *AppCommonError) LogError() AppError

LogError logs the AppError at error level using the default logger.

func (*AppCommonError) LogFatal

func (e *AppCommonError) LogFatal()

LogFatal logs the AppError at fatal level using the default logger and exits.

func (*AppCommonError) LogInfo

func (e *AppCommonError) LogInfo() AppError

LogInfo logs the AppError at info level using the default logger.

func (*AppCommonError) LogPanic

func (e *AppCommonError) LogPanic()

LogPanic logs the AppError at panic level using the default logger and panics.

func (*AppCommonError) LogTrace

func (e *AppCommonError) LogTrace() AppError

LogTrace logs the AppError at trace level using the default logger.

func (*AppCommonError) LogWarn

func (e *AppCommonError) LogWarn() AppError

LogWarn logs the AppError at warn level using the default logger.

func (*AppCommonError) Unwrap

func (e *AppCommonError) Unwrap() error

Unwrap returns the next error in the error chain.

func (*AppCommonError) With

func (e *AppCommonError) With(format string, args ...any) AppError

With adds formatted context to the current AppError's underlying error chain. Returns a new AppError instance to avoid modifying the original.

Example
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	err := errors.NewAppError(1003, "validation failed")
	contextErr := err.With("invalid email format for user %s", "john@")

	fmt.Println(contextErr.Error())
}
Output:
invalid email format for user john@: validation failed, code=1003

func (*AppCommonError) WithCause

func (e *AppCommonError) WithCause(cause error) AppError

WithCause creates a new AppError instance based on the current AppError template (e)

Example
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	originalErr := fmt.Errorf("connection timeout")
	appErr := errors.NewAppError(1002, "service unavailable")
	wrappedErr := appErr.WithCause(originalErr)

	fmt.Println(wrappedErr.Error())
}
Output:
service unavailable: connection timeout, code=1002

type AppError

type AppError interface {
	error

	Code() int

	WithCause(cause error) AppError
	With(format string, args ...any) AppError
	Is(target error) bool
	Unwrap() error

	LogError() AppError
	LogWarn() AppError
	LogInfo() AppError
	LogDebug() AppError
	LogTrace() AppError
	LogFatal()
	LogPanic()
}

func NewAppError

func NewAppError(code int, msg string) AppError

NewAppError creates a new basic AppCommonError instance with a code and a base message.

Example
package main

import (
	"fmt"

	"github.com/gaoxing520/errors"
)

func main() {
	err := errors.NewAppError(1001, "user not found")
	fmt.Printf("Error: %s, Code: %d\n", err.Error(), err.Code())
}
Output:
Error: user not found, code=1001, Code: 1001

Jump to

Keyboard shortcuts

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