2020-02-15 日志记录

1. 日志模块简介

运维工作有很多情况需要查问题、解决bug,而查问题和解决bug的过程离不开查看日志,我们编写脚本或程序时总是需要有日志输出,Python的logging模块就是为记录日志使用的,而且是线程安全的,意味着使用它完全不用担心因日志模块的异常导致程序崩溃。
将日志打印到屏幕:

import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

输出为:

WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message

默认情况下,Python的logging模块将日志打印到标准输出中,而且只显示大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL>ERROR>WARNING>INFO>DEBUG)
默认的日志格式:日志级别为Logger,名称为用户输出消息。
各日志级别代表的含义如下:

  • DEBUG:调试时的信息打印。
  • INFO:正常的日志信息记录。
  • WARNING:发生了警告信息,但程序仍能正常工作。
  • ERROR:发生了错误,部分功能已不正常。
  • CRITICAL:发生严重错误,程序可能已崩溃。

将日志信息记录至文件(文件名:lx_log1.py):

import logging
logging.basicConfig(filename='./lx_log1.log')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

执行以上代码后发现,在当前目录多了一个文件ls_log1.log,文件内容与第一个例子的输出是一致的。多次执行lx_log1.py发现log文件的内容变多了,说明默认的写log文件的方式是追加。

2. logging模块的配置与使用

我们可以通过logging模块的配置改变log文件的写入方式、日志级别、时间戳等信息。

logging.basicConfig(level=logging.DEBUG,    ---设置日志的级别
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',    ---日志的格式
    datefmt='%Y-%m-%d &H:%M:%S',    ---时间格式
    filename='./lx_log1.log',    ---指定文件位置
    filemode='w')    ---指定写入方式

可见在logging.basicConfig()函数中可通过具体参数来更改logging模块的默认行为。

  • filename:用指定的文件名创建FileHandler,这样日志会被存储在指定的文件中。
  • filemode:文件打开方式,在指定了filename时使用这个参数,默认值为a,还可指定为w。
  • format:指定handler使用的日志显示格式。
  • datefmt:指定日期时间格式。
  • level:设置rootlogger的日志级别。
  • stream:用指定的stream创建StreamHandler。可以指定输出到sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

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 字符串形式的当前时间。默认格式是“2020-02-14 16:14:52,896”。逗号后面的是毫秒。
  • %(thread)d 线程ID,可能没有。
  • %(threadName)s 线程名,可能没有。
  • %(process)d 进程ID,可能没有。
  • %(message)s 用户输出的消息。
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    filename='./lx_log1.log',
    filemode='w',
)
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

运行代码后我们会看到lx_log1.log文件的内容如下:

2020-02-15 01:58:20 lx_log1.py[line:9] DEBUG debug message
2020-02-15 01:58:20 lx_log1.py[line:10] INFO info message
2020-02-15 01:58:20 lx_log1.py[line:11] WARNING warning message
2020-02-15 01:58:20 lx_log1.py[line:12] ERROR error message
2020-02-15 01:58:20 lx_log1.py[line:13] CRITICAL critical message

这样的配置已基本满足我们歇写一些小程序或Python脚本的日志需求。然而这还不够体现logging模块的强大,毕竟以上功能通过自定义一个函数也可以方便实现。下面的是几个概念以及他们之间的关系图。

  • logger:记录器,应用程序代码能直接使用的接口。
  • handler:处理器,将(记录器产生的)日志记录发送至合适的目的地。
  • filter:过滤器,提供了更好的粒度控制,可以决定输出哪些日志记录。
  • formatter:格式化器,指明了最终输出中日志记录的布局。

日志事件信息在记录器(logger)、处理器(handler)、过滤器(filter)、格式化器(formatter)之间通过一个日志记录实例来传递。通过调用记录器实例的方法来记录日志,每一个记录器实例都有一个名字,名字相当于其命名空间,是一个树状结构。例如,一个记录器叫scan,记录器scan.tex、scan.html、scan.pdf的夫节点。记录器的名称。可以任意取,但一个比较好的实践是通过下面的方式来命名一个记录器。

logger = logging.getLogger(__name__)

上面这条语句意味着记录器的名字会通过搜索包的层级来获致,根记录器叫root logger。记录器通过debug()、info()、warning()、error()和critical()方法记录相应级别的日志,根记录器也一样。
根记录器root logger输出的名称是'root'。当然,日志的输出位置可能是不同的,logging模块支持将日志信息输出到终端、文件、HTTP GET/POST请求、邮件、网络sockets、队列或操作系统级的日志等。日志的输出位置在处理器handler类中进行配置,如果内建的handler类无法满足需求,则可以自定义handler类来实现自己特殊的需求。默认情况下,日志的输出位置为终端(标准错误输出),可以通过logging模块的basicConfig()方法指定一个具体的位置来输出日志,如终端或文件。
logger和handler的工作流程如下:

logging模块的工作流程

现在让我们从整体到局部来说明logger的日志记录过程。
第一步:获取logger的名称。

