2020年12月26日 星期六

MicroPython on ESP8266 學習筆記 (二十五) : 安裝 logging (日誌) 模組

昨天收到一個網友留言詢問 MicroPython 是否能夠使用 logging 日誌功能, 這我倒從來沒測試過. 今天就用 D1 mini (ESP8266) 來測試看看, 使用的是 MicroPython 1.12 版韌體. 關於 logging 模組參考 CPython 的測試紀錄 :


D1 mini 開機後用 Putty 連線 COM 埠, 直接下 import logging 指令結果報錯, 可見 MicroPython 核心並未將此模組納入標準內建模組 :

MicroPython v1.12 on 2019-12-20; ESP module with ESP8266
Type "help()" for more information.
>>> import logging    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: no module named 'logging'

到 Pypi 網站查詢發現其實已經有大德寫好了 micropython-logging 套件, 參考 :


根據說明, 這個套件只是 CPython 在嵌入式設備上的縮小版實作, 用來實現 MicroPython 上基本的事件紀錄功能 (event logging), 但它只是 CPython 的一個子集 (subset), 不支援下列功能 : 
  • 不支援事件傳播 (event propogation) : 亦即事件不會向上傳遞到上層.
  • 不支援過濾器 (filter) 功能
在 MicroPython 安裝第三方套件須先連網後用 upip.install() 安裝, 關於 ESP8266/ESP32 連接 WiFi 基地台方法可參考 :


MicroPython v1.12 on 2019-12-20; ESP module with ESP8266
Type "help()" for more information.
>>> import network    
>>> sta=network.WLAN(network.STA_IF)   
>>> sta.active(True)   
>>> sta.connect('TonyNote8', '123456')   
>>> sta.ifconfig()   
('192.168.43.227', '255.255.255.0', '192.168.43.1', '192.168.43.1')

可見 ESP8266 已獲得路由器配發 192.168.43.227 位址, 表示已順利連網了. 此處是連上我三星 Note8 手機分享的熱點, 透過行動網路連上 Internet.  

接著用 os 模組檢視檔案系統 :

>>> import os   
>>> os.listdir()   
['boot.py']    

可見根目錄下只有一個 boot.py 開機程式而已. 

接著匯入 upip 模組並呼叫其 install() 函數來安裝 micropython-loggin 套件 :

>>> import upip   
>>> upip.install('micropython-logging')   
Installing to: /lib/
Warning: micropython.org SSL certificate is not validated
Installing micropython-logging 0.3 from https://micropython.org/pi/logging/logging-0.3.tar.gz

這樣就順利安裝 micropython-logging 套件了, 用 os.listdir() 再次檢視根目錄可發現多了一個 lib 目錄, 切換到此 lib 子目錄即可找到 logging.py 模組 : 

>>> os.listdir()  
['boot.py', 'lib']
>>> os.chdir('lib')    
>>> os.listdir()     
['logging.py']

所以雖然套件名稱是 micropython-logging, 但安裝進來的模組名稱仍與 CPython 一樣是 logging, 所以用法與 CPython 完全相同. 先將路徑切回根目錄 : 

>>> os.chdir('..')   
>>> os.listdir()   
['boot.py', 'lib']

然後就可以直接匯入 logging 模組, 並用 dir 觀察其結構 :

>>> import logging   
>>> dir(logging)    
['__class__', '__name__', 'info', 'sys', 'debug', 'DEBUG', 'getLogger', 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'NOTSET', '_level_dict', '_stream', 'Logger', '_level', '_loggers', 'basicConfig']

