catcher

package
v1.0.47 Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2025 License: MIT Imports: 12 Imported by: 0

README ΒΆ

ThreatWinds Catcher - Error Handling, Logging and Retry System

Complete error handling, structured logging and retry operations system for ThreatWinds APIs.

🎯 Features

  • πŸ”§ Robust error handling with complete stack traces and unique codes
  • πŸ“ Dual logging system - Error() for errors, Info() for informational events
  • πŸ”„ Advanced retry system with exponential backoff and granular configuration
  • 🏷️ Enriched metadata for better debugging and monitoring
  • πŸ”— Native integration with Gin framework and HTTP status codes
  • 🎯 Structured logging - JSON with unique codes and stack traces

πŸ“¦ Installation

go get github.com/threatwinds/go-sdk/catcher

πŸš€ Quick Start

Basic Error Handling
package main

import (
    "errors"
    "github.com/threatwinds/go-sdk/catcher"
)

func main() {
	// Create an enriched error
    err := catcher.Error("database operation failed", 
        errors.New("connection timeout"), 
        map[string]any{
            "operation": "insert",
            "table": "users",
            "status": 500,
        })

	// Error is automatically logged
	// Output: {"code":"abc123...", "trace":[...], "msg":"database operation failed", ...}
}
Basic Logging
func main() {
// Informational startup log
catcher.Info("service starting", map[string]any{
"service": "api-gateway",
"version": "v1.0.0",
"port": 8080,
})

// Create error with context
err := catcher.Error("database connection failed", dbErr, map[string]any{
"host": "localhost:5432",
"status": 500,
})
}
Retry with Logging
func fetchData() error {
config := &catcher.RetryConfig{
MaxRetries: 5,
WaitTime:   2 * time.Second,
}

return catcher.Retry(func () error {
data, err := apiCall()
if err != nil {
return catcher.Error("API call failed", err, map[string]any{
"endpoint": "/api/data",
"status": 500,
})
}

// Log successful operation
catcher.Info("data fetched successfully", map[string]any{
"endpoint": "/api/data",
"records": len(data),
})

return nil
}, config, "authentication_failed")
}

βš™οΈ Retry Configuration

type RetryConfig struct {
MaxRetries int           // Maximum number of retries (0 = infinite)
WaitTime   time.Duration // Wait time between retries
}

// Default configuration
var DefaultRetryConfig = &RetryConfig{
MaxRetries: 5,
WaitTime:   1 * time.Second,
}

πŸ“ Logging System

The catcher package provides two distinct logging systems for different purposes:

πŸ”΄ Error Logging - For Error Conditions

Purpose: Exclusively for logging real error conditions with complete context for debugging.

// Returns *SdkError, logs automatically
err := catcher.Error("operation failed", originalErr, map[string]any{
"operation": "payment",
"status": 500,
})

Features:

  • βœ… Complete stack trace (25 frames)
  • βœ… Unique MD5 code based on message
  • βœ… Error chaining with original cause
  • βœ… Enriched metadata in args
  • βœ… Gin integration with GinError()
  • βœ… Automatic logging when creating error
πŸ”΅ Info Logging - For Informational Events

Purpose: For logging important informational events with structured context, without being errors.

// Logs directly, returns no value
catcher.Info("operation completed", map[string]any{
"operation": "payment",
"success": true,
})

Features:

  • βœ… Lightweight stack trace for context
  • βœ… Unique MD5 code based on message
  • βœ… Structured metadata in args
  • βœ… Consistent JSON format
  • ❌ No error chaining (not an error)
  • βœ… Direct logging without returning object
When to Use Each System
Use Error() Use Info()
❌ Connection failures βœ… Service startup
❌ Validation errors βœ… Operations completed
❌ Timeouts βœ… Configuration loaded
❌ Exceptions βœ… Important metrics
❌ Authentication failures βœ… Business events
❌ Resource not found (critical) βœ… System state changes
Log Structure Comparison

