我的python學習–第十二天(二)
- 2020 年 1 月 8 日
- 筆記
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
