日志记录logging

发布时间:2024年01月20日

1. logging基础使用

1.1 日志的6个级别

序号级别级别数值使用情况
1NOTEST/不记录任何日志信息
2DEBUG10用于记录开发过程中的细节信息,例如函数调用,变量值等
3INFO20用于记录程序正常运行过程中的一般信息
4WARNING30用于记录可能导致问题的潜在问题,例如语法警告、网络连接中断等
5ERROR40用于记录程序运行过程中发生的错误,例如函数调用失败,异常发生等
6CRITICAL50用于记录严重的错误,例如程序奔溃等

级别从低到高依次为: NOTEST < DEBUG < INFO < WARNING < ERROR < CRITICAL, 默认为WARNING级别, 默认情况下日志打印只显示大于等于 WARNING 级别的日志

1.2 logging.basicConfig

通过logging.basicConfig函数对日志的输出格式及方式做相关配置

logging.basicConfig(
    level = logging.INFO,
    format = '%(asctime)s %(name) |%(pathname)s line:(lineno)d'
    datefmt = "%Y-%m-%d %H:%M:%S",
    filename ='demo.log',
    filemode = 'w'
)
  • level: 指定打印日志的级别,debug,info,warning,error,critical
  • format: 日志输出相关格式

1.3 案例

案例1:显示消息日期

import logging
# 显示消息时间
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
2019-10-16 18:57:45,988 is when this event was logged.
2019-10-16 18:57:45,988 is when this event was logged.
  • 案例2:将日志信息记录到文件
# 日志信息记录到文件
logging.basicConfig(filename='F:/example.log', level=logging.DEBUG)

logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

在相应的路径下会有 example.log 日志文件,内容如下:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too

2. logging的高级应用

logging 采用了模块化设计,主要由四个部分组成:

  • (1) Loggers: 日志记录器,提供程序直接调用的接口
  • (2) Handers: 日志处理器,将记录的日志发送到指定的位置(终端打印or 保存到文件)
  • (3)Filters: 日志过滤器,提供更细粒度控制,决定哪些日志被输出
  • (4) Formatters: 日志格式器,用于控制信息的输出格式

2.1 记录器Logger

Logger 持有日志记录器的方法,日志记录器不直接实例化,而是通过模块级函数logging.getlogger (name)来实例化

  • 应用程序代码能直接调用日志接口。
  • Logger最常用的操作有两类:配置和发送日志消息
  • 初始化 logger = logging.getLogger("endlesscode")获取 logger 对象,getLogger() 方法后面最好加上所要日志记录的模块名字,配置文件和打印日志格式中的%(name)s对应的是这里的模块名字,如果不指定name则返回root对象。
  • logger.setLevel(logging.DEBUG),Logging 中有 NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL这几种级别,日志会记录设置级别以上的日志
  • 多次使用相同的name调用 getLogger 方法返回同一个 looger 对象;
# 实例化一个记录器,并将记录器的名字设为 `trainning_log`
logger = logging.getlogger (name)(name = 'training_log')

#设置 logger的日志级别
logger.setLevel(logging.INFO)

如果 logging.getlogger 不设置参数name的话,默认记录器名字为root

2.2 处理器- Handler

Handler 处理器类型有很多种,比较常用的有三个,StreamHandlerFileHandler,NullHandler

  • 创建一个handler, 该handler往console(终端)打印输出
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)
  • 创建一个handler, 该handle往文件中打印输出
fileHandler = logging.FileHander(filename ='demo.log')

2.3 格式器- Formatter

使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S

  • 创建方法:
formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt消息的格式化字符串datefmt日期字符串。如果不指明 fmt,将使用'%(message)s'。如果不指明 datefmt,将使用 ISO8601 日期格式。

# 创建一个标准版日志打印格式
standard_formatter = logging.setFormatter('%(asctime)s %(name)s [%(pathname)s line:(lineno)d %(levelname)s %(message)s]')

# 创建一个简单版的日志打印格式
simple_formatter = logging.setFormatter('%(levelname)s %(message)s')

2.4 创建关联

在这里插入图片描述
我们在创建好Logger对象,Handler对象以及Formatter对象之后,我们需要绑定他们之间的关系。

  • 首先为Handler设置Formatter, 然后将Handler绑定到logger上
#创建一个handler, 该handler往`console`(终端)打印输出
consoleHandler = logging.StreamHandler()
#创建一个handler, 该handle往`文件`中打印输出
fileHandler = logging.FileHander(filename ='demo.log')

# 让consoleHander,使用标注版日志打印输出
consoleHandler.setFormatter(standard_formatter)
fileHandler.setFormatter(simple_formatter) 

# 给logger绑定上consoleHandler和fileHandler
logger.addHandler(console_handle)
logger.addHandler(file_handle)

2.4 案例

import logging

#------------------1. 实例化 logger -------------------#
# 实例化一个记录器,使用默认记录器名称‘root’,并将日志级别设置为info
logger = logging.getLogger()
logger.setLevel(logging.Debug)