Error Log Structure:

{
  "code": "a1b2c3d4e5f6789...",
  "trace": [
    "main.processPayment 123",
    "api.handleRequest 45"
  ],
  "msg": "payment processing failed",
  "cause": "connection timeout",
  "args": {
    "payment_id": "pay_123",
    "amount": 100.00,
    "status": 500
  }
}

Info Log Structure:

{
  "code": "b7c8d9e0f1a2b3c4...",
  "trace": [
    "main.startService 89",
    "config.initDatabase 34"
  ],
  "msg": "service started successfully",
  "args": {
    "service": "payment-processor",
    "version": "v1.2.3",
    "port": 8080,
    "environment": "production"
  }
}

πŸ”§ Available Retry Functions

1. Retry - Limited retry with maximum attempts
err := catcher.Retry(func () error {
return performOperation()
}, config, "exception1", "exception2")
2. InfiniteRetry - Infinite retry until success or exception
err := catcher.InfiniteRetry(func () error {
return connectToDatabase()
}, config, "auth_failed")
3. InfiniteLoop - Infinite loop until exception
catcher.InfiniteLoop(func () error {
return processMessages()
}, config, "shutdown_signal")
4. InfiniteRetryIfXError - Retry only on specific error
err := catcher.InfiniteRetryIfXError(func () error {
return connectToService()
}, config, "connection_timeout")
5. RetryWithBackoff - Retry with exponential backoff
err := catcher.RetryWithBackoff(func () error {
return callExternalAPI()
}, config,
30*time.Second, // max backoff
2.0, // multiplier
"rate_limited")

πŸ” Error Handling

Creating Enriched Errors
// Basic error
err := catcher.Error("operation failed", originalErr, map[string]any{
"user_id": "123",
"status": 500,
})

// Database operation error
err := catcher.Error("database query failed", dbErr, map[string]any{
"query": "SELECT * FROM users",
"table": "users",
"operation": "select",
"status": 500,
"retry_able": true,
})

// External API error
err := catcher.Error("external API call failed", apiErr, map[string]any{
"service": "payment_processor",
"endpoint": "/api/v1/charge",
"method": "POST",
"status": 502,
"external": true,
})
Checking Error Types
// Basic exception checking
if catcher.IsException(err, "not_found", "forbidden") {
// Handle specific exception
}

// Advanced checking for SdkError
if sdkErr := catcher.ToSdkError(err); sdkErr != nil {
// Access error metadata
if operation, ok := sdkErr.Args["operation"]; ok {
log.Printf("Failed operation: %s", operation)
}

// Check exceptions in SdkError
if catcher.IsSdkException(sdkErr, "timeout") {
// Handle timeout specifically
}
}

🌐 Gin Integration

func handleRequest(c *gin.Context) {
err := performOperation()
if err != nil {
// If it's a SdkError, it will be sent automatically with appropriate headers
if sdkErr := catcher.ToSdkError(err); sdkErr != nil {
sdkErr.GinError(c)
return
}

// For other errors, create SdkError
sdkErr := catcher.Error("request failed", err, map[string]any{
"status": 500,
"request_id": c.GetHeader("X-Request-ID"),
})
sdkErr.GinError(c)
}
}

πŸ“‹ Practical Examples

