Utility Functions for Error Handling, Logging, and Retry Logic in Go
• Overview
• Features
• Installation
• Quick Start
• Usage Examples
• Error Handling
• Logging
• Structured Logging
• Retry and Exponential Backoff
• Graceful Actions
• Rate Limiters
• Utility Functions
• Configuration
• Documentation
• Contributing
• License
it is a Go package providing utility functions for error handling, logging, and execution retries, simplifying common patterns while adhering to Go best practices. It offers a collection of functions to manage errors, structured logging, retries with exponential backoff, and other robust utilities like graceful shutdown & restart of any server; with done notification as well as action functions to perform.
• Simplified error handling with Must and Should
• Logging functions for different log levels: Trace, Debug, Info, Warn, Error, and Fatal
• Structured logging in JSON format for easy parsing and analysis
• Configurable log levels, outputs, and color-coded console output
• Utility functions for retry mechanisms, exponential backoff, and context-based retries
• Enhanced error wrapping, panic recovery, and timing functions
To install the it package, use go get:
go get github.com/theHamdiz/it
Import the package in your Go code:
import "github.com/theHamdiz/it"
package main
import (
"fmt"
"github.com/theHamdiz/it"
)
func main() {
// Use Must for critical operations
hardResult := it.Must(SomeCriticalFunction())
// Use Should for operations where you can proceed on error
softResult := it.Should(SomeNonCriticalFunction())
fmt.Println("Hard Result:", hardResult)
fmt.Println("Soft Result:", softResult)
}
func SomeCriticalFunction() (string, error) {
return "", fmt.Errorf("critical error occurred")
}
func SomeNonCriticalFunction() (string, error) {
return "default value", fmt.Errorf("non-critical error occurred")
}
Use Must when an error is unrecoverable and should halt the program execution.
result := it.Must(SomeFunction())
Use Should when you want to log an error but continue execution.
result := it.Should(SomeFunction())
Panics if err is not nil. Use it when a critical error cannot be recovered.
it.Ensure(SomeCriticalFunction())
Logs the error but continues execution. Use it for non-critical errors.
it.Attempt(SomeFunction())
Adds contextual information to an error message, making it easier to track errors.
err := it.WrapError(err, "processing file", map[string]string{"file": filename})
it.Info("Application started")
it.Warn("Low disk space")
it.Error("Failed to connect to database")
it.Infof("Server started on port %d", port)
it.Warnf("Disk space low: %d%% remaining", diskSpace)
it.Errorf("Error %d: %s", errorCode, errorMessage)
Debug
and Trace
Logging
Set the log level to include debug and trace messages:
it.SetLogLevel(it.LevelDebug)
it.Debug("Cache initialized")
it.Trace("Entered function X")
Logs the current stack trace, which is helpful for debugging complex issues by displaying the call stack.
func LogStackTrace()
Example Usage:
it.LogStackTrace()
Logs an error along with the current stack trace. This provides more detailed information to aid in debugging by capturing the error context and call stack.
func LogErrorWithStack(err error)
err
: The error to log, along with its stack trace.
Example Usage:
it.LogErrorWithStack(err)
Logs a message only once, avoiding repetitive log entries in loops.
it.LogOnce("This message will only be logged once")
Logs an audit-specific message for tracking important actions.
it.Audit("User login attempt recorded")
Logs messages in JSON format with additional data.
userData := map[string]string{"username": "johndoe", "ip": "192.168.1.1"}
it.StructuredInfo("User logged in", userData)
Logs a structured debug-level message in JSON format, useful for detailed logging with additional contextual data.
func StructuredDebug(message string, data any)
message
: The debug message to log.data
: Additional contextual data in key-value format.
Example Usage:
it.StructuredDebug("Cache hit", map[string]string{"key": "user:1234"})
Logs a structured warning message in JSON format with additional data. Useful for tracking non-critical issues in a structured way.
func StructuredWarning(message string, data any)
message
: The warning message to log.data
: Additional key-value data to provide context.
Example Usage:
it.StructuredWarning("High memory usage detected", map[string]interface{}{"usage": 95})
Logs an error message in JSON format with additional contextual data, useful for error tracking with structured logs.
func StructuredError(message string, data any)
message
: The error message to log.data
: Additional context in a key-value format.
Example Usage:
it.StructuredError("File not found", map[string]string{"filename": "config.yaml"})
Logs to any specified writer with buffering, supporting os.Stdout, file, or custom io.Writer.
file, _ := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
logger := it.NewBufferedLogger(file)
logger.Log("Buffered log message")
logger.Flush()
Retries a function with a fixed delay. Useful for handling transient errors.
err := it.Retry(3, time.Second, SomeFunction)
Retries a function with exponential backoff, doubling the delay after each attempt.
err := it.RetryExponential(5, time.Second, SomeFunction)
Retries a function with a fixed delay, stopping if the context is canceled.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := it.RetryWithContext(ctx, 3, time.Second, SomeFunction)
Retries a function with exponential backoff, doubling the delay after each attempt, but stops if the context is canceled.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := it.RetryExponentialWithContext(ctx, 5, time.Second, SomeFunction)
GracefulShutdown
listens for an interrupt signal (e.g., SIGINT
or SIGTERM
) and attempts to gracefully shut down the given server within the specified timeout. If an action function is provided, it will execute this function after shutdown completes. If a done channel is provided, it will signal completion on the channel after the shutdown and executing the action.
func GracefulShutdown(ctx context.Context, server interface{ Shutdown(context.Context) error }, timeout time.Duration, done chan<- bool, action func())
• ctx
: The base context for shutdown, which can be context.Background()
or another context
.
• server
: The server object to shut down, which must have a Shutdown method that takes a context.Context
.
• timeout
: The maximum time to wait for the server to shut down gracefully before forcing termination.
• done
: An optional channel to signal completion once the shutdown and action are complete. If done is nil, no notification is sent.
• action
: An optional function to execute after the server has shut down. This can be used for cleanup or other post-shutdown tasks. If action is nil, no action is performed.
Example Usage:
- Without a done channel or action:
it.GracefulShutdown(context.Background(), server, 5*time.Second, nil, nil)
- With a done channel and action:
done := make(chan bool)
cleanupAction := func() {
log.Println("Performing post-shutdown cleanup...")
// Additional cleanup code here
}
go it.GracefulShutdown(context.Background(), server, 5*time.Second, done, cleanupAction)
<-done // Wait for the shutdown process to complete
GracefulRestart
listens for a signal to restart the server gracefully. It attempts to shut down the given server within the specified timeout and then optionally performs an action before signaling completion on the done channel, if provided.
func GracefulRestart(ctx context.Context, server interface{ Shutdown(context.Context) error }, timeout time.Duration, done chan<- bool, action func())
• ctx
: The context for shutdown, typically context.Background()
or similar.
• server
: The server instance to restart, which must implement a Shutdown method.
• timeout
: The maximum time allowed for the graceful shutdown before initiating a restart.
• done
: An optional channel to signal completion once the shutdown, action, and restart are complete. If done is nil, no notification is sent.
• action
: An optional function to execute after the server has shut down. This can be used to reinitialize services, reload configurations, or perform any other custom restart logic. If action is nil, no action is performed.
Example Usage:
- Without a done channel or action:
it.GracefulRestart(context.Background(), server, 5*time.Second, nil, nil)
- With a done channel and an action:
done := make(chan bool)
restartAction := func() {
log.Println("Performing custom restart actions...")
// Additional initialization or setup code here
}
go it.GracefulRestart(context.Background(), server, 5*time.Second, done, restartAction)
<-done // Wait for the restart process to complete
RateLimiter
returns a rate-limited version of any handler
function, allowing it to execute only once per specified rate interval. This function is designed to work with handlers of any type, including HTTP route handlers and custom functions with any signature.
func RateLimiter(rate time.Duration, fn interface{}) interface{}
• rate: The interval at which fn is allowed to execute. Only one call to fn will be allowed per rate period.
• fn: The original handler function to be rate-limited. It can have any number and type of input arguments and return any type.
A rate-limited function that has the same signature as fn. The returned function will wait for the specified rate interval before each execution.
With an HTTP Handler
func myHTTPHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
limitedHandler := it.RateLimiter(1*time.Second, myHTTPHandler).(func(http.ResponseWriter, *http.Request))
http.HandleFunc("/limited", limitedHandler)
In this example, myHTTPHandler
is wrapped with RateLimiter
, and it will execute at most once per second when called.
With a Custom Function
func customHandler(name string, age int) string {
return fmt.Sprintf("Name: %s, Age: %d", name, age)
}
limitedCustomHandler := it.RateLimiter(2*time.Second, customHandler).(func(string, int) string)
result := limitedCustomHandler("Alice", 30)
fmt.Println(result) // Output: "Name: Alice, Age: 30"
Here, customHandler is rate-limited to execute at most once every two seconds, regardless of how often limitedCustomHandler is called.
Notes
• Generic Handler Support: RateLimiter is designed to work with any function signature, making it compatible with various handler types.
• Type Assertion: Since RateLimiter returns an interface{}, you will need to use type assertion to call the wrapped function with the correct parameter and return types.
• Rate Control: Calls to the returned function are delayed based on the specified rate, ensuring that fn is only invoked once per interval.
Waits until a specified condition is met or times out.
it.WaitFor(time.Second*10, func() bool { return someCondition() })
Creates a deferred function that logs a message upon completion, helpful for complex defer chains.
defer it.DeferWithLog("Cleanup complete")()
Measures and logs the execution time of a function.
it.TimeFunction("compute", compute)
Starts a timer and logs the execution time of a code block.
defer it.TimeBlock("main")()
GenerateSecret: generates a random 32-bit secret key.
key := it.GenerateSecret()
Redirect logs to a file or other output destination.
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
it.SetLogOutput(file)
Control verbosity of logs:
it.SetLogLevel(it.LevelInfo)
• it.LevelTrace
• it.LevelDebug
• it.LevelInfo
• it.LevelWarn
• it.LevelError
• it.LevelFatal
• it.LevelAudit
Initialize logger settings from environment variables:
os.Setenv("LOG_LEVEL", "DEBUG")
os.Setenv("LOG_FILE", "app.log")
it.InitFromEnv()
• LOG_LEVEL: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
• LOG_FILE: Path to a file for log output
For detailed documentation of all functions, visit the GoDoc page.
Contributions are welcome! Please submit issues and pull requests for bug fixes, enhancements, or new features.
- Fork the repository.
- Create a new branch (git checkout -b feature/your-feature).
- Commit your changes (git commit -am 'Add new feature').
- Push to the branch (git push origin feature/your-feature).
- Open a pull request.
Please ensure your code adheres to Go conventions and includes tests where appropriate.
This project is licensed under the MIT License - see the LICENSE file for details.
Thank you for using it! If you have any questions or feedback, feel free to open an issue or submit a pull request.