日志这个东西,说有用就有用说没用也没用,但是板子在那运行,里边到底跑了些什么,有时候还真得靠日志找一找,就像示波器一样。
那么,本次就是一次日志的MICROPYTHON 的测试了,首先贴上库代码位置为ulogger/--init--.py
查了好几个资料,发现好几个都不能用,唯一这个库比较靠谱,那就先把代码帖下面
try: import time
except: import utime as time
try: import re
except: import ure as re
try: from micropython import const
except: const = lambda x:x # for debug
from io import TextIOWrapper
__version__ = "v1.0"
DEBUG: int = const(10)
INFO: int = const(20)
WARN: int = const(30)
ERROR: int = const(40)
CRITICAL: int = const(50)
TO_FILE = const(100)
TO_TERM = const(200)
# fmt map 的可选参数
_level = const(0)
_msg = const(1)
_time = const(2)
_name = const(3)
_fnname = const(4)
def level_name(level: int, color: bool = False) -> str:
if not color:
if level == INFO:
return "INFO"
elif level == DEBUG:
return "DEBUG"
elif level == WARN:
return "WARN"
elif level == ERROR:
return "ERROR"
elif level == CRITICAL:
return "CRITICAL"
else:
if level == INFO:
return "\033[97mINFO\033[0m"
elif level == DEBUG:
return "\033[37mDEBUG\033[0m"
elif level == WARN:
return "\033[93mWARN\033[0m"
elif level == ERROR:
return "\033[35mERROR\033[0m"
elif level == CRITICAL:
return "\033[91mCRITICAL\033[0m"
class BaseClock ():
"""
This is a BaseClock for the logger.
Please inherit this class by your custom.
"""
def __call__(self) -> str:
"""
Acquire the time of now, please inherit this method.
We will use the return value of this function as the time format of the log,
such as `2021 - 6 - 13` or `12:45:23` and so on.
:return: the time string.
"""
return '%d' % time.time()
class Handler():
"""The Handler for logger.
"""
_template: str
_map: list
level: int
_direction: int
_clock: BaseClock
_color: bool
_file_name: str
_max_size: int
_file = TextIOWrapper
def __init__(self,
level: int = INFO,
colorful: bool = True,
fmt: str = "&(time)% - &(level)% - &(name)% - &(msg)%",
clock: BaseClock = None,
direction: int = TO_TERM,
file_name: str = "logging.log",
max_file_size: int = 4096
):
"""
Create a Handler that you can add to the logger later
## Options available for fmt.
- &(level)% : the log level
- &(msg)% : the log message
- &(time)% : the time acquire from clock, see `BaseClock`
- &(name)% : the logger's name
- &(fnname)% : the function name which you will pass on.
- more optional is developing.
## Options available for level.
- DEBUG
- INFO
- WARN
- ERROR
- CRITICAL
## Options available for direction.
- TO_FILE : output to a file
- TO_TERM : output to terminal
:param level: Set a minimum level you want to be log
:type level: int(see the consts in this module)
:param colorful: Whether to use color display information to terminal(Not applicable to files)
:type colorful: bool
:param fmt: the format string like: "&(time)% - &(level)% - &(name)% - &(fnname)% - &(msg)%"(default)
:type fmt: str
:param clock: The Clock which will provide time str. see `BaseClock`
:type clock: BaseClock(can be inherit )
:param direction: Set the direction where logger will output
:type direction: int (`TO_FILE` or `TO_TERM`)
:param file_name: available when you set `TO_FILE` to param `direction`. (default for `logging.log`)
:type file_name: str
:param max_file_size: available when you set `TO_FILE` to param `direction`. The unit is `byte`, (default for 4k)
:type max_file_size: str
"""
#TODO: 文件按日期存储, 最大份数的设置.
self._map = []
self._direction = direction
self.level = level
self._clock = clock if clock else BaseClock()
self._color = colorful
self._file_name = file_name if direction == TO_FILE else ''
self._max_size = max_file_size if direction == TO_FILE else 0
if direction == TO_FILE:
self._file = open(file_name, 'a+')
# 特么的re居然不能全局匹配, 烦了, 只能自己来.
# m = re.match(r"&\((.*?)\)%", fmt)
# i = 0
# while True:
# # 由于蛋疼的 ure 不能直接获取匹配结果的数量, 只能使用这种蠢蛋方法来循环.
# try:
# text = m.group(i)
# except:
# # 发生错误说明已经遍历完毕
# break
# # 使用指针代替文本来减少开销
# if text == "level":
# self._map.append(_level)
# elif text == "msg":
# self._map.append(_msg)
# elif text == "time":
# self._map.append(_time)
# elif text == "name":
# self._map.append(_name)
# elif text == "fnname":
# self._map.append(_fnname)
# i += 1
# 添加映射
idx = 0
while True:
idx = fmt.find("&(", idx)
if idx >= 0: # 有找到
a_idx = fmt.find(")%", idx+2)
if a_idx < 0:
# 没找到后缀, 报错
raise Exception(
"Unable to parse text format successfully.")
text = fmt[idx+2:a_idx]
idx = a_idx+2 # 交换位置
if text == "level":
self._map.append(_level)
elif text == "msg":
self._map.append(_msg)
elif text == "time":
self._map.append(_time)
elif text == "name":
self._map.append(_name)
elif text == "fnname":
self._map.append(_fnname)
else: # 没找到, 代表后面没有了
break
# 将 template 变成可被格式化的文本
# 确保最后一个是换行字符
self._template = fmt.replace("&(level)%", "%s")\
.replace("&(msg)%", "%s")\
.replace("&(time)%", "%s")\
.replace("&(name)%", "%s")\
.replace("&(fnname)%", "%s")\
+ "\n" if fmt[:-1] is not '\n' else ''
def _msg(self, *args, level: int, name: str, fnname: str):
"""
Log a msg
"""
if level < self.level:
return
# generate msg
temp_map = []
for item in self._map:
if item == _msg:
for text_ in args: # 将元组内的文本添加到一起
text = "%s %s" % ('', text_) # 防止用户输入其他类型(int, float)
temp_map.append(text)
elif item == _level:
if self._direction == TO_TERM: # only terminal can use color.
temp_map.append(level_name(level, self._color))
else:
temp_map.append(level_name(level))
elif item == _time:
temp_map.append(self._clock())
elif item == _name:
temp_map.append(name)
elif item == _fnname:
temp_map.append(fnname if fnname else "unknownfn")
if self._direction == TO_TERM:
self._to_term(tuple(temp_map))
else:
self._to_file(tuple(temp_map))
# TODO: 待验证: 转换为 tuple 和使用 fromat 谁更快
def _to_term(self, map: tuple):
print(self._template % map, end='')
def _to_file(self, map: tuple):
fp = self._file
# 检查是否超出大小限制.
prev_idx = fp.tell() # 保存原始指针位置
# 将读写指针跳到文件最大限制的地方,
# 如果能读出数据, 说明文件大于指定的大小
fp.seek(self._max_size)
if fp.read(1): # 能读到数据, 说明超出大小限制了
fp = self._file = open(self._file_name, 'w') # 使用 w 选项清空文件内容
else:
# 没有超出限制
fp.seek(prev_idx) # 指针回到原来的地方
# 检查完毕, 开始写入数据
fp.write(self._template % map)
fp.flush()
class Logger():
_handlers: list
def __init__(self,
name: str,
handlers: list = None,
):
self.name = name
if not handlers:
# 如果没有指定处理器, 自动创建一个默认的
self._handlers = [Handler()]
else:
self._handlers = handlers
@property
def handlers(self):
return self._handlers
def _msg(self, *args, level: int, fn: str):
for item in self._handlers:
try:
item._msg(*args, level=level, fnname=fn, name=self.name)
except:
print("Failed while trying to record")
def debug(self, *args, fn: str = None):
self._msg(*args, level=DEBUG, fn=fn)
def info(self, *args, fn: str = None):
self._msg(*args, level=INFO, fn=fn)
def warn(self, *args, fn: str = None):
self._msg(*args, level=WARN, fn=fn)
def error(self, *args, fn: str = None):
self._msg(*args, level=ERROR, fn=fn)
def critical(self, *args, fn: str = None):
self._msg(*args, level=CRITICAL, fn=fn)
__all__ = [
Logger,
Handler,
BaseClock,
DEBUG,
INFO,
WARN,
ERROR,
CRITICAL,
TO_FILE,
TO_TERM,
__version__
]
有了上面的库,我们还要另外一个调用模块来简化调用配置过程,主要是日志需要时间戳,单片机不联网的时候时间戳掉电就从头来了,所以要有同步时间的动作,同步了时间以后还得配置下日志输出的设置,最后那个函数才是在主函数中调用的
from machine import RTC
import machine,json,network,time,_thread,ulogger,ntptime
#####################################线程联网类
class Wlan_star():
def __init__(self,name,password):
self.wlan_name=name
self.wlan_password=password
wl=network.WLAN(network.STA_IF)
self.wl=wl
print('配置线程')
def threadRun1(self,wl,hold_times):
while 1:
if wl.isconnected():#判断联网
time.sleep(hold_times)
else:
while 1:
try:
wl.active(True)
time.sleep(2)
break
except:
wl.active(False)
time.sleep(2)
print('卡联网使能')
continue
time.sleep(10)
try:
wl.connect(self.wlan_name,self.wlan_password)
except:
print('联网出错,但是被我拦截了,再看看')
wl.active(False)
time.sleep(1)
time.sleep(20)
def wlan_ever(self):
self.wl.active(True)#网络可用设定
_thread.start_new_thread(self.threadRun1,(self.wl,60))# wl是传入的WL的实例,b=5这个仅仅是一个占位参数,备用
print('进入阻塞等待')
while 1 : #阻塞等待
if self.wl.isconnected():
print('联网成功')
return 1
##########################################时钟同步
class Clock(ulogger.BaseClock):
#这个类存在的意义就是给日志正确的时间和正确的时间格式,所以它要继承里边那玩意
def __init__(self):
self.rtc = RTC()
#实例化RTC
#ntptime.host = "ntp.ntsc.ac.cn"
ntptime.host = "ntp.aliyun.com"
# 设置更快的ntf服务器, 可以减少延迟,不设置也行就是慢一点,还可能连不上
now = ntptime.time ()
#注意先联网,没网络这一步就超时报错了,网络不好服务器没响应也是超时报错,反正这里需要注意下。。。获取并设置时间,这个是国际时间
tp_time = time.localtime (now+28800)
#28800秒正好8小时,把国际时间调整到北京时间
tp_time=tp_time[:3]+(0,)+tp_time[3:-1]
#由于RTC格式为(年,月,日,星期,时,分,秒,次秒)所以中间拼接一个星期,最后去掉一个数据,这样就和设置格式一致了,调整一下求稳,求稳。
self.rtc.init (tp_time)
# 将调整好的格式写入RTC
def __call__(self) -> str:
y,m,d,_,h,mi,s,_ = self.rtc.datetime () #取出所需项
return '%d-%d-%d %d:%d:%d' % (y,m,d,h,mi,s) #返回给日志
class Wlan_clock_log_run():
def __init__(self,name,password,log_name):
self.wlan_star=Wlan_star(name,password) #实例化,
self.handlers=()
self.log_name=log_name
def wlan_run(self):
self.wlan_star.wlan_ever() #阻塞等联网,成功后放行,可防止需要联网的应用报错,线程的作用是断网自动重连
#################################################################
#时间获取最好像下面这么用~ 因为可能超时取不出时间还报错,所以阻塞取
def clock_run(self):
while 1: #阻塞等待获取时间同步
try :
clock = Clock()
except:
print('时间获取失败,重试')
continue
break
print('同步时间成功')
##################################################
##########################配置日志
handler_to_term = ulogger.Handler(
level=ulogger.INFO,
colorful=True,
fmt="&(time)% - &(level)% - &(name)% - &(fnname)% - &(msg)%",
clock=clock,
direction=ulogger.TO_TERM,
)
handler_to_file = ulogger.Handler(
level=ulogger.INFO,
fmt="&(time)% - &(level)% - &(name)% - &(fnname)% - &(msg)%",
clock=clock,
direction=ulogger.TO_FILE,
file_name="logging.log",
max_file_size=10240 # max for 10k
)
self.handlers = (handler_to_term,handler_to_file )
print('日志配置完毕')
def loggers_run(self):
logger=ulogger.Logger(self.log_name ,self.handlers)
return logger
if __name__=='__main__':
#from loggers import Wlan_clock_log_run
a=Wlan_clock_log_run('300king',wifi密码,'esp32') #WIFI账号 wifi密码 自定日志名称
a.wlan_run() #启动联网,带线程断线重连
a.clock_run() #启动时钟同步,并配置日志静态内容
b=a.loggers_run() #实例化日志
b.info('ccc') #日志内容写入
主函数调用方法: 例如上面配置模块保存为loggers.py的文件,则 调用方法为
from loggers import Wlan_clock_log_run
a=Wlan_clock_log_run('300king',wifi密码,'esp32') #WIFI账号 wifi密码 自定日志名称
a.wlan_run() #启动联网,带线程断线重连
a.clock_run() #启动时钟同步,并配置日志静态内容
b=a.loggers_run() #实例化日志
b.info('ccc') #日志内容写入
以上功能也可以分开使用比如单独是哟合格联网和同步时钟啥的
日志截图