Database Operation
func getUserByID(userID string) (*User, error) {
var user *User

config := &catcher.RetryConfig{
MaxRetries: 5,
WaitTime:   500 * time.Millisecond,
}

err := catcher.RetryWithBackoff(func () error {
u, err := db.GetUser(userID)
if err != nil {
return catcher.Error("failed to get user", err, map[string]any{
"user_id": userID,
"operation": "getUserByID",
"table": "users",
"status": 500,
})
}
user = u
return nil
}, config, 2*time.Second, 2.0, "user_not_found")

return user, err
}
Connect to External Service
func connectToRedis() error {
return catcher.InfiniteRetryIfXError(func () error {
err := redis.Connect()
if err != nil {
return catcher.Error("redis connection failed", err, map[string]any{
"service": "redis",
"host": "localhost:6379",
"critical": true,
"status": 500,
})
}

// Log successful connection
catcher.Info("redis connected successfully", map[string]any{
"service": "redis",
"host": "localhost:6379",
"pool_size": 10,
})

return nil
}, &catcher.RetryConfig{
WaitTime: 5 * time.Second,
}, "connection_refused")
}
Process Message Queue
func processMessageQueue() {
catcher.InfiniteLoop(func () error {
message, err := queue.GetNext()
if err != nil {
return catcher.Error("failed to get message", err, map[string]any{
"queue": "processing",
"operation": "getMessage",
})
}

if message != nil {
err = processMessage(message)
if err != nil {
// Log error but continue processing
catcher.Error("failed to process message", err, map[string]any{
"message_id": message.ID,
"queue": "processing",
})
} else {
// Log successful processing
catcher.Info("message processed successfully", map[string]any{
"message_id": message.ID,
"queue": "processing",
})
}
}

return nil
}, &catcher.RetryConfig{
WaitTime: 1 * time.Second,
}, "shutdown")
}

πŸ“Š Logging and Monitoring

Complete Application Example
package main

import (
	"github.com/threatwinds/go-sdk/catcher"
	"github.com/gin-gonic/gin"
)

func main() {
	// Informational startup log
	catcher.Info("payment service starting", map[string]any{
		"version": "v1.0.0",
		"port":    8080,
	})

	r := gin.Default()
	r.POST("/payment", handlePayment)

	catcher.Info("payment service ready", map[string]any{
		"endpoints": []string{"/payment"},
		"status":    "ready",
	})

	r.Run(":8080")
}

func handlePayment(c *gin.Context) {
	paymentID := c.Param("id")

	// Informational operation log
	catcher.Info("processing payment", map[string]any{
		"payment_id": paymentID,
		"user_id":    c.GetString("user_id"),
	})

	err := processPayment(paymentID)
	if err != nil {
		// Error log with complete context
		sdkErr := catcher.Error("payment processing failed", err, map[string]any{
			"payment_id": paymentID,
			"user_id":    c.GetString("user_id"),
			"status":     500,
		})
		sdkErr.GinError(c)
		return
	}

	// Informational success log
	catcher.Info("payment processed successfully", map[string]any{
		"payment_id": paymentID,
		"status":     "completed",
	})

	c.JSON(200, gin.H{"status": "success"})
}
Automatic Retry Logging

The system automatically logs:

  • βœ… Retry start with configuration
  • πŸ”„ Failed attempts with error details
  • βœ… Success after retries
  • ❌ Final failure after maximum retries
  • πŸ›‘ Exception stop

πŸ§ͺ Testing

func TestRetryOperation(t *testing.T) {
attempts := 0

err := catcher.Retry(func () error {
attempts++
if attempts < 3 {
return errors.New("temporary error")
}
return nil
}, &catcher.RetryConfig{
MaxRetries: 5,
WaitTime:   10 * time.Millisecond,
})

assert.NoError(t, err)
assert.Equal(t, 3, attempts)
}

πŸ” Debugging and Monitoring

Filter by Type
# Only errors (have "cause")
jq 'select(.cause != null)' app.log

# Only info logs (no "cause")  
jq 'select(.cause == null)' app.log

# Filter by specific code
jq 'select(.code == "a1b2c3d4e5f6789...")' app.log
Error Analysis
# Top most frequent errors
jq -r '.code' app.log | sort | uniq -c | sort -nr | head -10

# Errors from specific service
jq 'select(.args.service == "payment-processor" and .cause != null)' app.log

πŸš€ Monitoring Integration

