From f7a601c6bc92d01c3679071a43d03604420c28ad Mon Sep 17 00:00:00 2001
From: Luke Hinds <luke@stacklok.com>
Date: Wed, 22 Jan 2025 12:19:48 +0000
Subject: [PATCH 1/5] =?UTF-8?q?Add=20configurable=20LiteLLM=20logging=20co?=
 =?UTF-8?q?ntrol=20=F0=9F=99=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

I could not take the noise anymore

Add the ability to control LiteLLM logging through configuration:
- Add --enable-litellm CLI flag
- Add CODEGATE_ENABLE_LITELLM environment variable
- Add external_loggers.litellm config file option
- Set logger level to CRITICAL+1 when disabled to suppress all logging
- Update documentation with new logging configuration options

This change allows users to enable LiteLLM debug logging when needed
while keeping it disabled by default to reduce noise.
---
 config.yaml.example              |  18 ++++
 docs/cli.md                      | 102 +++++++-----------
 docs/configuration.md            | 178 +++++++++++++++++++------------
 docs/logging.md                  |  45 +++++++-
 src/codegate/cli.py              |  23 ++--
 src/codegate/codegate_logging.py |  44 +++++++-
 src/codegate/config.py           |  18 ++++
 7 files changed, 286 insertions(+), 142 deletions(-)

diff --git a/config.yaml.example b/config.yaml.example
index 05edcbc9..3e7d0914 100644
--- a/config.yaml.example
+++ b/config.yaml.example
@@ -62,3 +62,21 @@ chat_model_n_ctx: 32768
 
 # Number of layers to offload to GPU. If -1, all layers are offloaded.
 chat_model_n_gpu_layers: -1
+
+# External logger configuration
+external_loggers:
+  litellm: false      # Enable/disable LiteLLM logging (includes LiteLLM Proxy, Router, and core)
+  sqlalchemy: false   # Enable/disable SQLAlchemy logging
+  uvicorn.error: false # Enable/disable Uvicorn error logging
+  aiosqlite: false    # Enable/disable aiosqlite logging
+
+# Note: External logger configuration can be overridden by:
+# 1. Environment variables:
+#    CODEGATE_ENABLE_LITELLM=true     # Controls all LiteLLM loggers
+#    CODEGATE_ENABLE_SQLALCHEMY=true
+#    CODEGATE_ENABLE_UVICORN_ERROR=true
+#    CODEGATE_ENABLE_AIOSQLITE=true
+# 2. CLI arguments:
+#    --enable-litellm    # Enables LiteLLM logging
+#    --enable-sqlalchemy # Enables SQLAlchemy logging
+#    etc.
diff --git a/docs/cli.md b/docs/cli.md
index 83c3d6aa..825363e6 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -47,99 +47,63 @@ codegate serve [OPTIONS]
 - `--prompts FILE`: Path to YAML prompts file
   - Optional
   - Must be a valid YAML file
-  - Overrides default prompts and configuration file prompts
 
-- `--vllm-url TEXT`: vLLM provider URL (default: `http://localhost:8000`)
+- `--vllm-url TEXT`: vLLM provider URL
   - Optional
-  - Base URL for vLLM provider (/v1 path is added automatically)
-  - Overrides configuration file and environment variables
+  - Default: http://localhost:8000/v1
 
-- `--openai-url TEXT`: OpenAI provider URL (default:
-  `https://api.openai.com/v1`)
+- `--openai-url TEXT`: OpenAI provider URL
   - Optional
-  - Base URL for OpenAI provider
-  - Overrides configuration file and environment variables
+  - Default: https://api.openai.com/v1
 
-- `--anthropic-url TEXT`: Anthropic provider URL (default:
-  `https://api.anthropic.com/v1`)
+- `--anthropic-url TEXT`: Anthropic provider URL
   - Optional
-  - Base URL for Anthropic provider
-  - Overrides configuration file and environment variables
+  - Default: https://api.anthropic.com/v1
 
-- `--ollama-url TEXT`: Ollama provider URL (default: `http://localhost:11434`)
+- `--ollama-url TEXT`: Ollama provider URL
   - Optional
-  - Base URL for Ollama provider (/api path is added automatically)
-  - Overrides configuration file and environment variables
+  - Default: http://localhost:11434/api
 
-- `--model-base-path TEXT`: Base path for loading models needed for the system
+- `--model-base-path TEXT`: Path to model base directory
   - Optional
+  - Default: ./codegate_volume/models
 
-- `--embedding-model TEXT`: Name of the model used for embeddings
+- `--embedding-model TEXT`: Name of embedding model
   - Optional
+  - Default: all-minilm-L6-v2-q5_k_m.gguf
 
-- `--db-path TEXT`: Path to a SQLite DB. Will be created if it doesn't exist.
-  (default: `./codegate_volume/db/codegate.db`)
-  - Optional
-  - Overrides configuration file and environment variables
-
-### `show-prompts`
-
-Display the loaded system prompts:
-
-```bash
-codegate show-prompts [OPTIONS]
-```
-
-#### Options
-
-- `--prompts FILE`: Path to YAML prompts file
+- `--certs-dir TEXT`: Directory for certificate files
   - Optional
-  - Must be a valid YAML file
-  - If not provided, shows default prompts from `prompts/default.yaml`
-
-### `generate_certs`
-
-Generate certificates for the CodeGate server.
+  - Default: ./certs
 
-```bash
-codegate generate-certs [OPTIONS]
-```
-
-#### Options
-
-- `--certs-out-dir PATH`: Directory path where the certificates are generated
-  (default: ./codegate_volume/certs)
+- `--ca-cert TEXT`: CA certificate file name
   - Optional
-  - Overrides configuration file and environment variables
+  - Default: ca.crt
 
-- `--ca-cert-name TEXT`: Name that will be given to the created CA certificate
-  (default: ca.crt)
+- `--ca-key TEXT`: CA key file name
   - Optional
-  - Overrides configuration file and environment variables
+  - Default: ca.key
 
-- `--ca-key-name TEXT`: Name that will be given to the created CA key (default:
-  ca.key)
+- `--server-cert TEXT`: Server certificate file name
   - Optional
-  - Overrides configuration file and environment variables
+  - Default: server.crt
 
-- `--server-cert-name TEXT`: Name that will be given to the created server
-  certificate (default: server.crt)
+- `--server-key TEXT`: Server key file name
   - Optional
-  - Overrides configuration file and environment variables
+  - Default: server.key
 
-- `--server-key-name TEXT`: Name that will be given to the created server key
-  (default: server.key)
+- `--db-path TEXT`: Path to main SQLite database file
   - Optional
-  - Overrides configuration file and environment variables
+  - Default: ./codegate_volume/db/codegate.db
 
-- `--log-level [ERROR|WARNING|INFO|DEBUG]`: Set the log level (default: INFO)
+- `--vec-db-path TEXT`: Path to vector SQLite database file
   - Optional
-  - Case-insensitive
-  - Overrides configuration file and environment variables
+  - Default: ./sqlite_data/vectordb.db
 
-- `--log-format [JSON|TEXT]`: Set the log format (default: JSON)
-  - Optional
-  - Case-insensitive
+- `--enable-litellm`: Enable LiteLLM logging
+  - Optional flag
+  - Default: false
+  - Enables logging for LiteLLM Proxy, Router, and core components
   - Overrides configuration file and environment variables
 
 ## Error handling
@@ -175,6 +139,12 @@ Start server with custom logging:
 codegate serve --log-level DEBUG --log-format TEXT
 ```
 
+Start server with LiteLLM logging enabled:
+
+```bash
+codegate serve --enable-litellm --log-level DEBUG
+```
+
 Start server with configuration file:
 
 ```bash
diff --git a/docs/configuration.md b/docs/configuration.md
index 67058151..10d8e576 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,74 +1,67 @@
-# Configuration system
+# Configuration
 
-The configuration system in CodeGate is managed through the `Config` class in
-`config.py`. It supports multiple configuration sources with a clear priority
-order.
-
-## Configuration priority
-
-Configuration sources are evaluated in the following order, from highest to
-lowest priority:
-
-1. CLI arguments
-2. Environment variables
-3. Configuration file (YAML)
-4. Default values (including default prompts from `prompts/default.yaml`)
-
-Values from higher-priority sources take precedence over lower-priority values.
-
-## Default configuration values
-
-- Port: `8989`
-- Proxy port: `8990`
-- Host: `"localhost"`
-- Log level: `"INFO"`
-- Log format: `"JSON"`
-- Prompts: default prompts from `prompts/default.yaml`
-- Provider URLs:
-  - vLLM: `"http://localhost:8000"`
-  - OpenAI: `"https://api.openai.com/v1"`
-  - Anthropic: `"https://api.anthropic.com/v1"`
-  - Ollama: `"http://localhost:11434"`
-- Certificate configuration:
-  - Certs directory: `"./certs"`
-  - CA certificate: `"ca.crt"`
-  - CA private key: `"ca.key"`
-  - Server certificate: `"server.crt"`
-  - Server private key: `"server.key"`
+CodeGate's configuration system provides flexible configuration through multiple
+methods with clear priority resolution.
 
 ## Configuration methods
 
