Tornado 日志系统完整指南:tornado.log 模块的三条日志流、格式化器与命令行配置实战
【免费下载链接】tornadoTornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.项目地址: https://gitcode.com/gh_mirrors/to/tornado
导读
本文以 Tornado 官方文档 docs/log.rst 为主题,系统讲解tornado.log模块的三大日志流(tornado.access、tornado.application、tornado.general)、内置的LogFormatter格式化器,以及通过tornado.options命令行/配置文件自动启用彩色日志、文件输出与日志轮转的完整机制。读完本文,你将掌握 Tornado 应用从「开箱即用的日志」到「按需独立配置日志流」的完整实战能力,并能从源码层面理解访问日志、应用异常日志与框架内部日志的流向。
一、概述:Tornado 为什么自带一套日志封装
Tornado 是 Python 的 Web 框架与异步网络库(最初由 FriendFeed 开发),其源码模块 tornado/log.py 位于仓库根目录的tornado/包内。该模块并不另起炉灶实现一套日志框架,而是基于 Python 标准库logging之上的一层轻量封装:它预定义了三个具名 logger,并提供了一个能输出彩色、带时间戳、抗编码问题的格式化器LogFormatter。
模块 docstring(tornado/log.py)明确指出 Tornado 使用三条日志流:
| Logger 名称 | 用途 |
|---|---|
tornado.access | HTTP 服务器的按请求(per-request)日志,未来也可能被其他服务器复用 |
tornado.application | 应用代码产生的错误日志,例如回调中未捕获的异常 |
tornado.general | 通用日志,包括 Tornado 自身的错误与警告 |
三条流可以在标准库logging模块之上独立配置——例如你希望把tornado.access日志单独输出到一份文件用于分析,其余日志仍走终端。这正是 Tornado 日志设计的第一原则:与 Python 生态完全兼容,不引入任何自定义日志语法。
在源码中,这三个 logger 以模块级单例形式创建(tornado/log.py):
access_log = logging.getLogger("tornado.access") app_log = logging.getLogger("tornado.application") gen_log = logging.getLogger("tornado.general")Tornado 各核心模块正是通过导入这三个 logger 来打日志的,例如:
- tornado/autoreload.py 使用
gen_log输出文件变更重启、进程退出等框架级信息; - tornado/ioloop.py、tornado/iostream.py 使用
app_log/gen_log记录回调异常、读写错误; - tornado/process.py 使用
gen_log记录多进程 fork 与子进程状态。
二、三条日志流在 Web 应用中的真实流向
2.1tornado.access:HTTP 访问日志
当 HTTP 请求完成时,Application.log_request会被调用(tornado/web.py)。其默认实现(tornado/web.py)按响应状态码选择不同级别的tornado.access日志:
if handler.get_status() < 400: log_method = access_log.info elif handler.get_status() < 500: log_method = access_log.warning else: log_method = access_log.error request_time = 1000.0 * handler.request.request_time() log_method( "%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time, )即:2xx/3xx 记INFO,4xx 记WARNING,5xx 记ERROR,每条日志包含状态码、请求摘要与毫秒级耗时。应用可以通过两种方式定制:在Application的 settings 中传入log_function回调(传参为RequestHandler对象),或继承Application重写log_request。
2.2tornado.application:应用未捕获异常
当 handler 处理过程中抛出未捕获异常时,RequestHandler.log_exception(tornado/web.py)负责记录:
- 若异常是
HTTPError(如raise HTTPError(404)),则仅以gen_log.warning记录状态码与摘要,不带堆栈; - 其他所有异常,以
app_log.error记录完整堆栈(exc_info传入)。
if isinstance(value, HTTPError): ... gen_log.warning(format, *args) else: app_log.error( "Uncaught exception %s\n%r", self._request_summary(), self.request, exc_info=(typ, value, tb), )若write_error自身抛出异常,同样会走app_log.error("Uncaught exception in write_error", ...)(tornado/web.py)。此外,tornado/gen.py、tornado/concurrent.py 等异步模块在协程/Future 回调异常时也统一使用app_log.error。
2.3tornado.general:框架内部事件
gen_log覆盖 Tornado 自身的事件,例如:
- tornado/http1connection.py:
gen_log.info("Malformed HTTP message from %s: %s", ...)记录畸形 HTTP 报文; - tornado/http1connection.py:记录请求体读取超时;
- tornado/iostream.py:
gen_log.error("Reached maximum read buffer size")记录读缓冲上限。
三、LogFormatter:彩色、带时间戳、抗编码的格式化器
3.1 三大特性与默认格式
LogFormatter(tornado/log.py)是 Tornado 内置的日志格式化器,核心特性:
- 颜色支持:输出到支持颜色的终端时按日志级别着色;
- 每条日志带时间戳;
- 对 str/bytes 编码问题健壮:字节串/非 UTF-8 内容不会导致日志管线崩溃。
默认格式与默认颜色映射(tornado/log.py):
DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S" DEFAULT_COLORS = { logging.DEBUG: 4, # Blue logging.INFO: 2, # Green logging.WARNING: 3, # Yellow logging.ERROR: 1, # Red logging.CRITICAL: 5, # Magenta }实际输出形如:
[I 240101 12:00:00 module:42] Hello, world [E 240101 12:00:01 web:1952] Uncaught exception ...%(color)s与%(end_color)s之间的文本会按级别着色(format方法在 tornado/log.py 中注入record.color/record.end_color)。
3.2 构造参数
构造函数签名(tornado/log.py):
LogFormatter(fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT, style="%", color=True, colors=DEFAULT_COLORS)| 参数 | 含义 |
|---|---|
color | 是否启用颜色支持,默认True(实际是否着色还取决于终端检测结果) |
fmt | 日志格式模板,应用于 log record 的属性字典,%(color)s~%(end_color)s之间部分按级别着色 |
colors | 日志级别到终端颜色码的映射 |
datefmt | 时间格式,用于格式化(asctime)占位符 |
注意:color只是"是否允许"开关,最终是否真正着色取决于_stderr_supports_color()(tornado/log.py)——它要求stderr是 TTY,且要么curses能查到终端颜色数> 0,要么在 Windows 上使用colorama包装过的stderr。
3.3 颜色支持与 Windows(colorama)
Tornado 4.5 起LogFormatter增加对colorama的支持(tornado/log.py),用于不支持 ANSI 颜色码的 Windows 版本。应用如需在 Windows 获得颜色,必须先调用colorama.init()初始化。当curses不可用(典型场景即 Windows + colorama),代码会回退到硬编码 ANSI 颜色码(tornado/log.py):
self._colors[levelno] = "\033[2;3%dm" % code self._normal = "\033[0m"自 Tornado 4.5 起,构造函数签名同时兼容
logging.config.dictConfig,因此可直接在字典配置中引用。
3.4 抗编码问题的健壮性设计
format方法(tornado/log.py)做了两件关键的事:
- 消息统一经
_safe_unicode转成 Unicode——正常情况用_unicode,遇到UnicodeDecodeError则退化为repr(s)(tornado/log.py),保证任意字节串都能成功落盘; - 异常堆栈逐行做
_safe_unicode后再拼接,避免某一行非 UTF-8 字节破坏整个 traceback 的换行结构;最终输出中所有换行统一缩进为"\n ",保证多行日志在文件里层次清晰。
四、enable_pretty_logging与命令行日志配置
4.1 自动启用机制
LogFormatter会在调用tornado.options.parse_command_line或tornado.options.parse_config_file时自动启用(除非使用--logging=none)。其背后是两条主线:
define_logging_options在 options 上注册全部日志相关参数(tornado/log.py);options.add_parse_callback(lambda: enable_pretty_logging(options))将enable_pretty_logging注册为解析回调,命令行/配置文件解析完成后自动执行。
enable_pretty_logging(tornado/log.py)的完整逻辑:
if options.logging is None or options.logging.lower() == "none": return # 显式关闭,Tornado 不碰 logging 配置 logger.setLevel(getattr(logging, options.logging.upper())) # 设置根 logger 级别 if options.log_file_prefix: # 配置了文件输出 → 按轮转模式建 Handler if rotate_mode == "size": channel = logging.handlers.RotatingFileHandler( filename=options.log_file_prefix, maxBytes=options.log_file_max_size, backupCount=options.log_file_num_backups, encoding="utf-8", ) elif rotate_mode == "time": channel = logging.handlers.TimedRotatingFileHandler( filename=options.log_file_prefix, when=options.log_rotate_when, interval=options.log_rotate_interval, backupCount=options.log_file_num_backups, encoding="utf-8", ) else: raise ValueError('The value of log_rotate_mode option should be "size" or "time"') channel.setFormatter(LogFormatter(color=False)) logger.addHandler(channel) if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers): channel = logging.StreamHandler() channel.setFormatter(LogFormatter()) # stderr 可着色 logger.addHandler(channel)注意两个细节:
- 文件输出必须禁用颜色(
LogFormatter(color=False)),避免日志文件里混入终端转义序列; - 若未设置
log_file_prefix,且根 logger 没有任何 handler,则默认输出到stderr;log_to_stderr显式为False可强制关掉 stderr 输出。
4.2 全部日志相关命令行选项
define_logging_options(tornado/log.py)注册的参数如下,均可通过命令行--xxx=value或配置文件键值对设置:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--logging | str | info | Python 日志级别,取值debug\|info\|warning\|error\|none;为none时 Tornado 不修改 logging 配置 |
--log_to_stderr | bool | None | 是否输出到 stderr(可着色)。默认:未设置log_file_prefix且无其他日志配置时输出到 stderr |
--log_file_prefix | str | None | 日志文件路径前缀。多进程运行时每个进程必须使用不同前缀(例如包含端口号),否则日志互相覆盖 |
--log_file_max_size | int | 100 * 1000 * 1000(约 100MB) | size 模式下日志文件轮转前的大小上限 |
--log_file_num_backups | int | 10 | 保留的日志文件份数 |
--log_rotate_when | str | midnight | TimedRotatingFileHandler 的时间间隔类型,可选S(秒)、M(分)、H(时)、D(天)、W0-W6(星期几) |
--log_rotate_interval | int | 1 | 时间轮转的间隔数值 |
--log_rotate_mode | str | size | 轮转模式:size(按大小)或time(按时间) |
4.3 实战示例
以仓库自带示例 demos/helloworld/helloworld.py 为蓝本,最简单的方式是直接调用tornado.options.parse_command_line(),日志配置即自动生效:
python demos/helloworld/helloworld.py --logging=info按大小轮转并写入文件:
python demos/helloworld/helloworld.py \ --log_file_prefix=/var/log/myapp/app.log \ --log_rotate_mode=size \ --log_file_max_size=104857600 \ --log_file_num_backups=10按天轮转(每天午夜切割):
python demos/helloworld/helloworld.py \ --log_file_prefix=/var/log/myapp/app.log \ --log_rotate_mode=time \ --log_rotate_when=midnight \ --log_rotate_interval=1 \ --log_file_num_backups=30关闭 Tornado 的日志接管、完全交给应用自己管理:
python demos/helloworld/helloworld.py --logging=none若在代码中希望禁用,可在parse_command_line()之前设置options.logging = None(参考 tornado/options.py 的说明与 tornado/test/log_test.py 的测试用例)。
多进程注意:
--log_file_prefix在多进程场景(例如 tornado/process.py 的fork_processes)下必须每个进程不同,官方建议把端口号拼进前缀。
五、程序化独立配置:摆脱命令行,自由掌控
如果不想依赖命令行解析,也可在代码中直接用标准库logging配置 Tornado 的任意日志流。例如将访问日志单独送入文件、应用日志保留在 stderr:
import logging # 独立配置 tornado.access access = logging.getLogger("tornado.access") access.setLevel(logging.INFO) fh = logging.FileHandler("access.log", encoding="utf-8") fh.setFormatter(logging.Formatter("%(asctime)s %(message)s")) access.addHandler(fh) # 其余日志(tornado.application / tornado.general / 根 logger)保持默认 logging.basicConfig(level=logging.INFO)define_logging_options也可用于自定义的OptionParser实例(tornado/log.py 注明:默认 options 实例已自动包含这些选项,仅当自建OptionParser时才需要显式调用):
from tornado.options import OptionParser from tornado.log import define_logging_options, enable_pretty_logging parser = OptionParser() define_logging_options(parser) enable_pretty_logging(parser) # 或调用 parser.parse_command_line() 自动触发此时LogFormatter亦可直接复用在任何 handler 上:
from tornado.log import LogFormatter handler = logging.StreamHandler() handler.setFormatter(LogFormatter(color=True)) logging.getLogger().addHandler(handler)六、从源码与测试看行为保证
仓库 tornado/test/log_test.py 是验证上述行为的最佳参考:
LogFormatterTest.test_basic_logging等用例验证单条日志输出格式与正则[E 240101 12:00:00 log_test:N]完全一致(tornado/test/log_test.py);test_bytes_logging/test_utf8_logging/test_bytes_exception_logging验证了字节串消息、非 UTF-8 消息、字节型异常堆栈均不会导致日志崩溃,且异常堆栈中的换行不会被转义(tornado/test/log_test.py);EnablePrettyLoggingTest.test_log_file验证设置log_file_prefix后文件内容格式为[E ...] hello,test_log_file_with_timed_rotating验证time轮转模式,test_wrong_rotate_mode_value验证非法log_rotate_mode会抛ValueError(tornado/test/log_test.py);LoggingOptionTest通过子进程验证:默认不解析时不启用日志、调用parse_command_line()后info级日志可见、--logging=none(含大小写None)可关闭、命令行参数可覆盖代码中设置的options.logging(tornado/test/log_test.py)。
七、常见问题与最佳实践小结
--logging=none的含义:不是"不记日志",而是"Tornado 不接管 logging 配置",此时根 logger 级别与 handler 完全由你掌控。- 颜色丢失:日志重定向到文件/管道时
_stderr_supports_color()返回False,属预期行为;如需在文件中保留颜色必须自行设置。 - 编码崩溃:消息可能包含任意 bytes 时,优先使用
LogFormatter,其_safe_unicode/repr回退机制保证日志管线永不因编码异常中断。 - 多进程日志:多进程场景务必让每个进程的
log_file_prefix唯一(如包含端口号),否则多个进程写同一文件会互相覆盖。 - 访问日志定制:需要自定义访问日志格式时,优先在
Applicationsettings 传入log_function,或在子类中重写log_request(tornado/web.py)。 - 异常分级:HTTP 4xx 异常(
HTTPError)走gen_log.warning且无堆栈,非 HTTP 异常走app_log.error带完整堆栈——监控告警可据此分流。
延伸阅读(仓库内相关文档)
- tornado.options 命令行解析说明:日志选项是如何注册与解析的
- tornado.web 的 log_function / log_request 说明:访问日志钩子的应用层入口
- 异步与协程指南:理解
tornado.application日志对应的异步回调异常场景
【免费下载链接】tornadoTornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.项目地址: https://gitcode.com/gh_mirrors/to/tornado
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考