Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable LiteLLM logging control 🙉 #722

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,18 @@ 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 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,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 } } -->
25 changes: 25 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ Values from higher-priority sources take precedence over lower-priority values.
- 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
Expand Down Expand Up @@ -85,6 +92,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to add the word "logging" to these environment variables. Else, it hints at enabling us disabling a whole component


```python
config = Config.from_env()
Expand Down Expand Up @@ -200,6 +211,20 @@ Available log formats (case-insensitive):
- `JSON`
- `TEXT`

### External logger configuration

External loggers can be configured in several ways:

1. Configuration file:

```yaml
external_loggers:
litellm: false
sqlalchemy: false
uvicorn.error: false
aiosqlite: false
```

### Prompts configuration

Prompts can be configured in several ways:
Expand Down
48 changes: 39 additions & 9 deletions docs/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ 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. Environment variable:
```bash
export CODEGATE_ENABLE_LITELLM=true
```

2. 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
Expand Down Expand Up @@ -45,6 +73,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

Expand Down Expand Up @@ -86,24 +115,21 @@ except Exception as e:

The logging system can be configured through:

1. CLI arguments:

```bash
codegate serve --log-level DEBUG --log-format TEXT
```

2. Environment variables:
1. 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:
2. Configuration file:

```yaml
log_level: DEBUG
log_format: TEXT
external_loggers:
litellm: true
```

## Best practices
Expand All @@ -129,3 +155,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
15 changes: 8 additions & 7 deletions src/codegate/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -270,8 +270,8 @@ def serve(
) -> 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:
Expand All @@ -281,6 +281,7 @@ def serve(
if ollama_url:
cli_provider_urls["ollama"] = ollama_url


# Load configuration with priority resolution
cfg = Config.load(
config_path=config,
Expand All @@ -290,7 +291,7 @@ 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,
model_base_path=model_base_path,
embedding_model=embedding_model,
certs_dir=certs_dir,
Expand All @@ -302,8 +303,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)
Expand Down Expand Up @@ -505,7 +506,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()
Expand Down
57 changes: 56 additions & 1 deletion src/codegate/codegate_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,47 @@ 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
"""
# 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)
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):
# 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"):
Expand All @@ -58,13 +99,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
Expand All @@ -74,6 +119,16 @@ 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
Expand Down
Loading
Loading