一、日志相关概念
日志是一种可以追踪软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。
1.日志的作用
- 调试与故障定位
方便用户了解系统或软件、应用的运行情况;软件程序运行故障分析与问题定位 - 数据分析
如果应用的日志信息足够详细和丰富,还可以用来做用户行为分析,如:分析用户的操作行为、类型洗好、地域分布以及其它更多的信息,由此可以实现改进业务、提高商业利益。
2.日志的等级
我们先来思考下下面的两个问题:
作为开发人员,在开发一个应用程序时需要什么日志信息?在应用程序正式上线后需要什么日志信息?
作为应用运维人员,在部署开发环境时需要什么日志信息?在部署生产环境时需要什么日志信息?
为什么要给日志分级???
在软件开发阶段或部署开发环境时,为了尽可能详细的查看应用程序的运行状态来保证上线后的稳定性,我们可能需要把该应用程序所有的运行日志全部记录下来进行分析,这是非常耗费机器性能的。当应用程序正式发布或在生产环境部署应用程序时,我们通常只需要记录应用程序的异常信息、错误信息等,这样既可以减小服务器的I/O压力,也可以避免我们在排查故障时被淹没在日志的海洋里。那么,怎样才能在不改动应用程序代码的情况下实现在不同的环境记录不同详细程度的日志呢?这就是日志等级的作用了,我们通过配置文件指定我们需要的日志等级就可以了。
不同的应用程序所定义的日志等级可能会有所差别,分的详细点的会包含以下几个等级:
- DEBUG
- INFO
- NOTICE
- WARNING
- ERROR
- CRITICAL
- ALERT
- EMERGENCY
3.日志字段信息与日志格式
本节开始问题提到过,一条日志信息对应的是一个事件的发生,而一个事件通常需要包括以下几个内容:
事件发生时间
事件发生位置
事件的严重程度--日志级别
事件内容
上面这些都是一条日志记录中可能包含的字段信息,当然还可以包括一些其他信息,如进程ID、进程名称、线程ID、线程名称等。日志格式就是用来定义一条日志记录中包含那些字段的,且日志格式通常都是可以自定义的。
4.日志功能的实现
几乎所有开发语言都会内置日志相关功能,或者会有比较优秀的第三方库来提供日志操作功能,比如:log4j,log4php等。它们功能强大、使用简单。Python自身也提供了一个用于记录日志的标准库模块--logging。
二、logging模块简介
logging模块定义的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。logging模块是Python的一个标准库模块,由标准库模块提供日志记录API的关键好处是所有Python模块都可以使用这个日志记录功能。所以,你的应用日志可以将你自己的日志信息与来自第三方模块的信息整合起来。
1.helloworld
学习一个新的模块前,我们先来看一个demo试试水。
代码:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
输出结果:
DEBUG:root:This is a debug log.
INFO:root:This is a info log.
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.
是不是简单到爆炸!!!对的,就是这样。总结来看,要使用日志功能,其实就分为3个步骤:
- 导入logging模块;
- 配置logging模块的基本参数与格式;
- 输出不同等级的日志msg;
2. logging模块的使用方式介绍
logging模块提供了两种记录日志的方式:
第一种:使用logging提供的模块级别的函数(简单常用)
第二种:使用Logging日志系统的四大组件(高级用法,定制化强)
(其实,logging所提供的模块级别的日志记录函数也是对logging日志系统相关类的封装而已。)
这里先说说第一种简单的:
logging模块级别的常用函数:
logging.debug(msg, *args, **kwargs)
创建一条严重级别为DEBUG的日志记录logging.info(msg, *args, **kwargs)
创建一条严重级别为INFO的日志记录logging.warning(msg, *args, **kwargs)
创建一条严重级别为WARNING的日志记录logging.error(msg, *args, **kwargs)
创建一条严重级别为ERROR的日志记录logging.critical(msg, *args, **kwargs)
创建一条严重级别为CRITICAL的日志记录logging.log(level, *args, **kwargs)
创建一条严重级别为level的日志记录logging.basicConfig(**kwargs)
对root logger进行一次性配置
其中logging.basicConfig(**kwargs)函数用于指定“要记录的日志级别”、“日志格式”、“日志输出位置”、“日志文件的打开模式”等信息,其他几个都是用于记录各个级别日志的函数。
咦?这个log函数怎么在浑水摸鱼,Demo中没见过,其实它和前面几种按照等级输出日志的函数功能等价,没有区别。
logging.debug("This is a debug log.")
和
logging.log(logging.DEBUG, "This is a debug log.")
没区别,看个人癖好啦!!
3. logging模块的日志级别
logging模块默认定义了以下几个日志等级,它允许开发人员自定义其他日志级别,但是这是不被推荐的,尤其是在开发供别人使用的库时,因为这会导致日志级别的混乱。只有级别大于或等于日志记录器指定级别的日志记录才会被输出,小于该级别的日志记录将会被丢弃。
什么鬼?怎么设置日志记录器的输出级别??嗯,你应该猜到了,就在basicConfig函数里面设置,嘻嘻,我们后面再看。
日志等级(level)
- DEBUG: 最详细的日志信息,典型应用场景是问题诊断;
- INFO: 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
- WARNING: 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的;
- ERROR: 由于一个更严重的问题导致某些功能不能正常运行时记录的信息;
- CRITICAL: 当发生严重错误,导致应用程序不能继续运行时记录的信息;
上面的日志等级是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的;
墨迹了这么久,就为了搞明白这个等级的高低与用途,现在我们看一下,这个日志记录器的输出等级怎么设置,该怎么用!其实Demo中都写了
logging.basicConfig(level=logging.DEBUG)
这条语句的作用就是,DEBUG等级以上的log都输出。修改下代码:
代码:
import logging
logging.basicConfig(level=logging.WARNING)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
输出结果:
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.
开发应用程序或部署开发环境时,可以使用DEBUG或INFO级别的日志获取尽可能详细的日志信息来进行开发或部署调试;应用上线或部署生产环境时,应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志信息的效率。日志级别的指定通常都是在应用程序的配置文件中进行指定的。
4. logging.basicConfig()函数说明
写到这,不少人就会喷水,这个配置函数就只能设置输出等级吗?
日志输出的格式这么丑,不能改吗?
为啥日志只能输出到控制台,怎么输出到文件呢?
把这些问题通通丢给配置函数!!
basicConfig()函数的关键参数:
- filename: 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了;
- filemode: 指定日志文件的打开模式,默认为'a'。需要注意的是,该选项要在filename指定时才有效;
- format: 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
- datefmt: 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
- level :指定日志器的日志级别
- stream: 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
- style: Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%'
- handlers: Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。
这里面的参数比较简单,想要重点介绍的就是format和datefmt,他们两个控制着日志的格式化输出,下面我们看一下,格式化字段是怎样的。
5. logging模块定义的格式字符串字段
我们来列举一下logging模块中定义好的可以用于format格式字符串中字段有哪些:
- asctime: %(asctime)s:日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
- created: %(created)f: 日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
- relativeCreated: %(relativeCreated)d: 日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
- msecs :%(msecs)d 日志事件发生事件的毫秒部分
- levelname: %(levelname)s: 该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
- levelno: %(levelno)s: 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
- name: %(name)s: 所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger
- message: %(message)s: 日志记录的文本内容,通过 msg % args计算得到的
- pathname: %(pathname)s: 调用日志记录函数的源码文件的全路径
- filename: %(filename)s: pathname的文件名部分,包含文件后缀
- module: %(module)s: filename的名称部分,不包含后缀
- lineno: %(lineno)d: 调用日志记录函数的源代码所在的行号
- funcName: %(funcName)s: 调用日志记录函数的函数名
- process: %(process)d: 进程ID
- processName:%(processName)s: 进程名称,Python 3.1新增
- thread: %(thread)d: 线程ID
- threadName: %(thread)s: 线程名称
我们来看个例子,上面这些参数怎么用。
代码:
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
输出:
05/08/2017 14:29:04 PM - DEBUG - This is a debug log.
05/08/2017 14:29:04 PM - INFO - This is a info log.
05/08/2017 14:29:04 PM - WARNING - This is a warning log.
05/08/2017 14:29:04 PM - ERROR - This is a error log.
05/08/2017 14:29:04 PM - CRITICAL - This is a critical log.
6.添加自定义的字段
上面的例子中使用的格式化字段都是系统定义好的,如果想添加一个自定义的字段,该怎么办呢?可以借助日志输出的extra参数来实现。
logging.debug(), logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数。它们支持3个关键字参数: exc_info, stack_info, extra,下面对这几个关键字参数作个说明。
exc_info: 其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
stack_info: 其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
extra: 这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。
举个栗子:
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(user)s - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
logging.basicConfig(format=LOG_FORMAT, datefmt=DATE_FORMAT)
logging.warning("Some one delete the log file." extra={'user': 'Tom'})
输出:
05/08/2017 16:35:00 PM - WARNING - Tom - Some one delete the log file.
7.logging的作用域
logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。所以,在一个py文件中配置一次logging,在其他文件使用的时候,都遵循之前的设置,不必重复设置。
(原文是非常好的日志介绍,基本的内容我就不重复写了,直接转过来,本文在原文的基础上融入自己的理解与总结:https://www.cnblogs.com/yyds/p/6901864.html)