python的logging模块提供了记录程序运行情况的日志功能,类似于Apache的log4j,很好很强大,这里我们就来看一下Python中内置的日志模块logging用法详解

logging模块简介

Python的logging模块提供了通用的日志系统,可以方便第三方模块或者是应用使用。这个模块提供不同的日志级别,并可以采用不同的方式记录日志,比如文件,HTTP GET/POST,SMTP,Socket等,甚至可以自己实现具体的日志记录方式。
logging模块与log4j的机制是一样的,只是具体的实现细节不同。模块提供logger,handler,filter,formatter。

  • logger:提供日志接口,供应用代码使用。logger最长用的操作有两类:配置和发送日志消息。可以通过logging.getLogger(name)获取logger对象,如果不指定name则返回root对象,多次使用相同的name调用getLogger方法返回同一个logger对象。
  • handler:将日志记录(log record)发送到合适的目的地(destination),比如文件,socket等。一个logger对象可以通过addHandler方法添加0到多个handler,每个handler又可以定义不同日志级别,以实现日志分级过滤显示。
  • filter:提供一种优雅的方式决定一个日志记录是否发送到handler。
  • formatter:指定日志记录输出的具体格式。formatter的构造方法需要两个参数:消息的格式字符串和日期字符串,这两个参数都是可选的。

与log4j类似,logger,handler和日志消息的调用可以有具体的日志级别(Level),只有在日志消息的级别大于logger和handler的级别。

logging用法解析

1. 初始化 logger = logging.getLogger("endlesscode"),getLogger()方法后面最好加上所要日志记录的模块名字,后面的日志格式中的%(name)s 对应的是这里的模块名字
2. 设置级别 logger.setLevel(logging.DEBUG),Logging中有NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL这几种级别,日志会记录设置级别以上的日志
3. Handler,常用的是StreamHandler和FileHandler,windows下你可以简单理解为一个是console和文件日志,一个打印在CMD窗口上,一个记录在一个文件上
4. formatter,定义了最终log信息的顺序,结构和内容,我喜欢用这样的格式 '[%(asctime)s] [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S',
  %(name)s Logger的名字
  %(levelname)s 文本形式的日志级别
  %(message)s 用户输出的消息
  %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
  %(levelno)s 数字形式的日志级别
  %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
  %(filename)s 调用日志输出函数的模块的文件名
  %(module)s  调用日志输出函数的模块名
  %(funcName)s 调用日志输出函数的函数名
  %(lineno)d 调用日志输出函数的语句所在的代码行
  %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
  %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
  %(thread)d 线程ID。可能没有
  %(threadName)s 线程名。可能没有
  %(process)d 进程ID。可能没有
5. 记录 使用object.debug(message)来记录日志

6. logging是线程安全的

简单示例如下:

import logging
logger = logging.getLogger("simple_example")
logger.setLevel(logging.DEBUG)
# 建立一个filehandler来把日志记录在文件里,级别为debug以上
fh = logging.FileHandler("spam.log")
fh.setLevel(logging.DEBUG)
# 建立一个streamhandler来把日志打在CMD窗口上,级别为error以上
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 设置日志格式
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)
#将相应的handler添加在logger对象中
logger.addHandler(ch)
logger.addHandler(fh)
# 开始打日志
logger.debug("debug message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")
logger.critical("critical message")

当你的项目较大时,你就需要一个比较方便规范的使用的方式,建议将你的kog系统设计为class方式,这样就可以方便使用

#! /usr/bin/env python
#coding=gbk
import logging,os
 
class Logger:
 def __init__(self, path,clevel = logging.DEBUG,Flevel = logging.DEBUG):
  self.logger = logging.getLogger(path)
  self.logger.setLevel(logging.DEBUG)
  fmt = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S')
  #设置CMD日志
  sh = logging.StreamHandler()
  sh.setFormatter(fmt)
  sh.setLevel(clevel)
  #设置文件日志
  fh = logging.FileHandler(path)
  fh.setFormatter(fmt)
  fh.setLevel(Flevel)
  self.logger.addHandler(sh)
  self.logger.addHandler(fh)
 
 def debug(self,message):
  self.logger.debug(message)
 
 def info(self,message):
  self.logger.info(message)
 
 def war(self,message):
  self.logger.warn(message)
 
 def error(self,message):
  self.logger.error(message)
 
 def cri(self,message):
  self.logger.critical(message)
 
