# Debugging Guide

This guide covers debugging techniques and tools for troubleshooting Go AI applications.

## Enable Debug Logging

### SDK Warning Logging

The SDK has no global debug/log-level switch. What it does provide is a
warnings logger: every call that returns provider warnings (unsupported
settings, compatibility fallbacks, etc.) is routed through `ai.LogWarnings`,
which writes to stderr by default. Install your own sink with
`ai.SetLogWarnings`, or silence it with `ai.DisableLogWarnings()` (or the
`AI_SDK_LOG_WARNINGS=false` environment variable):

```go
package main

import (
    "log"

    "github.com/digitallysavvy/go-ai/pkg/ai"
)

func main() {
    // Route SDK warnings into your own logger instead of stderr.
    ai.SetLogWarnings(func(opts ai.LogWarningsOptions) {
        for _, w := range opts.Warnings {
            log.Printf("[%s/%s] %s", opts.Provider, opts.Model, ai.FormatWarning(w, opts.Provider, opts.Model))
        }
    })

    // Your code here...
}
```

For full HTTP request/response visibility, wrap the HTTP client instead — see below.

### Request/Response Logging

```go
package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

// Custom HTTP transport with logging
type LoggingTransport struct {
    Transport http.RoundTripper
}

func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    // Log request
    log.Printf("=== REQUEST ===")
    log.Printf("%s %s", req.Method, req.URL)
    log.Printf("Headers: %v", req.Header)

    if req.Body != nil {
        bodyBytes, _ := io.ReadAll(req.Body)
        req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
        log.Printf("Body: %s", string(bodyBytes))
    }

    // Execute request
    resp, err := t.Transport.RoundTrip(req)
    if err != nil {
        log.Printf("Request error: %v", err)
        return nil, err
    }

    // Log response
    log.Printf("=== RESPONSE ===")
    log.Printf("Status: %d %s", resp.StatusCode, resp.Status)
    log.Printf("Headers: %v", resp.Header)

    if resp.Body != nil {
        bodyBytes, _ := io.ReadAll(resp.Body)
        resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
        log.Printf("Body: %s", string(bodyBytes))
    }

    return resp, nil
}

func main() {
    ctx := context.Background()

    // Create HTTP client with logging transport
    httpClient := &http.Client{
        Transport: &LoggingTransport{
            Transport: http.DefaultTransport,
        },
    }

    provider := openai.New(openai.Config{
        APIKey:     os.Getenv("OPENAI_API_KEY"),
        HTTPClient: httpClient,
    })

    model, _ := provider.LanguageModel("gpt-4o-mini")

    result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
        Model:  model,
        Prompt: "Hello!",
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result.Text)
}
```

## Debugging Goroutines

### Detect Goroutine Leaks

```go
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "runtime"
    "time"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func printGoroutineCount(label string) {
    count := runtime.NumGoroutine()
    log.Printf("[%s] Active goroutines: %d", label, count)
}

func detectLeaks() {
    printGoroutineCount("Start")

    ctx := context.Background()
    provider := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
    })
    model, _ := provider.LanguageModel("gpt-4o-mini")

    // Start streaming
    stream, err := ai.StreamText(ctx, ai.StreamTextOptions{
        Model:  model,
        Prompt: "Write a short story",
    })
    if err != nil {
        log.Fatal(err)
    }

    printGoroutineCount("After stream start")

    // IMPORTANT: Consume the stream completely
    for chunk := range stream.Chunks() {
        fmt.Print(chunk.Text)
    }

    printGoroutineCount("After stream complete")

    // Wait for cleanup
    time.Sleep(100 * time.Millisecond)

    printGoroutineCount("After cleanup wait")

    // Force garbage collection
    runtime.GC()
    time.Sleep(100 * time.Millisecond)

    printGoroutineCount("After GC")
}

func main() {
    detectLeaks()
}
```

### Stack Traces for Debugging

```go
package main

import (
    "fmt"
    "os"
    "os/signal"
    "runtime"
    "syscall"
)

func dumpStackTraces() {
    buf := make([]byte, 1024*1024)
    stackSize := runtime.Stack(buf, true)
    fmt.Printf("=== Stack Traces ===\n%s\n", buf[:stackSize])
}

func setupDebugHandlers() {
    // Dump stack traces on SIGUSR1
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGUSR1)

    go func() {
        for range sigCh {
            dumpStackTraces()
        }
    }()
}

func main() {
    setupDebugHandlers()

    // Your application code...
    select {}
}
```

## Debugging with pprof