-### Configuration file
+Configuration can be set through:
 
-Load configuration from a YAML file:
+1. CLI arguments (highest priority)
+2. Environment variables
+3. Configuration file
+4. Default values (lowest priority)
 
-```python
-config = Config.from_file("config.yaml")
-```
+## Configuration file
 
-Example config.yaml:
+The configuration file uses YAML format:
 
 ```yaml
+# Network settings
 port: 8989
 proxy_port: 8990
-host: localhost
-log_level: INFO
-log_format: JSON
-provider_urls:
-  vllm: "https://vllm.example.com"
-  openai: "https://api.openai.com/v1"
-  anthropic: "https://api.anthropic.com/v1"
-  ollama: "http://localhost:11434"
-certs_dir: "./certs"
+host: "localhost"
+
+# Logging configuration
+log_level: INFO  # ERROR, WARNING, INFO, or DEBUG
+log_format: JSON # JSON or TEXT
+
+# External logger configuration
+external_loggers:
+  litellm: false      # Enable/disable LiteLLM logging (includes LiteLLM Proxy, Router, and core)
+  sqlalchemy: false   # Enable/disable SQLAlchemy logging
+  uvicorn.error: false # Enable/disable Uvicorn error logging
+  aiosqlite: false    # Enable/disable aiosqlite logging
+
+# Model configuration
+model_base_path: "./codegate_volume/models"
+chat_model_n_ctx: 32768
+chat_model_n_gpu_layers: -1
+embedding_model: "all-minilm-L6-v2-q5_k_m.gguf"
+
+# Database configuration
+db_path: "./codegate_volume/db/codegate.db"
+vec_db_path: "./sqlite_data/vectordb.db"
+
+# Certificate configuration
+certs_dir: "./codegate_volume/certs"
 ca_cert: "ca.crt"
 ca_key: "ca.key"
 server_cert: "server.crt"
 server_key: "server.key"
+force_certs: false
+
+# Provider URLs
+provider_urls:
+  vllm: "http://localhost:8000"
+  openai: "https://api.openai.com/v1"
+  anthropic: "https://api.anthropic.com/v1"
+  ollama: "http://localhost:11434"
 ```
 
-### Environment variables
+## Environment variables
 
-Environment variables are automatically loaded with these mappings:
+Environment variables follow the pattern `CODEGATE_*`:
 
 - `CODEGATE_APP_PORT`: server port
 - `CODEGATE_APP_PROXY_PORT`: server proxy port
@@ -85,6 +78,10 @@ Environment variables are automatically loaded with these mappings:
 - `CODEGATE_CA_KEY`: CA key file name
 - `CODEGATE_SERVER_CERT`: server certificate file name
 - `CODEGATE_SERVER_KEY`: server key file name
+- `CODEGATE_ENABLE_LITELLM`: enable LiteLLM logging
+- `CODEGATE_ENABLE_SQLALCHEMY`: enable SQLAlchemy logging
+- `CODEGATE_ENABLE_UVICORN_ERROR`: enable Uvicorn error logging
+- `CODEGATE_ENABLE_AIOSQLITE`: enable aiosqlite logging
 
 ```python
 config = Config.from_env()
@@ -118,24 +115,54 @@ Network settings can be configured in several ways:
    codegate serve --port 8989 --proxy-port 8990 --host localhost
    ```
 
+### Logging configuration
+
+Logging can be configured through:
+
+1. Configuration file:
+
+   ```yaml
+   log_level: DEBUG
+   log_format: TEXT
+   external_loggers:
+     litellm: true
+     sqlalchemy: false
+     uvicorn.error: false
+     aiosqlite: false
+   ```
+
+2. Environment variables:
+
+   ```bash
+   export CODEGATE_APP_LOG_LEVEL=DEBUG
+   export CODEGATE_LOG_FORMAT=TEXT
+   export CODEGATE_ENABLE_LITELLM=true
+   ```
+
+3. CLI flags:
+
+   ```bash
+   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm
+   ```
+
 ### Provider URLs
 
-Provider URLs can be configured in several ways:
+Provider URLs can be configured through:
 
 1. Configuration file:
 
    ```yaml
    provider_urls:
-     vllm: "https://vllm.example.com" # /v1 path is added automatically
+     vllm: "http://localhost:8000"
      openai: "https://api.openai.com/v1"
      anthropic: "https://api.anthropic.com/v1"
-     ollama: "http://localhost:11434" # /api path is added automatically
+     ollama: "http://localhost:11434"
    ```
 
 2. Environment variables:
 
    ```bash
-   export CODEGATE_PROVIDER_VLLM_URL=https://vllm.example.com
+   export CODEGATE_PROVIDER_VLLM_URL=http://localhost:8000
    export CODEGATE_PROVIDER_OPENAI_URL=https://api.openai.com/v1
    export CODEGATE_PROVIDER_ANTHROPIC_URL=https://api.anthropic.com/v1
    export CODEGATE_PROVIDER_OLLAMA_URL=http://localhost:11434
@@ -144,19 +171,12 @@ Provider URLs can be configured in several ways:
 3. CLI flags:
 
    ```bash
-   codegate serve --vllm-url https://vllm.example.com --ollama-url http://localhost:11434
+   codegate serve --vllm-url http://localhost:8000 --openai-url https://api.openai.com/v1
    ```
 
-Note:
-
-- For the vLLM provider, the `/v1` path is automatically appended to the base
-  URL if not present.
-- For the Ollama provider, the `/api` path is automatically appended to the base
-  URL if not present.
-
 ### Certificate configuration
 
-Certificate files can be configured in several ways:
+Certificate settings can be configured through:
 
 1. Configuration file:
 
@@ -200,6 +220,32 @@ Available log formats (case-insensitive):
 - `JSON`
 - `TEXT`
 
+### External loggers
+
+External logger configuration controls logging for third-party components:
+
+1. Configuration file:
+   ```yaml
+   external_loggers:
+     litellm: false      # LiteLLM logging (Proxy, Router, core)
+     sqlalchemy: false   # SQLAlchemy logging
+     uvicorn.error: false # Uvicorn error logging
+     aiosqlite: false    # aiosqlite logging
+   ```
+
+2. Environment variables:
+   ```bash
+   export CODEGATE_ENABLE_LITELLM=true
+   export CODEGATE_ENABLE_SQLALCHEMY=true
+   export CODEGATE_ENABLE_UVICORN_ERROR=true
+   export CODEGATE_ENABLE_AIOSQLITE=true
+   ```
+
+3. CLI flags:
+   ```bash
+   codegate serve --enable-litellm
+   ```
+
 ### Prompts configuration
 
 Prompts can be configured in several ways:
diff --git a/docs/logging.md b/docs/logging.md
index a88cb212..0666cac7 100644
--- a/docs/logging.md
+++ b/docs/logging.md
@@ -10,6 +10,39 @@ Logs are automatically routed based on their level:
 - **stdout**: INFO and DEBUG messages
 - **stderr**: ERROR, CRITICAL, and WARNING messages
 
+## External Logger Configuration
+
+CodeGate provides control over external loggers through configuration:
+
+### LiteLLM Logging
+
+LiteLLM logging can be controlled through:
+
+1. CLI flag:
+   ```bash
+   codegate serve --enable-litellm
+   ```
+
+2. Environment variable:
+   ```bash
+   export CODEGATE_ENABLE_LITELLM=true
+   ```
+
+3. Configuration file:
+   ```yaml
+   external_loggers:
+     litellm: true      # Enable/disable LiteLLM logging (includes LiteLLM Proxy, Router, and core)
+     sqlalchemy: false  # Enable/disable SQLAlchemy logging
+     uvicorn.error: false # Enable/disable Uvicorn error logging
+     aiosqlite: false   # Enable/disable aiosqlite logging
+   ```
+
+The configuration follows the priority order:
+1. CLI arguments (highest priority)
+2. Environment variables
+3. Config file
+4. Default values (lowest priority)
+
 ## Log formats
 
 ### JSON format
@@ -45,6 +78,7 @@ YYYY-MM-DDThh:mm:ss.mmmZ - LEVEL - NAME - MESSAGE
 - **Exception support**: full exception and stack trace integration
 - **Dual output**: separate handlers for error and non-error logs
 - **Configurable levels**: support for ERROR, WARNING, INFO, and DEBUG levels
+- **External logger control**: fine-grained control over third-party logging
 
 ## Usage examples
 
@@ -89,14 +123,15 @@ The logging system can be configured through:
 1. CLI arguments:
 
    ```bash
