我的python学习--第十二天(二)

2020-01-08 20:16:50 浏览数 (1)

Python异常处理

  Python的异常处理能力是很强大的,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。

所有异常都是基类Exception的成员,所有异常都从基类Exception继承,而且都在exceptions模块中定义,

Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。

一、格式

代码语言:javascript复制
try:
    block                
except 异常类型:
    block               
finally:                                
    block

该种异常处理语法的规则是:

  • 执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句。
  • 如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句。
  • 如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
  • 如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。
  • 不管上面执行的怎么样,都要执行finally下面的内容。

示例代码:

代码语言:javascript复制
try:
    f = open(“file.txt”,”r”)
except IOError, e:      # 捕获到的IOError错误的详细原因会被放置在对象e中,然后运行该异常的except代码块
    print e

可以使用Exception来捕获所有的异常,所有的异常信息都收来了,简单省心

代码语言:javascript复制
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:传统方式的异常处理

代码语言:javascript复制
In [1]: try:
   ...:     1/0
   ...: except Exception,e:
   ...:     print e
   ...:     
integer division or modulo by zero               # 只显示简单的错误信息

2:加入了traceback之后的异常处理

代码语言:javascript复制
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"。

定义一个异常:

代码语言:javascript复制
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 错误错误编号和信息

代码语言:javascript复制
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工作流演示

代码语言:javascript复制
#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 ')

运行结果:

代码语言:javascript复制
[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生产环境的使用方法:将其封装为函数

代码语言:javascript复制
#/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的各项配置参数,不好用

代码语言:javascript复制
# 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")

结果

代码语言:javascript复制
[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的参数,最好用的方式

代码语言:javascript复制
[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

调用

代码语言:javascript复制
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')

生产环境中的调用方法:通过函数

代码语言:javascript复制
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处理,

示例:

代码语言:javascript复制
# 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')

输出结果:

代码语言:javascript复制
[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

代码语言:javascript复制
[root@yaoliang day_12]# pip install gunicorn supervisor

2、启动gunicorn

代码语言:javascript复制
[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、先生成配置文件

代码语言:javascript复制
[root@yaoliang day_12]# echo_supervisord_conf > /etc/supervisor.conf

  3.2、修改配置文件,开启web管理界面,并在/etc/supervisor.conf底部添加新配置

代码语言:javascript复制
[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的基本使用方法

代码语言:javascript复制
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

代码语言:javascript复制
[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管理界面,并启动

代码语言:javascript复制
[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

0 人点赞