### CPU and Memory Profiling

```go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof"
    "os"
    "runtime"
    "runtime/pprof"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func startProfiling() {
    // Start pprof HTTP server
    go func() {
        log.Println("Starting pprof server on :6060")
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // Or profile to files
    cpuFile, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(cpuFile)
    defer pprof.StopCPUProfile()

    memFile, _ := os.Create("mem.prof")
    defer func() {
        runtime.GC()
        pprof.WriteHeapProfile(memFile)
        memFile.Close()
    }()
}

func main() {
    startProfiling()

    ctx := context.Background()
    provider := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
    })
    model, _ := provider.LanguageModel("gpt-4o-mini")

    // Run your code to profile
    for i := 0; i < 10; i++ {
        result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
            Model:  model,
            Prompt: fmt.Sprintf("Request %d", i),
        })
        fmt.Println(result.Text)
    }

    // View profiles:
    // go tool pprof cpu.prof
    // go tool pprof mem.prof
    // Or visit http://localhost:6060/debug/pprof/
}
```

## Debugging Streams

### Stream Debug Wrapper

```go
package main

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

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/provider"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func debugStream(ctx context.Context, model provider.LanguageModel, prompt string) error {
    log.Printf("Starting stream for prompt: %s", prompt)

    stream, err := ai.StreamText(ctx, ai.StreamTextOptions{
        Model:  model,
        Prompt: prompt,
    })
    if err != nil {
        return fmt.Errorf("stream creation failed: %w", err)
    }

    startTime := time.Now()
    chunkCount := 0
    totalBytes := 0
    var firstChunkTime time.Duration

    log.Println("Waiting for chunks...")

    for chunk := range stream.Chunks() {
        if chunkCount == 0 {
            firstChunkTime = time.Since(startTime)
            log.Printf("Time to first chunk: %v", firstChunkTime)
        }

        chunkCount++

        switch chunk.Type {
        case provider.ChunkTypeText:
            totalBytes += len(chunk.Text)
            log.Printf("Chunk %d: %d bytes (type: text)", chunkCount, len(chunk.Text))
            fmt.Print(chunk.Text)

        case provider.ChunkTypeToolCall:
            log.Printf("Chunk %d: tool call %s (id: %s)",
                chunkCount, chunk.ToolCall.ToolName, chunk.ToolCall.ID)

        case provider.ChunkTypeError:
            log.Printf("Chunk %d: ERROR - %v", chunkCount, chunk.Err)

        case provider.ChunkTypeFinish:
            log.Printf("Chunk %d: FINISH - reason: %s", chunkCount, chunk.FinishReason)
        }
    }

    if err := stream.Err(); err != nil {
        log.Printf("Stream error: %v", err)
        return err
    }

    totalTime := time.Since(startTime)
    bytesPerSecond := float64(totalBytes) / totalTime.Seconds()

    log.Printf("\n=== Stream Statistics ===")
    log.Printf("Total chunks: %d", chunkCount)
    log.Printf("Total bytes: %d", totalBytes)
    log.Printf("Time to first chunk: %v", firstChunkTime)
    log.Printf("Total time: %v", totalTime)
    log.Printf("Throughput: %.2f bytes/sec", bytesPerSecond)

    return nil
}

func main() {
    ctx := context.Background()
    provider := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
    })
    model, _ := provider.LanguageModel("gpt-4")

    if err := debugStream(ctx, model, "Write a short story about a robot"); err != nil {
        log.Fatalf("Debug stream failed: %v", err)
    }
}
```

## Error Tracking

### Structured Error Logging

