Showing
5 changed files
with
144 additions
and
35 deletions
| 1 | import os | 1 | import os |
| 2 | import time | 2 | import time |
| 3 | -import logging | 3 | + |
| 4 | + | ||
| 5 | + | ||
| 4 | from aabd.base.enhance_dict import value_or_default | 6 | from aabd.base.enhance_dict import value_or_default |
| 5 | 7 | ||
| 6 | -logger = logging.getLogger(__name__) | 8 | + |
| 7 | 9 | ||
| 8 | from utils.football_replay_video_event_by_llm import FootballReplayVideoEvent | 10 | from utils.football_replay_video_event_by_llm import FootballReplayVideoEvent |
| 9 | from utils.football_replay_match_live import FootballReplayMatchLive | 11 | from utils.football_replay_match_live import FootballReplayMatchLive |
| 10 | 12 | ||
| 13 | +from utils.my_logger import get_logger | ||
| 14 | + | ||
| 15 | +logger = get_logger() | ||
| 16 | + | ||
| 11 | class FootballReplayMatch: | 17 | class FootballReplayMatch: |
| 12 | def __init__(self, settings): | 18 | def __init__(self, settings): |
| 13 | self.settings = settings | 19 | self.settings = settings |
| @@ -93,25 +99,25 @@ class FootballReplayMatch: | @@ -93,25 +99,25 @@ class FootballReplayMatch: | ||
| 93 | start_time = time.time() | 99 | start_time = time.time() |
| 94 | replay_det_info = self.det_goal_replay(replay_info, task_id) | 100 | replay_det_info = self.det_goal_replay(replay_info, task_id) |
| 95 | end_time = time.time() | 101 | end_time = time.time() |
| 96 | - logger.info(f"判断是否是进球耗时: {end_time - start_time} 秒") | ||
| 97 | - print(f"判断是否是进球耗时: {end_time - start_time} 秒") | 102 | + logger.info(f"Goal_recognition_all_cost_time:{end_time-start_time:.2f}seconds") |
| 103 | + # print(f"判断是否是进球耗时: {end_time - start_time} 秒") | ||
| 98 | replay_event_name = replay_det_info.get('event_name', '') | 104 | replay_event_name = replay_det_info.get('event_name', '') |
| 99 | if replay_event_name == '进球': | 105 | if replay_event_name == '进球': |
| 100 | matched_event = self.match_by_llm(replay_info, goal_live_events, task_id) | 106 | matched_event = self.match_by_llm(replay_info, goal_live_events, task_id) |
| 101 | if matched_event is None: | 107 | if matched_event is None: |
| 102 | - logger.info(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 是进球但是未能找到匹配的事件") | ||
| 103 | - print(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 是进球但是未能找到匹配的事件") | 108 | + logger.info(f"{task_id}_LLM_think_{replay_info['url']}_{replay_start_utc}_{replay_end_utc}_is_goal_but_no_matched_event") |
| 109 | + # print(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 是进球但是未能找到匹配的事件") | ||
| 104 | else: | 110 | else: |
| 105 | matched_event_id = matched_event.get('video_id') | 111 | matched_event_id = matched_event.get('video_id') |
| 106 | - logger.info(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | ||
| 107 | - print(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | 112 | + logger.info(f"{task_id}_LLM_think_{replay_info['url']}_{replay_start_utc}_{replay_end_utc}_matched_with_{matched_event_id}") |
| 113 | + # print(f"{task_id}_LLM认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | ||
| 108 | else: | 114 | else: |
| 109 | - logger.info(f"{task_id}_LLM判断{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 不是进球,无需匹配") | ||
| 110 | - print(f"{task_id}_LLM判断{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 不是进球,无需匹配") | 115 | + logger.info(f"{task_id}_LLM_think_{replay_info['url']}_{replay_start_utc}_{replay_end_utc}_is_not_goal_no_need_match") |
| 116 | + # print(f"{task_id}_LLM判断{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 不是进球,无需匹配") | ||
| 111 | else: | 117 | else: |
| 112 | matched_event_id = matched_event.get('id') | 118 | matched_event_id = matched_event.get('id') |
| 113 | - logger.info(f"{task_id}_通过时间找到匹配认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | ||
| 114 | - print(f"{task_id}_通过时间找到匹配认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | 119 | + logger.info(f"{task_id}_matched_by_time{replay_info['url']}_{replay_start_utc}_{replay_end_utc}_matched_with_{matched_event_id}") |
| 120 | + # print(f"{task_id}_通过时间找到匹配认为视频{replay_info['url']}_{replay_start_utc}_{replay_end_utc} 对应的进球的事件视频是{matched_event_id}") | ||
| 115 | return { | 121 | return { |
| 116 | "id": task_id, | 122 | "id": task_id, |
| 117 | "match_id": match_id, | 123 | "match_id": match_id, |
| @@ -20,6 +20,10 @@ try: | @@ -20,6 +20,10 @@ try: | ||
| 20 | except ImportError: | 20 | except ImportError: |
| 21 | from .api import FootballReplayMatch | 21 | from .api import FootballReplayMatch |
| 22 | 22 | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 23 | from pathlib import Path | 27 | from pathlib import Path |
| 24 | def get_video_files_pathlib(folder_path, extensions=None): | 28 | def get_video_files_pathlib(folder_path, extensions=None): |
| 25 | if extensions is None: | 29 | if extensions is None: |
| @@ -32,7 +36,7 @@ def get_video_files_pathlib(folder_path, extensions=None): | @@ -32,7 +36,7 @@ def get_video_files_pathlib(folder_path, extensions=None): | ||
| 32 | if f.is_file() and f.suffix.lower() in extensions] | 36 | if f.is_file() and f.suffix.lower() in extensions] |
| 33 | return sorted(image_files) | 37 | return sorted(image_files) |
| 34 | 38 | ||
| 35 | -def _fake_settings(match_by_time_threshold: int = 30): | 39 | +def _fake_settings(match_by_time_threshold: int = 30, cache_dir_tag: str = "test"): |
| 36 | """构造一个 settings 伪对象,兼容 api.py 中的 value_or_default 读取逻辑。""" | 40 | """构造一个 settings 伪对象,兼容 api.py 中的 value_or_default 读取逻辑。""" |
| 37 | return SimpleNamespace( | 41 | return SimpleNamespace( |
| 38 | match_by_time_threshold=match_by_time_threshold, | 42 | match_by_time_threshold=match_by_time_threshold, |
| @@ -41,18 +45,34 @@ def _fake_settings(match_by_time_threshold: int = 30): | @@ -41,18 +45,34 @@ def _fake_settings(match_by_time_threshold: int = 30): | ||
| 41 | temperature=0.7, | 45 | temperature=0.7, |
| 42 | api_key='no_key' | 46 | api_key='no_key' |
| 43 | ), | 47 | ), |
| 44 | - common=SimpleNamespace(cache_dir="/root/lzw/tmp_0527_replay_cache_08/videos_cache", | ||
| 45 | - task_log_dir="/root/lzw/tmp_0527_replay_cache_08/tasks_log" | 48 | + common=SimpleNamespace(cache_dir=f"/root/lzw/tmp_0528_replay_cache_{cache_dir_tag}/videos_cache", |
| 49 | + task_log_dir=f"/root/lzw/tmp_0528_replay_cache_{cache_dir_tag}/tasks_log" | ||
| 46 | ), | 50 | ), |
| 47 | save_frames_enable=True, | 51 | save_frames_enable=True, |
| 48 | ) | 52 | ) |
| 49 | 53 | ||
| 50 | 54 | ||
| 55 | +def get_replays_and_events_pair(): | ||
| 56 | + | ||
| 57 | + videos_dir = rf"/root/lzw/finished" | ||
| 58 | + | ||
| 59 | + dir_pairs = [] | ||
| 60 | + for video_name in sorted(os.listdir(videos_dir)): | ||
| 51 | 61 | ||
| 62 | + live_dir = os.path.join(videos_dir, video_name, 'live') | ||
| 63 | + live_video_dir = os.path.join(live_dir, 'videos') | ||
| 52 | 64 | ||
| 53 | -def get_event_list(): | 65 | + replays_dir = os.path.join(videos_dir, video_name, 'replays') |
| 66 | + replay_videos_dir = os.path.join(replays_dir, 'videos') | ||
| 67 | + | ||
| 68 | + dir_pairs.append((replay_videos_dir, live_video_dir)) | ||
| 69 | + return dir_pairs | ||
| 70 | + | ||
| 71 | + | ||
| 72 | + | ||
| 73 | +def get_event_list(events_mp4_dir): | ||
| 54 | # url = "http://video.mam.miguvideo.com/mnt6/fastclip3/wsc/2026/04/29/df760b8d15394cbc9ed0d0a4a9c4b786_1080PS/29133605/vodtmp/3b3e99cf4ca84c3782503d8817242de2.m3u8" | 74 | # url = "http://video.mam.miguvideo.com/mnt6/fastclip3/wsc/2026/04/29/df760b8d15394cbc9ed0d0a4a9c4b786_1080PS/29133605/vodtmp/3b3e99cf4ca84c3782503d8817242de2.m3u8" |
| 55 | - events_mp4_dir = r"/root/lzw/69e4ed7c872d573c9554510b-auto-10154973/goal" | 75 | + # events_mp4_dir = r"/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/live/videos" |
| 56 | 76 | ||
| 57 | events_mp4_names = get_video_files_pathlib(events_mp4_dir) | 77 | events_mp4_names = get_video_files_pathlib(events_mp4_dir) |
| 58 | 78 | ||
| @@ -60,7 +80,7 @@ def get_event_list(): | @@ -60,7 +80,7 @@ def get_event_list(): | ||
| 60 | for mp4 in events_mp4_names: | 80 | for mp4 in events_mp4_names: |
| 61 | mp4_path = os.path.join(events_mp4_dir, mp4) | 81 | mp4_path = os.path.join(events_mp4_dir, mp4) |
| 62 | event_lis.append({ | 82 | event_lis.append({ |
| 63 | - "id": f"event_{mp4.split('.')[0]}", | 83 | + "id": mp4, |
| 64 | "type": "1", | 84 | "type": "1", |
| 65 | "url": mp4_path, | 85 | "url": mp4_path, |
| 66 | # "event_utc": None | 86 | # "event_utc": None |
| @@ -73,7 +93,7 @@ def _base_data(i, replay_path, event_lis): | @@ -73,7 +93,7 @@ def _base_data(i, replay_path, event_lis): | ||
| 73 | "id": f"task_{str(i).zfill(3)}", | 93 | "id": f"task_{str(i).zfill(3)}", |
| 74 | "match_id": f"match_{str(i).zfill(3)}", | 94 | "match_id": f"match_{str(i).zfill(3)}", |
| 75 | "replay": { | 95 | "replay": { |
| 76 | - "id": f"replay_{str(i).zfill(3)}", | 96 | + "id": os.path.basename(replay_path), |
| 77 | "url": replay_path, | 97 | "url": replay_path, |
| 78 | # "start_utc": None, | 98 | # "start_utc": None, |
| 79 | # "end_utc": None | 99 | # "end_utc": None |
| @@ -82,24 +102,36 @@ def _base_data(i, replay_path, event_lis): | @@ -82,24 +102,36 @@ def _base_data(i, replay_path, event_lis): | ||
| 82 | } | 102 | } |
| 83 | 103 | ||
| 84 | 104 | ||
| 85 | -class TestReplayMatchEvent(unittest.TestCase): | ||
| 86 | 105 | ||
| 87 | - @patch("utils.football_replay_match_live.FootballReplayMatchLive") | ||
| 88 | - @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent") | ||
| 89 | - def test_match_by_time_success(self, mock_video_cls, mock_live_cls): | 106 | + |
| 107 | + | ||
| 108 | + | ||
| 109 | +def test_match_by_time_success(): | ||
| 90 | # """场景1:时间差在阈值内,直接通过 match_by_time 命中。""" | 110 | # """场景1:时间差在阈值内,直接通过 match_by_time 命中。""" |
| 91 | - settings = _fake_settings(match_by_time_threshold=10) # threshold = 30 * 1000 = 30000 ms | 111 | + |
| 112 | + replays_and_events_pair = get_replays_and_events_pair() | ||
| 113 | + | ||
| 114 | + for replays_mp4_dir, events_mp4_dir in replays_and_events_pair: | ||
| 115 | + print(100*"=") | ||
| 116 | + print(f"replays_mp4_dir: {replays_mp4_dir}, events_mp4_dir: {events_mp4_dir}") | ||
| 117 | + | ||
| 118 | + tag = os.path.basename(os.path.dirname(os.path.dirname(replays_mp4_dir))) | ||
| 119 | + settings = _fake_settings(match_by_time_threshold=30, cache_dir_tag=tag) # threshold = 30 * 1000 = 30000 ms | ||
| 120 | + | ||
| 121 | + | ||
| 92 | frm = FootballReplayMatch(settings) | 122 | frm = FootballReplayMatch(settings) |
| 93 | 123 | ||
| 94 | - replays_mp4_dir = "/root/lzw/69e4ed7c872d573c9554510b-auto-10154973/replay" | 124 | + # replays_mp4_dir = "/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/replays/videos" |
| 95 | relays_mp4_names = get_video_files_pathlib(replays_mp4_dir) | 125 | relays_mp4_names = get_video_files_pathlib(replays_mp4_dir) |
| 96 | 126 | ||
| 97 | - event_list = get_event_list() | 127 | + |
| 128 | + # events_mp4_dir = r"/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/live/videos" | ||
| 129 | + event_list = get_event_list(events_mp4_dir) | ||
| 98 | 130 | ||
| 99 | all_time = 0.0 | 131 | all_time = 0.0 |
| 100 | max_time = 0.0 | 132 | max_time = 0.0 |
| 101 | for i, replay_name in enumerate(relays_mp4_names): | 133 | for i, replay_name in enumerate(relays_mp4_names): |
| 102 | - print(100*"*" + f"replay_name: {replay_name}") | 134 | + print(100*"*") |
| 103 | replay_path = os.path.join(replays_mp4_dir, replay_name) | 135 | replay_path = os.path.join(replays_mp4_dir, replay_name) |
| 104 | data = _base_data(i+1, replay_path, event_list) | 136 | data = _base_data(i+1, replay_path, event_list) |
| 105 | 137 | ||
| @@ -115,13 +147,31 @@ class TestReplayMatchEvent(unittest.TestCase): | @@ -115,13 +147,31 @@ class TestReplayMatchEvent(unittest.TestCase): | ||
| 115 | print(result) | 147 | print(result) |
| 116 | print() | 148 | print() |
| 117 | print() | 149 | print() |
| 118 | - print() | ||
| 119 | 150 | ||
| 120 | print(100*"#") | 151 | print(100*"#") |
| 121 | print(f"max_time: {max_time}") | 152 | print(f"max_time: {max_time}") |
| 122 | print(f"all_time: {all_time}") | 153 | print(f"all_time: {all_time}") |
| 123 | # exit() | 154 | # exit() |
| 124 | 155 | ||
| 156 | + | ||
| 157 | + | ||
| 158 | + | ||
| 159 | +if __name__ == "__main__": | ||
| 160 | + test_match_by_time_success() | ||
| 161 | + | ||
| 162 | + | ||
| 163 | + | ||
| 164 | + | ||
| 165 | + | ||
| 166 | + | ||
| 167 | + | ||
| 168 | + | ||
| 169 | + | ||
| 170 | + | ||
| 171 | + | ||
| 172 | + | ||
| 173 | + | ||
| 174 | + | ||
| 125 | # self.assertEqual(result["id"], "task_001") | 175 | # self.assertEqual(result["id"], "task_001") |
| 126 | # self.assertEqual(result["match_id"], "match_001") | 176 | # self.assertEqual(result["match_id"], "match_001") |
| 127 | # self.assertEqual(result["replay_id"], "replay_001") | 177 | # self.assertEqual(result["replay_id"], "replay_001") |
| @@ -245,7 +295,3 @@ class TestReplayMatchEvent(unittest.TestCase): | @@ -245,7 +295,3 @@ class TestReplayMatchEvent(unittest.TestCase): | ||
| 245 | 295 | ||
| 246 | # with self.assertRaises(KeyError): | 296 | # with self.assertRaises(KeyError): |
| 247 | # frm.replay_match_event(data) | 297 | # frm.replay_match_event(data) |
| 248 | - | ||
| 249 | - | ||
| 250 | -if __name__ == "__main__": | ||
| 251 | - unittest.main() |
| 1 | import re | 1 | import re |
| 2 | import json | 2 | import json |
| 3 | +import time | ||
| 3 | import os.path | 4 | import os.path |
| 4 | from pathlib import Path | 5 | from pathlib import Path |
| 5 | from datetime import timedelta | 6 | from datetime import timedelta |
| @@ -9,9 +10,14 @@ from langchain_openai import ChatOpenAI | @@ -9,9 +10,14 @@ from langchain_openai import ChatOpenAI | ||
| 9 | 10 | ||
| 10 | try: | 11 | try: |
| 11 | from .llm_image import Video2Frame | 12 | from .llm_image import Video2Frame |
| 13 | + from .my_logger import get_logger | ||
| 12 | except: | 14 | except: |
| 13 | from llm_image import Video2Frame | 15 | from llm_image import Video2Frame |
| 16 | + from my_logger import get_logger | ||
| 14 | 17 | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +logger = get_logger() | ||
| 15 | # req_prompt = """ | 21 | # req_prompt = """ |
| 16 | # # Role | 22 | # # Role |
| 17 | # 你是一名拥有20年经验的足球视频技术分析师,擅长结合**视觉画面**与**解说音频(ASR)**进行跨镜头的事件匹配。你能通过解说员的描述锁定时间背景,并通过视觉特征确认物理细节。 | 23 | # 你是一名拥有20年经验的足球视频技术分析师,擅长结合**视觉画面**与**解说音频(ASR)**进行跨镜头的事件匹配。你能通过解说员的描述锁定时间背景,并通过视觉特征确认物理细节。 |
| @@ -192,11 +198,14 @@ class FootballReplayMatchLive: | @@ -192,11 +198,14 @@ class FootballReplayMatchLive: | ||
| 192 | user_contents.append({'type': "text", "text": "\n请根据上述片段进行匹配并按要求输出结果/no_think\n"}) | 198 | user_contents.append({'type': "text", "text": "\n请根据上述片段进行匹配并按要求输出结果/no_think\n"}) |
| 193 | system_message = SystemMessage(content=req_prompt) | 199 | system_message = SystemMessage(content=req_prompt) |
| 194 | user_message = HumanMessage(content=user_contents) | 200 | user_message = HumanMessage(content=user_contents) |
| 195 | - result = self.model.invoke([system_message, user_message]).content | ||
| 196 | 201 | ||
| 202 | + start_time = time.time() | ||
| 203 | + result = self.model.invoke([system_message, user_message]).content | ||
| 204 | + end_time = time.time() | ||
| 205 | + logger.info(f"match_once_invoke_cost_time:{end_time-start_time:.2f}seconds") | ||
| 197 | 206 | ||
| 198 | # print(100*"3") | 207 | # print(100*"3") |
| 199 | - # print(result) | 208 | + # logger.info(f"match once invoke result: {result}") |
| 200 | # print(100*"4") | 209 | # print(100*"4") |
| 201 | # try: | 210 | # try: |
| 202 | # result_json = json.loads(result) | 211 | # result_json = json.loads(result) |
| @@ -299,7 +308,7 @@ class FootballReplayMatchLive: | @@ -299,7 +308,7 @@ class FootballReplayMatchLive: | ||
| 299 | start=event_start, | 308 | start=event_start, |
| 300 | end=event_end, | 309 | end=event_end, |
| 301 | roi=None, | 310 | roi=None, |
| 302 | - max_px_area=400_000, | 311 | + max_px_area=300_000, |
| 303 | prompt_start="\n【回放片段信息】\n", | 312 | prompt_start="\n【回放片段信息】\n", |
| 304 | prompt_end=f"\n回放解说内容:无\n" | 313 | prompt_end=f"\n回放解说内容:无\n" |
| 305 | ) | 314 | ) |
| 1 | import json | 1 | import json |
| 2 | +import time | ||
| 2 | import os.path | 3 | import os.path |
| 3 | from pathlib import Path | 4 | from pathlib import Path |
| 4 | 5 | ||
| 6 | + | ||
| 7 | + | ||
| 5 | from langchain_core.messages import SystemMessage, HumanMessage | 8 | from langchain_core.messages import SystemMessage, HumanMessage |
| 6 | from langchain_openai import ChatOpenAI | 9 | from langchain_openai import ChatOpenAI |
| 7 | 10 | ||
| 8 | try: | 11 | try: |
| 9 | from .llm_image import Video2Frame | 12 | from .llm_image import Video2Frame |
| 13 | + from .my_logger import get_logger | ||
| 10 | except: | 14 | except: |
| 11 | from llm_image import Video2Frame | 15 | from llm_image import Video2Frame |
| 16 | + from my_logger import get_logger | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +logger = get_logger() | ||
| 12 | 21 | ||
| 13 | 22 | ||
| 14 | req_prompt = """ | 23 | req_prompt = """ |
| @@ -108,7 +117,12 @@ class FootballReplayVideoEvent: | @@ -108,7 +117,12 @@ class FootballReplayVideoEvent: | ||
| 108 | system_message = SystemMessage(content=req_prompt) | 117 | system_message = SystemMessage(content=req_prompt) |
| 109 | video_message = HumanMessage(content=contents) | 118 | video_message = HumanMessage(content=contents) |
| 110 | # asr_message = HumanMessage(content=f"解说内容:{asr_text}") | 119 | # asr_message = HumanMessage(content=f"解说内容:{asr_text}") |
| 120 | + | ||
| 121 | + start_time = time.time() | ||
| 111 | result = self.model.invoke([system_message, video_message]).content | 122 | result = self.model.invoke([system_message, video_message]).content |
| 123 | + end_time = time.time() | ||
| 124 | + logger.info(f"video_event_invoke_cost_time:{end_time-start_time:.2f}seconds") | ||
| 125 | + # logger.info(f"video event invoke result: {result}") | ||
| 112 | try: | 126 | try: |
| 113 | result_json = json.loads(result) | 127 | result_json = json.loads(result) |
| 114 | except json.JSONDecodeError: | 128 | except json.JSONDecodeError: |
| 1 | +import os | ||
| 2 | +import time | ||
| 3 | +import logging | ||
| 4 | +from functools import lru_cache | ||
| 5 | + | ||
| 6 | +@lru_cache(maxsize=1) | ||
| 7 | +def get_logger(): | ||
| 8 | + name = "football_replay_match" | ||
| 9 | + logger = logging.getLogger(name) | ||
| 10 | + logger.setLevel(logging.INFO) | ||
| 11 | + | ||
| 12 | + # 1. 配置日志输出格式 | ||
| 13 | + # 修改点:增加了 %(filename)s:%(lineno)d 来显示文件名和行号 | ||
| 14 | + formatter = logging.Formatter( | ||
| 15 | + '%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s', | ||
| 16 | + datefmt='%Y-%m-%d %H:%M:%S' | ||
| 17 | + ) | ||
| 18 | + | ||
| 19 | + # 2. 创建控制台处理器 | ||
| 20 | + console_handler = logging.StreamHandler() | ||
| 21 | + console_handler.setFormatter(formatter) | ||
| 22 | + logger.addHandler(console_handler) | ||
| 23 | + | ||
| 24 | + # 3. 创建文件处理器 | ||
| 25 | + log_dir = "logs" | ||
| 26 | + if not os.path.exists(log_dir): | ||
| 27 | + os.makedirs(log_dir) | ||
| 28 | + | ||
| 29 | + log_file = os.path.join(log_dir, f"run_{time.strftime('%Y%m%d')}.log") | ||
| 30 | + file_handler = logging.FileHandler(log_file, encoding='utf-8') | ||
| 31 | + file_handler.setFormatter(formatter) | ||
| 32 | + logger.addHandler(file_handler) | ||
| 33 | + | ||
| 34 | + return logger |
-
Please register or login to post a comment