Python异常处理
Python的异常处理能力是很强大的,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。
所有异常都是基类Exception的成员,所有异常都从基类Exception继承,而且都在exceptions模块中定义,
Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。
一、格式
try:
block
except 异常类型:
block
finally:
block
该种异常处理语法的规则是:
执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句。
如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。
不管上面执行的怎么样,都要执行finally下面的内容。
示例代码:
try:
f = open(“file.txt”,”r”)
except IOError, e: # 捕获到的IOError错误的详细原因会被放置在对象e中,然后运行该异常的except代码块
print e
可以使用Exception来捕获所有的异常,所有的异常信息都收来了,简单省心
try:
f = open(“file.txt”,”r”)
except Exception,e: # Exception是所有异常类的基类,所有类型的错误信息都会输入到e中
print e
常见异常类型
AttributeError 试图访问一个对象没有的树形,比如foo.x,但foo没有属性x
IOError 输入输出异常;基本是无法打开文件错误
ImportError 无法引入模块或者包;基本上是路径问题或者名称错误
IndentationError 语法错误;代码没有正确的对齐
IndexError: 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
NameError 使用一个还未赋值的变量
SyntaxError 代码非法,
TypeError 传入对象类型与要求的不符合
ValueError 传给函数的参数类型不正确,比如给int()函数传入字符串形
二、traceback获取详细的异常信息
1:传统方式的异常处理
In [1]: try:
...: 1/0
...: except Exception,e:
...: print e
...:
integer division or modulo by zero # 只显示简单的错误信息
2:加入了traceback之后的异常处理
In [1]: import traceback
In [2]: try:
...: 1/0
...: except Exception:
...: traceback.print_exc() # 打印出详细的错误信息
...:
Traceback (most recent call last):
File "<ipython-input-2-7989d926ba7a>", line 2, in <module>
1/0
ZeroDivisionError: integer division or modulo by zero
3:traceback.print_exc() vs traceback.format_exc()
format_exc():返回字符串,可以结合logging模块使用
logging.getLogger().error("Get users list error: %s" % traceback.format_exc())
print_exc():直接给打印出来。也可以接受file参数直接写入到一个文件
traceback.print_exc() # 打印到屏幕
traceback.print_exc(file=open('tb.txt','w+')) # 错误信息重定向到文件
三、手动触发异常
在Python中,除了程序自身错误引发的异常外,也可以根据自己需要手工引发异常,最简单的形式就是输入关键
字raise,后跟要引发的异常的名称。
raise语法格式如下:
raise[Exception[, args [, traceback]]]
语句中Exception是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可选的,如果不提供,异
常的参数是"None"。
定义一个异常:
In [1]: import traceback
In [2]: try:
...: print 'hello world'
...: raise Exception('just a test') # 自己定义一个异常
...: except Exception:
...: traceback.print_exc()
...:
hello world
Traceback (most recent call last):
File "<ipython-input-2-32f7ee25cfcc>", line 3, in <module>
raise Exception('just a test')
Exception: just a test
生产中自定义异常的方式:直接return 错误错误编号和信息
try:
... ...
if role != 0:
return json.dumps({'code':1,'errmsg':'you are not admin'})
... ...
except:
logging.getLogger().error("select Cabinet list error: %s" % traceback.format_exc())
return json.dumps({'code': 1, 'errmsg': 'select Cabinet list error'})
logging模块
一、概述
在实际项目中,需要对一些数据进行日志记录,并将日志记录到不同的存储单元中,例如数据库,文本,或者推送到图形化界面中,当需要时发现自己实现一个日志库其实是要很大的代价,因此,第三方的日志库上进行定制化处理 正文内容是对logging的理解和使用方式,非常方便
1:四个主要类,使用官方文档中的概括:
logger 提供了应用程序可以直接使用的接口;
handler 将(logger创建的)日志记录发送到合适的目的输出;
filter 提供了细度设备来决定输出哪条日志记录;用处不太大
formatter 决定日志记录的最终输出格式
2:模块级函数
logging.getLogger([name]) # 返回一个logger对象,如果没有指定名字将返回root logger,最常用
logging.basicConfig(): # 给logger对象的配置管理函数,不常用
logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical(): # logger的日志级别
二、logging工作流演示
#coding:utf-8
import logging
# 创建一个logger命名为mylogger(可以是任意字符串), %(name)s可调用这个名字
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件,只输出debug级别以上的日志
fh = logging.FileHandler('test.log')
fh.setLevel(logging.DEBUG)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(filename)s- %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)
# 记录两条日志
logger.info('foorbar')
logger.debug('just a test ')
运行结果:
[root@yaoliang day_12]# python test.py
2016-10-17 17:26:10,111 - mylogger - test.py- INFO - foorbar
2016-10-17 17:26:10,113 - mylogger - test.py- DEBUG - just a test
三、logging模块的api
1:logging.getLogger([name])
返回一个logger实例,如果没有指定name,返回root logger。只要name相同,返回的logger实例都是同一个而且只有一个,即name和logger实例是一一对应的。这意味着,无需把logger实例在各个模块中传递。只要知道name,就能得到同一个logger实例
2:logger.setLevel(lvl):设置logger记录日志的级别
level有以下几个级别:
NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICA
如果把logger的级别设置为INFO,那么小于INFO级别的日志都不输出,大于等于INFO级别的日志都输出。也就意味着同一个logger实例,如果多个地方调用,会出现很多重复的日志
3:logger.addHandler(hd):logger雇佣handler来帮它处理日志
handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
handler主要有以下几种:
(常用)
logging.StreamHandler: # 日志输出到流即控制台,可以是sys.stderr、sys.stdout
logging.FileHandler: # 日志输出到文件
logging.handlers.RotatingFileHandler: # 日志输出到文件,并按照设定的日志文件大小切割
logging.handlers.TimedRotatingFileHandler # 日志输出到文件,并按设定的时间切割日志文件
(不常用)
logging.handlers.SocketHandler: # 远程输出日志到TCP/IP sockets
logging.handlers.DatagramHandler: # 远程输出日志到UDP sockets
logging.handlers.SMTPHandler: # 远程输出日志到邮件地址
logging.handlers.SysLogHandler: # 日志输出到syslog
logging.handlers.NTEventLogHandler: # 远程输出日志到Windows NT/2000/XP的事件日志
logging.handlers.MemoryHandler: # 日志输出到内存中的制定buffer
由于StreamHandler和FileHandler是常用的日志处理方式,所以直接包含在logging模块中,而其他方式则包含在logging.handlers模块中,
handle常见调用
Handler.setLevel(lel) # 指定被处理的信息级别,低于lel级别的信息将被忽略
Handler.setFormatter() # 给这个handler选择一个格式
Handler.addFilter(filter) # 新增或删除一个filter对象
Handler.removeFilter(filter) # 新增或删除一个filter对象
logging生产环境的使用方法:将其封装为函数
#/usr/bin/env python
#coding:utf-8
import logging,logging.handlers
def WriteLog(log_name):
log_filename = "/tmp/test.log"
log_level = logging.DEBUG # 日志级别
format = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)2d]-%(funcName)s %(levelname)s %(message)s') # 日志格式
handler = logging.handlers.RotatingFileHandler(log_filename, mode='a', maxBytes=10*1024*1024, backupCount=5) # 日志输出到文件,文件最大10M,最多5个
handler.setFormatter(format)
logger = logging.getLogger(log_name)
logger.setLevel(log_level)
if not logger.handlers: # 每调用一次就会添加一个logger.handler,每次就额外多打印一次日志,if判断使其只调用一次
logger.addHandler(handler)
return logger # 函数最终将实例化的logger对象返回,后面直接调用即可
if __name__ == "__main__":
WriteLog('api').info('123') # 模块内部直接调用函数。等价下面两行
# 下面的方法不推荐
# writelog = WriteLog('api')
# writelog.info('123')
4、logging.basicConfig([**kwargs]):加载logger的各项配置参数,不好用
# coding:utf-8
import logging
logging.basicConfig(level=logging.DEBUG, # 输出debug及其级别更高级别的日志
format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%d %b %Y %H:%M:%S',
filename='myapp.log', # 日志文件输出的文件地址,不写默认打印到桌面
filemode='w')
logging.debug("this is debug message")
logging.info("this is info message")
logging.warning("this is warning message")
结果
[root@yaoliang day_12]# tail myapp.log
17 Oct 2016 17:42:48 test2.py [line:9] DEBUG this is debug message
17 Oct 2016 17:42:48 test2.py [line:10] INFO this is info message
17 Oct 2016 17:42:48 test2.py [line:11] WARNING this is warning message
关于logging.basicConfig函数的常用配置:
filename: # 指定日志文件名
filemode: # 和file函数意义相同,指定日志文件的打开模式,'w'或'a'
datefmt: # 指定时间格式,同time.strftime()
level: # 设置日志级别,默认为logging.WARNING,即warning及级别更高日志才输出
stream # 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,
默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
format # 指定输出的格式和内容,format可以输出很多有用信息
%(name)s: # 打印logger名,默认为root
%(levelno)s: # 打印日志级别的数值
%(levelname)s: # 打印日志级别名称
%(pathname)s: # 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: # 打印当前执行程序名
%(funcName)s: # 打印日志的当前函数
%(lineno)d: # 打印日志的当前行号
%(asctime)s: # 打印日志的时间
%(message)s: # 打印日志信息
%(thread)d: # 打印线程ID
%(threadName)s: # 打印线程名称
%(process)d: # 打印进程ID
5、logging.config模块通过配置文件的方式,加载logger的参数,最好用的方式
[root@yaoliang day_12]# cat logger.conf
# 定义logger模块,root是父类,必需存在的,其它的是自定义。
# logging.getLogger(NAME)就相当于向logging模块注册了实例化了
# name 中用 . 表示 log 的继承关系
[loggers]
keys=root,example01,example02
# [logger_xxxx] logger_模块名称
# level 级别,级别有DEBUG、INFO、WARNING、ERROR、CRITICAL
# handlers 处理类,可以有多个,用逗号分开
# qualname logger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块。
# propagate 是否继承父类的log信息,0:否 1:是
[logger_root]
level=DEBUG
handlers=hand01,hand02
[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0
[logger_example02]
handlers=hand01,hand03
qualname=example02
propagate=0
# [handler_xxxx]
# class handler类名
# level 日志级别
# formatter,上面定义的formatter
# args handler初始化函数参数
[handlers]
keys=hand01,hand02,hand03
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stderr,)
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('myapp.log', 'a')
[handler_hand03]
class=handlers.RotatingFileHandler
level=INFO
formatter=form02
args=('myapp.log', 'a', 10*1024*1024, 5)
# 日志格式
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%a, %d %b %Y %H:%M:%S
[formatter_form02]
format=%(asctime)s%(name)-12s: %(levelname)-8s %(message)s
datefmt=%a, %d %b %Y %H:%M:%S
调用
import logging
import logging.config
logging.config.fileConfig("logger.conf")
logger = logging.getLogger("example01")
logger.debug('This is debug message')
logger.info('This is info message')
logger.warning('This is warning message')
生产环境中的调用方法:通过函数
import logging,
import logging.config
def write_log(loggername):
work_dir = os.path.dirname(os.path.realpath(__file__))
log_conf= os.path.join(work_dir, 'conf/logger.conf')
logging.config.fileConfig(log_conf)
logger = logging.getLogger(loggername)
return logger
四、关于root logger以及logger的父子关系
如何得到root logger?
root logger是默认的logger如果不创建logger实例, 直接调用logging.debug()、logging.info()logging.warning(),logging.error()、logging.critical()这些函数,
那么使用的logger就是 root logger, 它可以自动创建,也是单实例的。
root logger的日志级别?
root logger默认的level是logging.WARNING
如何表示父子关系?
logger的name的命名方式可以表示logger之间的父子关系. 比如:
parent_logger = logging.getLogger('foo')
child_logger = logging.getLogger('foo.bar')
什么是effective level?
logger有一个概念,叫effective level。 如果一个logger没有显示地设置level,那么它就
用父亲的level。如果父亲也没有显示地设置level, 就用父亲的父亲的level,以此推....
最后到达root logger,一定设置过level。默认为logging.WARNING
child loggers得到消息后,既把消息分发给它的handler处理,也会传递给所有祖先logger处理,
示例:
# coding:utf-8
import logging
# 设置root logger,祖先
r = logging.getLogger()
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
r.addHandler(ch)
# 创建一个logger作为父亲
p = logging.getLogger('foo')
p.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(message)s')
ch.setFormatter(formatter)
p.addHandler(ch)
# 创建一个孩子logger
c = logging.getLogger('foo.bar')
c.debug('foo')
输出结果:
[root@yaoliang day_12]# python test3.py
2016-10-17 17:56:01,375 - foo
2016-10-17 17:56:01,375 - DEBUG - foo
可见,孩子logger没有任何handler,所以对消息不做处理。但是它把消息转发给了它的父亲以及root logger。最后输出两条日志。
这也就出现了一个问题,同一条日志会重复输出
解决方案
1、每个logger实例都给一个独立的名字,输出之间互不影响,
2、logging.conf中定义不继承
nginx + gunicorn + supervisor + flask
1、安装gunicorn和supervisor
[root@yaoliang day_12]# pip install gunicorn supervisor
2、启动gunicorn
[root@yaoliang homework_11]# ls
app run.py
[root@yaoliang homework_11]# gunicorn -w4 -b0.0.0.0:9999 app:app -D
[root@yaoliang homework_11]# ps aux | grep gunicorn
root 43387 0.0 1.2 220196 12040 ? S 17:42 0:00 gunicorn: master [app:app]
root 43392 0.1 1.9 324784 19844 ? S 17:42 0:00 gunicorn: worker [app:app]
root 43393 0.1 1.9 324792 19848 ? S 17:42 0:00 gunicorn: worker [app:app]
root 43394 0.1 1.9 324800 19856 ? S 17:42 0:00 gunicorn: worker [app:app]
root 43397 0.1 1.9 324812 19864 ? S 17:42 0:00 gunicorn: worker [app:app]
root 43474 0.0 0.0 112648 976 pts/0 R+ 17:43 0:00 grep --color=auto gunicorn
此时可以通过9999端口进行访问
-w:表示启动多少个进程
-b:表示监听的ip和端口
第一个app:表示包含Flask(__name__)对象的模块或包
第二个app:表示实例化Flask(__name__)对象
-D:表示以守护进程运行
3、通过supervisor,一个专门用来管理进程的工具来管理系统的进程。
3.1、先生成配置文件
[root@yaoliang day_12]# echo_supervisord_conf > /etc/supervisor.conf
3.2、修改配置文件,开启web管理界面,并在/etc/supervisor.conf底部添加新配置
[inet_http_server] ; inet (TCP) server disabled by default port=*:9001 ; (ip_address:port specifier, *:port for all iface)
username=user ; (default is no username (open server))
password=123 ; (default is no password (open server))
[program:myapp]
command=/usr/bin/gunicorn -w4 -b0.0.0.0:9999 app:app ; supervisor启动命令
directory=/data/python/homework_11
startsecs=0 ; 启动时间
stopwaitsecs=0 ; 终止等待时间
autostart=false ; 是否自动启动
autorestart=false ; 是否自动重启
stdout_logfile=/tmp/gunicorn.log ; 日常输出日志
stderr_logfile=/tmp/gunicorn.err ; 错误日志
3.3、supervisor的基本使用方法
supervisord -c /etc/supervisor.conf # 通过配置文件启动supervisor
supervisorctl -c /etc/supervisor.conf status # 察看supervisor的状态
supervisorctl -c /etc/supervisor.conf reload # 重新载入 配置文件
supervisorctl -c /etc/supervisor.conf start [all]|[appname] # 启动指定/所有 supervisor管理的程序进程
supervisorctl -c /etc/supervisor.conf stop [all]|[appname] # 关闭指定/所有 supervisor管理的程序进程
3.4、启动supervisor
[root@yaoliang day_12]# supervisord -c /etc/supervisor.conf
[root@yaoliang day_12]# ps aux | grep supervisor
root 44393 0.0 1.1 224528 11308 ? Ss 17:59 0:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor.conf
root 44399 0.0 0.0 112648 980 pts/0 R+ 17:59 0:00 grep --color=auto supervisor
[root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf status
myapp STOPPED Not started
[root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf start myapp
myapp: started
[root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf status
myapp RUNNING pid 44417, uptime 0:00:04
3.5、通过nginx配置supervisor的web管理界面,并启动
[root@yaoliang day_12]# vim /etc/nginx/nginx.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:9001;
}
}
[root@yaoliang day_12]# systemctl start nginx
3.6、访问nginx