0
# Logging Configuration
1
2
Logging system with custom formatters, colored output support, and configurable handlers for server and access logs.
3
4
## Imports
5
6
```python
7
import logging
8
from uvicorn.logging import (
9
ColourizedFormatter,
10
DefaultFormatter,
11
AccessFormatter,
12
TRACE_LOG_LEVEL,
13
)
14
```
15
16
## Capabilities
17
18
### Colorized Formatter
19
20
Base formatter with colored output support for log messages.
21
22
```python { .api }
23
class ColourizedFormatter(logging.Formatter):
24
"""
25
Custom log formatter with colored output support.
26
27
Provides colored log level names and supports conditional color
28
output based on terminal capabilities.
29
"""
30
31
level_name_colors: dict[int, Callable[[str], str]]
32
"""
33
Class attribute mapping log levels to color functions.
34
35
Maps numeric log levels (e.g., logging.INFO, logging.ERROR) to functions
36
that colorize the level name string using click.style().
37
"""
38
39
def __init__(
40
self,
41
fmt: str | None = None,
42
datefmt: str | None = None,
43
style: Literal["%", "{", "$"] = "%",
44
use_colors: bool | None = None,
45
) -> None:
46
"""
47
Initialize formatter with optional color support.
48
49
Args:
50
fmt: Log format string (None uses default)
51
datefmt: Date format string (None uses default)
52
style: Format style ('%' for printf-style, '{' for str.format, '$' for string.Template)
53
use_colors: Enable colored output (None for auto-detection)
54
55
Attributes:
56
use_colors: Whether colors are enabled
57
level_name_colors: Mapping of log levels to color functions
58
"""
59
60
def color_level_name(self, level_name: str, level_no: int) -> str:
61
"""
62
Apply color to log level name.
63
64
Args:
65
level_name: Log level name (e.g., "INFO", "ERROR")
66
level_no: Numeric log level
67
68
Returns:
69
Colored level name if use_colors is True, otherwise unchanged
70
"""
71
72
def should_use_colors(self) -> bool:
73
"""
74
Determine if colors should be used.
75
76
Returns:
77
True if colors should be used based on configuration and terminal
78
79
This method can be overridden in subclasses to implement custom
80
color detection logic (e.g., checking if output is a TTY).
81
"""
82
83
def formatMessage(self, record: logging.LogRecord) -> str:
84
"""
85
Format log record with colored level name.
86
87
Args:
88
record: Log record to format
89
90
Returns:
91
Formatted log message with colored level name if enabled
92
93
This method replaces %(levelname)s with colored version.
94
"""
95
```
96
97
### Default Formatter
98
99
Default formatter for uvicorn server logs with TTY-based color detection.
100
101
```python { .api }
102
class DefaultFormatter(ColourizedFormatter):
103
"""
104
Default formatter for uvicorn logs.
105
106
Automatically enables colors when output is to a TTY (terminal).
107
"""
108
109
def should_use_colors(self) -> bool:
110
"""
111
Check if stderr is a TTY.
112
113
Returns:
114
True if sys.stderr is a TTY (terminal) and colors are not explicitly disabled
115
"""
116
```
117
118
### Access Log Formatter
119
120
Specialized formatter for HTTP access logs with status code coloring.
121
122
```python { .api }
123
class AccessFormatter(ColourizedFormatter):
124
"""
125
Formatter for HTTP access logs.
126
127
Provides status code coloring and includes HTTP status phrases
128
(e.g., "200 OK", "404 Not Found") in access logs.
129
"""
130
131
status_code_colours: dict[int, Callable[[int], str]]
132
"""
133
Class attribute mapping status code ranges to color functions.
134
135
Maps status code ranges (1xx=1, 2xx=2, etc.) to functions that
136
colorize the status code using click.style().
137
"""
138
139
def __init__(
140
self,
141
fmt: str | None = None,
142
datefmt: str | None = None,
143
style: Literal["%", "{", "$"] = "%",
144
use_colors: bool | None = None,
145
) -> None:
146
"""
147
Initialize access log formatter.
148
149
Args:
150
fmt: Log format string (None uses default access log format)
151
datefmt: Date format string (None uses default)
152
style: Format style
153
use_colors: Enable colored output (None for auto-detection)
154
155
Attributes:
156
status_code_colours: Mapping of status code ranges to colors
157
"""
158
159
def get_status_code(self, status_code: int) -> str:
160
"""
161
Format status code with HTTP phrase and color.
162
163
Args:
164
status_code: HTTP status code (e.g., 200, 404, 500)
165
166
Returns:
167
Formatted status code string with phrase (e.g., "200 OK")
168
and color if use_colors is True
169
"""
170
171
def formatMessage(self, record: logging.LogRecord) -> str:
172
"""
173
Format access log record.
174
175
Args:
176
record: Log record containing status_code attribute
177
178
Returns:
179
Formatted access log message with colored status code if enabled
180
181
The record must have a status_code attribute which is replaced
182
with the formatted status code from get_status_code().
183
"""
184
```
185
186
## Constants
187
188
```python { .api }
189
# Custom trace log level (below DEBUG)
190
TRACE_LOG_LEVEL: int = 5
191
"""
192
Custom TRACE log level for detailed diagnostic output.
193
194
Lower than DEBUG (10), used for extremely verbose logging.
195
Register with: logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")
196
"""
197
```
198
199
## Usage Examples
200
201
### Basic Logging Setup
202
203
```python
204
import logging
205
from uvicorn.logging import DefaultFormatter, TRACE_LOG_LEVEL
206
207
# Register TRACE level
208
logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")
209
210
# Create logger
211
logger = logging.getLogger("uvicorn")
212
logger.setLevel(logging.INFO)
213
214
# Create handler with default formatter
215
handler = logging.StreamHandler()
216
handler.setFormatter(DefaultFormatter(
217
fmt="%(levelprefix)s %(message)s",
218
use_colors=True,
219
))
220
221
logger.addHandler(handler)
222
223
# Use logger
224
logger.info("Server starting...")
225
logger.debug("Debug information")
226
logger.log(TRACE_LOG_LEVEL, "Trace information")
227
```
228
229
### Access Log Setup
230
231
```python
232
import logging
233
from uvicorn.logging import AccessFormatter
234
235
# Create access logger
236
access_logger = logging.getLogger("uvicorn.access")
237
access_logger.setLevel(logging.INFO)
238
239
# Create handler with access formatter
240
handler = logging.StreamHandler()
241
handler.setFormatter(AccessFormatter(
242
fmt='%(client_addr)s - "%(request_line)s" %(status_code)s',
243
use_colors=True,
244
))
245
246
access_logger.addHandler(handler)
247
248
# Log access (typically done by protocol handlers)
249
# Record needs status_code, client_addr, and request_line attributes
250
access_logger.info(
251
"",
252
extra={
253
"status_code": 200,
254
"client_addr": "127.0.0.1:54321",
255
"request_line": "GET / HTTP/1.1",
256
}
257
)
258
```
259
260
### Custom Log Configuration
261
262
```python
263
import logging
264
from uvicorn import Config
265
266
# Define logging configuration dictionary
267
logging_config = {
268
"version": 1,
269
"disable_existing_loggers": False,
270
"formatters": {
271
"default": {
272
"()": "uvicorn.logging.DefaultFormatter",
273
"fmt": "%(levelprefix)s %(asctime)s - %(message)s",
274
"datefmt": "%Y-%m-%d %H:%M:%S",
275
"use_colors": True,
276
},
277
"access": {
278
"()": "uvicorn.logging.AccessFormatter",
279
"fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
280
"use_colors": True,
281
},
282
},
283
"handlers": {
284
"default": {
285
"class": "logging.StreamHandler",
286
"formatter": "default",
287
"stream": "ext://sys.stderr",
288
},
289
"access": {
290
"class": "logging.StreamHandler",
291
"formatter": "access",
292
"stream": "ext://sys.stdout",
293
},
294
},
295
"loggers": {
296
"uvicorn": {
297
"handlers": ["default"],
298
"level": "INFO",
299
"propagate": False,
300
},
301
"uvicorn.error": {
302
"handlers": ["default"],
303
"level": "INFO",
304
"propagate": False,
305
},
306
"uvicorn.access": {
307
"handlers": ["access"],
308
"level": "INFO",
309
"propagate": False,
310
},
311
},
312
}
313
314
# Use with uvicorn
315
config = Config(
316
app="myapp:app",
317
log_config=logging_config,
318
)
319
```
320
321
### File-Based Log Configuration
322
323
```python
324
from uvicorn import Config
325
326
# JSON configuration file (logging_config.json)
327
"""
328
{
329
"version": 1,
330
"disable_existing_loggers": false,
331
"formatters": {
332
"default": {
333
"()": "uvicorn.logging.DefaultFormatter",
334
"fmt": "%(levelprefix)s %(message)s"
335
}
336
},
337
"handlers": {
338
"default": {
339
"class": "logging.StreamHandler",
340
"formatter": "default"
341
}
342
},
343
"loggers": {
344
"uvicorn": {
345
"handlers": ["default"],
346
"level": "INFO"
347
}
348
}
349
}
350
"""
351
352
# Load from file
353
config = Config(
354
app="myapp:app",
355
log_config="logging_config.json", # or .yaml, .ini
356
)
357
```
358
359
### Custom Formatter with Extra Styling
360
361
```python
362
import logging
363
from uvicorn.logging import ColourizedFormatter
364
import click
365
366
class CustomFormatter(ColourizedFormatter):
367
"""Custom formatter with additional styling."""
368
369
def __init__(self, *args, **kwargs):
370
super().__init__(*args, **kwargs)
371
372
# Customize level colors
373
self.level_name_colors = {
374
logging.CRITICAL: lambda level_name: click.style(
375
level_name, fg="red", bold=True, blink=True
376
),
377
logging.ERROR: lambda level_name: click.style(
378
level_name, fg="red", bold=True
379
),
380
logging.WARNING: lambda level_name: click.style(
381
level_name, fg="yellow", bold=True
382
),
383
logging.INFO: lambda level_name: click.style(
384
level_name, fg="green"
385
),
386
logging.DEBUG: lambda level_name: click.style(
387
level_name, fg="cyan"
388
),
389
5: lambda level_name: click.style( # TRACE
390
level_name, fg="blue"
391
),
392
}
393
394
def formatMessage(self, record):
395
# Add custom formatting
396
formatted = super().formatMessage(record)
397
398
# Add emoji indicators if colors are enabled
399
if self.use_colors:
400
if record.levelno >= logging.ERROR:
401
formatted = "โ " + formatted
402
elif record.levelno >= logging.WARNING:
403
formatted = "โ ๏ธ " + formatted
404
elif record.levelno <= logging.DEBUG:
405
formatted = "๐ " + formatted
406
407
return formatted
408
409
# Use custom formatter
410
handler = logging.StreamHandler()
411
handler.setFormatter(CustomFormatter(
412
fmt="%(levelprefix)s %(message)s",
413
use_colors=True,
414
))
415
416
logger = logging.getLogger("uvicorn")
417
logger.addHandler(handler)
418
```
419
420
### Conditional Color Output
421
422
```python
423
import sys
424
from uvicorn.logging import DefaultFormatter
425
426
# Auto-detect TTY for colors
427
formatter = DefaultFormatter(
428
fmt="%(levelprefix)s %(message)s",
429
use_colors=None, # Auto-detect based on TTY
430
)
431
432
# Force colors on
433
formatter_colored = DefaultFormatter(
434
fmt="%(levelprefix)s %(message)s",
435
use_colors=True,
436
)
437
438
# Force colors off
439
formatter_plain = DefaultFormatter(
440
fmt="%(levelprefix)s %(message)s",
441
use_colors=False,
442
)
443
444
# Check if colors will be used
445
print(f"Colors enabled: {formatter.should_use_colors()}")
446
print(f"Is TTY: {sys.stderr.isatty()}")
447
```
448
449
### Structured Logging
450
451
```python
452
import logging
453
import json
454
from uvicorn.logging import DefaultFormatter
455
456
class JSONFormatter(logging.Formatter):
457
"""Custom JSON formatter for structured logging."""
458
459
def format(self, record):
460
log_data = {
461
"timestamp": self.formatTime(record, self.datefmt),
462
"level": record.levelname,
463
"logger": record.name,
464
"message": record.getMessage(),
465
"module": record.module,
466
"function": record.funcName,
467
"line": record.lineno,
468
}
469
470
# Include exception info if present
471
if record.exc_info:
472
log_data["exception"] = self.formatException(record.exc_info)
473
474
# Include extra fields
475
for key, value in record.__dict__.items():
476
if key not in [
477
"name", "msg", "args", "created", "filename", "funcName",
478
"levelname", "levelno", "lineno", "module", "msecs",
479
"message", "pathname", "process", "processName",
480
"relativeCreated", "thread", "threadName", "exc_info",
481
"exc_text", "stack_info",
482
]:
483
log_data[key] = value
484
485
return json.dumps(log_data)
486
487
# Configure JSON logging
488
logging_config = {
489
"version": 1,
490
"formatters": {
491
"json": {
492
"()": "__main__.JSONFormatter",
493
},
494
},
495
"handlers": {
496
"default": {
497
"class": "logging.StreamHandler",
498
"formatter": "json",
499
},
500
},
501
"loggers": {
502
"uvicorn": {
503
"handlers": ["default"],
504
"level": "INFO",
505
},
506
},
507
}
508
```
509
510
### Log to File and Console
511
512
```python
513
import logging
514
from uvicorn import Config
515
516
logging_config = {
517
"version": 1,
518
"disable_existing_loggers": False,
519
"formatters": {
520
"default": {
521
"()": "uvicorn.logging.DefaultFormatter",
522
"fmt": "%(levelprefix)s %(message)s",
523
"use_colors": True,
524
},
525
"file": {
526
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
527
"datefmt": "%Y-%m-%d %H:%M:%S",
528
},
529
},
530
"handlers": {
531
"console": {
532
"class": "logging.StreamHandler",
533
"formatter": "default",
534
"stream": "ext://sys.stderr",
535
},
536
"file": {
537
"class": "logging.handlers.RotatingFileHandler",
538
"formatter": "file",
539
"filename": "uvicorn.log",
540
"maxBytes": 10485760, # 10MB
541
"backupCount": 5,
542
},
543
},
544
"loggers": {
545
"uvicorn": {
546
"handlers": ["console", "file"],
547
"level": "INFO",
548
"propagate": False,
549
},
550
"uvicorn.access": {
551
"handlers": ["console", "file"],
552
"level": "INFO",
553
"propagate": False,
554
},
555
},
556
}
557
558
config = Config(
559
app="myapp:app",
560
log_config=logging_config,
561
)
562
```
563
564
### Log Level Configuration
565
566
```python
567
from uvicorn import Config
568
import logging
569
570
# Set log level by name
571
config = Config(
572
app="myapp:app",
573
log_level="debug", # or "trace", "info", "warning", "error", "critical"
574
)
575
576
# Set log level by number
577
config = Config(
578
app="myapp:app",
579
log_level=logging.DEBUG,
580
)
581
582
# Disable access logs
583
config = Config(
584
app="myapp:app",
585
access_log=False,
586
)
587
588
# Enable trace logging
589
from uvicorn.logging import TRACE_LOG_LEVEL
590
591
config = Config(
592
app="myapp:app",
593
log_level=TRACE_LOG_LEVEL,
594
)
595
```
596