docs/LOGGER.md

# ExLLM Logger User Guide

The ExLLM Logger provides a unified logging system with features specifically designed for debugging and monitoring LLM interactions.

## Table of Contents

- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Basic Logging](#basic-logging)
- [Context Tracking](#context-tracking)
- [Structured Logging](#structured-logging)
- [Security & Redaction](#security--redaction)
- [Best Practices](#best-practices)

## Quick Start

```elixir
# Add the logger alias in your module
alias ExLLM.Logger

# Simple logging - just like Elixir's Logger!
Logger.info("Processing request")
Logger.error("Request failed", error: reason)
Logger.debug("Response received", tokens: 150)
```

## Configuration

Configure ExLLM logging in your `config/config.exs` or `config/runtime.exs`:

```elixir
config :ex_llm,
  # Overall log level: :debug, :info, :warn, :error, :none
  log_level: :info,
  
  # Control which components log
  log_components: %{
    requests: true,     # API request logging
    responses: true,    # API response logging
    streaming: false,   # Stream events (can be noisy)
    retries: true,      # Retry attempts
    cache: false,       # Cache hits/misses
    models: true        # Model loading events
  },
  
  # Security settings
  log_redaction: %{
    api_keys: true,     # Always redact API keys
    content: false      # Redact message content (recommended for production)
  }
```

### Environment-Specific Configuration

```elixir
# config/dev.exs
config :ex_llm,
  log_level: :debug,
  log_components: %{
    requests: true,
    responses: true,
    streaming: true,  # Enable in dev for debugging
    retries: true,
    cache: true,
    models: true
  },
  log_redaction: %{
    api_keys: true,
    content: false    # Show content in dev
  }

# config/prod.exs
config :ex_llm,
  log_level: :info,
  log_components: %{
    requests: false,  # Reduce noise in production
    responses: false,
    streaming: false,
    retries: true,    # Keep retry logs
    cache: false,
    models: true
  },
  log_redaction: %{
    api_keys: true,
    content: true     # Redact content in production
  }
```

## Basic Logging

The logger provides the same simple API as Elixir's Logger:

```elixir
alias ExLLM.Logger

# Log levels
Logger.debug("Detailed debugging info")
Logger.info("General information")
Logger.warn("Warning - something might be wrong")
Logger.error("Error occurred")

# With metadata
Logger.info("Request completed", 
  provider: :openai,
  duration_ms: 250,
  tokens: 1500
)

# Metadata is automatically included in log output
# 10:23:45.123 [info] Request completed [provider=openai duration_ms=250 tokens=1500 ex_llm=true]
```

## Context Tracking

Add context that automatically appears in all logs within a scope:

### Temporary Context (within a function)

```elixir
Logger.with_context(provider: :anthropic, operation: :chat) do
  Logger.info("Starting request")
  # ... do work ...
  Logger.info("Request completed", tokens: 150)
end
# Both logs automatically include provider=anthropic operation=chat
```

### Persistent Context (for a process)

```elixir
# Set context for the rest of this process
Logger.put_context(request_id: "abc123", user_id: "user456")

# All subsequent logs include this context
Logger.info("Processing")  # Includes request_id and user_id

# Clear context when done
Logger.clear_context()
```

### Real-World Example

```elixir
defmodule MyApp.LLMService do
  alias ExLLM.Logger
  
  def process_request(user_id, messages) do
    # Set process context
    Logger.put_context(user_id: user_id, request_id: generate_id())
    
    # Use scoped context for specific operations
    Logger.with_context(provider: :openai, operation: :chat) do
      Logger.info("Starting chat request")
      
      case ExLLM.chat(:openai, messages) do
        {:ok, response} ->
          Logger.info("Chat completed", 
            tokens: response.usage.total_tokens,
            cost: response.cost.total_cost
          )
          {:ok, response}
          
        {:error, reason} ->
          Logger.error("Chat failed", error: reason)
          {:error, reason}
      end
    end
  end
end
```

## Structured Logging

ExLLM provides specialized logging functions for common LLM operations:

### API Requests and Responses

```elixir
# Automatically redacts sensitive data
Logger.log_request(:openai, url, body, headers)
# Logs: API request [provider=openai url="..." method=POST ...]

Logger.log_response(:openai, response, duration_ms)
# Logs: API response [provider=openai duration_ms=250 status=200 ...]
```

### Streaming Events

```elixir
Logger.log_stream_event(:anthropic, :start, %{url: url})
Logger.log_stream_event(:anthropic, :chunk_received, %{size: 1024})
Logger.log_stream_event(:anthropic, :complete, %{total_chunks: 45})
Logger.log_stream_event(:anthropic, :error, %{reason: "timeout"})
```

### Retry Attempts

```elixir
Logger.log_retry(:openai, attempt, max_attempts, reason, delay_ms)
# Logs: Retry attempt 2/3 [provider=openai reason="rate_limit" delay_ms=2000 ...]
```

### Cache Operations

```elixir
Logger.log_cache_event(:hit, cache_key)
Logger.log_cache_event(:miss, cache_key)
Logger.log_cache_event(:put, cache_key, %{ttl: 300_000})
Logger.log_cache_event(:evict, cache_key, %{reason: :expired})
```

### Model Events

```elixir
Logger.log_model_event(:openai, :loading, %{model: "gpt-4"})
Logger.log_model_event(:openai, :loaded, %{model: "gpt-4", size_mb: 125})
Logger.log_model_event(:openai, :error, %{model: "gpt-4", error: "not found"})
```

## Security & Redaction

The logger automatically handles sensitive data based on your configuration:

### API Key Redaction (enabled by default)

```elixir
# Original
headers = [{"Authorization", "Bearer sk-1234567890abcdef"}]

# Logged as
# [headers=[{"Authorization", "***"}] ...]
```

### Content Redaction (optional)

```elixir
# When content redaction is enabled
body = %{"messages" => [%{"role" => "user", "content" => "sensitive data"}]}

# Logged as
# [body=%{"messages" => "[1 messages]"} ...]
```

### URL Parameter Redaction

```elixir
# API keys in URLs are automatically redacted
url = "https://api.example.com/v1/chat?api_key=secret123"

# Logged as
# [url="https://api.example.com/v1/chat?api_key=***" ...]
```

## Best Practices

### 1. Use Context for Request Tracking

```elixir
def handle_request(conn, params) do
  # Set context early
  Logger.put_context(
    request_id: conn.assigns.request_id,
    user_id: get_user_id(conn),
    endpoint: conn.request_path
  )
  
  # All logs in this request now have context
  process_request(params)
end
```

### 2. Use Appropriate Log Levels

```elixir
# Debug - Detailed information for debugging
Logger.debug("Parsing response", raw_response: response)

# Info - General operational information
Logger.info("Request completed", provider: provider, duration_ms: 250)

# Warn - Something unexpected but recoverable
Logger.warn("Rate limit approaching", remaining: 10, reset_at: timestamp)

# Error - Something failed
Logger.error("API request failed", error: reason, provider: provider)
```

### 3. Structure Your Metadata

```elixir
# Good - structured metadata
Logger.info("Operation completed",
  provider: :openai,
  operation: :chat,
  duration_ms: 250,
  tokens: %{input: 100, output: 200, total: 300}
)

# Less useful - unstructured
Logger.info("Operation completed for openai chat in 250ms with 300 tokens")
```

### 4. Configure for Your Environment

Development:
- Enable debug logging
- Show all components
- Don't redact content (easier debugging)

Production:
- Info or warn level only
- Disable noisy components (requests/responses)
- Enable content redaction
- Keep important events (retries, errors)

### 5. Use Structured Logging Functions

```elixir
# Instead of
Logger.info("Retrying request", provider: :openai, attempt: 2, ...)

# Use
Logger.log_retry(:openai, 2, 3, "rate_limit", 1000)
# Consistent structure and automatic metadata
```

## Filtering Logs

You can filter ExLLM logs using the metadata tag:

```elixir
# In your Logger backend configuration
config :logger, :console,
  metadata_filter: [ex_llm: false]  # Exclude ExLLM logs

# Or include only ExLLM logs
config :logger, :console,
  metadata_filter: [ex_llm: true]   # Only ExLLM logs
```

## Examples

### Complete Request Lifecycle Logging

```elixir
defmodule MyApp.AI do
  alias ExLLM.Logger
  
  def generate_summary(text, user_id) do
    # Set up context
    Logger.with_context(
      operation: :summary,
      user_id: user_id,
      request_id: UUID.uuid4()
    ) do
      Logger.info("Starting summary generation")
      
      messages = [
        %{role: "system", content: "You are a helpful assistant."},
        %{role: "user", content: "Summarize: #{text}"}
      ]
      
      # The actual API call (logging handled internally)
      case ExLLM.chat(:openai, messages) do
        {:ok, response} ->
          Logger.info("Summary generated",
            tokens: response.usage.total_tokens,
            cost_usd: response.cost.total_cost
          )
          {:ok, response.content}
          
        {:error, reason} ->
          Logger.error("Summary generation failed", error: reason)
          {:error, reason}
      end
    end
  end
end
```

### Custom Adapter Logging

```elixir
defmodule MyCustomAdapter do
  alias ExLLM.Logger
  
  def make_request(messages, options) do
    Logger.with_context(provider: :custom, operation: :chat) do
      url = build_url(options)
      body = build_body(messages, options)
      headers = build_headers(options)
      
      # Log the request
      Logger.log_request(:custom, url, body, headers)
      
      start_time = System.monotonic_time(:millisecond)
      
      case HTTPClient.post(url, body, headers) do
        {:ok, response} ->
          duration = System.monotonic_time(:millisecond) - start_time
          Logger.log_response(:custom, response, duration)
          parse_response(response)
          
        {:error, reason} ->
          Logger.error("Request failed", error: reason)
          {:error, reason}
      end
    end
  end
end
```

## Troubleshooting

### Logs Not Appearing

1. Check your log level configuration
2. Verify the component is enabled in `log_components`
3. Ensure your Logger backend is configured to show the appropriate level

### Too Many Logs

1. Disable noisy components (streaming, requests/responses)
2. Increase log level to :warn or :error
3. Use metadata filters to exclude ExLLM logs

### Missing Context

1. Ensure you're using `with_context` or `put_context`
2. Check that context is set before logging
3. Remember that context is process-specific

## Summary

The ExLLM Logger provides:
- **Simple API** - Works like Elixir's Logger
- **Automatic Context** - Track requests across operations
- **Security** - Built-in redaction for sensitive data
- **Structure** - Consistent logging for LLM operations
- **Control** - Fine-grained configuration options

Use it everywhere in your ExLLM applications for better debugging and monitoring!