# Logging Conventions

## Overview

The SDK uses structured logging with performance optimizations for high-throughput code paths.

## Structured Logging

### Using StructuredLogger

All modules should use the `StructuredLogger` wrapper instead of standard library logging:

```python
# ✅ Good: StructuredLogger
from pinecone._internal.logging import get_logger

logger = get_logger(__name__)

logger.info("Request completed", status=200, duration=0.5)
logger.debug("Processing batch", batch_size=100, model="text-embedding-ada-002")
```

```python
# ❌ Bad: Standard library logging
import logging

logger = logging.getLogger(__name__)

logger.info("Request completed", extra={"status": 200, "duration": 0.5})
```

### Benefits

1. **Cleaner API**: Use kwargs instead of `extra={}` dict
2. **Consistency**: Enforces structured logging across the SDK
3. **Future-proof**: Centralized place to add features (sampling, filtering, rate limiting)

### Logger Origin Preservation

`StructuredLogger` preserves all standard logging behavior:

- Module names appear correctly in log output
- Logger hierarchy works as expected
- Filtering by logger name works
- Stack traces show the correct module

```python
logger = get_logger(__name__)  # Uses __name__ internally
# Log output: INFO:pinecone.inference:Request completed [status=200]
```

## Performance Optimization

### Hot-Path Guards

For DEBUG logs in high-throughput code paths (e.g., HTTP client per-request logging), add manual guards to avoid dict allocation when logging is disabled:

```python
# ✅ Good: Manual guard for hot-path DEBUG logs
if logger._logger.isEnabledFor(logging.DEBUG):
    logger.debug("HTTP request", method=method, url=url)

# ❌ Bad: No guard (allocates kwargs dict even when DEBUG disabled)
logger.debug("HTTP request", method=method, url=url)
```

### Performance Impact

Benchmarks show significant improvements with manual guards:

- **Without guard**: 0.0726s for 100k iterations
- **With guard**: 0.0286s for 100k iterations
- **Speedup**: 2.54x faster (55% time reduction)

### When to Use Guards

Use manual guards for:

- DEBUG logs in request/response loops (HTTP client)
- DEBUG logs called many times per second
- Any logging in tight loops with high iteration counts

Do NOT use guards for:

- INFO, WARNING, ERROR logs (typically enabled in production)
- Logs called infrequently (< 100x per second)
- Logs outside hot code paths

### Why Guards are Needed

Python allocates the `**kwargs` dict when calling a function, BEFORE the function body executes. The `StructuredLogger` wrapper cannot prevent this allocation because it happens at the call site.

```python
# Python allocates {'key': 'value'} BEFORE entering the function
logger.debug("message", key="value")
```

Manual guards check the log level BEFORE constructing the kwargs:

```python
# Level check happens first, kwargs never allocated if DEBUG disabled
if logger._logger.isEnabledFor(logging.DEBUG):
    logger.debug("message", key="value")
```

## Logging Levels

### DEBUG

Use for detailed diagnostic information useful during development:

```python
logger.debug("Request payload", payload=request_data, size=len(request_data))
logger.debug("Cache hit", key=cache_key, ttl=ttl_seconds)
```

Typically disabled in production. Use guards in hot paths.

### INFO

Use for important but routine events:

```python
logger.info("Generating embeddings", model=model, text_count=len(texts))
logger.info("Retrying after 5s", status=503, attempt=2)
```

May be enabled in production. Guards not typically needed.

### WARNING

Use for unexpected but handled situations:

```python
logger.warning("Request timed out, retrying", error=str(e), attempt=attempt)
logger.warning("Using fallback configuration", reason="config file not found")
```

Typically enabled in production. Guards not needed.

### ERROR

Use for errors that prevent normal operation:

```python
logger.error("Request failed after all retries", attempts=max_retries, url=url)
logger.error("HTTP error response", status=status_code, message=error_message)
```

Always enabled. Guards not needed.

### EXCEPTION

Use within exception handlers to include traceback:

```python
try:
    result = api_call()
except Exception as e:
    logger.exception("API call failed", url=url, method="POST")
    raise
```

Always enabled. Guards not needed.

## Examples

### Good: Inference API Logging

```python
from pinecone._internal.logging import get_logger

logger = get_logger(__name__)

def embed(model: str, texts: list[str]) -> EmbedResponse:
    logger.info(
        "Generating embeddings",
        model=model,
        text_count=len(texts),
        has_parameters=parameters is not None,
    )

    result = self._make_request(...)

    logger.debug(
        "Embeddings generated successfully",
        model=result.model,
        embedding_count=len(result.embeddings),
        total_tokens=result.usage.total_tokens,
    )

    return result
```

### Good: HTTP Client with Guards

```python
from pinecone._internal.logging import get_logger
import logging

logger = get_logger(__name__)

def request(method: str, url: str) -> Response:
    # Guard for hot-path DEBUG log
    if logger._logger.isEnabledFor(logging.DEBUG):
        logger.debug("HTTP request", method=method, url=url, attempt=1)

    response = self._client.request(method, url)

    # Guard for hot-path DEBUG log
    if logger._logger.isEnabledFor(logging.DEBUG):
        logger.debug("HTTP response", status=response.status_code, duration=duration)

    # INFO log without guard (typically enabled)
    if response.status_code == 429:
        logger.info("Retrying after backoff", status=429, attempt=1)

    return response
```

## Anti-Patterns

### ❌ Using Standard Logging

```python
# Bad: Standard library logging with extra={}
import logging
logger = logging.getLogger(__name__)
logger.info("Request", extra={"status": 200})
```

Use `StructuredLogger` instead for consistency.

### ❌ Guards Everywhere

```python
# Bad: Guard on ERROR log (always enabled)
if logger._logger.isEnabledFor(logging.ERROR):
    logger.error("Failed", attempts=3)
```

Only use guards on DEBUG logs in hot paths.

### ❌ String Formatting Before Level Check

```python
# Bad: String formatting happens before guard
expensive_debug_info = compute_expensive_debug_data()
if logger._logger.isEnabledFor(logging.DEBUG):
    logger.debug("Debug info", data=expensive_debug_info)
```

Move expensive computations inside the guard:

```python
# Good: Computation only when DEBUG enabled
if logger._logger.isEnabledFor(logging.DEBUG):
    expensive_debug_info = compute_expensive_debug_data()
    logger.debug("Debug info", data=expensive_debug_info)
```

## Testing

Logging behavior is tested in:

- `tests/unit/test_logging_performance.py` - Performance benchmarks
- `tests/unit/test_logging_allocation.py` - Allocation behavior tests

## References

- Python logging documentation: https://docs.python.org/3/library/logging.html
- Structured logging best practices: https://www.structlog.org/
- Performance conventions: [performance.md](./performance.md)