```go
package main

import (
    "context"
    "encoding/json"
    "errors"
    "log"
    "time"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    providererrors "github.com/digitallysavvy/go-ai/pkg/provider/errors"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

type ErrorLog struct {
    Timestamp    time.Time              `json:"timestamp"`
    ErrorType    string                 `json:"error_type"`
    Message      string                 `json:"message"`
    Provider     string                 `json:"provider,omitempty"`
    StatusCode   int                    `json:"status_code,omitempty"`
    ErrorCode    string                 `json:"error_code,omitempty"`
    Operation    string                 `json:"operation"`
    Prompt       string                 `json:"prompt,omitempty"`
    StackTrace   string                 `json:"stack_trace,omitempty"`
    Additional   map[string]interface{} `json:"additional,omitempty"`
}

func logError(err error, operation string, prompt string) {
    errorLog := ErrorLog{
        Timestamp: time.Now(),
        Message:   err.Error(),
        Operation: operation,
        Prompt:    prompt,
        Additional: make(map[string]interface{}),
    }

    // Check for specific error types
    var providerErr *providererrors.ProviderError
    if errors.As(err, &providerErr) {
        errorLog.ErrorType = "provider_error"
        errorLog.Provider = providerErr.Provider
        errorLog.StatusCode = providerErr.StatusCode
        errorLog.ErrorCode = providerErr.ErrorCode
    }

    var rateLimitErr *providererrors.RateLimitError
    if errors.As(err, &rateLimitErr) {
        errorLog.ErrorType = "rate_limit_error"
        errorLog.Provider = rateLimitErr.Provider
        if rateLimitErr.RetryAfterSeconds != nil {
            errorLog.Additional["retry_after_seconds"] = *rateLimitErr.RetryAfterSeconds
        }
    }

    var validationErr *providererrors.ValidationError
    if errors.As(err, &validationErr) {
        errorLog.ErrorType = "validation_error"
        errorLog.Additional["field"] = validationErr.Field
    }

    // Log as JSON
    jsonLog, _ := json.MarshalIndent(errorLog, "", "  ")
    log.Printf("ERROR LOG:\n%s", string(jsonLog))
}

func main() {
    ctx := context.Background()
    provider := openai.New(openai.Config{
        APIKey: "invalid-key", // Will cause auth error
    })
    model, _ := provider.LanguageModel("gpt-4")

    prompt := "Hello, world!"
    _, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
        Model:  model,
        Prompt: prompt,
    })

    if err != nil {
        logError(err, "generate_text", prompt)
    }
}
```

## Testing and Debugging

### Mock Provider for Testing

```go
package main

import (
    "context"
    "fmt"
    "testing"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/provider"
    "github.com/digitallysavvy/go-ai/pkg/provider/types"
    "github.com/digitallysavvy/go-ai/pkg/testutil"
)

func int64Ptr(v int64) *int64 {
    return &v
}

func TestGeneration(t *testing.T) {
    mock := &testutil.MockLanguageModel{
        DoGenerateFunc: func(ctx context.Context, opts *provider.GenerateOptions) (*types.GenerateResult, error) {
            return &types.GenerateResult{
                Text:         "Test response",
                FinishReason: types.FinishReasonStop,
                Usage: types.Usage{
                    InputTokens:  int64Ptr(10),
                    OutputTokens: int64Ptr(20),
                    TotalTokens:  int64Ptr(30),
                },
            }, nil
        },
    }

    result, err := ai.GenerateText(context.Background(), ai.GenerateTextOptions{
        Model:  mock,
        Prompt: "Test prompt",
    })

    if err != nil {
        t.Fatalf("Expected no error, got: %v", err)
    }

    if result.Text != "Test response" {
        t.Fatalf("Expected 'Test response', got: %s", result.Text)
    }
}

func TestGenerationError(t *testing.T) {
    mock := &testutil.MockLanguageModel{
        DoGenerateFunc: func(ctx context.Context, opts *provider.GenerateOptions) (*types.GenerateResult, error) {
            return nil, fmt.Errorf("mock error")
        },
    }

    _, err := ai.GenerateText(context.Background(), ai.GenerateTextOptions{
        Model:  mock,
        Prompt: "Test prompt",
    })

    if err == nil {
        t.Fatal("Expected error, got nil")
    }
}
```

`testutil.MockLanguageModel` implements `provider.LanguageModel` for you — see
[Testing](https://goaisdk.com/docs/ai-sdk-core/testing.md) for its other hooks (`DoStreamFunc`,
call tracking, etc.).

## Best Practices

### 1. Use Structured Logging

```go
log.Printf("[%s] %s: %v", level, operation, details)
```

### 2. Log Request Context

```go
log.Printf("Request: model=%s, prompt_length=%d, max_tokens=%d",
    modelName, len(prompt), maxTokens)
```

### 3. Monitor Goroutines

```go
log.Printf("Active goroutines: %d", runtime.NumGoroutine())
```

### 4. Profile Performance

```go
import _ "net/http/pprof"
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()
```

### 5. Test with Mocks

```go
mock := &testutil.MockLanguageModel{}
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{Model: mock})
```

## See Also

- [Error Handling](https://goaisdk.com/docs/ai-sdk-core/error-handling.md)
- [Testing](https://goaisdk.com/docs/ai-sdk-core/testing.md)
- [Common Errors](https://goaisdk.com/docs/troubleshooting/common-errors.md)
- [Performance](https://goaisdk.com/docs/troubleshooting/performance.md)
