python报错提示以及logger的一些应用

本篇是拆解这篇【python︱函数、for、if、name、迭代器、防范报错、类定义、装饰器、argparse模块、yield 】 将报错 + logger提示拿出来


1、防范报错

1.1 assert 断言

Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息。例如:

py> x = 23
py> assert x > 0, "x is not zero or negative"
py> assert x%2 == 0, "x is not an even number"
Traceback (most recent call last):
File "", line 1, in 
AssertionError: x is not an even number

类似R中的stopifnot

参考博客:Python 中何时使用断言?
.

2 try…except…

两种将报错异常展示的方式:

# 第一种
try:
    0/0
except Exception as e:
    print (e)
>>> division by zero

# 第二种,更加全面
import traceback
import sys
try:
    0/0
except:
    traceback.print_exc()
>>> 
Traceback (most recent call last):
  File "<ipython-input-4-17cf35f30609>", line 4, in <module>
    0/0
ZeroDivisionError: division by zero

try:
    f = open('xxx')
except:
    print 'fail to open'
    exit(-1)

如果try中open不出来,那么就except返回相应的内容“fail to open”

try:
<语句>        #运行别的代码
except <名字>:
<语句>        #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句>        #如果引发了'name'异常,获得附加的数据
else:
<语句>        #如果没有异常发生

参考
看一个案例

try:
    print('I am sure no exception is going to occur!')
except Exception:
    print('exception')
else:
    # 这里的代码只会在try语句里没有触发异常时运行,
    # 但是这里的异常将 *不会* 被捕获
    print('This would only run if no exception occurs. And an error here '
          'would NOT be caught.')
finally:
    print('This would be printed in every case.')

# Output: I am sure no exception is going to occur!
# This would only run if no exception occurs.
# This would be printed in every case.

报错并提示异常信息

来源:Python中获取异常(Exception)信息
1、str(e)

返回字符串类型,只给出异常信息,不包括异常信息的类型,如1/0的异常信息

‘integer division or modulo by zero’
2、repr(e)

给出较全的异常信息,包括异常信息的类型,如1/0的异常信息

“ZeroDivisionError(‘integer division or modulo by zero’,)”
3、e.message

获得的信息同str(e)

4、采用traceback模块

需要导入traceback模块,此时获取的信息最全,与python命令行运行程序出现错误信息一致。使用traceback.print_exc()打印异常信息到标准错误,就像没有获取一样,或者使用traceback.format_exc()将同样的输出获取为字符串。你可以向这些函数传递各种各样的参数来限制输出,或者重新打印到像文件类型的对象。

try:
    1/0
except Exception, e:
    print 'str(Exception):\t', str(Exception)
    print 'str(e):\t\t', str(e)
    print 'repr(e):\t', repr(e)
    print 'e.message:\t', e.message
    print 'traceback.print_exc():'; traceback.print_exc()
    print 'traceback.format_exc():\n%s' % traceback.format_exc()

或者:

def this_fails():
    x = 1/0

try:
    this_fails()
except :
    print('Handling run-time error:')
    raise
print(1)

优雅的异常报错:suppress

但是常碰到的情形是这样的:

• 我们知道这个异常有可能发生
• 我们不关心这个异常,如果发生了,什么也不用处理,直接忽略就好

如果要处理这种情形的异常,那么不必使用 try-except,Python 内置的 contextlib 库提供了一个函数,叫 suppress,是处理这种异常更优雅的方式,Pythonista 一定要尝试使用。

# 单个非0报错
from contextlib import suppress

nums = [3,0,3,0,3]
result = 0
for num in nums:
    with suppress(ZeroDivisionError):
        result += 1/num
print(result) # 1.0

# 免疫所有报错
for num in nums:
    with suppress(Exception):
        result += 1/num

# 别这么写
result = 0 
nums = [3,0,3,0,3]
with suppress(Exception):
    for num in nums:
        result += 1/num

print(result) # 0.3333333333333333

3、报错日志记录:Logger.exception

以ERROR级别记录日志消息,异常跟踪信息将被自动添加到日志消息里。Logger.exception通过用在异常处理块中,如:
来源:Python模块学习:logging 日志记录

import logging
logging.basicConfig(filename = os.path.join(os.getcwd(), 'log.txt'), level = logging.DEBUG)
log = logging.getLogger('root')
try:
    a===b
except:
    log.exception('exception') #异常信息被自动添加到日志消息中

就会在指定目录里面,载入到Log.txt之中

来看看在Log中显示如下:

ERROR:root:exception
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    a==b
NameError: name 'a' is not defined

所以,在 log.exception()可以简单定义一些关键词来帮助定位问题所在。

3.1 logger - basicConfig 常规存储

来源:python logging模块打印log到指定文件

