[TOC]
## 日志模块四大组件
### 组件功能
| 组件名称 | 对应类名 | 功能描述 |
| --- | --- | --- |
| 日志器 | Logger | 提供了应用程序可一直使用的接口 |
| 处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 |
| 过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
| 格式器 | Formatter | 决定日志记录的最终输出格式 |
### 组件间关系:
* 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
* 不同的处理器(handler)可以将日志输出到不同的位置;
* 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
* 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
* 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
**总结:**
日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
![日志模块间关系](https://www.github.com/noah-luo/imags/raw/master/year/1548481029754.png)
## 每个组件的主要功能
### Logger组件
#### logger任务
Logger对象有3个任务要做:
* 1)向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
* 2)基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
* 3)将日志消息传送给所有感兴趣的日志handlers。
> Logger对象最常用的方法分为两类:配置方法 和 消息发送方法
#### 常用配置方法
| 方法 | 描述 |
| --- | --- |
| Logger.setLevel() | 设置日志器将会处理的日志消息的最低严重级别 |
| Logger.addHandler() 和 Logger.removeHandler() | 为该logger对象添加 和 移除一个handler对象 |
| Logger.addFilter() 和 Logger.removeFilter() | 为该logger对象添加 和 移除一个filter对象 |
#### 常用消息发送方法
| 方法 | 描述 |
| --- | --- |
| Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() | 创建一个与它们的方法名对应等级的日志记录 |
| Logger.exception() | 创建一个类似于Logger.error()的日志消息 |
| Logger.log() | 需要获取一个明确的日志level参数来创建一个日志记录 |
> Logger.exception()与Logger.error()的区别在于:Logger.exception()将会输出堆栈追踪信息,另外通常只是在一个exception handler中调用该方法。
#### 获取logger对象
一种方式是通过Logger类的实例化方法创建一个Logger类的实例,更通常的方法是用`logging.getLogger()`方法。
logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
* 聊天工具的图形界面模块可以这样获得它的Logger:
```
LOG=logging.getLogger(”chat.gui”)`
```
* 核心模块可以这样:
```
LOG=logging.getLogger(”chat.kernel”)
```
#### logger的层级结构与有效等级
* 层级结构
logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children
* 有效等级(effective level)
如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level,直到找到个一个明确设置了level的祖先为止。root logger总是会有一个明确的level设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。
* 继承关系
child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此不必所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
### **handler组件**
#### Handler组件用途
Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求
* 1)把所有日志都发送到一个日志文件中;
* 2)把所有严重级别大于等于error的日志发送到stdout(标准输出);
* 3)把所有严重级别为critical的日志发送到一个email邮件地址。
>这种场景就需要3个不同的handlers,每个handler负责发送一个特定级别的日志到一个特定的位置。
#### handler的配置方法
| 方法 | 描述 |
| --- | --- |
| Handler.setLevel() | 设置handler将会处理的日志消息的最低严重级别 |
| Handler.setFormatter() | 为handler设置一个格式器对象 |
| Handler.addFilter() 和 Handler.removeFilter() | 为handler添加 和 删除一个过滤器对象 |
>应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了所有handlers都应该有的接口
#### 常用的Handler
| Handler | 描述 |
| --- | --- |
| logging.StreamHandler | 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。 |
| logging.FileHandler | 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长 |
| logging.handlers.RotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
| logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
| logging.handlers.HTTPHandler | 将日志消息以GET或POST的方式发送给一个HTTP服务器 |
| logging.handlers.SMTPHandler | 将日志消息发送给一个指定的email地址 |
| logging.NullHandler | 该Handler实例会忽略error messages |
#### RotatingFileHandler按大小切割
当文件达到一定大小之后,自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出。
>比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1。若chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。
```
RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
```
* maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
* backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。
#### TimedRotatingFileHandler按时间切割
间隔一定时间就自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。
```
TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
```
* `interval`是时间间隔。
* `when`参数是一个字符串。
表示时间间隔的单位,不区分大小写。有以下取值:
* S 秒
* M 分
* H 小时
* D 天
* W 每星期(interval==0时代表星期一)
* midnight 每天凌晨
### Formater组件
#### 用途和语法
日志的formatter是个独立的组件,可以跟handler组合。Formater对象用于配置日志信息的最终顺序、结构和内容。
Formatter类的构造方法定义如下:
```
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
```
* fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
* datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
* style:可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
#### 与handler组合举例
```
fh = logging.FileHandler("access.log")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter) #把formmater绑定到fh上
```
### Filter组件
#### 用途和语法
Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:
```
class logging.Filter(name='A.B')
filter(record)
```
>一个filter实例化时传递的name参数值为'A.B',那么该filter将只允许名称为类似`'A.B','A.B,C','A.B.C.D','A.B.D'`的loggers产生的日志记录通过过滤。如果name为空字符串,则允许所有的日志事件通过过滤。
filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。
> 若需要,可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
> 还可以通过filter做一些统计工作,如计算一个特殊的logger或handler所处理的record数量等。
#### 自定义filter举例
自定义一个filter对日志内容进行过滤
```
class IgnoreBackupLogFilter(logging.Filter):
"""忽略带db backup 的日志"""
def filter(self, record): #固定写法
return "db backup" not in record.getMessage()
```
> 注意filter函数会返加True or False,logger根据此值决定是否输出此日志
然后把这个filter添加到logger中
```
logger.addFilter(IgnoreBackupLogFilter())
```
下面的日志就会把符合filter条件的过滤掉
```
logger.debug("test ....")
logger.info("test info ....")
logger.warning("start to run db backup job ....")
logger.error("test error ....")
```
## 完整案例
### 同时输出到屏幕和文件带filter
* 代码
```python
import logging
class IgnoreBackupLogFilter(logging.Filter):
"""忽略带db backup 的日志"""
def filter(self, record): #固定写法
return "db backup" not in record.getMessage()
# 1.定义两个handler,分别输出到标准流和文件
hand_c=logging.StreamHandler()
hand_f=logging.FileHandler('mysql.log')
hand_c.setLevel(logging.INFO)
# 2.定义formatter并与handler绑定
format1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
hand_c.setFormatter(format1)
hand_f.setFormatter(format1)
# 3.定义logger并设置日志级别
logg1=logging.getLogger('mysql')
logg1.setLevel(logging.DEBUG)
# 4.logger添加自定义的handler
logg1.addHandler(hand_f)
logg1.addHandler(hand_c)
# 5.添加自定义的filter
logg1.addFilter(IgnoreBackupLogFilter())
# 6.写入日志
logg1.debug("1. test ....")
logg1.info("2. test info ....")
logg1.warning("3. start to run db backup job ....")
logg1.error("4. test error ....")
```
* 输出结果
```
#屏幕输出
2019-01-28 11:42:23,266 - mysql - INFO - 2. test info ....
2019-01-28 11:42:23,266 - mysql - ERROR - 4. test error ....
#文件输出
2019-01-28 11:42:23,266 - mysql - DEBUG - 1. test ....
2019-01-28 11:42:23,266 - mysql - INFO - 2. test info ....
2019-01-28 11:42:23,266 - mysql - ERROR - 4. test error ....
# 解释
logger中定义了日志级别,会向下基础,所以文件中就用的logger定义的级别,而屏幕上的输出由于还有handler定义的级别,导致第一条被过滤了掉了
```
### 文件自动截断例子
```
import logging
logger = logging.getLogger(__name__)
log_file = "timelog.log"
fh = logging.handlers.TimedRotatingFileHandler(filename=log_file,when="S",interval=5,backupCount=3)
formatter = logging.Formatter('%(asctime)s %(module)s:%(lineno)d %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.warning("test1")
logger.warning("test12")
logger.warning("test13")
logger.warning("test14")
```
- 基础部分
- 基础知识
- 变量
- 数据类型
- 数字与布尔详解
- 列表详解list
- 字符串详解str
- 元组详解tup
- 字典详解dict
- 集合详解set
- 运算符
- 流程控制与循环
- 字符编码
- 编的小程序
- 三级菜单
- 斐波那契数列
- 汉诺塔
- 文件操作
- 函数相关
- 函数基础知识
- 函数进阶知识
- lambda与map-filter-reduce
- 装饰器知识
- 生成器和迭代器
- 琢磨的小技巧
- 通过operator函数将字符串转换回运算符
- 目录规范
- 异常处理
- 常用模块
- 模块和包相关概念
- 绝对导入&相对导入
- pip使用第三方源
- time&datetime模块
- random随机数模块
- os 系统交互模块
- sys系统模块
- shutil复制&打包模块
- json&pickle&shelve模块
- xml序列化模块
- configparser配置模块
- hashlib哈希模块
- subprocess命令模块
- 日志logging模块基础
- 日志logging模块进阶
- 日志重复输出问题
- re正则表达式模块
- struct字节处理模块
- abc抽象类与多态模块
- requests与urllib网络访问模块
- 参数控制模块1-optparse-过时
- 参数控制模块2-argparse
- pymysql数据库模块
- requests网络请求模块
- 面向对象
- 面向对象相关概念
- 类与对象基础操作
- 继承-派生和组合
- 抽象类与接口
- 多态与鸭子类型
- 封装-隐藏与扩展性
- 绑定方法与非绑定方法
- 反射-字符串映射属性
- 类相关内置方法
- 元类自定义及单例模式
- 面向对象的软件开发
- 网络-并发编程
- 网络编程SOCKET
- socket简介和入门
- socket代码实例
- 粘包及粘包解决办法
- 基于UDP协议的socket
- 文件传输程序实战
- socketserver并发模块
- 多进程multiprocessing模块
- 进程理论知识
- 多进程与守护进程
- 锁-信号量-事件
- 队列与生产消费模型
- 进程池Pool
- 多线程threading模块
- 进程理论和GIL锁
- 死锁与递归锁
- 多线程与守护线程
- 定时器-条件-队列
- 线程池与进程池(新方法)
- 协程与IO模型
- 协程理论知识
- gevent与greenlet模块
- 5种网络IO模型
- 非阻塞与多路复用IO实现
- 带着目标学python
- Pycharm基本使用
- 爬虫
- 案例-爬mzitu美女
- 案例-爬小说
- beautifulsoup解析模块
- etree中的xpath解析模块
- 反爬对抗-普通验证码
- 反爬对抗-session登录
- 反爬对抗-代理池
- 爬虫技巧-线程池
- 爬虫对抗-图片懒加载
- selenium浏览器模拟