# Logging Configuration

Logging system with custom formatters, colored output support, and configurable handlers for server and access logs.

## Imports

```python
import logging
from uvicorn.logging import (
    ColourizedFormatter,
    DefaultFormatter,
    AccessFormatter,
    TRACE_LOG_LEVEL,
)
```

## Capabilities

### Colorized Formatter

Base formatter with colored output support for log messages.

```python { .api }
class ColourizedFormatter(logging.Formatter):
    """
    Custom log formatter with colored output support.

    Provides colored log level names and supports conditional color
    output based on terminal capabilities.
    """

    level_name_colors: dict[int, Callable[[str], str]]
    """
    Class attribute mapping log levels to color functions.

    Maps numeric log levels (e.g., logging.INFO, logging.ERROR) to functions
    that colorize the level name string using click.style().
    """

    def __init__(
        self,
        fmt: str | None = None,
        datefmt: str | None = None,
        style: Literal["%", "{", "$"] = "%",
        use_colors: bool | None = None,
    ) -> None:
        """
        Initialize formatter with optional color support.

        Args:
            fmt: Log format string (None uses default)
            datefmt: Date format string (None uses default)
            style: Format style ('%' for printf-style, '{' for str.format, '$' for string.Template)
            use_colors: Enable colored output (None for auto-detection)

        Attributes:
            use_colors: Whether colors are enabled
            level_name_colors: Mapping of log levels to color functions
        """

    def color_level_name(self, level_name: str, level_no: int) -> str:
        """
        Apply color to log level name.

        Args:
            level_name: Log level name (e.g., "INFO", "ERROR")
            level_no: Numeric log level

        Returns:
            Colored level name if use_colors is True, otherwise unchanged
        """

    def should_use_colors(self) -> bool:
        """
        Determine if colors should be used.

        Returns:
            True if colors should be used based on configuration and terminal

        This method can be overridden in subclasses to implement custom
        color detection logic (e.g., checking if output is a TTY).
        """

    def formatMessage(self, record: logging.LogRecord) -> str:
        """
        Format log record with colored level name.

        Args:
            record: Log record to format

        Returns:
            Formatted log message with colored level name if enabled

        This method replaces %(levelname)s with colored version.
        """
```

### Default Formatter

Default formatter for uvicorn server logs with TTY-based color detection.

```python { .api }
class DefaultFormatter(ColourizedFormatter):
    """
    Default formatter for uvicorn logs.

    Automatically enables colors when output is to a TTY (terminal).
    """

    def should_use_colors(self) -> bool:
        """
        Check if stderr is a TTY.

        Returns:
            True if sys.stderr is a TTY (terminal) and colors are not explicitly disabled
        """
```

### Access Log Formatter

Specialized formatter for HTTP access logs with status code coloring.

```python { .api }
class AccessFormatter(ColourizedFormatter):
    """
    Formatter for HTTP access logs.

    Provides status code coloring and includes HTTP status phrases
    (e.g., "200 OK", "404 Not Found") in access logs.
    """

    status_code_colours: dict[int, Callable[[int], str]]
    """
    Class attribute mapping status code ranges to color functions.

    Maps status code ranges (1xx=1, 2xx=2, etc.) to functions that
    colorize the status code using click.style().
    """

    def __init__(
        self,
        fmt: str | None = None,
        datefmt: str | None = None,
        style: Literal["%", "{", "$"] = "%",
        use_colors: bool | None = None,
    ) -> None:
        """
        Initialize access log formatter.

        Args:
            fmt: Log format string (None uses default access log format)
            datefmt: Date format string (None uses default)
            style: Format style
            use_colors: Enable colored output (None for auto-detection)

        Attributes:
            status_code_colours: Mapping of status code ranges to colors
        """

    def get_status_code(self, status_code: int) -> str:
        """
        Format status code with HTTP phrase and color.

        Args:
            status_code: HTTP status code (e.g., 200, 404, 500)

        Returns:
            Formatted status code string with phrase (e.g., "200 OK")
            and color if use_colors is True
        """

    def formatMessage(self, record: logging.LogRecord) -> str:
        """
        Format access log record.

        Args:
            record: Log record containing status_code attribute

        Returns:
            Formatted access log message with colored status code if enabled

        The record must have a status_code attribute which is replaced
        with the formatted status code from get_status_code().
        """
```

