Error Handling


Overview

The Togo MQ SDK provides detailed error information to help you handle failures gracefully and debug issues effectively. All SDK operations return errors that can be type-asserted to get more detailed information.

TogoMQ Error Structure

The SDK uses a custom error type that provides structured error information:

type TogoMQError struct {
    Code    string // Error code for programmatic handling
    Message string // Human-readable error message
    Err     error  // Original underlying error (if any)
}

Type Assertion

Check if an error is a TogoMQ error:

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    if togomqErr, ok := err.(*togomq.TogoMQError); ok {
        // It's a TogoMQ error
        log.Printf("Code: %s, Message: %s\n", togomqErr.Code, togomqErr.Message)
    } else {
        // It's a different type of error
        log.Printf("Unexpected error: %v\n", err)
    }
}

Error Codes

The SDK defines the following error codes:

Error Code Description Common Causes
ErrCodeConnection Connection or network errors Network issues, firewall, DNS problems
ErrCodeAuth Authentication failures Invalid token, expired token, revoked token
ErrCodeValidation Invalid input or configuration Missing topic, invalid config, malformed data
ErrCodePublish Publishing errors Server-side publish failures
ErrCodeSubscribe Subscription errors Server-side subscription failures
ErrCodeStream General streaming errors Stream interrupted, connection lost
ErrCodeConfiguration Configuration errors Invalid host, port, or other config

Handling Errors

Basic Error Handling

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    log.Printf("Failed to publish: %v\n", err)
    return
}

Detailed Error Handling

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    // Check error type
    if togomqErr, ok := err.(*togomq.TogoMQError); ok {
        switch togomqErr.Code {
        case togomq.ErrCodeAuth:
            log.Println("Authentication failed - check your token")
            // Maybe refresh token or alert admin

        case togomq.ErrCodeConnection:
            log.Println("Connection error - will retry")
            // Implement retry logic

        case togomq.ErrCodeValidation:
            log.Printf("Validation error: %s\n", togomqErr.Message)
            // Fix the invalid input

        case togomq.ErrCodePublish:
            log.Printf("Publish failed: %s\n", togomqErr.Message)
            // Queue for retry or alert monitoring

        default:
            log.Printf("Error (%s): %s\n", togomqErr.Code, togomqErr.Message)
        }
    } else {
        log.Printf("Unexpected error: %v\n", err)
    }
    return
}

Error Handling in Subscriptions

msgChan, errChan, err := client.Sub(ctx, opts)
if err != nil {
    log.Fatalf("Failed to subscribe: %v", err)
}

for {
    select {
    case msg := <-msgChan:
        if msg == nil {
            return
        }
        if err := processMessage(msg); err != nil {
            log.Printf("Failed to process message %s: %v\n", msg.UUID, err)
            // Decide whether to continue or stop
        }

    case err := <-errChan:
        if togomqErr, ok := err.(*togomq.TogoMQError); ok {
            log.Printf("Subscription error (%s): %s\n", togomqErr.Code, togomqErr.Message)

            // Handle specific errors
            if togomqErr.Code == togomq.ErrCodeAuth {
                log.Fatal("Authentication failed - stopping")
            }
        } else {
            log.Printf("Unexpected subscription error: %v\n", err)
        }
        return
    }
}

Common Error Scenarios

Authentication Errors

Cause: Invalid, expired, or revoked token

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    if togomqErr, ok := err.(*togomq.TogoMQError); ok {
        if togomqErr.Code == togomq.ErrCodeAuth {
            log.Println("Authentication failed!")
            log.Println("Possible causes:")
            log.Println("- Token is invalid")
            log.Println("- Token has been revoked")
            log.Println("- Token has expired")
            log.Println("Please check your token in the dashboard")
        }
    }
}

Solution:

  • Verify your token is correct
  • Check if token was revoked in your dashboard
  • Generate a new token if needed

Connection Errors

Cause: Network issues, firewall, or DNS problems

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    if togomqErr, ok := err.(*togomq.TogoMQError); ok {
        if togomqErr.Code == togomq.ErrCodeConnection {
            log.Println("Connection error - implementing retry logic")

            // Exponential backoff retry
            for attempt := 1; attempt <= 3; attempt++ {
                time.Sleep(time.Duration(attempt) * time.Second)

                resp, err = client.PubBatch(ctx, messages)
                if err == nil {
                    log.Println("Retry successful!")
                    break
                }
                log.Printf("Retry %d failed\n", attempt)
            }
        }
    }
}

Solution:

  • Check network connectivity
  • Verify firewall rules allow gRPC traffic
  • Implement retry logic with exponential backoff

Validation Errors

Cause: Invalid input data (e.g., missing topic)

// This will cause a validation error - topic is required!
msg := togomq.NewMessage("", []byte("data"))

resp, err := client.PubBatch(ctx, []*togomq.Message{msg})
if err != nil {
    if togomqErr, ok := err.(*togomq.TogoMQError); ok {
        if togomqErr.Code == togomq.ErrCodeValidation {
            log.Printf("Validation error: %s\n", togomqErr.Message)
            // Fix: Always provide a topic
            msg.Topic = "events"
        }
    }
}

Solution:

  • Ensure all required fields are provided
  • Validate data before publishing
  • Check message size limits (max 50 MB)

Context Timeout Errors