-   codegate serve --log-level DEBUG --log-format TEXT
+   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm
    ```
 
 2. Environment variables:
 
    ```bash
-   export APP_LOG_LEVEL=DEBUG
+   export CODEGATE_APP_LOG_LEVEL=DEBUG
    export CODEGATE_LOG_FORMAT=TEXT
+   export CODEGATE_ENABLE_LITELLM=true
    ```
 
 3. Configuration file:
@@ -104,6 +139,8 @@ The logging system can be configured through:
    ```yaml
    log_level: DEBUG
    log_format: TEXT
+   external_loggers:
+     litellm: true
    ```
 
 ## Best practices
@@ -129,3 +166,7 @@ The logging system can be configured through:
    better log aggregation and analysis.
 
 4. Enable `DEBUG` level logging during development for maximum visibility.
+
+5. Configure external loggers based on your needs:
+   - Enable LiteLLM logging when debugging LLM-related issues
+   - Keep external loggers disabled in production unless needed for troubleshooting
diff --git a/src/codegate/cli.py b/src/codegate/cli.py
index 7bcd035d..f1ee8a2c 100644
--- a/src/codegate/cli.py
+++ b/src/codegate/cli.py
@@ -246,6 +246,12 @@ def show_prompts(prompts: Optional[Path]) -> None:
     default=None,
     help="Path to the vector SQLite database file (default: ./sqlite_data/vectordb.db)",
 )
+@click.option(
+    "--enable-litellm",
+    is_flag=True,
+    default=False,
+    help="Enable LiteLLM logging (includes LiteLLM Proxy, Router, and core)",
+)
 def serve(
     port: Optional[int],
     proxy_port: Optional[int],
@@ -267,11 +273,12 @@ def serve(
     ca_key: Optional[str],
     server_cert: Optional[str],
     server_key: Optional[str],
+    enable_litellm: bool,
 ) -> None:
     """Start the codegate server."""
     try:
-        # Create provider URLs dict from CLI options
-        cli_provider_urls: Dict[str, str] = {}
+        # Create provider URLs dictionary from CLI arguments
+        cli_provider_urls = {}
         if vllm_url:
             cli_provider_urls["vllm"] = vllm_url
         if openai_url:
@@ -281,6 +288,9 @@ def serve(
         if ollama_url:
             cli_provider_urls["ollama"] = ollama_url
 
+        # Create external loggers dictionary from CLI arguments
+        cli_external_loggers = {"litellm": enable_litellm}
+
         # Load configuration with priority resolution
         cfg = Config.load(
             config_path=config,
@@ -290,7 +300,8 @@ def serve(
             cli_host=host,
             cli_log_level=log_level,
             cli_log_format=log_format,
-            cli_provider_urls=cli_provider_urls,
+            cli_provider_urls=cli_provider_urls if cli_provider_urls else None,
+            cli_external_loggers=cli_external_loggers,
             model_base_path=model_base_path,
             embedding_model=embedding_model,
             certs_dir=certs_dir,
@@ -302,8 +313,8 @@ def serve(
             vec_db_path=vec_db_path,
         )
 
-        # Set up logging first
-        setup_logging(cfg.log_level, cfg.log_format)
+        # Initialize logging
+        setup_logging(cfg.log_level, cfg.log_format, cfg.external_loggers)
         logger = structlog.get_logger("codegate").bind(origin="cli")
 
         init_db_sync(cfg.db_path)
@@ -505,7 +516,7 @@ def generate_certs(
         cli_log_level=log_level,
         cli_log_format=log_format,
     )
-    setup_logging(cfg.log_level, cfg.log_format)
+    setup_logging(cfg.log_level, cfg.log_format, cfg.external_loggers)
     logger = structlog.get_logger("codegate").bind(origin="cli")
 
     ca = CertificateAuthority.get_instance()
diff --git a/src/codegate/codegate_logging.py b/src/codegate/codegate_logging.py
index 36d0d351..44fe1380 100644
--- a/src/codegate/codegate_logging.py
+++ b/src/codegate/codegate_logging.py
@@ -3,7 +3,7 @@
 import sys
 from datetime import datetime
 from enum import Enum
-from typing import Any, Dict, Optional
+from typing import Any, Dict, List, Optional
 
 import structlog
 
@@ -48,6 +48,29 @@ def _missing_(cls, value: str) -> Optional["LogFormat"]:
             )
 
 
+# Define all LiteLLM logger names
+LITELLM_LOGGERS = [
+    "LiteLLM Proxy",
+    "LiteLLM Router",
+    "LiteLLM"
+]
+
+
+def configure_litellm_logging(enabled: bool = False, level: LogLevel = LogLevel.INFO) -> None:
+    """Configure LiteLLM logging.
+    
+    Args:
+        enabled: Whether to enable LiteLLM logging
+        level: Log level to use if enabled
+    """
+    for logger_name in LITELLM_LOGGERS:
+        logger = logging.getLogger(logger_name)
+        if not enabled:
+            logger.setLevel(logging.CRITICAL + 1)  # Effectively disables all logging
+        else:
+            logger.setLevel(getattr(logging, level.value))
+
+
 def add_origin(logger, log_method, event_dict):
     # Add 'origin' if it's bound to the logger but not explicitly in the event dict
     if "origin" not in event_dict and hasattr(logger, "_context"):
@@ -58,13 +81,17 @@ def add_origin(logger, log_method, event_dict):
 
 
 def setup_logging(
-    log_level: Optional[LogLevel] = None, log_format: Optional[LogFormat] = None
+    log_level: Optional[LogLevel] = None, 
+    log_format: Optional[LogFormat] = None,
+    external_loggers: Optional[Dict[str, bool]] = None
 ) -> logging.Logger:
     """Configure the logging system.
 
     Args:
         log_level: The logging level to use. Defaults to INFO if not specified.
         log_format: The log format to use. Defaults to JSON if not specified.
+        external_loggers: Dictionary of external logger names and whether they should be enabled.
+                        e.g. {"litellm": False, "sqlalchemy": False, "uvicorn.error": False}
 
     This configures two handlers:
     - stderr_handler: For ERROR, CRITICAL, and WARNING messages
@@ -74,6 +101,19 @@ def setup_logging(
         log_level = LogLevel.INFO
     if log_format is None:
         log_format = LogFormat.JSON
+    if external_loggers is None:
+        external_loggers = {
+            "litellm": False,
+            "sqlalchemy": False, 
+            "uvicorn.error": False,
+            "aiosqlite": False
+        }
+
+    # Configure LiteLLM logging based on external_loggers setting
+    configure_litellm_logging(
+        enabled=external_loggers.get("litellm", False),
+        level=log_level
+    )
 
     # The configuration was taken from structlog documentation
     # https://www.structlog.org/en/stable/standard-library.html
diff --git a/src/codegate/config.py b/src/codegate/config.py
index 3f99fd04..3ba1b433 100644
--- a/src/codegate/config.py
+++ b/src/codegate/config.py
@@ -38,6 +38,14 @@ class Config:
     log_format: LogFormat = LogFormat.JSON
     prompts: PromptConfig = field(default_factory=PromptConfig)
 
+    # External logger configuration
+    external_loggers: Dict[str, bool] = field(default_factory=lambda: {
+        "litellm": False,
+        "sqlalchemy": False,
+        "uvicorn.error": False,
+        "aiosqlite": False
+    })
+
     model_base_path: str = "./codegate_volume/models"
     chat_model_n_ctx: int = 32768
     chat_model_n_gpu_layers: int = -1
@@ -222,6 +230,7 @@ def load(
         cli_log_level: Optional[str] = None,
         cli_log_format: Optional[str] = None,
         cli_provider_urls: Optional[Dict[str, str]] = None,
+        cli_external_loggers: Optional[Dict[str, bool]] = None,
         model_base_path: Optional[str] = None,
         embedding_model: Optional[str] = None,
         certs_dir: Optional[str] = None,
@@ -250,6 +259,7 @@ def load(
             cli_log_level: Optional CLI log level override
             cli_log_format: Optional CLI log format override
             cli_provider_urls: Optional dict of provider URLs from CLI
+            cli_external_loggers: Optional dict of external logger configuration from CLI
             model_base_path: Optional path to model base directory
             embedding_model: Optional name of the model to use for embeddings
             certs_dir: Optional path to certificates directory
@@ -317,6 +327,12 @@ def load(
         for provider, url in env_config.provider_urls.items():
             config.provider_urls[provider] = url
 
+        # Override external logger configuration from environment
+        for logger_name, enabled in env_config.external_loggers.items():
+            env_var = f"CODEGATE_ENABLE_{logger_name.upper().replace('.', '_')}"
+            if env_var in os.environ:
+                config.external_loggers[logger_name] = enabled
+
         # Override with CLI arguments
         if cli_port is not None:
             config.port = cli_port
@@ -352,6 +368,8 @@ def load(
             config.vec_db_path = vec_db_path
         if force_certs is not None:
             config.force_certs = force_certs
+        if cli_external_loggers is not None:
+            config.external_loggers.update(cli_external_loggers)
 
         # Set the __config class attribute
         Config.__config = config

From 66cdc60d4f8a0a9f92853b4ab894014bb4663bce Mon Sep 17 00:00:00 2001
From: Luke Hinds <luke@stacklok.com>
Date: Wed, 22 Jan 2025 12:31:56 +0000
Subject: [PATCH 2/5] Fix docs

---
 docs/cli.md           | 102 ++++++++++++++++--------
 docs/configuration.md | 181 +++++++++++++++++++-----------------------
 docs/logging.md       |   2 +-
 3 files changed, 150 insertions(+), 135 deletions(-)

diff --git a/docs/cli.md b/docs/cli.md
index 825363e6..b0e0fdd4 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -47,58 +47,100 @@ codegate serve [OPTIONS]
 - `--prompts FILE`: Path to YAML prompts file
   - Optional
   - Must be a valid YAML file
+  - Overrides default prompts and configuration file prompts
 
-- `--vllm-url TEXT`: vLLM provider URL
+- `--vllm-url TEXT`: vLLM provider URL (default: `http://localhost:8000`)
   - Optional