## Constants

```python { .api }
# Custom trace log level (below DEBUG)
TRACE_LOG_LEVEL: int = 5
"""
Custom TRACE log level for detailed diagnostic output.

Lower than DEBUG (10), used for extremely verbose logging.
Register with: logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")
"""
```

## Usage Examples

### Basic Logging Setup

```python
import logging
from uvicorn.logging import DefaultFormatter, TRACE_LOG_LEVEL

# Register TRACE level
logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")

# Create logger
logger = logging.getLogger("uvicorn")
logger.setLevel(logging.INFO)

# Create handler with default formatter
handler = logging.StreamHandler()
handler.setFormatter(DefaultFormatter(
    fmt="%(levelprefix)s %(message)s",
    use_colors=True,
))

logger.addHandler(handler)

# Use logger
logger.info("Server starting...")
logger.debug("Debug information")
logger.log(TRACE_LOG_LEVEL, "Trace information")
```

### Access Log Setup

```python
import logging
from uvicorn.logging import AccessFormatter

# Create access logger
access_logger = logging.getLogger("uvicorn.access")
access_logger.setLevel(logging.INFO)

# Create handler with access formatter
handler = logging.StreamHandler()
handler.setFormatter(AccessFormatter(
    fmt='%(client_addr)s - "%(request_line)s" %(status_code)s',
    use_colors=True,
))

access_logger.addHandler(handler)

# Log access (typically done by protocol handlers)
# Record needs status_code, client_addr, and request_line attributes
access_logger.info(
    "",
    extra={
        "status_code": 200,
        "client_addr": "127.0.0.1:54321",
        "request_line": "GET / HTTP/1.1",
    }
)
```

### Custom Log Configuration

```python
import logging
from uvicorn import Config

# Define logging configuration dictionary
logging_config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": "%(levelprefix)s %(asctime)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
            "use_colors": True,
        },
        "access": {
            "()": "uvicorn.logging.AccessFormatter",
            "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
            "use_colors": True,
        },
    },
    "handlers": {
        "default": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "stream": "ext://sys.stderr",
        },
        "access": {
            "class": "logging.StreamHandler",
            "formatter": "access",
            "stream": "ext://sys.stdout",
        },
    },
    "loggers": {
        "uvicorn": {
            "handlers": ["default"],
            "level": "INFO",
            "propagate": False,
        },
        "uvicorn.error": {
            "handlers": ["default"],
            "level": "INFO",
            "propagate": False,
        },
        "uvicorn.access": {
            "handlers": ["access"],
            "level": "INFO",
            "propagate": False,
        },
    },
}

# Use with uvicorn
config = Config(
    app="myapp:app",
    log_config=logging_config,
)
```

### File-Based Log Configuration

```python
from uvicorn import Config

# JSON configuration file (logging_config.json)
"""
{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": "%(levelprefix)s %(message)s"
        }
    },
    "handlers": {
        "default": {
            "class": "logging.StreamHandler",
            "formatter": "default"
        }
    },
    "loggers": {
        "uvicorn": {
            "handlers": ["default"],
            "level": "INFO"
        }
    }
}
"""

# Load from file
config = Config(
    app="myapp:app",
    log_config="logging_config.json",  # or .yaml, .ini
)
```

### Custom Formatter with Extra Styling

```python
import logging
from uvicorn.logging import ColourizedFormatter
import click

class CustomFormatter(ColourizedFormatter):
    """Custom formatter with additional styling."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Customize level colors
        self.level_name_colors = {
            logging.CRITICAL: lambda level_name: click.style(
                level_name, fg="red", bold=True, blink=True
            ),
            logging.ERROR: lambda level_name: click.style(
                level_name, fg="red", bold=True
            ),
            logging.WARNING: lambda level_name: click.style(
                level_name, fg="yellow", bold=True
            ),
            logging.INFO: lambda level_name: click.style(
                level_name, fg="green"
            ),
            logging.DEBUG: lambda level_name: click.style(
                level_name, fg="cyan"
            ),
            5: lambda level_name: click.style(  # TRACE
                level_name, fg="blue"
            ),
        }

    def formatMessage(self, record):
        # Add custom formatting
        formatted = super().formatMessage(record)

        # Add emoji indicators if colors are enabled
        if self.use_colors:
            if record.levelno >= logging.ERROR:
                formatted = "❌ " + formatted
            elif record.levelno >= logging.WARNING:
                formatted = "⚠️  " + formatted
            elif record.levelno <= logging.DEBUG:
                formatted = "🔍 " + formatted

        return formatted

# Use custom formatter
handler = logging.StreamHandler()
handler.setFormatter(CustomFormatter(
    fmt="%(levelprefix)s %(message)s",
    use_colors=True,
))

logger = logging.getLogger("uvicorn")
logger.addHandler(handler)
```

### Conditional Color Output

```python
import sys
from uvicorn.logging import DefaultFormatter

# Auto-detect TTY for colors
formatter = DefaultFormatter(
    fmt="%(levelprefix)s %(message)s",
    use_colors=None,  # Auto-detect based on TTY
)

# Force colors on
formatter_colored = DefaultFormatter(
    fmt="%(levelprefix)s %(message)s",
    use_colors=True,
)

# Force colors off
formatter_plain = DefaultFormatter(
    fmt="%(levelprefix)s %(message)s",
    use_colors=False,
)

# Check if colors will be used
print(f"Colors enabled: {formatter.should_use_colors()}")
print(f"Is TTY: {sys.stderr.isatty()}")
```

### Structured Logging

```python
import logging
import json
from uvicorn.logging import DefaultFormatter

class JSONFormatter(logging.Formatter):
    """Custom JSON formatter for structured logging."""

    def format(self, record):
        log_data = {
            "timestamp": self.formatTime(record, self.datefmt),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
        }

        # Include exception info if present
        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)

        # Include extra fields
        for key, value in record.__dict__.items():
            if key not in [
                "name", "msg", "args", "created", "filename", "funcName",
                "levelname", "levelno", "lineno", "module", "msecs",
                "message", "pathname", "process", "processName",
                "relativeCreated", "thread", "threadName", "exc_info",
                "exc_text", "stack_info",
            ]:
                log_data[key] = value

        return json.dumps(log_data)

# Configure JSON logging
logging_config = {
    "version": 1,
    "formatters": {
        "json": {
            "()": "__main__.JSONFormatter",
        },
    },
    "handlers": {
        "default": {
            "class": "logging.StreamHandler",
            "formatter": "json",
        },
    },
    "loggers": {
        "uvicorn": {
            "handlers": ["default"],
            "level": "INFO",
        },
    },
}
```

### Log to File and Console

```python
import logging
from uvicorn import Config

logging_config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": "%(levelprefix)s %(message)s",
            "use_colors": True,
        },
        "file": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "stream": "ext://sys.stderr",
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "file",
            "filename": "uvicorn.log",
            "maxBytes": 10485760,  # 10MB
            "backupCount": 5,
        },
    },
    "loggers": {
        "uvicorn": {
            "handlers": ["console", "file"],
            "level": "INFO",
            "propagate": False,
        },
        "uvicorn.access": {
            "handlers": ["console", "file"],
            "level": "INFO",
            "propagate": False,
        },
    },
}

config = Config(
    app="myapp:app",
    log_config=logging_config,
)
```

### Log Level Configuration

```python
from uvicorn import Config
import logging

# Set log level by name
config = Config(
    app="myapp:app",
    log_level="debug",  # or "trace", "info", "warning", "error", "critical"
)

# Set log level by number
config = Config(
    app="myapp:app",
    log_level=logging.DEBUG,
)

# Disable access logs
config = Config(
    app="myapp:app",
    access_log=False,
)

# Enable trace logging
from uvicorn.logging import TRACE_LOG_LEVEL

config = Config(
    app="myapp:app",
    log_level=TRACE_LOG_LEVEL,
)
```