Both systems generate structured logs ideal for:

  • πŸ“Š Elasticsearch/OpenSearch - Indexing and search
  • πŸ“ˆ Grafana - Dashboards and alerts
  • πŸ”” Alertmanager - Notifications by error codes
  • πŸ“‹ Jaeger/Zipkin - Distributed tracing using unique codes

πŸ“ˆ Benefits of the Catcher System

  1. πŸ” Better Debugging: Complete stack traces and unique error codes
  2. πŸ“Š Advanced Monitoring: Rich metadata for alerts and metrics
  3. βš™οΈ Flexibility: Granular retry configuration per operation
  4. πŸš€ Performance: Exponential backoff for external services
  5. πŸ› οΈ Maintainability: Clear separation between logging and retry logic
  6. πŸ”— Integration: Native support for web frameworks

πŸ†˜ Troubleshooting

❓ Problem: Why don't I see successful retry logs?

βœ… Solution: This is intentional - catcher only logs real errors, not successful operations

❓ Problem: Complex configuration

βœ… Solution: Use catcher.DefaultRetryConfig or create reusable configs

❓ Problem: Duplicate error codes

βœ… Solution: MD5 codes are unique per message + stack trace combination


πŸ’‘ Tips and Best Practices

  1. Use descriptive metadata in your errors for better debugging
  2. Configure retry strategies specific to operation type
  3. Avoid infinite retry in time-critical operations
  4. Use exponential backoff for external services
  5. Group configurations by application domain (DB, API, etc.)
  6. Use Error() only for real errors - not for informational events
  7. Include unique identifiers (IDs) when relevant
  8. Don't include sensitive information in logs

The catcher system is ready to improve the robustness and observability of your ThreatWinds applications! πŸš€

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var DefaultRetryConfig = &RetryConfig{
	MaxRetries: 5,
	WaitTime:   1 * time.Second,
}

DefaultRetryConfig provides sensible defaults for retry operations

Functions ΒΆ

func GetSeverityIcon ΒΆ added in v1.0.36

func GetSeverityIcon(severity string) string

GetSeverityIcon returns an icon based on the severity level

func InfiniteLoop ΒΆ added in v1.0.29

func InfiniteLoop(f func() error, config *RetryConfig, exceptions ...string)

InfiniteLoop continuously executes a provided function until it produces a matching exception error. Enhanced version of logger.InfiniteLoop for catcher system.

Parameters:

  • f: Function to execute repeatedly
  • config: Configuration for wait time and logging (MaxRetries is ignored)
  • exceptions: Error patterns that should stop the loop

func InfiniteRetry ΒΆ added in v1.0.29

func InfiniteRetry(f func() error, config *RetryConfig, exceptions ...string) error

InfiniteRetry executes a function repeatedly until it succeeds or returns an error containing specified exception patterns. Enhanced version of logger.InfiniteRetry.

Parameters:

  • f: Function to execute that returns an error
  • config: Retry configuration (MaxRetries is ignored, use nil for defaults)
  • exceptions: Error patterns that should stop retrying immediately

Returns:

  • error: nil on success, error on exception match

func InfiniteRetryIfXError ΒΆ added in v1.0.29

func InfiniteRetryIfXError(f func() error, config *RetryConfig, exception string) error

InfiniteRetryIfXError retries a function f() infinitely only if the error returned matches the specified exception. Enhanced version of logger.InfiniteRetryIfXError.

This function provides advanced error filtering: - Retries only if error matches the specific exception - Returns immediately on different errors or success - Logs the exception only once to avoid log saturation - Logs when the issue is resolved

Parameters:

  • f: Function to execute that returns an error
  • config: Retry configuration (MaxRetries is ignored)
  • exception: Specific error pattern to retry on

Returns:

  • error: nil on success, non-matching error immediately, or context error

func Info ΒΆ added in v1.0.29

func Info(msg string, args map[string]any)

Info logs a message with a unique code, stack trace, and optional contextual arguments in a structured format.

