Error Handling and Logging

Motivation

This project aims to ensure robust and informative error handling and logging practices. The goals are:

  1. Clear and Consistent Error Reporting: Provide developers with comprehensive error information to facilitate debugging and issue resolution.
  2. Structured Logging: Log events in a structured and machine-readable format for easier analysis and aggregation.
  3. Appropriate Error Handling: Implement sensible error handling mechanisms to avoid program crashes and maintain application stability.

Error Handling

Error Handling Best Practices

  • Explicit Error Handling: Always handle errors explicitly using if err != nil checks.
  • Detailed Error Information: Provide context and details about the error within error messages.
  • Return Errors Up the Call Stack: Propagate errors upward until they can be handled appropriately.
  • Use the errors Package: Leverage the errors package for creating and wrapping errors to maintain context and information.
package main
          
          import (
              "errors"
              "fmt"
          )
          
          func doSomething() error {
              // Simulate an error
              if err := someOperation(); err != nil {
                  return fmt.Errorf("failed to perform operation: %w", err) // Wrap the error with more context
              }
              return nil
          }
          
          func main() {
              if err := doSomething(); err != nil {
                  fmt.Println("Error:", err) // Output the detailed error
              }
          }
          

Error Types

  • Standard Errors: Utilize built-in Go error types (e.g., io.EOF, os.PathError).
  • Custom Error Types: Define custom error types for specific scenarios and provide context-specific information.
package main
          
          import (
              "errors"
              "fmt"
          )
          
          // Define a custom error type
          type InvalidInputError struct {
              Value string
          }
          
          func (e *InvalidInputError) Error() string {
              return fmt.Sprintf("invalid input: %s", e.Value)
          }
          
          func validateInput(input string) error {
              if input == "" {
                  return &InvalidInputError{Value: input}
              }
              return nil
          }
          
          func main() {
              if err := validateInput(""); err != nil {
                  fmt.Println("Error:", err) // Output the custom error
              }
          }
          

Logging

Logging Levels

  • Debug: Detailed information for development and debugging.
  • Info: Informational messages about application events.
  • Warning: Potential issues or non-critical errors.
  • Error: Critical errors that impact application functionality.
  • Fatal: Severe errors that cause application termination.

Logging Best Practices

  • Structured Logging: Log events in a structured format for easier analysis and aggregation. Use libraries like logrus, zap, or zerolog.
  • Contextual Information: Include relevant context within log messages (e.g., timestamps, user IDs, request IDs).
  • Log Rotation: Implement log rotation to prevent log files from growing indefinitely.
package main
          
          import (
              "fmt"
          
              "github.com/sirupsen/logrus"
          )
          
          func main() {
              // Configure the logger
              logger := logrus.New()
              logger.Formatter = &logrus.JSONFormatter{}
          
              // Log messages at different levels
              logger.Debug("Debug message")
              logger.Info("Info message")
              logger.Warn("Warning message")
              logger.Error("Error message")
              logger.Fatal("Fatal message")
          }
          

References