-  - Default: http://localhost:8000/v1
+  - Base URL for vLLM provider (/v1 path is added automatically)
+  - Overrides configuration file and environment variables
 
-- `--openai-url TEXT`: OpenAI provider URL
+- `--openai-url TEXT`: OpenAI provider URL (default:
+  `https://api.openai.com/v1`)
   - Optional
-  - Default: https://api.openai.com/v1
+  - Base URL for OpenAI provider
+  - Overrides configuration file and environment variables
+
+- `--anthropic-url TEXT`: Anthropic provider URL (default:
+  `https://api.anthropic.com/v1`)
+  - Optional
+  - Base URL for Anthropic provider
+  - Overrides configuration file and environment variables
 
-- `--anthropic-url TEXT`: Anthropic provider URL
+- `--ollama-url TEXT`: Ollama provider URL (default: `http://localhost:11434`)
   - Optional
-  - Default: https://api.anthropic.com/v1
+  - Base URL for Ollama provider (/api path is added automatically)
+  - Overrides configuration file and environment variables
 
-- `--ollama-url TEXT`: Ollama provider URL
+- `--model-base-path TEXT`: Base path for loading models needed for the system
   - Optional
-  - Default: http://localhost:11434/api
 
-- `--model-base-path TEXT`: Path to model base directory
+- `--embedding-model TEXT`: Name of the model used for embeddings
   - Optional
-  - Default: ./codegate_volume/models
 
-- `--embedding-model TEXT`: Name of embedding model
+- `--db-path TEXT`: Path to a SQLite DB. Will be created if it doesn't exist.
+  (default: `./codegate_volume/db/codegate.db`)
   - Optional
-  - Default: all-minilm-L6-v2-q5_k_m.gguf
+  - Overrides configuration file and environment variables
+
+### `show-prompts`
+
+Display the loaded system prompts:
+
+```bash
+codegate show-prompts [OPTIONS]
+```
+
+#### Options
+
+- `--prompts FILE`: Path to YAML prompts file
+  - Optional
+  - Must be a valid YAML file
+  - If not provided, shows default prompts from `prompts/default.yaml`
+
+### `generate_certs`
 
-- `--certs-dir TEXT`: Directory for certificate files
+Generate certificates for the CodeGate server.
+
+```bash
+codegate generate-certs [OPTIONS]
+```
+
+#### Options
+
+- `--certs-out-dir PATH`: Directory path where the certificates are generated
+  (default: ./codegate_volume/certs)
   - Optional
-  - Default: ./certs
+  - Overrides configuration file and environment variables
 
-- `--ca-cert TEXT`: CA certificate file name
+- `--ca-cert-name TEXT`: Name that will be given to the created CA certificate
+  (default: ca.crt)
   - Optional
-  - Default: ca.crt
+  - Overrides configuration file and environment variables
 
-- `--ca-key TEXT`: CA key file name
+- `--ca-key-name TEXT`: Name that will be given to the created CA key (default:
+  ca.key)
   - Optional
-  - Default: ca.key
+  - Overrides configuration file and environment variables
 
-- `--server-cert TEXT`: Server certificate file name
+- `--server-cert-name TEXT`: Name that will be given to the created server
+  certificate (default: server.crt)
   - Optional
-  - Default: server.crt
+  - Overrides configuration file and environment variables
 
-- `--server-key TEXT`: Server key file name
+- `--server-key-name TEXT`: Name that will be given to the created server key
+  (default: server.key)
   - Optional
-  - Default: server.key
+  - Overrides configuration file and environment variables
 
-- `--db-path TEXT`: Path to main SQLite database file
+- `--log-level [ERROR|WARNING|INFO|DEBUG]`: Set the log level (default: INFO)
   - Optional
-  - Default: ./codegate_volume/db/codegate.db
+  - Case-insensitive
+  - Overrides configuration file and environment variables
 
-- `--vec-db-path TEXT`: Path to vector SQLite database file
+- `--log-format [JSON|TEXT]`: Set the log format (default: JSON)
   - Optional
-  - Default: ./sqlite_data/vectordb.db
+  - Case-insensitive
+  - Overrides configuration file and environment variables
 
 - `--enable-litellm`: Enable LiteLLM logging
   - Optional flag
@@ -139,12 +181,6 @@ Start server with custom logging:
 codegate serve --log-level DEBUG --log-format TEXT
 ```
 
-Start server with LiteLLM logging enabled:
-
-```bash
-codegate serve --enable-litellm --log-level DEBUG
-```
-
 Start server with configuration file:
 
 ```bash
@@ -187,4 +223,4 @@ Generate certificates with default settings:
 codegate generate-certs
 ```
 
-<!-- markdownlint-configure-file { "no-duplicate-heading": { "siblings_only": true } } -->
+<!-- markdownlint-configure-file { "no-duplicate-heading": { "siblings_only": true } } -->
\ No newline at end of file
diff --git a/docs/configuration.md b/docs/configuration.md
index 10d8e576..6f4c16f4 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,67 +1,81 @@
-# Configuration
+# Configuration system
 
-CodeGate's configuration system provides flexible configuration through multiple
-methods with clear priority resolution.
+The configuration system in CodeGate is managed through the `Config` class in
+`config.py`. It supports multiple configuration sources with a clear priority
+order.
 
-## Configuration methods
+## Configuration priority
 
-Configuration can be set through:
+Configuration sources are evaluated in the following order, from highest to
+lowest priority:
 
-1. CLI arguments (highest priority)
+1. CLI arguments
 2. Environment variables
-3. Configuration file
-4. Default values (lowest priority)
+3. Configuration file (YAML)
+4. Default values (including default prompts from `prompts/default.yaml`)
+
+Values from higher-priority sources take precedence over lower-priority values.
+
+## Default configuration values
+
+- Port: `8989`
+- Proxy port: `8990`
+- Host: `"localhost"`
+- Log level: `"INFO"`
+- Log format: `"JSON"`
+- Prompts: default prompts from `prompts/default.yaml`
+- Provider URLs:
+  - vLLM: `"http://localhost:8000"`
+  - OpenAI: `"https://api.openai.com/v1"`
+  - Anthropic: `"https://api.anthropic.com/v1"`
+  - Ollama: `"http://localhost:11434"`
+- Certificate configuration:
+  - Certs directory: `"./certs"`
+  - CA certificate: `"ca.crt"`
+  - CA private key: `"ca.key"`
+  - Server certificate: `"server.crt"`
+  - Server private key: `"server.key"`
+
+- External logger configuration
+  - external_loggers:
+   - litellm: `false`
+   - sqlalchemy: `false`
+   - uvicorn.error: `false`
+   - aiosqlite: `false`
+
+## Configuration methods
+
+### Configuration file
 
-## Configuration file
+Load configuration from a YAML file:
+
+```python
+config = Config.from_file("config.yaml")
+```
 
-The configuration file uses YAML format:
+Example config.yaml:
 
 ```yaml
-# Network settings
 port: 8989
 proxy_port: 8990
