黑马大模型RAG与Agent智能体实战教程LangChain提示词——47、Agent智能体——Agent项目日志工具和路径工具开发
有没有“必要”:不绝对必须,但这样封装一个统一的get_logger模块在中大型项目里是很常见、也算是合理的实践。是否最佳实践:整体思路是对的(统一格式、控制台+文件 handler、目录管理),但还可以按官方推荐方式再优化一下。
教程:https://www.bilibili.com/video/BV1yjz5BLEoY
代码:https://github.com/shangxiang0907/HeiMa-AI-LLM-RAG-Agent-Dev
文章目录
- Agent智能体-07、[Agent项目]日志和路径工具开发
-
- 介绍:完成utils部分代码
- 【路径工具】
- →提示词:@zhisaotong_agent/PROJECT_OVERVIEW.md 帮我实现文档utils中的path_tool.py代码,需要实现`def get_project_root() -> str`和`def get_abs_path(relative_path: str) -> str`两个函数,并在`if __name__ == __main__` 中编写相应测试代码,代码需要满足强约束 / 安全性更高的场景
- ←AI回复
- 【日志工具】
- →提示词:有必要实现这个日志工具吗,是否符合最佳实践?
- ←AI回复
- →提示词:@zhisaotong_agent/PROJECT_OVERVIEW.md 根据你以上建议,帮我实现日志工具
- ←AI回复
Agent智能体-07、[Agent项目]日志和路径工具开发
介绍:完成utils部分代码

【路径工具】
→提示词:@zhisaotong_agent/PROJECT_OVERVIEW.md 帮我实现文档utils中的path_tool.py代码,需要实现def get_project_root() -> str和def get_abs_path(relative_path: str) -> str两个函数,并在if __name__ == __main__ 中编写相应测试代码,代码需要满足强约束 / 安全性更高的场景
←AI回复
我先看下当前项目目录和是否已有 path_tool.py 文件,然后在其中实现两个函数,并加上简单的自测代码,确保能正确返回项目根路径和任意相对路径的绝对路径。
[3 tools called]
已实现内容说明
-
get_project_root():返回当前项目根目录(约定为zhisaotong_agent/)的绝对路径,基于当前文件所在位置zhisaotong_agent/utils/path_tool.py向上一级目录推断。 -
get_abs_path(relative_path: str):- 若传入的是绝对路径,返回其规范化后的绝对路径;
- 若传入的是相对路径,则以项目根目录为基准拼接,并返回规范化后的绝对路径。
-
在
if __name__ == "__main__":中添加了简单自测代码:- 打印项目根目录;
- 测试多种相对路径(如
PROJECT_OVERVIEW.md、data、utils/path_tool.py、./data/../PROJECT_OVERVIEW.md)转换为绝对路径; - 使用当前文件的绝对路径测试“绝对路径输入时应原样返回”的逻辑。
你可以在项目根目录下执行:
python -m zhisaotong_agent.utils.path_tool
查看测试输出,确认路径是否符合预期。
代码:path_tool.py
from pathlib import Path
def get_project_root() -> str:
"""
获取项目根目录的绝对路径。
约定:本文件位于 `zhisaotong_agent/utils/path_tool.py`,
项目根目录为其上一级目录 `zhisaotong_agent/`。
"""
return str(Path(__file__).resolve().parent.parent)
def get_abs_path(relative_path: str) -> str:
"""
将“项目内相对路径”转换为基于项目根目录的绝对路径。
要求:
- 只接受相对路径;
- 如果传入的是绝对路径,则抛出 ValueError,避免误用掩盖问题。
"""
p = Path(relative_path)
if p.is_absolute():
raise ValueError(f"get_abs_path 仅接受相对路径,实际收到绝对路径: {relative_path!r}")
root = Path(get_project_root())
return str((root / p).resolve())
if __name__ == "__main__":
# 简单自测代码
print("项目根目录:", get_project_root())
# 1. 测试相对路径转换
rel_examples = [
"PROJECT_OVERVIEW.md", # 根目录下已存在的文件(根据当前项目结构)
"data", # 根目录下已有的 data 目录
"utils/path_tool.py", # 本文件自身
"./data/../PROJECT_OVERVIEW.md" # 含有 . 和 .. 的相对路径
]
for rel in rel_examples:
# 这里的 !r 表示使用 repr() 的形式显示变量值(带引号、便于调试区分空字符串等)
print(f"relative: {rel!r} -> abs: {get_abs_path(rel)!r}")
# 2. 测试绝对路径误用场景(应抛出异常)
current_file_abs = str(Path(__file__).resolve())
try:
print("absolute input ->", get_abs_path(current_file_abs))
except ValueError as e:
print("absolute input error:", e)
执行
python3 zhisaotong_agent/utils/path_tool.py
结果
项目根目录: /home/devbox/project/zhisaotong_agent
relative: 'PROJECT_OVERVIEW.md' -> abs: '/home/devbox/project/zhisaotong_agent/PROJECT_OVERVIEW.md'
relative: 'data' -> abs: '/home/devbox/project/zhisaotong_agent/data'
relative: 'utils/path_tool.py' -> abs: '/home/devbox/project/zhisaotong_agent/utils/path_tool.py'
relative: './data/../PROJECT_OVERVIEW.md' -> abs: '/home/devbox/project/zhisaotong_agent/PROJECT_OVERVIEW.md'
absolute input error: get_abs_path 仅接受相对路径,实际收到绝对路径: '/home/devbox/project/zhisaotong_agent/utils/path_tool.py'
【日志工具】
→提示词:有必要实现这个日志工具吗,是否符合最佳实践?



