In Go project development, error handling is one of the keys to building stable and reliable web services. An efficient error handling mechanism can not only catch unhandled exceptions but also provide clear and user-friendly error information to clients through a unified response structure. In this article, we will discuss how to implement global error capture, custom HTTP error codes, business error classification handling in Gin, as well as integrating Sentry for advanced error monitoring.
Global Error Capture: Enhancing the Recovery Middleware
Gin provides a Recovery middleware for capturing unhandled panics and preventing the program from crashing. However, by default, the Recovery middleware only returns a generic HTTP 500 error. By enhancing it, we can achieve more flexible global exception capture and handling.
Basic Recovery Example
By default, Ginβs Recovery will capture all unhandled panics and return an internal server error:
r := gin.Default() // Logger and Recovery are enabled by default
r.GET("/panic", func(c *gin.Context) {
panic("an unexpected error occurred")
})
When accessing /panic, the client will receive:
{
"error": "Internal Server Error"
}
Custom Recovery: Capturing Exceptions and Logging
By customizing the Recovery middleware, we can log errors into the logging system while returning a structured error response.
func CustomRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Log the error
fmt.Printf("Panic occurred: %v\n", err)
// Return a unified error response
c.JSON(500, gin.H{
"code": 500,
"message": "Internal Server Error",
"error": fmt.Sprintf("%v", err),
})
c.Abort() // Stop further execution
}
}()
c.Next()
}
}
func main() {
r := gin.New()
r.Use(CustomRecovery()) // Use custom Recovery middleware
r.GET("/panic", func(c *gin.Context) {
panic("something went wrong")
})
r.Run(":8080")
}
With the customized Recovery, we capture exceptions and return more detailed error information, while keeping error logs for future debugging.
Custom HTTP Error Codes and Response Structure
A unified error code and response structure is a best practice for modern APIs. It helps the frontend clearly understand what error has occurred and take corresponding actions.
Defining a Standard Response Structure
We can define a universal response format to unify all successful and failed responses.
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"` // Data returned on success, omitted if empty
Error interface{} `json:"error,omitempty"` // Specific error info on failure, omitted if empty
}
Error Response Utility Functions
By encapsulating utility functions, we simplify the process of generating error responses.
func SuccessResponse(c *gin.Context, data interface{}) {
c.JSON(200, APIResponse{
Code: 200,
Message: "success",
Data: data,
})
}
func ErrorResponse(c *gin.Context, code int, message string, err error) {
c.JSON(code, APIResponse{
Code: code,
Message: message,
Error: err.Error(),
})
}
Route Example:
r.GET("/data", func(c *gin.Context) {
data := map[string]interface{}{
"key": "value",
}
SuccessResponse(c, data) // Return success response
})
r.GET("/error", func(c *gin.Context) {
err := fmt.Errorf("some error occurred")
ErrorResponse(c, 400, "bad request", err) // Return error response
})
Successful response:
{
"code": 200,
"message": "success",
"data": {
"key": "value"
}
}
Error response:
{
"code": 400,
"message": "bad request",
"error": "some error occurred"
}
Business Error Classification Handling
In complex systems, different types of errors (such as database errors, authentication errors) need to be handled separately. By encapsulating error types, we can achieve clearer error classification and responses.
Defining Business Error Types
type BusinessError struct {
Code int
Message string
}
func (e *BusinessError) Error() string {
return e.Message
}
var (
ErrDatabase = &BusinessError{Code: 5001, Message: "database error"}
ErrAuth = &BusinessError{Code: 4001, Message: "authentication failed"}
)
Handling Business Errors
By checking the error type, different responses can be generated.
func handleError(c *gin.Context, err error) {
if be, ok := err.(*BusinessError); ok {
// Handle business error
c.JSON(400, APIResponse{
Code: be.Code,
Message: be.Message,
Error: err.Error(),
})
return
}
// Handle other unknown errors
c.JSON(500, APIResponse{
Code: 500,
Message: "Internal Server Error",
Error: err.Error(),
})
}
Route Examples
r.GET("/auth", func(c *gin.Context) {
handleError(c, ErrAuth)
})
r.GET("/db", func(c *gin.Context) {
handleError(c, ErrDatabase)
})
/auth response:
{
"code": 4001,
"message": "authentication failed",
"error": "authentication failed"
}
/db response:
{
"code": 5001,
"message": "database error",
"error": "database error"
}
Integrating Sentry for Error Monitoring
Sentry is a popular error tracking platform that helps developers monitor and analyze exceptions in production environments in real time.
For specific integration methods, please refer to the official documentation. We will not elaborate in detail here.
Official Example for the Go SDK
package main
import (
"log"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: "https://<key>@sentry.io/<project>",
EnableTracing: true,
// Specify a fixed sample rate:
// We recommend adjusting this value in production
TracesSampleRate: 1.0,
// Or provide a custom sample rate:
TracesSampler: sentry.TracesSampler(func(ctx sentry.SamplingContext) float64 {
// As an example, this does not send some
// transactions to Sentry based on their name.
if ctx.Span.Name == "GET /health" {
return 0.0
}
return 1.0
}),
})
if err != nil {
log.Fatalf("sentry.Init: %s", err)
}
// Flush buffered events before the program terminates.
// Set the timeout to the maximum duration the program can afford to wait.
defer sentry.Flush(2 * time.Second)
}
When an exception occurs in the application, the error will be automatically sent to the Sentry dashboard. Developers can view detailed error stack information in real time and clearly see which endpoint had how many error requests during a specific period.
Best Practices
-
Layered error handling:
- Globally capture serious exceptions.
- Refine error classification at the business layer and provide specific feedback information.
-
Unified response structure:
- Ensure that the response formats for both success and failure are consistent, making it easier for the frontend to handle.
-
Monitoring and alerting:
- Integrate tools (such as Sentry) to capture exceptions in the environment and locate issues as soon as possible.
-
Custom error codes:
- Define a unique error code for each error type, making it easier to quickly locate and troubleshoot problems.
We are Leapcell, your top choice for hosting Go projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage β no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead β just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.