日志记录logging详解
比如在general.py中定义logger对象LOGGERimport osimport sys= "utf-8":try:import ioelse:")首先通过设置日志级别(在主进程中使用info级别,其他进程error级别),通过将信息打印到控制台,并绑定输出的信息样式Formatter,然后将handler绑定到logger对象上定义的LOGGER 可以全局使用,包括等等中使用,使用时候
文章目录
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 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 案例1
在使用日志打印信息前,首先需要在使用的文件中,比如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
, 这样的话,在不需要日志信息输出时,可以通过调整日志级别,有选择的打印信息。
3.3 案例3
(1) 自定义Logger类
- 新建
utils/logger.py
, 基于logging
库实现自定义的Logger
类,并定义全局使用的LOGGER对象。 - 后续直接通过导入全局的LOGGER对象,来使用logging功能
import logging
class Logger:
def __init__(self, name):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.INFO)
sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S'))
self.logger.addHandler(sh)
def set_outpth(self, outpth):
fh = logging.FileHandler(outpth, encoding='utf8')
fh.setFormatter(logging.Formatter(
'%(asctime)s %(filename)s:%(lineno)d [%(levelname)s] %(message)s',
'%Y-%m-%d %H:%M:%S'))
self.logger.addHandler(fh)
def info(self, msg):
self.logger.info(msg)
def warning(self, msg):
self.logger.warning(msg)
def error(self, msg, exception=Exception, raise_error=True):
self.logger.error(msg)
if raise_error:
raise exception(msg)
def lassert(self, condition, msg):
if not condition:
self.error(msg, exception=AssertionError)
LOGGER = Logger('laneeval')
(2) 使用
- import LOGGER
from utils.logger import LOGGER
- 应用
- 在main函数中设置输出logger本地的保存路径
outdir = "./evaluations/"
LOGGER.set_outpth(os.path.join(outdir, 'main.log'))
outdir = os.path.join(
outdir,
f'eval_{time.strftime("%Y%m%d-%H%M%S", time.localtime(int(time.time())))}')
if not os.path.exists(outdir):
os.makedirs(outdir)
LOGGER.set_outpth(os.path.join(outdir, 'main.log'))
- 输出logger
LOGGER.warning('item_mask is not supported now.')
LOGGER.error(f"Length of arrow attribute in current version must be"+
"8, but get {len(attr)}.", ValueError)
if not os.path.isfile(results):
LOGGER.error(f'{results} is not exist.', FileNotFoundError)
with open(os.path.join(outdir, 'config.yaml'), "w") as f:
f.write(yaml.dump(config))
LOGGER.info(f'Saved config to {os.path.join(outdir, "config.yaml")}.')
- 本地保存的
main.log
信息如下:
参考
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)