佘小宝的爹 发表于 2018-8-9 10:34:02

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

  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
[*]  KeyError         试图访问字典里不存在的键
[*]  NameError      使用一个还未赋值的变量
[*]  SyntaxError       代码非法,
[*]  TypeError      传入对象类型与要求的不符合
[*]  ValueError       传给函数的参数类型不正确,比如给int()函数传入字符串形
  二、traceback获取详细的异常信息
  1:传统方式的异常处理
In : try:  
   ...:   1/0
  
   ...: except Exception,e:
  
   ...:   print e
  
   ...:
  
integer division or modulo by zero               # 只显示简单的错误信息
  2:加入了traceback之后的异常处理
In : import traceback  

  
In : 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是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可选的,如果不提供,异
  常的参数是"None"。
  定义一个异常:
In : import traceback  

  
In : 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("selectCabinet list error: %s" % traceback.format_exc())
  
    return json.dumps({'code': 1, 'errmsg': 'selectCabinet list error'})
  logging模块
  一、概述
  在实际项目中,需要对一些数据进行日志记录,并将日志记录到不同的存储单元中,例如数据库,文本,或者推送到图形化界面中,当需要时发现自己实现一个日志库其实是要很大的代价,因此,第三方的日志库上进行定制化处理 正文内容是对logging的理解和使用方式,非常方便
  1:四个主要类,使用官方文档中的概括:

[*]  logger       提供了应用程序可以直接使用的接口;
[*]  handler      将(logger创建的)日志记录发送到合适的目的输出;
[*]  filter       提供了细度设备来决定输出哪条日志记录;用处不太大
[*]  formatter   决定日志记录的最终输出格式
  2:模块级函数

[*]  logging.getLogger()       # 返回一个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 ')
  运行结果:
# 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()
  返回一个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 -%(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 %(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")
  结果
# tail myapp.log  
17 Oct 2016 17:42:48 test2.py DEBUG this is debug message
  
17 Oct 2016 17:42:48 test2.py INFO this is info message
  
17 Oct 2016 17:42:48 test2.py 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
[*]  %(filename)s:      # 打印当前执行程序名
[*]  %(funcName)s:      # 打印日志的当前函数
[*]  %(lineno)d:       # 打印日志的当前行号
[*]  %(asctime)s:      # 打印日志的时间
[*]  %(message)s:      # 打印日志信息
[*]  %(thread)d:       # 打印线程ID
[*]  %(threadName)s:    # 打印线程名称
[*]  %(process)d:      # 打印进程ID
  5、logging.config模块通过配置文件的方式,加载logger的参数,最好用的方式
# cat logger.conf  
# 定义logger模块,root是父类,必需存在的,其它的是自定义。
  
# logging.getLogger(NAME)就相当于向logging模块注册了实例化了
  
# name 中用 . 表示 log 的继承关系
  

  
keys=root,example01,example02
  
# logger_模块名称
  
# level   级别,级别有DEBUG、INFO、WARNING、ERROR、CRITICAL
  
# handlers处理类,可以有多个,用逗号分开
  
# qualnamelogger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块。
  
# propagate 是否继承父类的log信息,0:否 1:是
  

  
level=DEBUG
  
handlers=hand01,hand02
  

  
handlers=hand01,hand02
  
qualname=example01
  
propagate=0
  

  
handlers=hand01,hand03
  
qualname=example02
  
propagate=0
  
#
  
# class handler类名
  
# level 日志级别
  
# formatter,上面定义的formatter
  
# args handler初始化函数参数
  

  
keys=hand01,hand02,hand03
  

  

  
class=StreamHandler
  
level=INFO
  
formatter=form02
  
args=(sys.stderr,)
  

  

  
class=FileHandler
  
level=DEBUG
  
formatter=form01
  
args=('myapp.log', 'a')
  

  
class=handlers.RotatingFileHandler
  
level=INFO
  
formatter=form02
  
args=('myapp.log', 'a', 10*1024*1024, 5)
  
# 日志格式
  

  
keys=form01,form02
  

  
format=%(asctime)s %(filename)s %(levelname)s %(message)s
  
datefmt=%a, %d %b %Y %H:%M:%S
  

  
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,  
importlogging.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')
  输出结果:
# 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
# pip install gunicorn supervisor  2、启动gunicorn
# ls  
apprun.py
  
# gunicorn -w4 -b0.0.0.0:9999 app:app -D
  
# ps aux | grep gunicorn
  
root      433870.01.2 220196 12040 ?      S    17:42   0:00 gunicorn: master
  
root      433920.11.9 324784 19844 ?      S    17:42   0:00 gunicorn: worker
  
root      433930.11.9 324792 19848 ?      S    17:42   0:00 gunicorn: worker
  
root      433940.11.9 324800 19856 ?      S    17:42   0:00 gunicorn: worker
  
root      433970.11.9 324812 19864 ?      S    17:42   0:00 gunicorn: worker
  
root      434740.00.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、先生成配置文件
# echo_supervisord_conf > /etc/supervisor.conf  3.2、修改配置文件,开启web管理界面,并在/etc/supervisor.conf底部添加新配置
         ; 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))
  

  

  
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 |   # 启动指定/所有 supervisor管理的程序进程
  
supervisorctl -c /etc/supervisor.conf stop |      # 关闭指定/所有 supervisor管理的程序进程
  3.4、启动supervisor
# supervisord -c /etc/supervisor.conf  
# ps aux | grep supervisor
  
root      443930.01.1 224528 11308 ?      Ss   17:59   0:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor.conf
  
root      443990.00.0 112648   980 pts/0    R+   17:59   0:00 grep --color=auto supervisor
  
# supervisorctl -c /etc/supervisor.conf status
  
myapp                            STOPPED   Not started
  
# supervisorctl -c /etc/supervisor.conf start myapp
  
myapp: started
  
# supervisorctl -c /etc/supervisor.conf status
  
myapp                            RUNNING   pid 44417, uptime 0:00:04
  3.5、通过nginx配置supervisor的web管理界面,并启动
# vim /etc/nginx/nginx.conf  
    server {
  
      listen       80;
  
      server_namelocalhost;
  

  
      location / {
  
            proxy_pass http://127.0.0.1:9001;
  
      }
  
    }
  
# systemctl start nginx
  3.6、访问nginx
页: [1]
查看完整版本: 我的python学习--第十二天(二)