or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfig.mdindex.mdlogging.mdmiddleware.mdserver.mdsupervisors.mdtypes.md

logging.mddocs/

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