Skip to content

Commit 4f53602

Browse files
authored
fix: clarify runtime logging context (#1643)
* fix: clarify market review logging context * docs: update changelog for logging context
1 parent ff6494b commit 4f53602

18 files changed

Lines changed: 404 additions & 60 deletions

api/v1/endpoints/analysis.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,16 @@ def _run_market_review_background(
144144
"send_notification": send_notification,
145145
"override_region": override_region,
146146
"return_structured": True,
147-
"config": runtime_config,
147+
"trigger_source": "api",
148148
}
149149
if query_id:
150150
review_kwargs["query_id"] = query_id
151+
logger.info(
152+
"[MarketReview] component=market_review action=background_start "
153+
"trigger_source=api task_id=%s region=%s",
154+
query_id or "-",
155+
override_region or getattr(runtime_config, "market_review_region", "cn") or "cn",
156+
)
151157
report = run_market_review(**review_kwargs)
152158
if not report:
153159
raise RuntimeError("大盘复盘未返回可持久化报告")
@@ -495,6 +501,13 @@ def trigger_market_review(
495501

496502
try:
497503
task_id = uuid.uuid4().hex
504+
logger.info(
505+
"[MarketReview] component=market_review action=submit trigger_source=api "
506+
"task_id=%s region=%s send_notification=%s",
507+
task_id,
508+
getattr(runtime_config, "market_review_region", "cn") or "cn",
509+
request.send_notification,
510+
)
498511
task = get_task_queue().submit_background_task(
499512
lambda: _run_market_review_background(
500513
request.send_notification,

bot/commands/market.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def _run_market_review(
146146
search_service=search_service,
147147
send_notification=True,
148148
override_region=override_region,
149+
trigger_source="bot",
149150
)
150151
if review_report:
151152
logger.info("[MarketCommand] 大盘复盘完成并已推送")

data_provider/akshare_fetcher.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,24 +1764,60 @@ def get_market_stats(self) -> Optional[Dict[str, Any]]:
17641764
self._set_random_user_agent()
17651765
self._enforce_rate_limit()
17661766

1767-
logger.info("[API调用] ak.stock_zh_a_spot_em() 获取市场统计...")
1767+
started_at = time.monotonic()
1768+
logger.info(
1769+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1770+
"api=ak.stock_zh_a_spot_em action=request_start"
1771+
)
17681772
df = ak.stock_zh_a_spot_em()
1773+
elapsed = time.monotonic() - started_at
1774+
logger.info(
1775+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1776+
"api=ak.stock_zh_a_spot_em action=request_complete elapsed=%.2fs",
1777+
elapsed,
1778+
)
17691779
if df is not None and not df.empty:
17701780
return self._calc_market_stats(df)
1781+
logger.warning(
1782+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1783+
"api=ak.stock_zh_a_spot_em action=parse status=empty"
1784+
)
17711785
except Exception as e:
1772-
logger.warning(f"[Akshare] 东财接口获取市场统计失败: {e},尝试新浪接口")
1786+
logger.warning(
1787+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1788+
"api=ak.stock_zh_a_spot_em action=failed error=%s fallback=ak.stock_zh_a_spot",
1789+
e,
1790+
)
17731791

17741792
# 东财失败后,尝试新浪接口
17751793
try:
17761794
self._set_random_user_agent()
17771795
self._enforce_rate_limit()
17781796

1779-
logger.info("[API调用] ak.stock_zh_a_spot() 获取市场统计(新浪)...")
1797+
started_at = time.monotonic()
1798+
logger.info(
1799+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1800+
"api=ak.stock_zh_a_spot action=request_start"
1801+
)
17801802
df = ak.stock_zh_a_spot()
1803+
elapsed = time.monotonic() - started_at
1804+
logger.info(
1805+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1806+
"api=ak.stock_zh_a_spot action=request_complete elapsed=%.2fs",
1807+
elapsed,
1808+
)
17811809
if df is not None and not df.empty:
17821810
return self._calc_market_stats(df)
1811+
logger.warning(
1812+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1813+
"api=ak.stock_zh_a_spot action=parse status=empty"
1814+
)
17831815
except Exception as e:
1784-
logger.error(f"[Akshare] 新浪接口获取市场统计也失败: {e}")
1816+
logger.error(
1817+
"[MarketStats] component=market_stats provider=AkshareFetcher "
1818+
"api=ak.stock_zh_a_spot action=failed error=%s",
1819+
e,
1820+
)
17851821

17861822
return None
17871823

data_provider/base.py

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,12 +1375,12 @@ def prefetch_realtime_quotes(self, stock_codes: List[str]) -> int:
13751375

13761376
# Issue #455: PREFETCH_REALTIME_QUOTES=false 可禁用预取,避免全市场拉取
13771377
if not getattr(config, "prefetch_realtime_quotes", True):
1378-
logger.debug("[预取] PREFETCH_REALTIME_QUOTES=false,跳过批量预取")
1378+
logger.debug("[预取] component=realtime_prefetch action=skip reason=disabled")
13791379
return 0
13801380

13811381
# 如果实时行情被禁用,跳过预取
13821382
if not config.enable_realtime_quote:
1383-
logger.debug("[预取] 实时行情功能已禁用,跳过预取")
1383+
logger.debug("[预取] component=realtime_prefetch action=skip reason=realtime_quote_disabled")
13841384
return 0
13851385

13861386
# 检查优先级中是否包含全量拉取数据源
@@ -1400,15 +1400,29 @@ def prefetch_realtime_quotes(self, stock_codes: List[str]) -> int:
14001400

14011401
# 如果没有全量数据源,或者全量数据源排在第 3 位之后,跳过预取
14021402
if first_bulk_source_index is None or first_bulk_source_index >= 2:
1403-
logger.info(f"[预取] 当前优先级使用轻量级数据源(sina/tencent),无需预取")
1403+
logger.info(
1404+
"[预取] component=realtime_prefetch action=skip reason=no_early_bulk_source priority=%s",
1405+
priority,
1406+
)
14041407
return 0
14051408

14061409
# 如果股票数量少于 5 个,不进行批量预取(逐个查询更高效)
14071410
if len(stock_codes) < 5:
1408-
logger.info(f"[预取] 股票数量 {len(stock_codes)} < 5,跳过批量预取")
1411+
logger.info(
1412+
"[预取] component=realtime_prefetch action=skip reason=small_batch "
1413+
"stock_count=%d threshold=5 bulk_source=%s",
1414+
len(stock_codes),
1415+
priority_list[first_bulk_source_index],
1416+
)
14091417
return 0
14101418

1411-
logger.info(f"[预取] 开始批量预取实时行情,共 {len(stock_codes)} 只股票...")
1419+
bulk_source = priority_list[first_bulk_source_index]
1420+
logger.info(
1421+
"[预取] component=realtime_prefetch action=start stock_count=%d bulk_source=%s first_code=%s",
1422+
len(stock_codes),
1423+
bulk_source,
1424+
stock_codes[0],
1425+
)
14121426

14131427
# 尝试通过 efinance 或 akshare 预取
14141428
# 只需要调用一次 get_realtime_quote,缓存机制会自动拉取全市场数据
@@ -1418,14 +1432,30 @@ def prefetch_realtime_quotes(self, stock_codes: List[str]) -> int:
14181432
quote = self.get_realtime_quote(first_code)
14191433

14201434
if quote:
1421-
logger.info(f"[预取] 批量预取完成,缓存已填充")
1435+
logger.info(
1436+
"[预取] component=realtime_prefetch action=complete status=success "
1437+
"stock_count=%d bulk_source=%s",
1438+
len(stock_codes),
1439+
bulk_source,
1440+
)
14221441
return len(stock_codes)
14231442
else:
1424-
logger.warning(f"[预取] 批量预取失败,将使用逐个查询模式")
1443+
logger.warning(
1444+
"[预取] component=realtime_prefetch action=complete status=failed "
1445+
"stock_count=%d bulk_source=%s fallback=per_stock",
1446+
len(stock_codes),
1447+
bulk_source,
1448+
)
14251449
return 0
14261450

14271451
except Exception as e:
1428-
logger.error(f"[预取] 批量预取异常: {e}")
1452+
logger.error(
1453+
"[预取] component=realtime_prefetch action=complete status=error "
1454+
"stock_count=%d bulk_source=%s error=%s",
1455+
len(stock_codes),
1456+
bulk_source,
1457+
e,
1458+
)
14291459
return 0
14301460

14311461
@staticmethod
@@ -2116,27 +2146,72 @@ def get_main_indices(self, region: str = "cn") -> List[Dict[str, Any]]:
21162146
continue
21172147
return []
21182148

2119-
def get_market_stats(self) -> Dict[str, Any]:
2149+
def get_market_stats(self, *, purpose: str = "unspecified") -> Dict[str, Any]:
21202150
"""获取市场涨跌统计(自动切换数据源)"""
2151+
logger.info("[MarketStats] component=market_stats action=start purpose=%s", purpose)
21212152
tickflow_fetcher = self._get_tickflow_fetcher()
21222153
if tickflow_fetcher is not None:
2154+
started_at = time.monotonic()
21232155
try:
21242156
data = tickflow_fetcher.get_market_stats()
2157+
elapsed = time.monotonic() - started_at
21252158
if data:
2126-
logger.info("[TickFlowFetcher] 获取市场统计成功")
2159+
logger.info(
2160+
"[MarketStats] component=market_stats action=provider_success "
2161+
"purpose=%s provider=TickFlowFetcher elapsed=%.2fs",
2162+
purpose,
2163+
elapsed,
2164+
)
21272165
return data
2166+
logger.info(
2167+
"[MarketStats] component=market_stats action=provider_empty "
2168+
"purpose=%s provider=TickFlowFetcher elapsed=%.2fs",
2169+
purpose,
2170+
elapsed,
2171+
)
21282172
except Exception as e:
2129-
logger.warning(f"[TickFlowFetcher] 获取市场统计失败: {e}")
2173+
elapsed = time.monotonic() - started_at
2174+
logger.warning(
2175+
"[MarketStats] component=market_stats action=provider_failed "
2176+
"purpose=%s provider=TickFlowFetcher elapsed=%.2fs error=%s",
2177+
purpose,
2178+
elapsed,
2179+
e,
2180+
)
21302181

21312182
for fetcher in self._fetchers:
2183+
started_at = time.monotonic()
21322184
try:
21332185
data = fetcher.get_market_stats()
2186+
elapsed = time.monotonic() - started_at
21342187
if data:
2135-
logger.info(f"[{fetcher.name}] 获取市场统计成功")
2188+
logger.info(
2189+
"[MarketStats] component=market_stats action=provider_success "
2190+
"purpose=%s provider=%s elapsed=%.2fs",
2191+
purpose,
2192+
fetcher.name,
2193+
elapsed,
2194+
)
21362195
return data
2196+
logger.info(
2197+
"[MarketStats] component=market_stats action=provider_empty "
2198+
"purpose=%s provider=%s elapsed=%.2fs",
2199+
purpose,
2200+
fetcher.name,
2201+
elapsed,
2202+
)
21372203
except Exception as e:
2138-
logger.warning(f"[{fetcher.name}] 获取市场统计失败: {e}")
2204+
elapsed = time.monotonic() - started_at
2205+
logger.warning(
2206+
"[MarketStats] component=market_stats action=provider_failed "
2207+
"purpose=%s provider=%s elapsed=%.2fs error=%s",
2208+
purpose,
2209+
fetcher.name,
2210+
elapsed,
2211+
e,
2212+
)
21392213
continue
2214+
logger.warning("[MarketStats] component=market_stats action=complete status=empty purpose=%s", purpose)
21402215
return {}
21412216

21422217
def _run_with_timeout(

data_provider/efinance_fetcher.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -934,19 +934,41 @@ def get_market_stats(self) -> Optional[Dict[str, Any]]:
934934
current_time - _realtime_cache['timestamp'] < _realtime_cache['ttl']
935935
):
936936
df = _realtime_cache['data']
937+
logger.info(
938+
"[MarketStats] component=market_stats provider=EfinanceFetcher "
939+
"api=ef.stock.get_realtime_quotes action=cache_hit cache_age=%.0fs",
940+
current_time - _realtime_cache['timestamp'],
941+
)
937942
else:
938-
logger.info("[API调用] ef.stock.get_realtime_quotes() 获取市场统计...")
943+
started_at = time.monotonic()
944+
logger.info(
945+
"[MarketStats] component=market_stats provider=EfinanceFetcher "
946+
"api=ef.stock.get_realtime_quotes action=request_start"
947+
)
939948
df = _ef_call_with_timeout(ef.stock.get_realtime_quotes)
949+
elapsed = time.monotonic() - started_at
950+
logger.info(
951+
"[MarketStats] component=market_stats provider=EfinanceFetcher "
952+
"api=ef.stock.get_realtime_quotes action=request_complete elapsed=%.2fs",
953+
elapsed,
954+
)
940955
_realtime_cache['data'] = df
941956
_realtime_cache['timestamp'] = current_time
942957

943958
if df is None or df.empty:
944-
logger.warning("[API返回] 市场统计数据为空")
959+
logger.warning(
960+
"[MarketStats] component=market_stats provider=EfinanceFetcher "
961+
"api=ef.stock.get_realtime_quotes action=parse status=empty"
962+
)
945963
return None
946964

947965
return self._calc_market_stats(df)
948966
except Exception as e:
949-
logger.error(f"[efinance] 获取市场统计失败: {e}")
967+
logger.error(
968+
"[MarketStats] component=market_stats provider=EfinanceFetcher "
969+
"api=ef.stock.get_realtime_quotes action=failed error=%s",
970+
e,
971+
)
950972
return None
951973

952974
def _calc_market_stats(

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
1111

1212
- [改进] #1390 P0 为个股分析与历史/回测展示新增可选八态 `action` / `action_label` 建议动作字段,保留 `operation_advice` 自由文本和 `decision_type=buy|hold|sell` 统计口径,不新增迁移或配置项。
1313
- [修复] #1390 收紧建议动作 legacy fallback:英文 `not to ...``avoid selling/reducing/trimming ...` 等否定/回避表达不再误判为买卖动作,Web 旧记录不再把中文金融上下文、`buy or sell`、多 guard 歧义文本或 `buyback` / `buy-back` / `buy back` / `selloff` / `sell-off` / `sell off` 等英文复合词渲染成 action badge,并在有结构化 `action` 时让回测/历史趋势等入口按界面语言显示 action 标签。
14+
- [改进] 完善运行时日志上下文,补充 logger name、触发来源、市场统计与实时行情预取链路状态,便于排查调度、API、Bot 和数据源降级路径。
1415
<!-- 新条目格式:- [类型] 描述(类型取值:新功能/改进/修复/文档/测试/chore)-->
1516
<!-- 每条独立一行追加到本段末尾,无需分类标题,合并时冲突最小 -->
1617
- [修复] 桌面发布打包改用冻结可执行文件运行时探针校验 `alphasift.dsa_adapter`,避免 macOS PyInstaller 将模块内嵌进可执行文件时被文件系统/zip 扫描误判为缺失。

main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,11 @@ def run_full_analysis(
587587
and not args.no_market_review
588588
and effective_region != ''
589589
):
590+
schedule_mode = bool(
591+
getattr(args, 'schedule', False)
592+
or getattr(config, 'schedule_enabled', False)
593+
)
594+
review_trigger_source = "schedule" if schedule_mode else "cli"
590595
review_result = _run_market_review_with_shared_lock(
591596
config,
592597
run_market_review,
@@ -596,6 +601,7 @@ def run_full_analysis(
596601
send_notification=not args.no_notify,
597602
merge_notification=merge_notification,
598603
override_region=effective_region,
604+
trigger_source=review_trigger_source,
599605
)
600606
# 如果有结果,赋值给 market_report 用于后续飞书文档生成
601607
if review_result:
@@ -965,6 +971,7 @@ def main() -> int:
965971
search_service=search_service,
966972
send_notification=not args.no_notify,
967973
override_region=effective_region,
974+
trigger_source="cli",
968975
)
969976
return 0
970977

0 commit comments

Comments
 (0)