func IsException ΒΆ added in v1.0.29

func IsException(err error, exceptions ...string) bool

IsException checks if an error matches any of the specified exception patterns

func IsSdkException ΒΆ added in v1.0.29

func IsSdkException(err *SdkError, exceptions ...string) bool

IsSdkException checks if an SdkError matches any of the specified exception patterns This provides enhanced checking for SdkError types including message and cause

func Retry ΒΆ added in v1.0.29

func Retry(f func() error, config *RetryConfig, exceptions ...string) error

Retry executes a function repeatedly until it succeeds, the maximum retries are reached, or a matching exception is encountered. Enhanced version of logger.Retry for catcher system.

Parameters:

  • f: Function to execute that returns an error
  • config: Retry configuration (use nil for defaults)
  • exceptions: Error patterns that should stop retrying immediately

Returns:

  • error: nil on success, last error on failure or exception match

func RetryWithBackoff ΒΆ added in v1.0.29

func RetryWithBackoff(f func() error, config *RetryConfig, maxBackoff time.Duration, backoffMultiplier float64, exceptions ...string) error

RetryWithBackoff executes a function with exponential backoff retry strategy. This is a new enhanced retry function not available in the original logger.

Parameters:

  • f: Function to execute that returns an error
  • config: Base retry configuration
  • maxBackoff: Maximum backoff duration
  • backoffMultiplier: Multiplier for exponential backoff (typically 2.0)
  • exceptions: Error patterns that should stop retrying immediately

Returns:

  • error: nil on success, last error on failure or exception match

Types ΒΆ

type RetryConfig ΒΆ added in v1.0.29

type RetryConfig struct {
	MaxRetries int           // Maximum number of retries (0 = infinite)
	WaitTime   time.Duration // Wait time between retries
}

RetryConfig defines configuration options for retry operations

type SdkError ΒΆ

type SdkError struct {
	Timestamp string         `json:"timestamp"`
	Code      string         `json:"code"`
	Trace     []string       `json:"trace"`
	Msg       string         `json:"msg"`
	Cause     *string        `json:"cause,omitempty"`
	Args      map[string]any `json:"args,omitempty"`
	Severity  string         `json:"severity"`
}

SdkError is a struct that implements the Go error interface.

func Error ΒΆ

func Error(msg string, cause error, args map[string]any) *SdkError

Error tries to cast the cause as an SdkError, if it is not an SdkError, it creates a new SdkError with the given parameters. It logs the error message and returns the error. If cause is nil, it will store a blank string in the Cause field. The field Code is a hash of the message and trace. It is used to identify the recurrence of an error. Params: msg: the error message. cause: the error that caused this error. args: a map of additional information. Returns: *SdkError: the error. This type implements the Go error interface.

func ToSdkError ΒΆ

func ToSdkError(err error) *SdkError

ToSdkError tries to cast an error to a SdkError. If the error isn't an SdkError, it returns nil.

func (SdkError) Error ΒΆ

func (e SdkError) Error() string

Error returns the error message.

func (SdkError) GinError ΒΆ

func (e SdkError) GinError(c *gin.Context)

GinError is a helper function to return an error to the client using Gin framework context. It sets the headers x-error and x-error-id with the error message and UUID respectively and sets the status code.

type SdkLog ΒΆ added in v1.0.29

type SdkLog struct {
	Timestamp string         `json:"timestamp"`
	Code      string         `json:"code"`
	Trace     []string       `json:"trace"`
	Msg       string         `json:"msg"`
	Args      map[string]any `json:"args,omitempty"`
	Severity  string         `json:"severity"`
}

SdkLog represents a structured log entry with unique code, stack trace, message, and optional additional arguments.

func (SdkLog) String ΒΆ added in v1.0.29

func (e SdkLog) String() string

String returns the JSON-encoded string representation of the SdkLog instance.

Jump to

Keyboard shortcuts

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