MaDi's Blog

一個紀錄自己在轉職軟體工程師路上的學習小空間

0%

[Python] config組態/logging模組/SMTP自動寄信

config: 程式碼內常常有許多參數,如果每次執行程式碼都要重新一一修改這些參數會是一件很麻煩也容易出錯的事情,因此我們可以透過建立config組態檔來打包所有主程式需要的參數,將參數設定與主程式的耦合介面拆離,達到更彈性的程式設計目的。

logging: 運行程式碼的過程常常伴隨著錯誤,為了記錄這些錯誤訊息,並給予他們不同的等級去分類,就可以更有效的管理這些錯誤,甚至將他們自動輸出成logger檔作為紀錄

SMTP: 當完成後的程式碼部署在雲端或是排程時,為了能夠及時得到運行成功與否的通知,我們可以透過SMTP自動寄信的服務來通知我們,節省更多時間。

config組態

組態設定檔,內部包有主程式需要的各個參數

config檔:

1
2
3
4
5
6
7
8
[主題一]
file = ids.csv
...

[主題二]
data = [1,2,3]
encoding = utf-8
...

主程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import configparser

file = 'config_CRAWLER.ini'

#設定config
config = configparser.ConfigParser()

#讀取config檔
if os.path.exists(file):
config.read(file)
else:
print('Config File Loss, Quit the crawler.')

# 存入config參數
file = config[主題一]['file']
data = eval(config[主題二]['data']) #eval把str直接執行
...

try-except抓錯

找出try-except發生error時的詳細位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import traceback

try:
code內文...
except Exception as e:
error_class = e.__class__.__name__ #取得錯誤類型
detail = e.args[0] #取得詳細內容
cl, exc, tb = sys.exc_info() #取得Call Stack
lastCallStack = traceback.extract_tb(tb)[-1] #取得Call Stack的最後一筆資料
fileName = lastCallStack[0] #取得發生的檔案名稱
lineNum = lastCallStack[1] #取得發生的行號
funcName = lastCallStack[2] #取得發生的函數名稱
errMsg = "\"{}\", line {}, in {}: [{}] {}\n".format(fileName, lineNum, funcName, error_class, detail)
print(errMsg)

logging模組

紀錄錯誤訊息並輸出成檔案,可以依照等級去過濾想得的訊息

1
import logging

1. level

1
2
3
4
5
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

預設baseline的等級是warning, 等級以下的(debug, info)不會印出,達到過濾掉不重要的資訊的效果,比print有效多了

自定義baseline等級為DEBUG

1
logging.basicConfig(level=logging.DEBUG)

2. 紀錄錯誤訊息

添加 exc_info=True 或是 logging.exception(輸出語句)

1
2
3
4
5
logging.debug(輸出語句, exc_info=True)
logging.info(輸出語句, exc_info=True)
logging.warning(輸出語句, exc_info=True)
logging.error(輸出語句, exc_info=True)
logging.critical(輸出語句, exc_info=True)

3. 輸出格式

預設的輸出格式: %(levelname)s : %(name)s : %(message)s

1
2
3
4
5
DEBUG:root:debug message
INFO:root:info message
WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message

自定義輸出格式

format: 輸出格式

datefmt: 輸出的日期格式,需仿照time.strftime()的格式

filename: 輸出的檔名

filemode: a附加 or w寫入,預設是a

format格式化:

1
2
3
LOGGING_FORMAT = '%(asctime)s %(levelname)s: %(message)s'
DATE_FORMAT = '%Y%m%d %H:%M:%S'
logging.basicConfig(level=logging.DEBUG, format=LOGGING_FORMAT, datefmt=DATE_FORMAT, filename='errorLog.log', filemode='w')

輸出:

1
20201103 17:27:07 DEBUG: debug message

SMTP寄信

透過SMTP達到自動寄信的功能

如果郵件伺服器使用的是舊版的 SSL 傳輸安全憑證就呼叫 SMTP_SSL(),如果使用新版的 TLS 安全憑證就呼叫 SMTP().

Gmail 郵件主機同時支援 SSL 與 TLS 安全憑證:

SSL 主機埠號為 465
TLS 主機埠號為 587

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

def _SMTP_Notify(sender,receiver,password,From,to,subject,content,files=[]):

# 建立一個帶附件的例項
message = MIMEMultipart()

# 信件內容
message.attach(MIMEText(dt.strftime(dt.now(),'%Y/%m/%d %H:%M:%S')+'\n'+content,'plain','utf-8'))

# 信件顯示 -> 寄件者
message['From'] = Header(From,'utf-8')

# 信件顯示 -> 收件者
message['To'] = Header(to,'utf-8')

# 信件顯示 -> 主旨
message['Subject'] = Header(subject,'utf-8')

# 夾帶當前目錄的附件檔案
for file in files:
att = MIMEText(open(abs_path(file),'rb').read(),'base64','utf-8')

# 設定Content-Type
att['Content-Type'] = 'application/octet-stream'

# filename可以任意寫 -> 郵件中顯示的名字
att['Content-Disposition'] = 'attachment; filename={}'.format(file)
message.attach(att)

# 串接Proxy/ TLS -> 587
smtpObj = smtplib.SMTP('10.110.15.79',587)
smtpObj.ehlo() # 顯示執行中的log
smtpObj.starttls() # 採用TLS(較隱私)
smtpObj.login(sender,password)
smtpObj.sendmail(sender,receiver,message.as_string())
smtpObj.close()
print('SMTP-Notification email sends successfully!')

參考