日志这个东西,说有用就有用说没用也没用,但是板子在那运行,里边到底跑了些什么,有时候还真得靠日志找一找,就像示波器一样。

那么,本次就是一次日志的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')   #日志内容写入

以上功能也可以分开使用比如单独是哟合格联网和同步时钟啥的

日志截图

esp32 modbus rtu 从机 esp32time_pycharm