我的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[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

wKioL1gFihyC8FWxAABIzsgy8-k695.png


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章