←AI回复
总结先说结论
- 有没有“必要”:不绝对必须,但这样封装一个统一的
get_logger模块在中大型项目里是很常见、也算是合理的实践。 - 是否最佳实践:整体思路是对的(统一格式、控制台+文件 handler、目录管理),但还可以按官方推荐方式再优化一下。
这个模块在做什么 & 什么时候值得保留
- 统一日志根目录:
LOG_ROOT = get_abs_path("logs")+os.makedirs(..., exist_ok=True) - 统一格式:
DEFAULT_LOG_FORMAT = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s') - 统一入口函数:通过
get_logger(name, console_level, file_level, log_file)控制:- 控制台日志级别
- 文件日志级别
- 日志输出文件路径(默认按日期生成)
适用场景:
- 项目中很多模块都要打日志,希望格式、路径、级别行为统一;
- 不想在每个文件里都重复配置 handler;
→ 这种情况下保留这个模块是有价值的。
当前实现里值得注意/可改进的点
-
避免重复添加 handler:你已经写了:
if logger.handlers: return logger这是对的,可以避免多次调用
get_logger时重复添加StreamHandler/FileHandler。 -
建议按模块名获取 logger:
每个业务模块里建议这样用:
logger = get_logger(__name__)而不是都叫
"agent",这样方便按模块过滤和定位问题。 -
文件日志建议用轮转 handler:
如果是长期跑的服务,推荐改成:
from logging.handlers import TimedRotatingFileHandler file_handler = TimedRotatingFileHandler( log_file, when="D", # 每天一个文件 interval=1, backupCount=7, # 保留 7 天 encoding="utf-8", )这样比单纯的
FileHandler更符合生产实践。 -
集中配置 vs. 自写工厂函数
更“官方”的一种做法是使用
logging.config.dictConfig做集中配置;
你现在这种写一个get_logger工厂函数,也是社区里非常常见的用法,不算反模式。
推荐可以演进成的一个小模板(示意)
# logger_handler.py
import logging
import os
from datetime import datetime
from logging.handlers import TimedRotatingFileHandler
from utils.path_tool import get_abs_path
LOG_ROOT = get_abs_path("logs")
os.makedirs(LOG_ROOT, exist_ok=True)
DEFAULT_LOG_FORMAT = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
)
def get_logger(
name: str = __name__,
console_level: int = logging.INFO,
file_level: int = logging.DEBUG,
log_file: str | None = None,
) -> logging.Logger:
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
if logger.handlers:
return logger
console_handler = logging.StreamHandler()
console_handler.setLevel(console_level)
console_handler.setFormatter(DEFAULT_LOG_FORMAT)
logger.addHandler(console_handler)
if not log_file:
log_file = os.path.join(
LOG_ROOT, f"{name}_{datetime.now().strftime('%Y%m%d')}.log"
)
file_handler = TimedRotatingFileHandler(
log_file, when="D", interval=1, backupCount=7, encoding="utf-8"
)
file_handler.setLevel(file_level)
file_handler.setFormatter(DEFAULT_LOG_FORMAT)
logger.addHandler(file_handler)
return logger
结论再强调一遍
- 保留这个模块是有意义的,尤其是你希望项目日志行为统一时。
- 不算“反模式”,整体方向 OK,只是可以:
- 用
__name__作为 logger 名称; - 用轮转 handler;
- 明确在项目里约定:所有模块只通过这个
get_logger获取 logger。
- 用
如果你愿意,我可以帮你把当前项目里的日志调用方式梳理一下,给出一份适配你项目结构的最终版本。
→提示词:@zhisaotong_agent/PROJECT_OVERVIEW.md 根据你以上建议,帮我实现日志工具
←AI回复
我先在项目里创建/补全 utils/logger_handler.py,实现一个统一的 get_logger,支持控制台+按天轮转文件日志,并结合你现有的 path_tool.get_abs_path。然后我会简单检查一下是否有明显的语法/导入问题,并给你说明如何在各模块里使用。
[3 tools called]
日志工具已实现
根据 PROJECT_OVERVIEW.md 和之前的建议,我已经在项目里实现了统一的日志工具 utils/logger_handler.py,核心特性如下:
-
统一日志目录:
- 使用
get_abs_path("logs"),在项目根目录下创建logs/目录(不存在则自动创建)。
- 使用
-
统一格式:
- 全局格式为:
"%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"。
- 全局格式为:
-
统一获取入口:
get_logger:- 推荐在各模块中这样用:
from utils.logger_handler import get_logger logger = get_logger(__name__) - 默认行为:
- 控制台:
INFO级别; - 日志文件:
DEBUG级别; - 日志文件名:
logs/<模块名>_YYYYMMDD.log(按天区分)。
- 控制台:
- 推荐在各模块中这样用:
-
文件日志按时间轮转(符合生产最佳实践):
- 使用
TimedRotatingFileHandler,默认配置:when="D":按天轮转;interval=1:每天一个新文件;backupCount=7:保留最近 7 天日志;encoding="utf-8"。
- 若多次调用
get_logger,会检测logger.handlers,不会重复添加 handler。
- 使用
-
可定制参数:
get_logger(name=__name__, console_level=logging.INFO, file_level=logging.DEBUG, log_file=None, when="D", interval=1, backup_count=7)- 若你传入
log_file且是相对路径,会自动通过get_abs_path转成基于项目根目录的绝对路径;也支持直接传绝对路径。
如果你愿意,我可以顺手在例如 app.py 或未来的 rag_service.py 里给你加一两个使用示例(包括一些典型的 info / warning / error 调用),方便后续直接照着用。
代码:logger_handler.py
"""
项目统一日志工具。
设计目标:
- 所有模块通过 `get_logger(__name__)` 获取 logger,保证格式与输出行为一致;
- 默认输出到控制台 + 按天轮转的日志文件(保留最近若干天);
- 日志文件统一存放在项目根目录下的 `logs/` 目录。
"""
from __future__ import annotations
import logging
import os
from datetime import datetime
from logging.handlers import TimedRotatingFileHandler
from typing import Optional
from .path_tool import get_abs_path
# 日志根目录:项目根目录下的 logs 目录
LOG_ROOT = get_abs_path("logs")
os.makedirs(LOG_ROOT, exist_ok=True)
# 默认日志格式:时间 - logger 名称 - 级别 - 文件名:行号 - 消息
DEFAULT_LOG_FORMATTER = logging.Formatter(
# 时间 - logger 名称 - 级别 - 文件名:行号 - 消息
# 2026-03-04 13:31:41,240 - zhisaotong_agent.utils.logger_handler - DEBUG - test.py:3 - 这是一条 DEBUG 日志(默认只写入文件)。
"%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
)
def _build_log_file_path(name: str) -> str:
"""
根据 logger 名称和当前日期生成日志文件路径。
例如:logs/agent_20260304.log
"""
safe_name = name.replace(":", "_").replace("/", "_")
filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d')}.log"
return os.path.join(LOG_ROOT, filename)
def get_logger(
name: str = __name__,
console_level: int = logging.INFO,
file_level: int = logging.DEBUG,
log_file: Optional[str] = None,
when: str = "D",
interval: int = 1, # 多久轮转一次日志文件(配合 when 使用),默认 1 天
backup_count: int = 7, # 最多保留多少个历史日志文件,超过会自动删除旧文件
) -> logging.Logger:
"""
获取一个已经按项目标准配置好的 logger。
参数说明:
- name: logger 名称。实际项目中推荐在各业务模块里显式写 `get_logger(__name__)`,
这样每个模块都会拿到带有自己模块名(如 `zhisaotong_agent.rag.rag_service`)的 logger,
便于在日志中按模块区分来源;如果不传则使用本模块自己的名称。
- console_level: 控制台输出日志级别(默认 INFO),控制“在终端里能看到多详细的日志”。
- file_level: 文件日志级别(默认 DEBUG);
- log_file: 指定日志文件的绝对路径;如果为 None,则自动根据 name+日期生成;
- when / interval / backup_count: TimedRotatingFileHandler 的轮转策略:
- when: "S", "M", "H", "D", "W0"-"W6", "midnight" 等;
- interval: 间隔单位数量;
- backup_count: 保留的历史文件数量。
说明:
- 为避免重复添加 handler,如果 logger 已经配置过 handler,则直接返回。
"""
logger = logging.getLogger(name)
# 顶层 logger 级别设置为 DEBUG,具体输出由各 handler 控制
logger.setLevel(logging.DEBUG)
# 如果已经配置过 handler,直接返回,避免重复添加
if logger.handlers:
return logger
# 1. 控制台 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(console_level)
console_handler.setFormatter(DEFAULT_LOG_FORMATTER)
logger.addHandler(console_handler)
# 2. 文件 Handler(按时间轮转)
if log_file is None:
log_file = _build_log_file_path(name)
# 如果传入了相对路径,自动转为基于项目根目录的绝对路径
if not os.path.isabs(log_file):
log_file = get_abs_path(log_file)
file_handler = TimedRotatingFileHandler(
filename=log_file,
when=when,
interval=interval,
backupCount=backup_count,
encoding="utf-8",
)
file_handler.setLevel(file_level)
file_handler.setFormatter(DEFAULT_LOG_FORMATTER)
logger.addHandler(file_handler)
return logger
__all__ = ["get_logger", "LOG_ROOT"] # 明确模块对外暴露的公共接口,配合 `from utils.logger_handler import *` 使用
if __name__ == "__main__":
"""
简单自测代码:
- 在控制台打印不同级别日志;
- 在 `logs/` 目录下生成对应的日志文件。
运行方式(二选一):
# 在项目根目录下
python -m zhisaotong_agent.utils.logger_handler
# 或在包根目录 zhisaotong_agent/ 下
python -m utils.logger_handler
"""
# test_logger = get_logger("logger_handler_demo")
test_logger = get_logger()
test_logger.debug("这是一条 DEBUG 日志(默认只写入文件)。")
test_logger.info("这是一条 INFO 日志。")
test_logger.warning("这是一条 WARNING 日志。")
test_logger.error("这是一条 ERROR 日志。")
test_logger.critical("这是一条 CRITICAL 日志。")
运行
cd /home/devbox/project
python -m zhisaotong_agent.utils.logger_handler
结果
2026-03-04 18:18:45,614 - __main__ - INFO - logger_handler.py:124 - 这是一条 INFO 日志。
2026-03-04 18:18:45,614 - __main__ - WARNING - logger_handler.py:125 - 这是一条 WARNING 日志。
2026-03-04 18:18:45,614 - __main__ - ERROR - logger_handler.py:126 - 这是一条 ERROR 日志。
2026-03-04 18:18:45,614 - __main__ - CRITICAL - logger_handler.py:127 - 这是一条 CRITICAL 日志。
注意因为是直接执行,所以name显示__main__,如果是被其他模块作为包导入,并且以__name__作为参数值传入,才会显示日志打印者的模块路径
更多推荐


所有评论(0)