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.
- Custom Error Type (
AppError) - Defines a standard structure for application errors.
- Integer Error Codes:
- Each
AppErrorincludes a distinct integer code, useful for API responses or internal logic branches.
- Each
- (Error Chaining:
- Supports wrapping underlying errors (
WithCause) and adding contextual messages (With), preserving the error chain compatible witherrors.Isanderrors.As.
- Supports wrapping underlying errors (
zevalogIntegration:- Provides convenience methods (
LogError,LogWarn,LogInfo, etc.) to logAppErrorinstances directly, automatically including the code and the full error chain.
- Provides convenience methods (
- 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 theSuccesscode ensures a clean output.
- The
Install the package using go get:
go get github.com/gaoxing520/errors
go mod tidyCreate 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
}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=999999Add 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=1003The 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 panicsTo 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` fieldThis 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.
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 errUse 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())
}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)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")
}
}
}# Run all tests
make test
# Run tests with coverage
make cover
# Run benchmarks
make bench
# Run all checks (format, vet, test, bench)
make all├── 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
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Run the test suite (
make all) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the [MIT LICENSE] - see the LICENSE file for details.