-host: "localhost"
-
-# Logging configuration
-log_level: INFO  # ERROR, WARNING, INFO, or DEBUG
-log_format: JSON # JSON or TEXT
-
-# External logger configuration
-external_loggers:
-  litellm: false      # Enable/disable LiteLLM logging (includes LiteLLM Proxy, Router, and core)
-  sqlalchemy: false   # Enable/disable SQLAlchemy logging
-  uvicorn.error: false # Enable/disable Uvicorn error logging
-  aiosqlite: false    # Enable/disable aiosqlite logging
-
-# Model configuration
-model_base_path: "./codegate_volume/models"
-chat_model_n_ctx: 32768
-chat_model_n_gpu_layers: -1
-embedding_model: "all-minilm-L6-v2-q5_k_m.gguf"
-
-# Database configuration
-db_path: "./codegate_volume/db/codegate.db"
-vec_db_path: "./sqlite_data/vectordb.db"
-
-# Certificate configuration
-certs_dir: "./codegate_volume/certs"
-ca_cert: "ca.crt"
-ca_key: "ca.key"
-server_cert: "server.crt"
-server_key: "server.key"
-force_certs: false
-
-# Provider URLs
+host: localhost
+log_level: INFO
+log_format: JSON
 provider_urls:
-  vllm: "http://localhost:8000"
+  vllm: "https://vllm.example.com"
   openai: "https://api.openai.com/v1"
   anthropic: "https://api.anthropic.com/v1"
   ollama: "http://localhost:11434"
+certs_dir: "./certs"
+ca_cert: "ca.crt"
+ca_key: "ca.key"
+server_cert: "server.crt"
+server_key: "server.key"
 ```
 
-## Environment variables
+### Environment variables
 
-Environment variables follow the pattern `CODEGATE_*`:
+Environment variables are automatically loaded with these mappings:
 
 - `CODEGATE_APP_PORT`: server port
 - `CODEGATE_APP_PROXY_PORT`: server proxy port
@@ -115,54 +129,24 @@ Network settings can be configured in several ways:
    codegate serve --port 8989 --proxy-port 8990 --host localhost
    ```
 
-### Logging configuration
-
-Logging can be configured through:
-
-1. Configuration file:
-
-   ```yaml
-   log_level: DEBUG
-   log_format: TEXT
-   external_loggers:
-     litellm: true
-     sqlalchemy: false
-     uvicorn.error: false
-     aiosqlite: false
-   ```
-
-2. Environment variables:
-
-   ```bash
-   export CODEGATE_APP_LOG_LEVEL=DEBUG
-   export CODEGATE_LOG_FORMAT=TEXT
-   export CODEGATE_ENABLE_LITELLM=true
-   ```
-
-3. CLI flags:
-
-   ```bash
-   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm
-   ```
-
 ### Provider URLs
 
-Provider URLs can be configured through:
+Provider URLs can be configured in several ways:
 
 1. Configuration file:
 
    ```yaml
    provider_urls:
-     vllm: "http://localhost:8000"
+     vllm: "https://vllm.example.com" # /v1 path is added automatically
      openai: "https://api.openai.com/v1"
      anthropic: "https://api.anthropic.com/v1"
-     ollama: "http://localhost:11434"
+     ollama: "http://localhost:11434" # /api path is added automatically
    ```
 
 2. Environment variables:
 
    ```bash
-   export CODEGATE_PROVIDER_VLLM_URL=http://localhost:8000
+   export CODEGATE_PROVIDER_VLLM_URL=https://vllm.example.com
    export CODEGATE_PROVIDER_OPENAI_URL=https://api.openai.com/v1
    export CODEGATE_PROVIDER_ANTHROPIC_URL=https://api.anthropic.com/v1
    export CODEGATE_PROVIDER_OLLAMA_URL=http://localhost:11434
@@ -171,12 +155,19 @@ Provider URLs can be configured through:
 3. CLI flags:
 
    ```bash
-   codegate serve --vllm-url http://localhost:8000 --openai-url https://api.openai.com/v1
+   codegate serve --vllm-url https://vllm.example.com --ollama-url http://localhost:11434
    ```
 
+Note:
+
+- For the vLLM provider, the `/v1` path is automatically appended to the base
+  URL if not present.
+- For the Ollama provider, the `/api` path is automatically appended to the base
+  URL if not present.
+
 ### Certificate configuration
 
-Certificate settings can be configured through:
+Certificate files can be configured in several ways:
 
 1. Configuration file:
 
@@ -220,30 +211,18 @@ Available log formats (case-insensitive):
 - `JSON`
 - `TEXT`
 
-### External loggers
+### External logger configuration
 
-External logger configuration controls logging for third-party components:
+External loggers can be configured in several ways:
 
 1. Configuration file:
+
    ```yaml
    external_loggers:
-     litellm: false      # LiteLLM logging (Proxy, Router, core)
-     sqlalchemy: false   # SQLAlchemy logging
-     uvicorn.error: false # Uvicorn error logging
-     aiosqlite: false    # aiosqlite logging
-   ```
-
-2. Environment variables:
-   ```bash
-   export CODEGATE_ENABLE_LITELLM=true
-   export CODEGATE_ENABLE_SQLALCHEMY=true
-   export CODEGATE_ENABLE_UVICORN_ERROR=true
-   export CODEGATE_ENABLE_AIOSQLITE=true
-   ```
-
-3. CLI flags:
-   ```bash
-   codegate serve --enable-litellm
+     litellm: false
+     sqlalchemy: false
+     uvicorn.error: false
+     aiosqlite: false
    ```
 
 ### Prompts configuration
diff --git a/docs/logging.md b/docs/logging.md
index 0666cac7..7f348f73 100644
--- a/docs/logging.md
+++ b/docs/logging.md
@@ -123,7 +123,7 @@ The logging system can be configured through:
 1. CLI arguments:
 
    ```bash