跟 CPython 比起來, MicroPython 版的 logging 真的是縮小集 : 

Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import logging  
>>> dir(logging)  
['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'FileHandler', 'Filter', 'Filterer', 'Formatter', 'Handler', 'INFO', 'LogRecord', 'Logger', 'LoggerAdapter', 'Manager', 'NOTSET', 'NullHandler', 'PercentStyle', 'PlaceHolder', 'RootLogger', 'StrFormatStyle', 'StreamHandler', 'StringTemplateStyle', 'Template', 'WARN', 'WARNING', '_STYLES', '_StderrHandler', '__all__', '__author__', '__builtins__', '__cached__', '__date__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__status__', '__version__', '_acquireLock', '_addHandlerRef', '_checkLevel', '_defaultFormatter', '_defaultLastResort', '_handlerList', '_handlers', '_levelToName', '_lock', '_logRecordFactory', '_loggerClass', '_nameToLevel', '_register_at_fork_acquire_release', '_releaseLock', '_removeHandlerRef', '_showwarning', '_srcfile', '_startTime', '_warnings_showwarning', 'addLevelName', 'atexit', 'basicConfig', 'captureWarnings', 'collections', 'critical', 'currentframe', 'debug', 'disable', 'error', 'exception', 'fatal', 'getLevelName', 'getLogRecordFactory', 'getLogger', 'getLoggerClass', 'info', 'io', 'lastResort', 'log', 'logMultiprocessing', 'logProcesses', 'logThreads', 'makeLogRecord', 'os', 'raiseExceptions', 'root', 'setLogRecordFactory', 'setLoggerClass', 'shutdown', 'sys', 'threading', 'time', 'traceback', 'warn', 'warning', 'warnings', 'weakref']

不過核心功能還是有的, 例如六個安全等級常數 : 

>>> logging.NOTSET  
0
>>> logging.DEBUG   
10
>>> logging.INFO   
20
>>> logging.WARNING    
30
>>> logging.ERROR   
40
>>> logging.CRITICAL     
50

但不能呼叫 addLevelName() 函數自訂安全等級與其名稱, 例如 :

>>> logging.addLevelName(35, 'MINOR')    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'addLevelName'

從上面 dir(logging) 結果可知, micropython-logging 套件僅支援 debug() 與 info() 這兩個日誌輸出函數, 且預設安全等級為 DEBUG, 等級高於此的 INFO 才會被記錄, 更高的 WARNING, ERROR, 與 CRITICAL 都不支援輸出 :

>>> logging.debug("debug message")    #未超過預設安全等級 DEBUG : 不輸出訊息
>>> logging.info("info message")            #超過預設安全等級 DEBUG : 輸出訊息
INFO:None:info message
>>> logging.warning("warning message")      #不支援 WARNING
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'warning'
>>> logging.error("error message")                 #不支援 ERROR
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'error'
>>> logging.critical("critical message")           #不支援 CRITICAL 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'critical'
 
可見目前僅支援 DEBUG 與 INFO 這兩種安全等級, 且預設為 DEBUG, 其他等級尚未支援. 

從上面 dir(logging) 中可知支援 basicConfig() 函數以設定日誌之安全層級設定, 例如預設為 DEBUG, 若改為 WARNING 的話, 呼叫 logging.info() 就不會輸出訊息了 :

>>> logging.info("info message")      #預設為 DEBUG, 而 INFO 等級超出 DEBUG 故會輸出
INFO:None:info message
>>> logging.basicConfig(level=logging.INFO)    #更改安全等級為 INFO 還是會輸出 INFO ???
>>> logging.info("info message")     
INFO:None:info message 
>>> logging.basicConfig(level=logging.WARNING)     #可設為 WARNING, 但不支援 warning()
>>> logging.info("info message")        #等級低於 WARNING 不輸出
>>> logging.warning("warning message")     #不支援 warning() 輸出
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'warning'

此處我覺得不解的是, 將安全等級設為 INFO 後再呼叫 info() 還是會輸出, 奇怪.  

另外 basicConfig() 也不支援 format 參數, 例如 : 

>>> logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(levelname)s : %(message)s') 
logging.basicConfig: format arg is not supported

糟糕的是竟然也未支援最重要的 filename 參數 :

>>> logging.basicConfig(level=logging.DEBUG,filename='mylog.txt')   
logging.basicConfig: filename arg is not supported

亦即只能顯示在 REPL 介面上, 無法儲存到檔案裡. 

呼叫 logging.getLogger() 會傳回一個 Logger 物件 : 

>>> logger = logging.getLogger(__name__)    
>>> logger    
<Logger object at 3fff1720>
>>> type(logger)    
<class 'Logger'>

用 Logger 輸出訊息時使用者欄位會變成 __main__ :

>>> logger.debug("hello1")   
DEBUG:__main__:hello1

總結來說, 雖然只有兩個安全層級且欠缺 filename 參數來指定紀錄檔, 但基本上還是堪用的. 不過我在下面這篇文章中卻發現可以設定 filename 且可呼叫 error(), critical() 等方法, 不清楚其測試環境是甚麼 :


沒有留言 :