昨天收到一個網友留言詢問 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() 等方法, 不清楚其測試環境是甚麼 :
沒有留言:
張貼留言