if __name__ =='__main__':
 logyyx = Logger('yyx.log',logging.ERROR,logging.DEBUG)
 logyyx.debug('一个debug信息')
 logyyx.info('一个info信息')
 logyyx.war('一个warning信息')
 logyyx.error('一个error信息')
 logyyx.cri('一个致命critical信息')

多模块使用logginglogging模块保证在同一个python解释器内,多次调用logging.getLogger('log_name')都会返回同一个logger实例,即使是在多个模块的情况下。所以典型的多模块场景下使用logging的方式是在main模块中配置logging,这个配置会作用于多个的子模块,然后在其他模块中直接通过getLogger获取Logger对象即可。
配置文件:

[loggers]
keys=root,main
  
[handlers]
keys=consoleHandler,fileHandler
  
[formatters]
keys=fmt
  
[logger_root]
level=DEBUG
handlers=consoleHandler
  
[logger_main]
level=DEBUG
qualname=main
handlers=fileHandler
  
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=fmt
args=(sys.stdout,)
  
[handler_fileHandler]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=fmt
args=('tst.log','a',20000,5,)
  
[formatter_fmt]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 
datefmt=

主模块main.py:

import logging
import logging.config
  
logging.config.fileConfig('logging.conf')
root_logger = logging.getLogger('root')
root_logger.debug('test root logger...')
  
logger = logging.getLogger('main')
logger.info('test main logger')
logger.info('start import module \'mod\'...') 
import mod
  
logger.debug('let\'s test mod.testLogger()')
mod.testLogger()
  
root_logger.info('finish test...')

子模块mod.py:

import logging
import submod
  
logger = logging.getLogger('main.mod')
logger.info('logger of mod say something...') 
  
def testLogger():
  logger.debug('this is mod.testLogger...') 
  submod.tst()

子子模块submod.py:

import logging
  
logger = logging.getLogger('main.mod.submod')
logger.info('logger of submod say something...') 
  
def tst():
  logger.info('this is submod.tst()...')

然后运行python main.py,控制台输出:

2012-03-09 18:22:22,793 - root - DEBUG - test root logger...
2012-03-09 18:22:22,793 - main - INFO - test main logger
2012-03-09 18:22:22,809 - main - INFO - start import module 'mod'...
2012-03-09 18:22:22,809 - main.mod.submod - INFO - logger of submod say something... 
2012-03-09 18:22:22,809 - main.mod - INFO - logger say something...
2012-03-09 18:22:22,809 - main - DEBUG - let's test mod.testLogger()
2012-03-09 18:22:22,825 - main.mod - DEBUG - this is mod.testLogger...
2012-03-09 18:22:22,825 - main.mod.submod - INFO - this is submod.tst()...
2012-03-09 18:22:22,841 - root - INFO - finish test...

可以看出,和预想的一样,然后在看一下tst.log,logger配置中的输出的目的地:

2012-03-09 18:22:22,793 - main - INFO - test main logger
2012-03-09 18:22:22,809 - main - INFO - start import module 'mod'...
2012-03-09 18:22:22,809 - main.mod.submod - INFO - logger of submod say something... 
2012-03-09 18:22:22,809 - main.mod - INFO - logger say something...
2012-03-09 18:22:22,809 - main - DEBUG - let's test mod.testLogger()
2012-03-09 18:22:22,825 - main.mod - DEBUG - this is mod.testLogger...
2012-03-09 18:22:22,825 - main.mod.submod - INFO - this is submod.tst()...

另外在浏览网页时发现一个讲的比较好的:http://outofmemory.cn/code-snippet/450/python-rizhi-logging-module-usage-summary

下面这个是目前会用,但是不是甚懂的一个logger系统设计:

import logging
import logging.config

class SpiderFilter(logging.Filter):

    def __init__(self, allow=None, disable=None):
        self.allow_channels = allow
        self.disable_channels = disable

    def filter(self, record):
        if self.allow_channels is not None:
            if record.name in self.allow_channels:
                allow = True
            else:
                allow = False
        elif self.disable_channels is not None:
            if record.name in self.disable_channels:
                allow = False
            else:
                allow = True
        else:
            allow = False
        return allow


LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(asctime)s -- %(name)s !!!%(levelname)s!!!: %(message)s'
        },
    },
    'filters': {
        'basic': {
            '()': SpiderFilter,
            'allow': ('mongo', 'redis', 'mysql'),
        },
        'warn': {
            '()': SpiderFilter,
            'disable': ()
        }
    },
    'handlers': {
        'file': {
            'level': 'WARN',
            'formatter': 'simple',
            'class': 'logging.FileHandler',
            'filename': 'spider.log',
            'mode': 'a',
            'filters': ['warn']
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'database': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'formatter': 'simple',
            'filename': 'spider.log',
            'mode': 'a',
            'filters': ['basic']
        }
    },
    'loggers': {
        'mongo': {
            'handlers':['file'],
            'propagate': True,
            'level':'ERROR',
        },
        'mysql': {
            'handlers': ['database'],
            'level': 'DEBUG',
            'propagate': False,
        },
        'redis': {
            'handlers': ['console', 'database'],
            'level': 'INFO',
            'filters': ['basic']
        }
    },
    'root': {
        'level': 'DEBUG',
        'handlers': ['console']
    }
}

if __name__ == '__main__':
    logging.config.dictConfig(LOGGING)
    logging.getLogger('mysql').debug('Simple Log Test!')
    logging.getLogger('mongo').critical('Test!')

 

logging简单使用

看下面的代码就可以对logging的使用有一个基本的认识

# 亭子
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

'''
记录了logging的简单使用方式,主要怎么配置logging
'''
import logging

def simple_example():
    #默认过滤级别为warning,默认输出到控制台
    logging.warning("warning")
    logging.info("info")

#logging:配置日志级别,输出位置
def logging_to_file():
    '''
    #参数:filename:用指定的文件名创建filedhandler,这样日志会被存储到指定的文件中,如果不指定,则默认输出到控制台
    #参数:filemode默认filemode的默认值"a",表示append,当然你也可以指定为"w"
    #参数:level,一共5个级别,critical(50)>error(40)>warning(30)>info(20)>debug(10),默认级别为Warning,这里可以用对应的数值表示level
    '''

    #将日志信息只输入到指定的文件
    logging.basicConfig(filename="example.log", level=logging.DEBUG)
    # logging.basicConfig(filename="example.log", level=logging.DEBUG, filemode="w")
    # logging.basicConfig(filename="example.log", level=10, filemode="w")
    logging.debug("debu1g")
    logging.info("info")
    logging.warning("info")
    logging.error("error")
    logging.critical("critical")

    # logging variable data:打印变量
    logging.error("logging variable date: %s,%s,%s","var01","var02","var03")

#logging:配置日志格式
def displaying_format_massages():
    '''
    format参数中可能用到的格式化串
    %(name)s Logger的名字
    %(levelno)s 数字形式的日志级别
    %(levelname)s 文本形式的日志级别
    %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
    %(filename)s 调用日志输出函数的模块的文件名
    %(module)s 调用日志输出函数的模块名
    %(funcName)s 调用日志输出函数的函数名
    %(lineno)d 调用日志输出函数的语句所在的代码行
    %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
    %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
    %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
    %(thread)d 线程ID。可能没有
    %(threadName)s 线程名。可能没有
    %(process)d 进程ID。可能没有
    %(message)s用户输出的消息
    '''

    # format:指定handler使用的日志显示格式。
    logging.basicConfig(format="%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s - %(message)s")
    logging.warning("warning")

#logging:配置日志时间格式
def displaying_date_message():
    # datefmt:指定日期时间格式,也就是“%(asctime)s”的格式
    logging.basicConfig(format="%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s - %(message)s",datefmt="%m/%d/%Y %I:%M:%S %p")
    logging.warning("warning")

if __name__ == "__main__":
    #test
    displaying_date_message()

 

logging高级进阶

我们直接上代码了,先对logging的高级应用做一个简单的流程梳理,具体仔细的东西,后面慢慢看。代码中也有很多注释,所以看起来应该不是很费劲。

##亭子
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

'''
对logging进行更加灵活的控制:需要了解Logger,Handler,Formatter,Filter

步骤
1. 创建logger实例并配置
2. 创建formatter对象
3. 创建你需要的handler对象并配置
4. 将handler加载到logger实例中
5. 写日志

这个模块主要包含了几个内容
1. 输出日志到控制台中
2. 输出日志到指定的日志文件中
3. 输出的日志按照指定的大小来自动管理,当文件达到指定的大小后,自动创建新文件
4. 输出的文件按照时间来自动管理,当文件达到指定的时候后,自动创建新文件
'''

