logging模块的简单使用以及tornado中的log简单介绍

刚知道tornado原来是facebook开源的。

logging的简单介绍和使用

python内置模块logging,用于记录日志,格式化输出。通过getLogger()获得的是单例且线程安全的(进程不安全),下文会简单介绍logging的常用方法和简单logging源码分析,以及tornado中的日志模块。

使用basicConfig()(给root handler添加基本配置),除了levelfilename参数外还有filemode(默认a),format(输出格式),handlers(给root logger添加handlers)

import logging

logging.basicConfig(level=logging.DEBUG, filename='main.log')
logging.debug('bug log')

logging的日志分为这几种级别:

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

logging.info()等实际上是根looger(root logger)的info()方法。

利用logger的相对应的infodebugwaring等可以输出日志,只有当方法级别>日志级别时才能够输出(如level=logging.INFO时,用logging.error('error')能够输出,而logging.debug('debug')无输出)。

值得一提的是还有一个logging.exception()方法,能够简单的记录Traceback信息(实际上是相当于logging.error(e, exc_info=True)),所有的输出方法最终都是调用了logger的_log()方法。

getLogger()
import logging

root_logger = logging.getLogger()
mine_logger = logging.getLogger('mine')

参数缺省时默认获取的是root_logger,是所有logger的最终父logger。
在未作任何配置时,root_logger的日志级别是warning

阅读getLogger()源码可以发现

root = RootLogger(WARNING)
Logger.root = root
Logger.manager = Manager(Logger.root)

def getLogger(name=None):
    """
    Return a logger with the specified name, creating it if necessary.

    If no name is specified, return the root logger.
    """
    if name:
        return Logger.manager.getLogger(name)
    else:
        return root

执行logging.getLogger('mine')方法,即Manager(Logger.root).getLogger('mine'),再来看一下ManagergetLogger()

    def getLogger(self, name):
        """
        Get a logger with the specified name (channel name), creating it
        if it doesn't yet exist. This name is a dot-separated hierarchical
        name, such as "a", "a.b", "a.b.c" or similar.

        If a PlaceHolder existed for the specified name [i.e. the logger
        didn't exist but a child of it did], replace it with the created
        logger and fix up the parent/child references which pointed to the
        placeholder to now point to the logger.
        """
        rv = None
        if not isinstance(name, str):
            raise TypeError('A logger name must be a string')
        _acquireLock()
        try:
            if name in self.loggerDict:
                rv = self.loggerDict[name]
                if isinstance(rv, PlaceHolder):
                    ph = rv
                    rv = (self.loggerClass or _loggerClass)(name)
                    rv.manager = self
                    self.loggerDict[name] = rv
                    self._fixupChildren(ph, rv)
                    self._fixupParents(rv)
            else:
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupParents(rv)
        finally:
            _releaseLock()
        return rv

getLogger的时候会先从Manager的loggerDict中查找,找不到时才生成新logger ,通过loggerDictPlaceHodler,将a,a.b,a.b.c类似的,子logger继承父logger的levelhandler等,最终继承root_handler。

Handlers, Formatter

每个logger都能有多个handler,默认的是StreamHandler,能够通过addHandler()removeHandler()来添加删除handler。

import logging

mine_logger = logging.getLogger('mine')
mine_logger.setLevel(logging.INFO)

console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('./logs/test.log')

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

mine_logger.addHandler(console_handler)
mine_logger.addHandler(file_handler)

mine_logger.info('info message')

新建了console_handlerfile_handler,通过addHandler()添加到了mine_logger上,这样mine_logger.info('info message')既能输出到文件又能打印在控制台。

ps:可以利用logging.Formatter来格式化输出,但每个handler只能有一种类formatter。

高级handlers

logging.handers下面有很多封装好的功能强大的handler。
RotatingFileHandler :根据大小切割日志。
TimedRotatingFileHandler :根据时间切割日志。
还有MemoryHandlerSocketHandler等等。

config

利用配置文件能够快速的对logging进行配置。

[loggers]
keys=root, common

[logger_root]
level=INFO
handlers=root_handler

[logger_common]
level=DEBUG
handlers=common_handler
qualname=common

[handlers]
keys=root_handler,common_handler

[handler_root_handler]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stdout,)

[handler_common_handler]
class=FileHandler
level=DEBUG
formatter=form01
args=('main.log',"a")

[formatters]
keys=form01

[formatter_form01]
format=[%(asctime)s %(levelname)s] file: %(filename)s, line:%(lineno)d %(process)d %(message)s 
datefmt=%Y-%m-%d %H:%M:%S

通过配置文件来定义logger,handler,formatter等。上述配置中handler都是指向logging模块自带的handler,也可以指向自定义的handler。

利用logging.config加载配置文件。

import logging
import logging.config

logging_config_path = 'logging.conf'
logging.config.fileConfig(logging_config_path)

root_logger = logging.getLogger('root')
root_logger.info('test message')


common_logger = logging.getLogger('common')
common_logger.info('test common message')

tornado中的log简单介绍

在tornado的log.py模块中,初始化了三种logger,都继承于logging模块的root logger。

'''Tornado uses three logger streams:

* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and
  potentially other servers in the future)
* ``tornado.application``: Logging of errors from application code (i.e.
  uncaught exceptions from callbacks)
* ``tornado.general``: General-purpose logging, including any errors
  or warnings from Tornado itself.
'''

# Logger objects for internal tornado use
access_log = logging.getLogger("tornado.access")
app_log = logging.getLogger("tornado.application")
gen_log = logging.getLogger("tornado.general")

在启动tornado前利用tornado.options.parse_command_line()来初始化一些配置。
tornado.options.parse_command_line() 会默认调用enable_pretty_logging()方法,对日志输出做一些格式化和rotate并输出到日志文件,可以通过tornado.options.define()来传递这些参数。

def enable_pretty_logging(options=None, logger=None):
    """Turns on formatted logging output as configured.

    This is called automatically by `tornado.options.parse_command_line`
    and `tornado.options.parse_config_file`.
    """
    if options is None:
        import tornado.options
        options = tornado.options.options
    if options.logging is None or options.logging.lower() == 'none':
        return
    if logger is None:
        logger = logging.getLogger()
    logger.setLevel(getattr(logging, options.logging.upper()))
    if options.log_file_prefix:
        rotate_mode = options.log_rotate_mode
        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)
        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)
        else:
            error_message = 'The value of log_rotate_mode option should be ' +\
                            '"size" or "time", not "%s".' % rotate_mode
            raise ValueError(error_message)
        channel.setFormatter(LogFormatter(color=False))
        logger.addHandler(channel)

    if (options.log_to_stderr or
            (options.log_to_stderr is None and not logger.handlers)):
        # Set up color if we are in a tty and curses is installed
        channel = logging.StreamHandler()
        channel.setFormatter(LogFormatter())
        logger.addHandler(channel)

如现在我们对tornado的日志进行每间隔一天进行分割,并输出到文件中。

    tornado.options.define('log_file_prefix', default='./logs/test.log')
    tornado.options.define('log_rotate_mode', default='time')
    tornado.options.define('log_rotate_when', default='M')
    tornado.options.define('log_rotate_interval', default=1)
    tornado.options.parse_command_line() 

更多参数可以参考源码:

options.define("logging", default="info",
                   help=("Set the Python log level. If 'none', tornado won't touch the "
                         "logging configuration."),
                   metavar="debug|info|warning|error|none")
    options.define("log_to_stderr", type=bool, default=None,
                   help=("Send log output to stderr (colorized if possible). "
                         "By default use stderr if --log_file_prefix is not set and "
                         "no other logging is configured."))
    options.define("log_file_prefix", type=str, default=None, metavar="PATH",
                   help=("Path prefix for log files. "
                         "Note that if you are running multiple tornado processes, "
                         "log_file_prefix must be different for each of them (e.g. "
                         "include the port number)"))
    options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
                   help="max size of log files before rollover")
    options.define("log_file_num_backups", type=int, default=10,
                   help="number of log files to keep")

    options.define("log_rotate_when", type=str, default='midnight',
                   help=("specify the type of TimedRotatingFileHandler interval "
                         "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"))
    options.define("log_rotate_interval", type=int, default=1,
                   help="The interval value of timed rotating")

    options.define("log_rotate_mode", type=str, default='size',
                   help="The mode of rotating files(time or size)")

对于access_logger,初始化后tornado会记录每次请求的信息,该记录log的操作是在handler中完成的。

在RequestHandler的finish()中会执行_log()方法,该方法会找到该handler所属Applicationlog_request()方法:

    def log_request(self, handler):
        """Writes a completed HTTP request to the logs.

        By default writes to the python root logger.  To change
        this behavior either subclass Application and override this method,
        or pass a function in the application settings dictionary as
        ``log_function``.
        """
        if "log_function" in self.settings:
            self.settings["log_function"](handler)
            return
        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)

很直观的看出来在请求状态码不同时,输出的日志级别也不同,除了状态码,还记录了请求时间,以及ip,uri和方法,若要简单更改输出的日志格式,可以继承RequestHandler并重写_request_summary()方法。

    def _request_summary(self):
        return "%s %s (%s)" % (self.request.method, self.request.uri,
                               self.request.remote_ip)

ps: 也能够通过配置文件对tornado日志进行配置。


logging模块是线程安全的,但多进程时会出现问题,多进程解决方法参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,817评论 1 13
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,905评论 0 6
  • logging模块介绍: logging是python内置的标准库模块,模块提供不同的日志级别,并可以采用不同的方...
    4ffde5305e8f阅读 2,771评论 0 2
  • 本文章是我大概三年前,在上家单位使用 Python 工作时结合官方文档做的整理。现在 Python 官方文档听说已...
    好吃的野菜阅读 212,451评论 14 231
  • 南无观世音菩萨心咒:嗡ong 嘛ma 呢ni 叭bei 咪mei 吽hong南无绿度母心咒:嗡 ong达咧 dal...
    孙道台阅读 654评论 0 3