-   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm
+   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm # to enable LiteLLM debug
    ```
 
 2. Environment variables:

From 6e84fb6eb3754dfd1c3be103d306e944c641662b Mon Sep 17 00:00:00 2001
From: Luke Hinds <luke@stacklok.com>
Date: Wed, 22 Jan 2025 12:59:31 +0000
Subject: [PATCH 3/5] Fix tests

---
 src/codegate/codegate_logging.py | 24 +++++++++-
 src/codegate/config.py           | 40 +++++++++++-----
 tests/test_config.py             | 81 ++++++++++++++++++++++++++++++++
 tests/test_logging.py            | 39 +++++++++++++++
 tests/test_server.py             | 35 +++++++++++---
 5 files changed, 199 insertions(+), 20 deletions(-)

diff --git a/src/codegate/codegate_logging.py b/src/codegate/codegate_logging.py
index 44fe1380..36408231 100644
--- a/src/codegate/codegate_logging.py
+++ b/src/codegate/codegate_logging.py
@@ -63,12 +63,34 @@ def configure_litellm_logging(enabled: bool = False, level: LogLevel = LogLevel.
         enabled: Whether to enable LiteLLM logging
         level: Log level to use if enabled
     """
+    # Configure the main litellm logger
+    logger = logging.getLogger("litellm")
+    logger.disabled = not enabled
+    if not enabled:
+        logger.setLevel(logging.CRITICAL + 1)  # Effectively disables all logging
+    else:
+        logger.setLevel(getattr(logging, level.value))
+        logger.propagate = False
+        # Clear any existing handlers
+        logger.handlers.clear()
+        # Add a handler to ensure logs are properly routed
+        handler = logging.StreamHandler()
+        handler.setLevel(getattr(logging, level.value))
+        logger.addHandler(handler)
+
+    # Also configure the specific LiteLLM loggers
     for logger_name in LITELLM_LOGGERS:
         logger = logging.getLogger(logger_name)
+        logger.disabled = not enabled
         if not enabled:
-            logger.setLevel(logging.CRITICAL + 1)  # Effectively disables all logging
+            logger.setLevel(logging.CRITICAL + 1)
         else:
             logger.setLevel(getattr(logging, level.value))
+            logger.propagate = False
+            logger.handlers.clear()
+            handler = logging.StreamHandler()
+            handler.setLevel(getattr(logging, level.value))
+            logger.addHandler(handler)
 
 
 def add_origin(logger, log_method, event_dict):
diff --git a/src/codegate/config.py b/src/codegate/config.py
index 3ba1b433..95c524dd 100644
--- a/src/codegate/config.py
+++ b/src/codegate/config.py
@@ -137,6 +137,14 @@ def from_file(cls, config_path: Union[str, Path]) -> "Config":
             if "provider_urls" in config_data:
                 provider_urls.update(config_data.pop("provider_urls"))
 
+            # Get default external loggers
+            default_external_loggers = {
+                "litellm": False,
+                "sqlalchemy": False,
+                "uvicorn.error": False,
+                "aiosqlite": False
+            }
+
             return cls(
                 port=config_data.get("port", cls.port),
                 proxy_port=config_data.get("proxy_port", cls.proxy_port),
@@ -159,6 +167,7 @@ def from_file(cls, config_path: Union[str, Path]) -> "Config":
                 force_certs=config_data.get("force_certs", cls.force_certs),
                 prompts=prompts_config,
                 provider_urls=provider_urls,
+                external_loggers=config_data.get("external_loggers", default_external_loggers),
             )
         except yaml.YAMLError as e:
             raise ConfigurationError(f"Failed to parse config file: {e}")
@@ -170,12 +179,15 @@ def from_env(cls) -> "Config":
         """Load configuration from environment variables.
 
         Returns:
-            Config: Configuration instance
+            Config: Configuration instance with values from environment variables.
+
+        Raises:
+            ConfigurationError: If an environment variable has an invalid value.
         """
         try:
-            # Start with default prompts
-            config = cls(prompts=cls._load_default_prompts())
+            config = cls()
 
+            # Load basic configuration
             if "CODEGATE_APP_PORT" in os.environ:
                 config.port = int(os.environ["CODEGATE_APP_PORT"])
             if "CODEGATE_APP_PROXY_PORT" in os.environ:
@@ -183,15 +195,15 @@ def from_env(cls) -> "Config":
             if "CODEGATE_APP_HOST" in os.environ:
                 config.host = os.environ["CODEGATE_APP_HOST"]
             if "CODEGATE_APP_LOG_LEVEL" in os.environ:
-                config.log_level = LogLevel(os.environ["CODEGATE_APP_LOG_LEVEL"])
+                config.log_level = LogLevel(os.environ["CODEGATE_APP_LOG_LEVEL"].upper())
             if "CODEGATE_LOG_FORMAT" in os.environ:
-                config.log_format = LogFormat(os.environ["CODEGATE_LOG_FORMAT"])
+                config.log_format = LogFormat(os.environ["CODEGATE_LOG_FORMAT"].upper())
             if "CODEGATE_PROMPTS_FILE" in os.environ:
-                config.prompts = PromptConfig.from_file(
-                    os.environ["CODEGATE_PROMPTS_FILE"]
-                )  # noqa: E501
-
-            # Load certificate configuration from environment
+                config.prompts = PromptConfig.from_file(os.environ["CODEGATE_PROMPTS_FILE"])
+            if "CODEGATE_MODEL_BASE_PATH" in os.environ:
+                config.model_base_path = os.environ["CODEGATE_MODEL_BASE_PATH"]
+            if "CODEGATE_EMBEDDING_MODEL" in os.environ:
+                config.embedding_model = os.environ["CODEGATE_EMBEDDING_MODEL"]
             if "CODEGATE_CERTS_DIR" in os.environ:
                 config.certs_dir = os.environ["CODEGATE_CERTS_DIR"]
             if "CODEGATE_CA_CERT" in os.environ:
@@ -215,6 +227,12 @@ def from_env(cls) -> "Config":
                 if env_var in os.environ:
                     config.provider_urls[provider] = os.environ[env_var]
 
+            # Load external logger configuration from environment variables
+            for logger_name in config.external_loggers.keys():
+                env_var = f"CODEGATE_ENABLE_{logger_name.upper().replace('.', '_')}"
+                if env_var in os.environ:
+                    config.external_loggers[logger_name] = os.environ[env_var].lower() == "true"
+
             return config
         except ValueError as e:
             raise ConfigurationError(f"Invalid environment variable value: {e}")
@@ -316,8 +334,6 @@ def load(
             config.server_cert = env_config.server_cert
         if "CODEGATE_SERVER_KEY" in os.environ:
             config.server_key = env_config.server_key
-        if "CODEGATE_FORCE_CERTS" in os.environ:
-            config.force_certs = env_config.force_certs
         if "CODEGATE_DB_PATH" in os.environ:
             config.db_path = env_config.db_path
         if "CODEGATE_VEC_DB_PATH" in os.environ:
diff --git a/tests/test_config.py b/tests/test_config.py
index 94037393..bb401328 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -240,3 +240,84 @@ def test_env_var_priority(config_file_with_format: Path) -> None:
         assert config.log_format == LogFormat.JSON  # env var overrides file
     finally:
         del os.environ["CODEGATE_LOG_FORMAT"]
+
+
+def test_external_loggers_from_env() -> None:
+    """Test loading external logger configuration from environment variables."""
+    os.environ.update(
+        {
+            "CODEGATE_ENABLE_LITELLM": "true",
+            "CODEGATE_ENABLE_SQLALCHEMY": "true",
+            "CODEGATE_ENABLE_UVICORN_ERROR": "false",
+            "CODEGATE_ENABLE_AIOSQLITE": "false",
+        }
+    )
+    try:
+        config = Config.from_env()
+        assert config.external_loggers["litellm"] is True
+        assert config.external_loggers["sqlalchemy"] is True
+        assert config.external_loggers["uvicorn.error"] is False
+        assert config.external_loggers["aiosqlite"] is False
+    finally:
+        for key in [
+            "CODEGATE_ENABLE_LITELLM",
+            "CODEGATE_ENABLE_SQLALCHEMY",
+            "CODEGATE_ENABLE_UVICORN_ERROR",
+            "CODEGATE_ENABLE_AIOSQLITE",
+        ]:
+            os.environ.pop(key, None)
+
+
+def test_external_loggers_from_config_file(tmp_path: Path) -> None:
+    """Test loading external logger configuration from config file."""
+    config_file = tmp_path / "config.yaml"
+    config_data = {
+        "external_loggers": {
+            "litellm": True,
+            "sqlalchemy": False,
+            "uvicorn.error": True,
+            "aiosqlite": False,
+        }
+    }
+    with open(config_file, "w") as f:
+        yaml.dump(config_data, f)
+
+    config = Config.from_file(config_file)
+    assert config.external_loggers["litellm"] is True
+    assert config.external_loggers["sqlalchemy"] is False
+    assert config.external_loggers["uvicorn.error"] is True
+    assert config.external_loggers["aiosqlite"] is False
+
+
+def test_external_loggers_defaults() -> None:
+    """Test default values for external loggers."""
+    config = Config()
+    assert config.external_loggers["litellm"] is False
+    assert config.external_loggers["sqlalchemy"] is False
+    assert config.external_loggers["uvicorn.error"] is False
+    assert config.external_loggers["aiosqlite"] is False
+
+
+def test_external_loggers_env_override_config(tmp_path: Path) -> None:
+    """Test environment variables override config file for external loggers."""
+    config_file = tmp_path / "config.yaml"
+    config_data = {
+        "external_loggers": {
+            "litellm": False,
+            "sqlalchemy": False,
+            "uvicorn.error": False,
+            "aiosqlite": False,
+        }
+    }
+    with open(config_file, "w") as f:
+        yaml.dump(config_data, f)
+
+    os.environ["CODEGATE_ENABLE_LITELLM"] = "true"
+    try:
+        config = Config.load(config_path=config_file)
+        assert config.external_loggers["litellm"] is True  # env var overrides file
+        assert config.external_loggers["sqlalchemy"] is False  # from file
+        assert config.external_loggers["uvicorn.error"] is False  # from file
+        assert config.external_loggers["aiosqlite"] is False  # from file
+    finally:
+        del os.environ["CODEGATE_ENABLE_LITELLM"]
diff --git a/tests/test_logging.py b/tests/test_logging.py
index 81bf3011..d0ca422c 100644
--- a/tests/test_logging.py
+++ b/tests/test_logging.py
@@ -1,4 +1,5 @@
 import logging
+import sys
 from io import StringIO
 
 import structlog
@@ -36,3 +37,41 @@ def test_logging_stream_output():
     log_output.seek(0)
     formatted_log = log_output.getvalue().strip()
     assert "Debug message" in formatted_log
+
+
+def test_external_logger_configuration():
+    # Test enabling litellm logging
+    setup_logging(
+        log_level=LogLevel.DEBUG,
+        log_format=LogFormat.TEXT,
+        external_loggers={"litellm": True}
+    )
+    litellm_logger = logging.getLogger("litellm")
+    assert not litellm_logger.disabled
+    assert litellm_logger.level == logging.DEBUG
+
+    # Test disabling litellm logging
+    setup_logging(
+        log_level=LogLevel.DEBUG,
+        log_format=LogFormat.TEXT,
+        external_loggers={"litellm": False}
+    )
+    litellm_logger = logging.getLogger("litellm")
+    assert litellm_logger.disabled
+    assert litellm_logger.level > logging.CRITICAL
+
+
+def test_external_logger_defaults():
+    # Test default behavior (all external loggers disabled)
+    setup_logging(log_level=LogLevel.DEBUG, log_format=LogFormat.TEXT)
+    
+    # Check all external loggers are disabled by default
+    litellm_logger = logging.getLogger("litellm")
+    sqlalchemy_logger = logging.getLogger("sqlalchemy")
+    uvicorn_logger = logging.getLogger("uvicorn.error")
+    aiosqlite_logger = logging.getLogger("aiosqlite")
+
+    assert litellm_logger.disabled
+    assert sqlalchemy_logger.disabled
+    assert uvicorn_logger.disabled
+    assert aiosqlite_logger.disabled
diff --git a/tests/test_server.py b/tests/test_server.py
index 8e07c0ee..c747b7be 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -219,7 +219,11 @@ def test_serve_default_options(cli_runner):
         assert result.exit_code == 0
 
         # Check if the logging setup was called with expected defaults
-        mock_setup_logging.assert_called_once_with(LogLevel.INFO, LogFormat.JSON)
+        mock_setup_logging.assert_called_once_with(
+            LogLevel.INFO, 
+            LogFormat.JSON,
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+        )
 
         # Validate run_servers was called once
         mock_run.assert_called_once()
@@ -254,6 +258,7 @@ def test_serve_custom_options(cli_runner):
                 "custom-server.crt",
                 "--server-key",
                 "custom-server.key",
+                "--enable-litellm",
             ],
         )
 
@@ -261,7 +266,11 @@ def test_serve_custom_options(cli_runner):
         assert result.exit_code == 0
 
         # Assert logging setup was called with the provided log level and format
-        mock_setup_logging.assert_called_once_with(LogLevel.DEBUG, LogFormat.TEXT)
+        mock_setup_logging.assert_called_once_with(
+            LogLevel.DEBUG, 
+            LogFormat.TEXT,
+            {"litellm": True, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+        )
 
         # Validate run_servers was called once
         mock_run.assert_called_once()
@@ -328,7 +337,11 @@ def test_serve_with_config_file(cli_runner, temp_config_file):
 
         # Assertions to ensure the CLI ran successfully
         assert result.exit_code == 0
-        mock_setup_logging.assert_called_once_with(LogLevel.DEBUG, LogFormat.JSON)
+        mock_setup_logging.assert_called_once_with(
+            LogLevel.DEBUG, 
+            LogFormat.JSON,
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+        )
 
         # Validate that run_servers was called with the expected configuration
         mock_run.assert_called_once()
@@ -397,7 +410,11 @@ def test_serve_priority_resolution(cli_runner: CliRunner, temp_config_file: Path
         assert result.exit_code == 0
 
         # Ensure logging setup was called with the highest priority settings (CLI arguments)
-        mock_setup_logging.assert_called_once_with("ERROR", "TEXT")
+        mock_setup_logging.assert_called_once_with(
+            LogLevel.ERROR, 
+            LogFormat.TEXT,
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+        )
 
         # Verify that the run_servers was called with the overridden settings
         config_arg = mock_run.call_args[0][0]  # Assuming Config is the first positional arg
@@ -405,8 +422,8 @@ def test_serve_priority_resolution(cli_runner: CliRunner, temp_config_file: Path
         expected_values = {
             "port": 8080,
             "host": "example.com",
-            "log_level": "ERROR",
-            "log_format": "TEXT",
+            "log_level": LogLevel.ERROR,
+            "log_format": LogFormat.TEXT,
             "certs_dir": "./cli-certs",
             "ca_cert": "cli-ca.crt",
             "ca_key": "cli-ca.key",
@@ -449,7 +466,11 @@ def test_serve_certificate_options(cli_runner: CliRunner) -> None:
         assert result.exit_code == 0
 
         # Ensure logging setup was called with expected arguments
-        mock_setup_logging.assert_called_once_with("INFO", "JSON")
+        mock_setup_logging.assert_called_once_with(
+            LogLevel.INFO, 
+            LogFormat.JSON,
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+        )
 
         # Verify that run_servers was called with the provided certificate options
         config_arg = mock_run.call_args[0][0]  # Assuming Config is the first positional arg

From ff032a145aed37226e0628a3abf97849746e22e8 Mon Sep 17 00:00:00 2001
From: Luke Hinds <luke@stacklok.com>
Date: Wed, 22 Jan 2025 13:01:21 +0000
Subject: [PATCH 4/5] Fix tests

---
 src/codegate/cli.py              |  2 +-
 src/codegate/codegate_logging.py | 23 ++++++++---------------
 src/codegate/config.py           | 16 +++++++++-------
 tests/test_logging.py            | 11 +++--------
 tests/test_server.py             | 20 ++++++++++----------
 5 files changed, 31 insertions(+), 41 deletions(-)

diff --git a/src/codegate/cli.py b/src/codegate/cli.py
index f1ee8a2c..785186b6 100644
--- a/src/codegate/cli.py
+++ b/src/codegate/cli.py
@@ -4,7 +4,7 @@
 import signal
 import sys
 from pathlib import Path
-from typing import Dict, Optional
+from typing import Optional
 
 import click
 import structlog
diff --git a/src/codegate/codegate_logging.py b/src/codegate/codegate_logging.py
index 36408231..67c7efdb 100644
--- a/src/codegate/codegate_logging.py
+++ b/src/codegate/codegate_logging.py
@@ -3,7 +3,7 @@
 import sys
 from datetime import datetime
 from enum import Enum
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, Optional
 
 import structlog
 
@@ -49,16 +49,12 @@ def _missing_(cls, value: str) -> Optional["LogFormat"]:
 
 
 # Define all LiteLLM logger names
-LITELLM_LOGGERS = [
-    "LiteLLM Proxy",
-    "LiteLLM Router",
-    "LiteLLM"
-]
+LITELLM_LOGGERS = ["LiteLLM Proxy", "LiteLLM Router", "LiteLLM"]
 
 
 def configure_litellm_logging(enabled: bool = False, level: LogLevel = LogLevel.INFO) -> None:
     """Configure LiteLLM logging.
-    
+
     Args:
         enabled: Whether to enable LiteLLM logging
         level: Log level to use if enabled
@@ -103,9 +99,9 @@ def add_origin(logger, log_method, event_dict):
 
 
 def setup_logging(
-    log_level: Optional[LogLevel] = None, 
+    log_level: Optional[LogLevel] = None,
     log_format: Optional[LogFormat] = None,
-    external_loggers: Optional[Dict[str, bool]] = None
+    external_loggers: Optional[Dict[str, bool]] = None,
 ) -> logging.Logger:
     """Configure the logging system.
 
@@ -126,16 +122,13 @@ def setup_logging(
     if external_loggers is None:
         external_loggers = {
             "litellm": False,
-            "sqlalchemy": False, 
+            "sqlalchemy": False,
             "uvicorn.error": False,
-            "aiosqlite": False
+            "aiosqlite": False,
         }
 
     # Configure LiteLLM logging based on external_loggers setting
-    configure_litellm_logging(
-        enabled=external_loggers.get("litellm", False),
-        level=log_level
-    )
+    configure_litellm_logging(enabled=external_loggers.get("litellm", False), level=log_level)
 
     # The configuration was taken from structlog documentation
     # https://www.structlog.org/en/stable/standard-library.html
diff --git a/src/codegate/config.py b/src/codegate/config.py
index 95c524dd..f07ac948 100644
--- a/src/codegate/config.py
+++ b/src/codegate/config.py
@@ -39,12 +39,14 @@ class Config:
     prompts: PromptConfig = field(default_factory=PromptConfig)
 
     # External logger configuration
-    external_loggers: Dict[str, bool] = field(default_factory=lambda: {
-        "litellm": False,
-        "sqlalchemy": False,
-        "uvicorn.error": False,
-        "aiosqlite": False
-    })
+    external_loggers: Dict[str, bool] = field(
+        default_factory=lambda: {
+            "litellm": False,
+            "sqlalchemy": False,
+            "uvicorn.error": False,
+            "aiosqlite": False,
+        }
+    )
 
     model_base_path: str = "./codegate_volume/models"
     chat_model_n_ctx: int = 32768
@@ -142,7 +144,7 @@ def from_file(cls, config_path: Union[str, Path]) -> "Config":
                 "litellm": False,
                 "sqlalchemy": False,
                 "uvicorn.error": False,
-                "aiosqlite": False
+                "aiosqlite": False,
             }
 
             return cls(
diff --git a/tests/test_logging.py b/tests/test_logging.py
index d0ca422c..9436d261 100644
--- a/tests/test_logging.py
+++ b/tests/test_logging.py
@@ -1,5 +1,4 @@
 import logging
-import sys
 from io import StringIO
 
 import structlog
@@ -42,9 +41,7 @@ def test_logging_stream_output():
 def test_external_logger_configuration():
     # Test enabling litellm logging
     setup_logging(
-        log_level=LogLevel.DEBUG,
-        log_format=LogFormat.TEXT,
-        external_loggers={"litellm": True}
+        log_level=LogLevel.DEBUG, log_format=LogFormat.TEXT, external_loggers={"litellm": True}
     )
     litellm_logger = logging.getLogger("litellm")
     assert not litellm_logger.disabled
@@ -52,9 +49,7 @@ def test_external_logger_configuration():
 
     # Test disabling litellm logging
     setup_logging(
-        log_level=LogLevel.DEBUG,
-        log_format=LogFormat.TEXT,
-        external_loggers={"litellm": False}
+        log_level=LogLevel.DEBUG, log_format=LogFormat.TEXT, external_loggers={"litellm": False}
     )
     litellm_logger = logging.getLogger("litellm")
     assert litellm_logger.disabled
@@ -64,7 +59,7 @@ def test_external_logger_configuration():
 def test_external_logger_defaults():
     # Test default behavior (all external loggers disabled)
     setup_logging(log_level=LogLevel.DEBUG, log_format=LogFormat.TEXT)
-    
+
     # Check all external loggers are disabled by default
     litellm_logger = logging.getLogger("litellm")
     sqlalchemy_logger = logging.getLogger("sqlalchemy")
diff --git a/tests/test_server.py b/tests/test_server.py
index c747b7be..e820e7b3 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -220,9 +220,9 @@ def test_serve_default_options(cli_runner):
 
         # Check if the logging setup was called with expected defaults
         mock_setup_logging.assert_called_once_with(
-            LogLevel.INFO, 
+            LogLevel.INFO,
             LogFormat.JSON,
-            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
         )
 
         # Validate run_servers was called once
@@ -267,9 +267,9 @@ def test_serve_custom_options(cli_runner):
 
         # Assert logging setup was called with the provided log level and format
         mock_setup_logging.assert_called_once_with(
-            LogLevel.DEBUG, 
+            LogLevel.DEBUG,
             LogFormat.TEXT,
-            {"litellm": True, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+            {"litellm": True, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
         )
 
         # Validate run_servers was called once
@@ -338,9 +338,9 @@ def test_serve_with_config_file(cli_runner, temp_config_file):
         # Assertions to ensure the CLI ran successfully
         assert result.exit_code == 0
         mock_setup_logging.assert_called_once_with(
-            LogLevel.DEBUG, 
+            LogLevel.DEBUG,
             LogFormat.JSON,
-            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
         )
 
         # Validate that run_servers was called with the expected configuration
@@ -411,9 +411,9 @@ def test_serve_priority_resolution(cli_runner: CliRunner, temp_config_file: Path
 
         # Ensure logging setup was called with the highest priority settings (CLI arguments)
         mock_setup_logging.assert_called_once_with(
-            LogLevel.ERROR, 
+            LogLevel.ERROR,
             LogFormat.TEXT,
-            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
         )
 
         # Verify that the run_servers was called with the overridden settings
@@ -467,9 +467,9 @@ def test_serve_certificate_options(cli_runner: CliRunner) -> None:
 
         # Ensure logging setup was called with expected arguments
         mock_setup_logging.assert_called_once_with(
-            LogLevel.INFO, 
+            LogLevel.INFO,
             LogFormat.JSON,
-            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False}
+            {"litellm": False, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
         )
 
         # Verify that run_servers was called with the provided certificate options

From b9853c453c09c1d313632bb8800228ab144aa55c Mon Sep 17 00:00:00 2001
From: Luke Hinds <luke@stacklok.com>
Date: Fri, 24 Jan 2025 20:41:06 +0000
Subject: [PATCH 5/5] Remove from CLI

---
 config.yaml.example  |  5 +----
 docs/cli.md          |  6 ------
 docs/logging.md      | 19 ++++---------------
 src/codegate/cli.py  | 10 ----------
 tests/test_server.py |  8 --------
 5 files changed, 5 insertions(+), 43 deletions(-)

diff --git a/config.yaml.example b/config.yaml.example
index 3e7d0914..975223fa 100644
--- a/config.yaml.example
+++ b/config.yaml.example
@@ -76,7 +76,4 @@ external_loggers:
 #    CODEGATE_ENABLE_SQLALCHEMY=true
 #    CODEGATE_ENABLE_UVICORN_ERROR=true
 #    CODEGATE_ENABLE_AIOSQLITE=true
-# 2. CLI arguments:
-#    --enable-litellm    # Enables LiteLLM logging
-#    --enable-sqlalchemy # Enables SQLAlchemy logging
-#    etc.
+
diff --git a/docs/cli.md b/docs/cli.md
index b0e0fdd4..4dfef422 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -142,12 +142,6 @@ codegate generate-certs [OPTIONS]
   - Case-insensitive
   - Overrides configuration file and environment variables
 
-- `--enable-litellm`: Enable LiteLLM logging
-  - Optional flag
-  - Default: false
-  - Enables logging for LiteLLM Proxy, Router, and core components
-  - Overrides configuration file and environment variables
-
 ## Error handling
 
 The CLI provides user-friendly error messages for:
diff --git a/docs/logging.md b/docs/logging.md
index 7f348f73..954800e0 100644
--- a/docs/logging.md
+++ b/docs/logging.md
@@ -18,17 +18,12 @@ CodeGate provides control over external loggers through configuration:
 
 LiteLLM logging can be controlled through:
 
-1. CLI flag:
-   ```bash
-   codegate serve --enable-litellm
-   ```
-
-2. Environment variable:
+1. Environment variable:
    ```bash
    export CODEGATE_ENABLE_LITELLM=true
    ```
 
-3. Configuration file:
+2. Configuration file:
    ```yaml
    external_loggers:
      litellm: true      # Enable/disable LiteLLM logging (includes LiteLLM Proxy, Router, and core)