import logging
from logging import handlers
import time

if __name__ =="__main__":
    #初始化logger
    logger = logging.getLogger()
    # 配置日志级别,如果不显示配置,默认为Warning,表示所有warning级别已下的其他level直接被省略,
    # 内部绑定的handler对象也只能接收到warning级别以上的level,你可以理解为总开关
    logger.setLevel(logging.INFO)

    formatter = logging.Formatter(fmt="%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s - %(message)s",
                                  datefmt="%m/%d/%Y %I:%M:%S %p")  # 创建一个格式化对象

    console = logging.StreamHandler() # 配置日志输出到控制台
    console.setLevel(logging.WARNING) # 设置输出到控制台的最低日志级别
    console.setFormatter(formatter)  # 设置格式
    logger.addHandler(console)

    # file_logging = logging.FileHandler("example.log") # 配置日志输出到文件
    # file_logging.setLevel(logging.WARNING)
    # file_logging.setFormatter(formatter)
    # logger.addHandler(file_logging)

    # 和上面的FIleHandler差不多,只是handler对象可以管理文件大小,当文件大于指定的大小后,会自动将当前文件改名,然后重新创建一个新的同名文件继续输出
    # file_rotating_file = handlers.RotatingFileHandler("cat.log",maxBytes=1024,backupCount=3)
    # file_rotating_file.setLevel(logging.INFO)
    # file_rotating_file.setFormatter(formatter)
    # logger.addHandler(file_rotating_file)

    # 和上面的handler有点类似,不过,它是通过判断文件大小来决定何时重新创建日志文件,而是间隔一定的时候自动创建日志文件。
    # 代表每7天备份文件
    file_time_rotating = handlers.TimedRotatingFileHandler("app.log",when="s",interval=10,backupCount=5)
    file_time_rotating.setLevel(logging.INFO)
    file_time_rotating.setFormatter(formatter)
    logger.addHandler(file_time_rotating)

    #use logger
    logger.debug("debug")
    logger.info("info")
    logger.warning("warning")
    logger.error("error")
    logger.critical("critical message")

    time.sleep(12)

    logger.debug("debug")
    logger.info("info")
    logger.warning("warning")
    logger.error("error")
    logger.critical("critical message")

 

首先,这里说几个概念,是我们代码里面涉及到的,也是我们这个章节里面比较重要的。那就是“Logger”,“Handler”,“Formatter”,“Filter”,下面做简单的解释

  1. logger提供了应用程序可以直接使用的接口;
  2. handler将(logger创建的)日志记录发送到合适的目的输出;
  3. filter提供了细度设备来决定输出哪条日志记录;(这里我就先不涉及了,为什么,因为我也不懂,后面如果涉及到,再慢慢补充)
  4. formatter决定日志记录的最终输出格式。

Logger

每个程序在输出信息之前都要获得一个Logger。Logger通常对应了程序的模块名,比如聊天工具的图形界面模块可以这样获得它的Logger:
LOG=logging.getLogger(”chat.gui”)
而核心模块可以这样:
LOG=logging.getLogger(”chat.kernel”)

Logger.setLevel(lel):指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高
Logger.addFilter(filt)、Logger.removeFilter(filt):添加或删除指定的filter
Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或删除指定的handler
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical():可以设置的日志级别

Handler

handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
Handler.setLevel(lel):指定被处理的信息级别,低于lel级别的信息将被忽略
Handler.setFormatter():给这个handler选择一个格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象

每个Logger可以附加多个Handler。接下来我们就来介绍一些常用的Handler:

1) logging.StreamHandler

使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是:StreamHandler([strm])
其中strm参数是一个文件对象。默认是sys.stderr

2) logging.FileHandler

和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler会帮你打开这个文件。它的构造函数是:FileHandler(filename[,mode])
filename是文件名,必须指定一个文件名。
mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。

3) logging.handlers.RotatingFileHandler

这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建 一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。它的构造函数是:
RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
其中filename和mode两个参数和FileHandler一样。
maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。

4) logging.handlers.TimedRotatingFileHandler

这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。它的构造函数是:
TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。
interval是时间间隔。
when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒
M 分
H 小时
D 天
W 每星期(interval==0时代表星期一)
midnight 每天凌晨

 

参考地址

python 构建自己的log系统: