The lightning-fast ASGI server.
Logging system with custom formatters, colored output support, and configurable handlers for server and access logs.
import logging
from uvicorn.logging import (
ColourizedFormatter,
DefaultFormatter,
AccessFormatter,
TRACE_LOG_LEVEL,
)Base formatter with colored output support for log messages.
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 for uvicorn server logs with TTY-based color detection.
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
"""Specialized formatter for HTTP access logs with status code coloring.
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().
"""# 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")
"""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")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",
}
)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,
)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
)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)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()}")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",
},
},
}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,
)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,
)Install with Tessl CLI
npx tessl i tessl/pypi-uvicorn