文章目录
- 1. logging基础使用
- 1.1 日志的6个级别
- 1.2 logging.basicConfig
- 1.3 案例
- 2. logging的高级应用
- 2.1 记录器Logger
- 2.2 处理器- Handler
- 2.3 格式器- Formatter
- 2.4 创建关联
- 2.4 案例
- 3.在项目中的应用
- 3.1 定义全局使用的logger对象
- 3.2 使用案例
- 参考
1. logging基础使用
1.1 日志的6个级别
序号 | 级别 | 级别数值 | 使用情况 |
---|---|---|---|
1 | NOTEST | / | 不记录任何日志信息 |
2 | DEBUG | 10 | 用于记录开发过程中的细节信息,例如函数调用,变量值等 |
3 | INFO | 20 | 用于记录程序正常运行过程中的一般信息 |
4 | WARNING | 30 | 用于记录可能导致问题的潜在问题,例如语法警告、网络连接中断等 |
5 | ERROR | 40 | 用于记录程序运行过程中发生的错误,例如函数调用失败,异常发生等 |
6 | CRITICAL | 50 | 用于记录严重的错误,例如程序奔溃等 |
级别从低到高依次为: 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 处理器类型有很多种,比较常用的有三个,StreamHandler
,FileHandle
r,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 sysRANK = 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-8formatter = logging.Formatter("%(message)s") # Default formatterif 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 iosys.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 StreamHandlerstream_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 = Falsereturn 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 variablen = len(pkgs) # number of packages updatesLOGGER.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() - tLOGGER.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 Falseelse:return False
案例2
def on_pretrain_routine_end(trainer):global mlflowuri = 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 namesexperiment_name = os.environ.get("MLFLOW_EXPERIMENT_NAME") or trainer.args.project or "/Shared/YOLOv8"run_name = os.environ.get("MLFLOW_RUN") or trainer.args.namemlflow.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 standoffLOGGER.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 heartbeatsLOGGER.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 integrationPatchPyTorchModelIO.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 gigabytesn = min(self.ni, 30) # extrapolate from 30 random imagesfor _ in range(n):im = cv2.imread(random.choice(self.im_files)) # sample imageratio = self.imgsz / max(im.shape[0], im.shape[1]) # max(h, w) # ratiob += im.nbytes * ratio**2mem_required = b * self.ni / n * (1 + safety_margin) # GB required to cache dataset into RAMmem = psutil.virtual_memory()cache = mem_required < mem.available # to cache or not to cache, that is the questionif 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
, 这样的话,在不需要日志信息输出时,可以通过调整日志级别,有选择的打印信息。
参考
- https://zhuanlan.zhihu.com/p/425678081
- https://github.dev/ultralytics/ultralytics