#-----------------2. 定义 Handler --------------------#
# 创建一个往控制台打印输出的Handler,日志级别为 debug
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

# 再创建一个往文件中打印输出的handler,默认使用logger同样的日志级别
file_handler = logging.FileHandler(filename = 'demo.log',mode ='a')

#---------------3. 定义打印格式Formatter--------------#
# 创建一个标准版日志打印格式
standard_formatter = logging.setFormatter('%(asctime)s %(name)s [%(pathname)s line:(lineno)d %(levelname)s %(message)s]')

# 创建一个简单版日志打印格式
simple_formatter = logging.Formatter('%(levelname)s %(message)s')
#---------------------3. 定义过滤器------------------#
#fit = logging.Filter()
#--------------------4. 绑定 -----------------------#
# 让consoleHandler使用标准版日志打印格式
console_handler.setFormatter(standard_formatter)

# 让fileHandler使用简版的日志打印格式
file_handler.setFormatter(simple_formmatter)

# 给logger 绑定上consoleHandle和fileHandler
logger.addHandler(console_handler)
logger.addHandler(file_handler)

#----------------------5. 打印--------------------#
logger.debug('调试日志')
logger.info('消息日志')
logger.warning('警告日志')
logger.error('错误日志')
logger.critical('严重错误日志')
  • 运行程序,在终端打印出了日志信息,同样在文件demo.log也保存了日志信息

补充: 接下来,补充下Filter相关的知识

  • 比如,我们定义logger的名字为training.loss.log