import logging
import unittest
class lgtest(unittest.TestCase):
    logging.basicConfig(filename='../LOG/'+__name__+'.log',format='[%(asctime)s-%(filename)s-%(levelname)s:%(message)s]', level = logging.DEBUG,filemode='a',datefmt='%Y-%m-%d%I:%M:%S %p')

    def test(self):

        logging.error("这是一条error信息的打印")
        logging.info("这是一条info信息的打印")
        logging.warning("这是一条warn信息的打印")
        logging.debug("这是一条debug信息的打印")
if __name__=='__main__':
    unittest.main()
  • Filename:指定路径的文件。这里使用了+—name—+是将log命名为当前py的文件名
  • Format:设置log的显示格式(即在文档中看到的格式)。分别是时间+当前文件名+log输出级别+输出的信息
  • Level:输出的log级别,优先级比设置的级别低的将不会被输出保存到log文档中
  • Filemode: log打开模式
    • a:代表每次运行程序都继续写log。即不覆盖之前保存的log信息。
    • w:代表每次运行程序都重新写log。即覆盖之前保存的log信息

3.2 常规:logging + handlers + TimedRotatingFileHandler

参考:Python + logging 输出到屏幕,将log日志写入文件
参考:python中logging模块下篇

# 信息写入log
import logging
from logging import handlers
class Logger(object):
    level_relations = {
        'debug':logging.DEBUG,
        'info':logging.INFO,
        'warning':logging.WARNING,
        'error':logging.ERROR,
        'crit':logging.CRITICAL
    }#日志级别关系映射

    def __init__(self,filename,level='info',when='D',backCount=15,\
                 fmt='%(asctime)s - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)   # 创建log文件
        format_str = logging.Formatter(fmt)#设置日志格式
        self.logger.setLevel(self.level_relations.get(level))#设置日志级别
        if not self.logger.handlers:
            sh = logging.StreamHandler()  # 初始化 1 ,往屏幕上输出
            sh.setFormatter(format_str)   #  设置屏幕上显示的格式
            th = handlers.TimedRotatingFileHandler(filename=filename,when=when,\
                                                   backupCount=backCount,encoding='utf-8')
            
            th.setFormatter(format_str)#设置文件里写入的格式
            self.logger.addHandler(sh) #把对象加到logger里
            self.logger.addHandler(th)
if __name__ == '__main__':
    log = Logger('all.log',level='debug')
    log.logger.debug('debug')
    log.logger.info('info')
    log.logger.warning('警告')
    log.logger.error('报错'', exc_info=True)
    log.logger.critical('严重')
    Logger('error.log', level='error').logger.error('error')

屏幕上的结果如下:

2018-03-13 21:06:46,092 - DEBUG: debug
2018-03-13 21:06:46,092 - INFO: info
2018-03-13 21:06:46,092- WARNING: 警告
2018-03-13 21:06:46,099 - ERROR: 报错
2018-03-13 21:06:46,099- CRITICAL: 严重
2018-03-13 21:06:46,100 - ERROR: error

由于when=D,新生成的文件名上会带上时间,如下所示。
在这里插入图片描述

其中:

  • level_relations ,代表日志级别,默认等级是WARNING,这意味着仅仅这个等级及以上的才会反馈信息,除非logging模块被用来做其它事情。

    • 日志级别: debug < info < warning < error < critical
  • TimedRotatingFileHandler - 按照时间自动分割日志文件

  • interval是时间间隔

  • when是间隔的时间单位,单位有以下几种:

    • S 秒
    • M 分
    • H 小时、
    • D 天、
    • W 每星期(interval==0时代表星期一)
    • midnight 每天凌晨
  • backupCount 是保留日志个数。默认的0是不会自动删除掉日志。若设10,则在文件的创建过程中库会判断是否有超过这个10,若超过,则会从最先创建的开始删除。

  • exc_info,True,代表把错误信息也保存下来,默认是False,不保存

同时,
你会发现如果不设置if not self.logger.handlers:,那么会出现,重复写日志问题。问题出在,他会不断往log.logger.handlers添加handlers,上限是三个,就会出现:
第一条记录写一次,第二条记录写两次,第三条记录写三次。
(解决方案来自:python logging 重复写日志问题)
原理在于,如果self.logger.handlers已经有类了,那么就不要额外添加了。

# 正常的self.logger.handlers长什么样子:
[<logging.StreamHandler at 0x12eba1e3128>,
 <logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>]

# 不正常的self.logger.handlers长什么样子:
[<logging.StreamHandler at 0x12eba1e3128>,
 <logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>,
 [<logging.StreamHandler at 0x12eba1e3128>,
 <logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>,]

延伸错误信息一起载入log:

import logging
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s  %(message)s',
                   datefmt='%a, %d %b %Y %H:%M:%S +0000',
                   filename='my.log')

logging.info('this is a info')
try:
    do
except Exception:
    logging.error('There are something wrong', exc_info=True)

logging.info('continue')


# 报错信息暴露
Sun, 01 Jul 2018 21:10:53 +0000  There are something wrong
Traceback (most recent call last):
  File "learn.py", line 9, in <module>
    do
NameError: name 'do' is not defined
Sun, 01 Jul 2018 21:10:53 +0000  continue

延申错误二:logger无法显示中文

stream.write(msg)
UnicodeEncodeError: 'ascii' codec can't encode character '\ufffd' in position 363: ordinal not in range(128)

参考:https://github.com/EasyEngine/easyengine/issues/843

解决,其实是系统缺少UTF-8组件,需要载入:

This worked for me:

sudo locale-gen en_US.UTF-8
export LANG=en_US.UTF-8

From: http://askubuntu.com/questions/393638/unicodedecodeerror-ascii-codec-cant-decode-byte-0x-in-position-ordinal-n

3.3 用.yaml文件配置Log

python中logging模块下篇

我们不仅可以通过python代码进行logging配置,而且可以通过写一个yaml文件进行配置,每次需要用logging时只要调用这个文件就配置完成。

config.yaml文件内容如下

version: 1
formatters:
  simple:
    format: "%(message)s"
  more:
    format: "%(asctime)s - %(levelname)s - %(message)s"
handlers:
  console:
    class : logging.StreamHandler
    formatter: simple
    level: INFO
    stream: ext://sys.stdout
  file:
    class: logging.FileHandler
    formatter: more
    level: DEBUG
    filename: debug.log
loggers:
  mainlogger:
    level: DEBUG
    handlers: [console, file]
  root:
    level: DEBUG
    handlers: [console]

main.py文件中编写代码如下

import logging
import logging.config
import yaml
import a

with open('config.yaml', 'r', encoding='utf-8') as f:
    config = yaml.load(f)
    logging.config.dictConfig(config)

logging.info('main file log')
a.run()

4、with…as…

那么with和as也是一种防止报错的防范方式,
当python执行这一句时,会调用__enter__函数,然后把该函数return的值传给as后指定的变量。
之后,python会执行下面do something的语句块。最后不论在该语句块出现了什么异常,都会在离开时执行__exit__。

ith open("x.txt") as f:
    data = f.read()

with open("x.txt") as f1, open('xxx.txt') as f2:
    do something with f1,f2

那么try和with也可以合起:

try:
    with open( "a.txt" ) as f :
        do something
except xxxError:
    do something about exception

延伸:报错提示如何写ValueError()

if pretrained_model not in self._models:
    raise ValueError(
        'The n_class needs to be supplied as an argument.')

5 主动发出报错raise

参考url:https://blog.csdn.net/skullfang/article/details/78820541

raise RuntimeError('testError')

6 Logger好用的新库:loguru

6.1 文件保存

#!pip install loguru

使用起来:

from loguru import logger
import sys

# 文件保存
# logger.add("log/file.log", rotation="12:00")     # 每天中午12点,自动完成归档,并启用新日志文件
# logger.add('runtime_{time}.log', rotation="500 MB")  # 设置超过 500 MB 新创建一个 log 文件
# logger.add('runtime_{time}.log', rotation='1 week') # 设置每隔一个周新创建一个 log 文件
# logger.add('runtime_{time}.log', compression='zip')  # 可以配置日志文件的压缩格式,这样可以更加节省存储空间,比如设置使用 zip 文件格式保存
logger.add('log/runtime_{time}.log', rotation='00:00') # 每天0点,自动完成归档,并启用新日志文件
logger.add(sys.stdout, colorize=True)     # 将日志输出到控制台,并添加颜色

在这里插入图片描述

6.2 文件保存与删除


# 文件保存与删除
logger.remove(log_file) # 删除
logger.add('runtime_{time}.log', retention='15 days') # 可以设置日志的最长保留时间,比如设置日志文件最长保留 15 天
logger.add('runtime_{time}.log', retention=10)  # 设置日志文件最多保留 10 个

# 文件定时删除
    # 也可以是一个 datetime.timedelta 对象,比如设置日志文件最多保留 5 个小时
import datetime
from loguru import logger
logger.add('runtime_{time}.log', retention=datetime.timedelta(hours=5))



6.3 常规用法 + 异常函数追踪 - 非常给力!!



#  常规用法
logger.info("This is log info!")
logger.warning("This is log warn!")
logger.error("This is log error!")
logger.debug("This is log debug!")

# 异常函数追踪 - 非常给力!!
from loguru import logger
@logger.catch
def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

my_function(0, 0, 0)


参考文献:

Loguru:Python 日志终极解决方案
loguru:python开箱即用的日志库

猜你喜欢

转载自blog.csdn.net/sinat_26917383/article/details/130516068