Cause: Operation took longer than context timeout

// Set a very short timeout (for demonstration)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    if err == context.DeadlineExceeded {
        log.Println("Operation timed out!")
        log.Println("Consider increasing timeout or checking network")
    }
}

Solution:

  • Increase context timeout
  • Check network latency
  • Verify server health

Best Practices

1. Always Check Errors

Never ignore errors:

// Good ✅
resp, err := client.PubBatch(ctx, messages)
if err != nil {
    log.Printf("Error: %v\n", err)
    return
}

// Bad ❌
client.PubBatch(ctx, messages)

2. Use Type Assertion for Detailed Handling

Get detailed error information:

if togomqErr, ok := err.(*togomq.TogoMQError); ok {
    // Handle specific error codes
    switch togomqErr.Code {
    case togomq.ErrCodeAuth:
        // Handle auth errors
    case togomq.ErrCodeConnection:
        // Handle connection errors
    }
}

3. Implement Retry Logic

For transient errors (connection issues):

func publishWithRetry(client *togomq.Client, messages []*togomq.Message, maxRetries int) error {
    var err error

    for attempt := 0; attempt < maxRetries; attempt++ {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        _, err = client.PubBatch(ctx, messages)
        cancel()

        if err == nil {
            return nil // Success
        }

        // Check if we should retry
        if togomqErr, ok := err.(*togomq.TogoMQError); ok {
            if togomqErr.Code == togomq.ErrCodeAuth || 
               togomqErr.Code == togomq.ErrCodeValidation {
                // Don't retry auth or validation errors
                return err
            }
        }

        // Exponential backoff
        if attempt < maxRetries-1 {
            backoff := time.Duration(1<<uint(attempt)) * time.Second
            log.Printf("Retry %d after %v\n", attempt+1, backoff)
            time.Sleep(backoff)
        }
    }

    return err
}

4. Log Error Details

Include error codes and messages in logs:

if togomqErr, ok := err.(*togomq.TogoMQError); ok {
    log.Printf("TogoMQ Error - Code: %s, Message: %s, Underlying: %v\n",
        togomqErr.Code,
        togomqErr.Message,
        togomqErr.Err,
    )
}

5. Monitor Error Rates

Track error rates for alerting:

var (
    totalRequests  int64
    failedRequests int64
)

func publish(client *togomq.Client, messages []*togomq.Message) {
    atomic.AddInt64(&totalRequests, 1)

    resp, err := client.PubBatch(ctx, messages)
    if err != nil {
        atomic.AddInt64(&failedRequests, 1)

        errorRate := float64(failedRequests) / float64(totalRequests)
        if errorRate > 0.05 { // 5% error rate
            log.Printf("WARNING: High error rate: %.2f%%\n", errorRate*100)
            // Alert monitoring system
        }
    }
}

6. Use Context Timeouts

Always set reasonable timeouts:

// Set timeout based on operation type
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := client.PubBatch(ctx, messages)
if err != nil {
    if err == context.DeadlineExceeded {
        log.Println("Operation timed out")
    }
}

Complete Error Handling Example

package main

import (
    "context"
    "log"
    "os"
    "time"

    "github.com/TogoMQ/togomq-sdk-go"
)

func main() {
    config := togomq.NewConfig(
        togomq.WithToken(os.Getenv("TOGOMQ_TOKEN")),
        togomq.WithLogLevel("info"),
    )

    client, err := togomq.NewClient(config)
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer client.Close()

    messages := []*togomq.Message{
        togomq.NewMessage("events", []byte("test-message")),
    }

    // Publish with comprehensive error handling
    if err := publishWithErrorHandling(client, messages); err != nil {
        log.Fatalf("Final error: %v", err)
    }
}

func publishWithErrorHandling(client *togomq.Client, messages []*togomq.Message) error {
    maxRetries := 3

    for attempt := 0; attempt < maxRetries; attempt++ {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        resp, err := client.PubBatch(ctx, messages)
        cancel()

        if err == nil {
            log.Printf("Success! Published %d messages\n", resp.MessagesReceived)
            return nil
        }

        // Handle context errors
        if err == context.DeadlineExceeded {
            log.Printf("Attempt %d: Timeout\n", attempt+1)
            continue
        }

        // Handle TogoMQ errors
        if togomqErr, ok := err.(*togomq.TogoMQError); ok {
            log.Printf("Attempt %d: Error code %s - %s\n",
                attempt+1, togomqErr.Code, togomqErr.Message)

            switch togomqErr.Code {
            case togomq.ErrCodeAuth:
                log.Println("Authentication failed - not retrying")
                return err

            case togomq.ErrCodeValidation:
                log.Println("Validation error - not retrying")
                return err

            case togomq.ErrCodeConnection, togomq.ErrCodePublish:
                if attempt < maxRetries-1 {
                    backoff := time.Duration(1<<uint(attempt)) * time.Second
                    log.Printf("Retrying after %v\n", backoff)
                    time.Sleep(backoff)
                    continue
                }

            default:
                log.Printf("Unknown error code: %s\n", togomqErr.Code)
            }
        }

        // If we get here and it's the last attempt, return the error
        if attempt == maxRetries-1 {
            return err
        }
    }

    return nil
}

{success} Next: Check out the FAQ for answers to common questions about Togo MQ.