logger = logging.getLogger('training.loss.log')
  • 接下来给过滤器Filter一个字符串参数如果这个字符串是logger的名字的前缀,那么日志就不会被过滤,可以正常打印出来;如果指定Filter的字符串参数和logger名字的前缀不匹配,那么这个logger就打印不出来日志`
fit = logging.Filter('training.loss') #可以打印出日志,与logger名前缀想匹配
fit = logging.Filter('training.accuracy') #打印不出日志,与logger名前缀不匹配

最后需要将过滤器绑定logger或者handler,如果绑定logger则针对所有handler都使用该过滤器,如果绑定某一个handler则该handler使用绑定的过滤器filter

logger.addFilter(fit)
#或者
console_handler.addFilter(fit)

3.在项目中的应用

首先在一个文件中定义logger对象,在项目中任何需要使用的地方,直接引用该logger,利用logger就可以打印输出相关日志信息,信息主要是输出到控制台(console)显示使用。

3.1 定义全局使用的logger对象

比如在general.py中定义logger对象LOGGER

import logging
import os
import platform
import sys

RANK = int(os.getenv("RANK", -1))
LOGGING_NAME = "ultralytics"
MACOS, LINUX, WINDOWS = (platform.system() == x for x in ["Darwin", "Linux", "Windows"])

def set_logging(name=LOGGING_NAME, verbose=True):
    """Sets up logging for the given name with UTF-8 encoding support."""
    level = logging.INFO if verbose and RANK in {-1, 0} else logging.ERROR  # rank in world for Multi-GPU trainings

    # Configure the console (stdout) encoding to UTF-8
    formatter = logging.Formatter("%(message)s")  # Default formatter
    if WINDOWS and sys.stdout.encoding != "utf-8":
        try:
            if hasattr(sys.stdout, "reconfigure"):
                sys.stdout.reconfigure(encoding="utf-8")
            elif hasattr(sys.stdout, "buffer"):
                import io

                sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
            else:
                sys.stdout.encoding = "utf-8"
        except Exception as e:
            print(f"Creating custom formatter for non UTF-8 environments due to {e}")

            class CustomFormatter(logging.Formatter):
                def format(self, record):
                    """Sets up logging with UTF-8 encoding and configurable verbosity."""
                    return emojis(super().format(record))

            formatter = CustomFormatter("%(message)s")  # Use CustomFormatter to eliminate UTF-8 output as last recourse

    # Create and configure the StreamHandler
    stream_handler = logging.StreamHandler(sys.stdout)
    stream_handler.setFormatter(formatter)
    stream_handler.setLevel(level)

    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(stream_handler)
    logger.propagate = False
    return logger


# Set logger
LOGGER = set_logging(LOGGING_NAME, verbose=VERBOSE)  # define globally (used in train.py, val.py, predict.py, etc.)
  • 首先通过set_logging() 设置日志级别(在主进程中使用info级别,其他进程error级别),通过StreamHandler将信息打印到控制台,并绑定输出的信息样式Formatter,然后将handler绑定到logger对象上
  • 定义的LOGGER 可以全局使用,包括train.py, val.py, predict.py等等中使用,使用时候从general中导入LOGGER即可

3.2 使用案例

在使用日志打印信息前,首先需要在使用的文件中,比如train.py中导入LOGGER,比如:

from utils.general import LOGGER

其中yolov8项目导入LOGGER

from ultralytics.utils import LOGGER,
  • 案例1
 if install and AUTOINSTALL:  # check environment variable
     n = len(pkgs)  # number of packages updates
     LOGGER.info(f"{prefix} Ultralytics requirement{'s' * (n > 1)} {pkgs} not found, attempting AutoUpdate...")
     try:
         t = time.time()
         assert is_online(), "AutoUpdate skipped (offline)"
         LOGGER.info(subprocess.check_output(f"pip install --no-cache {s} {cmds}", shell=True).decode())
         dt = time.time() - t
         LOGGER.info(
             f"{prefix} AutoUpdate success ? {dt:.1f}s, installed {n} package{'s' * (n > 1)}: {pkgs}\n"
             f"{prefix} ?? {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"
         )
     except Exception as e:
         LOGGER.warning(f"{prefix} ? {e}")
         return False
 else:
     return False
  • 案例2
def on_pretrain_routine_end(trainer):
    global mlflow

    uri = os.environ.get("MLFLOW_TRACKING_URI") or str(RUNS_DIR / "mlflow")
    LOGGER.debug(f"{PREFIX} tracking uri: {uri}")
    mlflow.set_tracking_uri(uri)

    # Set experiment and run names
    experiment_name = os.environ.get("MLFLOW_EXPERIMENT_NAME") or trainer.args.project or "/Shared/YOLOv8"
    run_name = os.environ.get("MLFLOW_RUN") or trainer.args.name
    mlflow.set_experiment(experiment_name)

    mlflow.autolog()
    try:
        active_run = mlflow.active_run() or mlflow.start_run(run_name=run_name)
        LOGGER.info(f"{PREFIX}logging run_id({active_run.info.run_id}) to {uri}")
        if Path(uri).is_dir():
            LOGGER.info(f"{PREFIX}view at http://127.0.0.1:5000 with 'mlflow server --backend-store-uri {uri}'")
        LOGGER.info(f"{PREFIX}disable with 'yolo settings mlflow=False'")
        mlflow.log_params(dict(trainer.args))
    except Exception as e:
        LOGGER.warning(f"{PREFIX}WARNING ?? Failed to initialize: {e}\n" f"{PREFIX}WARNING ?? Not tracking this run")
  • 案例3
def on_train_end(trainer):
    """Upload final model and metrics to Ultralytics HUB at the end of training."""
    session = getattr(trainer, "hub_session", None)
    if session:
        # Upload final model and metrics with exponential standoff
        LOGGER.info(f"{PREFIX}Syncing final model...")
        session.upload_model(
            trainer.epoch,
            trainer.best,
            map=trainer.metrics.get("metrics/mAP50-95(B)", 0),
            final=True,
        )
        session.alive = False  # stop heartbeats
        LOGGER.info(f"{PREFIX}Done ?\n" f"{PREFIX}View model at {session.model_url} 🚀")
  • 案例4
def on_pretrain_routine_start(trainer):
    """Runs at start of pretraining routine; initializes and connects/ logs task to ClearML."""
    try:
        if task := Task.current_task():
            # Make sure the automatic pytorch and matplotlib bindings are disabled!
            # We are logging these plots and model files manually in the integration
            PatchPyTorchModelIO.update_current_task(None)
            PatchedMatplotlib.update_current_task(None)
        else:
            task = Task.init(
                project_name=trainer.args.project or "YOLOv8",
                task_name=trainer.args.name,
                tags=["YOLOv8"],
                output_uri=True,
                reuse_last_task_id=False,
                auto_connect_frameworks={"pytorch": False, "matplotlib": False},
            )
            LOGGER.warning(
                "ClearML Initialized a new task. If you want to run remotely, "
                "please add clearml-init and connect your arguments before initializing YOLO."
            )
        task.connect(vars(trainer.args), name="General")
    except Exception as e:
        LOGGER.warning(f"WARNING ?? ClearML installed but not initialized correctly, not logging this run. {e}")
  • 案例5
def check_cache_ram(self, safety_margin=0.5):
      """Check image caching requirements vs available memory."""
      b, gb = 0, 1 << 30  # bytes of cached images, bytes per gigabytes
      n = min(self.ni, 30)  # extrapolate from 30 random images
      for _ in range(n):
          im = cv2.imread(random.choice(self.im_files))  # sample image
          ratio = self.imgsz / max(im.shape[0], im.shape[1])  # max(h, w)  # ratio
          b += im.nbytes * ratio**2
      mem_required = b * self.ni / n * (1 + safety_margin)  # GB required to cache dataset into RAM
      mem = psutil.virtual_memory()
      cache = mem_required < mem.available  # to cache or not to cache, that is the question
      if not cache:
          LOGGER.info(
              f'{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images '
              f'with {int(safety_margin * 100)}% safety margin but only '
              f'{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, '
              f"{'caching images ?' if cache else 'not caching images ??'}"
          )
      return cache

总结
利用logger日志输出,可以替换print, 这样的话,在不需要日志信息输出时,可以通过调整日志级别,有选择的打印信息。

参考

  1. https://zhuanlan.zhihu.com/p/425678081
  2. https://github.dev/ultralytics/ultralytics
文章来源:https://blog.csdn.net/weixin_38346042/article/details/135717204
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。