Python日志

1. 为什么要日志

日志对于系统开发、调试、运维都是非常重要的,它有助于我们分析程序。

和print的比较

The logging package has a lot of useful features:

  • Easy to see where and when (even what line no.) a logging call is being made from.
  • You can log to files, sockets, pretty much anything, all at the same time.
  • You can differentiate your logging based on severity.

Print doesn`t have any of these.

Also, if your project is meant to be imported by other python tools, it’s bad practice for your package to print things to stdout, since the user likely won’t know where the print messages are coming from.

2. logging模块基本概念

logging模块提供了几个类,Logger、Handler、Filter、Formatter,我们需要了解下其功能

(1)logger功能

logger具有层级的概念。子级的logger默认和祖先logger保持相同配置(level和handler等),但你仍可以设置它们不同。

  1. 给应用代码暴露了多个方法,使得应用在运行时可以进行日志输出;
  2. 决定哪个级别的日志被输出;
  3. 将日志信息传给handler;

(2)Handler作用

将来自logger的log信息分发到特定的目标。一个handler对象有一个目标;一个logger可以有多个handler。比如你可以将日志同时输出到console和一个特定目录下的XX.log文件,通过指定两个不同的handler。

常见的Handler有FileHandlerRotatingFileHandler TimedRotatingFileHandler等。

(3)Filter作用

Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。

比如,一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:'A.B''A.B,C''A.B.C.D''A.B.D',而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。

(4)Formatter作用

主要用来设置日志的格式。重要的几个格式串:

属性名格式串
时间%(asctime)s
文件名%(filename)s
函数名%(funcName)s
级别名%(levelname)s
行号%(lineno)s
模块名%(module)s
日志详情%(message)s
日志名%(name)s
路径名%(pathname)s
进程ID%(process)d
进程名%(processName)s
线程ID%(thread)d
线程名%(threadName)s

更详细见 https://docs.python.org/2/library/logging.html#logrecord-attributes

3. 使用logging记日志

(1)打印日志到控制台

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info('Start reading database')
# read database here
records = {'john': 55, 'tom': 66}
logger.debug('Records: %s', records)
logger.info('Updating records ...')
# update records here
logger.info('Finish updating records')

output:

INFO:__main__:Start reading database
INFO:__main__:Updating records ...
INFO:__main__:Finish updating records

There are different importance levels you can use, debug, info, warning, error and critical. By giving different level to logger or handler, you can write only error messages to specific log file, or record debug details when debugging.

Let’s change the logger level to DEBUG and see the output again

logging.basicConfig(level=logging.DEBUG)

(这个函数会默认为日志创建一个输出到stdout的handler)

output:

INFO:__main__:Start reading database
DEBUG:__main__:Records: {'john': 55, 'tom': 66}
INFO:__main__:Updating records ...
INFO:__main__:Finish updating records

我们发现换成debug模式,会打印出debug类型的日志。

(2)将日志写入文件

you can use a FileHandler to write records to a file.(这个时候就不需要使用basicConfig()方法了)

import logging

# get logger
logger = logging.getLogger('<log_name>')
logger.setLevel(logging.INFO)
# create a file handler
handler = logging.FileHandler('<log_file_name>')
handler.setLevel(logging.INFO)
# create a logging format
formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)s - %(funcName)s() - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(handler)

上述代码只会把日志写到文件,控制台不会有任何输出。

(3)打印异常

有时候你需要把异常信息、异常栈等信息打入日志,logger提供了一个很简单的方法logger.exception()

fuck.py:

def func():
    return 1/0

if __name__ == '__main__':
    try:
        func()
    except Exception as e:
        logger.exception(e)

output:

2017-09-23 18:27:08,360 - MainProcess - ERROR - fuck.py:21 - integer division or modulo by zero
Traceback (most recent call last):
  File "fuck.py", line 19, in <module>
    func()
  File "fuck.py", line 15, in func
    return 1/0
ZeroDivisionError: integer division or modulo by zero

4. 使用配置文件设置log

这里是一个例子,fuck项目是某个server,有如下结构:

fuck
├── conf
│   └── logging.conf
├── log
│   └── foo_server.log
└── src
     └── fuck.py

各文件内容

logging.conf :

[loggers]
keys=root,foo_server

[handlers]
keys=fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler

[logger_foo_server]
level=DEBUG
handlers=fileHandler
qualname=foo_server
propagate=0

[handler_fileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=('../log/foo_server.log', 'D', 1, 7, None, False, False)

[formatter_simpleFormatter]
format=%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - %(funcName)s() - %(message)s
datefmt=

fuck.py :


# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
import logging
import logging.config

logging.config.fileConfig('../conf/logging.conf')
logger = logging.getLogger('foo_server')

logger.info('hello, world!')

进入src目录,执行python fuck.py,你会看到log/foo_server.log文件中的日志输出。

配置文件格式说明使用配置文件和fileConfig()函数实现日志配置

5. 向日志输出中添加上下文信息

参考:向日志输出中添加上下文信息

6. 多线程、多进程log

logging模块默认是线程安全的,但是并不是进程安全的。也就是说,多进程同时输出一个log可能发生错乱。



Ref
http://stackoverflow.com/questions/6918493/in-python-why-use-logging-instead-of-print
https://fangpenlin.com/posts/2012/08/26/good-logging-practice-in-python

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值