@@ -120,13 +115,7 @@ except Exception as e:
 
 The logging system can be configured through:
 
-1. CLI arguments:
-
-   ```bash
-   codegate serve --log-level DEBUG --log-format TEXT --enable-litellm # to enable LiteLLM debug
-   ```
-
-2. Environment variables:
+1. Environment variables:
 
    ```bash
    export CODEGATE_APP_LOG_LEVEL=DEBUG
@@ -134,7 +123,7 @@ The logging system can be configured through:
    export CODEGATE_ENABLE_LITELLM=true
    ```
 
-3. Configuration file:
+2. Configuration file:
 
    ```yaml
    log_level: DEBUG
diff --git a/src/codegate/cli.py b/src/codegate/cli.py
index 785186b6..8a428ab9 100644
--- a/src/codegate/cli.py
+++ b/src/codegate/cli.py
@@ -246,12 +246,6 @@ def show_prompts(prompts: Optional[Path]) -> None:
     default=None,
     help="Path to the vector SQLite database file (default: ./sqlite_data/vectordb.db)",
 )
-@click.option(
-    "--enable-litellm",
-    is_flag=True,
-    default=False,
-    help="Enable LiteLLM logging (includes LiteLLM Proxy, Router, and core)",
-)
 def serve(
     port: Optional[int],
     proxy_port: Optional[int],
@@ -273,7 +267,6 @@ def serve(
     ca_key: Optional[str],
     server_cert: Optional[str],
     server_key: Optional[str],
-    enable_litellm: bool,
 ) -> None:
     """Start the codegate server."""
     try:
@@ -288,8 +281,6 @@ def serve(
         if ollama_url:
             cli_provider_urls["ollama"] = ollama_url
 
-        # Create external loggers dictionary from CLI arguments
-        cli_external_loggers = {"litellm": enable_litellm}
 
         # Load configuration with priority resolution
         cfg = Config.load(
@@ -301,7 +292,6 @@ def serve(
             cli_log_level=log_level,
             cli_log_format=log_format,
             cli_provider_urls=cli_provider_urls if cli_provider_urls else None,
-            cli_external_loggers=cli_external_loggers,
             model_base_path=model_base_path,
             embedding_model=embedding_model,
             certs_dir=certs_dir,
diff --git a/tests/test_server.py b/tests/test_server.py
index e820e7b3..97d24e64 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -258,20 +258,12 @@ def test_serve_custom_options(cli_runner):
                 "custom-server.crt",
                 "--server-key",
                 "custom-server.key",
-                "--enable-litellm",
             ],
         )
 
         # Check the command executed successfully
         assert result.exit_code == 0
 
-        # Assert logging setup was called with the provided log level and format
-        mock_setup_logging.assert_called_once_with(
-            LogLevel.DEBUG,
-            LogFormat.TEXT,
-            {"litellm": True, "sqlalchemy": False, "uvicorn.error": False, "aiosqlite": False},
-        )
-
         # Validate run_servers was called once
         mock_run.assert_called_once()
         # Retrieve the actual Config object passed to run_servers