logger = logging.getLogger('logger name')    ---这里的logger name是自己定义的

第二步:配置logger。
1)配置该logger的输出级别,如logger.setLevel(loging.INFO)。
2)添加该logger的输出位置,即logger的handler,logger.addHandler(ch)。这里的ch是我们自定义的handler,如ch=logging.StreamHandler,即输出到终端。我们可以添加多个handler,一次性将日志输出到不同的位置。日志的输出格式是在handler中进行配置,如ch.setFormatter(formatter),formatter也是我们自定义的,如formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')。不同的handler可以配置不同的格式化器,可以实现不同的输出位置,不同的输出格式,完全可能灵活配置。
第三步:在应用程序中记录日志。

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

将日志信息显示在终端的同时也在文件中记录(lx_log2.py)。

# -*- coding: utf-8 -*-

import logging

# 创建logger,其名称为aimple_example,名称为任意,也可为空
logger = logging.getLogger("simple_example")
# 打印logger的名称
print(logger.name)
# 设置logger的日志级别
logger.setLevel(logger.INFO)

# 创建两个handler,一个负责将日志输出到终端,一个负责输出到文件,并分别设置它们的日志级别
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
fh = logging.FileHandler(filename="simple.log", mode="a", encoding="utf*8")
fh.setLevel(logging.WARNING)
# 创建一个格式化器,可以创建不同的格式化器用于不同的handler,这里我们使用一个
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

# 设置两个handler的格式化器
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 为logger添加两个handler
logger.addHandler(ch)
logger.addHandler(fh)

# 在程序中记录日志
logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

在以上程序中我们设置了logger的日志级别为INFO,handler ch的日志级别为DEBUG,handler fh的日志级别为WARNING,这样做是为了解释它们之前的优先级。
handler的日志级别以logger的日志为基础,logger的日志级别为INFO,低于INFO级别的(如DEBUG)均不会在handler中出现。handler中的日志级别如果高于logger,则只显示更高级别的日志信息,如fh应该只显示WARNING及以上的日志信息;handler中的日志级别如果低于或等于logger的日志级别,则显示logger的日志级别及以上信息,如ch应该显示INFO及以上的日志信息。
执行python lx_log2.py得到如下结果:

simple_example
2020-02-15 14:54:29,459 - simple_example - INFO - info message
2020-02-15 14:54:29,459 - simple_example - WARNING - warning message
2020-02-15 14:54:29,459 - simple_example - ERROR - error message
2020-02-15 14:54:29,459 - simple_example - CRITICAL - critical message

查看simple.log文件,内容如下:

2020-02-15 14:54:29,459 - simple_example - WARNING - warning message
2020-02-15 14:54:29,459 - simple_example - ERROR - error message
2020-02-15 14:54:29,459 - simple_example - CRITICAL - critical message

从运行结果来看,符合我们的预期。除了StreamHandler和FileHandler外,logging模块还提供了其他更为实用的Handler子类,它们都继承在Handler基类,如下所示。

  • BaseRotatingHandler:是循环日志处理器的基类,不能直接被实例化,可使用RotatingFileHandler和TimeRotatingFileHandler。
  • RotatingFileHandler:将日志文件记录至磁盘文件,可以设置每个日志文件的最大占用空间。
  • TimeRotatingFileHandler:将日志文件记录至磁盘文件,按固定的时间间隔来循环记录日志。
  • SocketHandler:可以将日志信息发送到TCP/IP套接字。
  • DatagramHandler:可以将日志信息发送到UDP套接字。
  • SMTPHandler:可以将日志文件发送至邮箱。
  • SysLogHandler:系统日志处理器,可以将日志文件发送至UNIX系统日志,也可以是一个远程机器。
  • NTEventLogHandler:Windows系统事件日志处理器,可以将日志文件发送到Windows系统事件日志。
  • MemoryHandler:MemoryHandler实例向内存中的缓冲区发送消息,只要满足特定的条件,缓冲区就会被刷新。
  • HTTPHandlerL:使用GET或POST方法向HTTP服务器发送消息。
  • WatchedFileHandler:WatchedFileHandler实例监视它们登录到的文件。如果文件发生更改,则使用文件名关闭并重新打开。这个处理器只适用于类unix系统,Windows不支持使用的底层机制。
  • QueueHandler:QueueHandler实例向队列发送消息,比如在队列或多处理模块中实现的消息。
  • NullHandler:NullHandler实例不使用错误消息。库开发人员使用日志记录,但希望避免在库用户未配置日志记录时显示“日志记录器XXX无法找到任何处理程序”消息。

日志的配置信息也可以来源于配置文件(lx_log3.py):

import logging
import logging.config

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

# 创建一个logger
logger = logging.getLogger('simpleExample')

# 日志记录
logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

下面是配置文件的信息logging.conf:

[loggers]
keys=root,simpleExample

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S

上面几种常用的方法已经基本满足我们的需求。

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

推荐阅读更多精彩内容