Skip to content

Commit c302533

Browse files
committed
fix(issue-1261): [bug]-docker-部署启动失败
1 parent 9f70705 commit c302533

5 files changed

Lines changed: 70 additions & 1 deletion

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
3131
- [文档] 更新多语言 README 首页浅色工作台 GIF,并精简功能特性表,保留原有赞助商、快速开始和推送效果结构。
3232
- [新功能] 通知网关新增默认关闭的进程内降噪配置,支持去重、冷却、静默时段和最低严重级别,并将每日摘要开关标记为预留能力。
3333
- [文档] 恢复多语言 README 新闻源配置表中推荐项的加粗样式,统一相关项目章节层级,并精简顶部导航、联系文案和尾部展示。
34+
- [修复] Docker 挂载的 `logs` 目录不可写时启动日志自动降级到控制台输出,并补充非 root 容器目录权限说明。
3435

3536
## [3.16.0] - 2026-05-10
3637

docs/full-guide.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,15 @@ services:
502502
- `./reports:/app/reports`:生成的分析报告
503503
- `./strategies:/app/strategies:ro`:自定义策略 YAML(只读挂载)
504504

505+
官方 Docker 镜像默认使用容器内非 root 用户 `dsa`(UID/GID `1000:1000`)运行。首次部署或更换宿主机目录后,请确保 `data`、`logs`、`reports` 对该用户可写,否则文件日志会自动降级到控制台输出,数据库或报告写入仍可能失败:
506+
507+
```bash
508+
mkdir -p data logs reports
509+
sudo chown -R 1000:1000 data logs reports
510+
```
511+
512+
如果你通过 `--user` 或 Compose `user:` 指定了其他运行用户,请将上面的 UID/GID 替换为实际容器用户,或使用等价的 ACL / 权限策略授予写入权限。
513+
505514
如果你需要覆盖内置静态资源,还可以额外挂载:
506515

507516
- `./static:/app/static:ro`

docs/full-guide_EN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,15 @@ Recommended host mappings:
467467
- `./reports:/app/reports` for generated reports
468468
- `./strategies:/app/strategies:ro` for custom strategy YAML files
469469

470+
Official Docker images run as the non-root `dsa` user inside the container (UID/GID `1000:1000`). On first deployment or after changing host directories, make sure `data`, `logs`, and `reports` are writable by that user. If `logs` is not writable, file logging falls back to console output; database or report writes may still fail until permissions are fixed:
471+
472+
```bash
473+
mkdir -p data logs reports
474+
sudo chown -R 1000:1000 data logs reports
475+
```
476+
477+
If you override the runtime user with `--user` or Compose `user:`, replace the UID/GID above with the actual container user, or grant write access with an equivalent ACL / permission policy.
478+
470479
Optional static asset override:
471480

472481
- `./static:/app/static:ro`

main.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,22 @@ def _setup_bootstrap_logging(debug: bool = False) -> None:
141141
root.addHandler(handler)
142142

143143

144+
def _setup_runtime_logging(log_dir: str, debug: bool = False) -> bool:
145+
"""Switch to configured logging, falling back to console on file I/O errors."""
146+
try:
147+
setup_logging(log_prefix="stock_analysis", debug=debug, log_dir=log_dir)
148+
return True
149+
except OSError as exc:
150+
logger.warning(
151+
"文件日志初始化失败,已降级为控制台日志输出;日志目录 %r 当前不可写或不可创建: %s。"
152+
"Docker bind mount 请确保宿主机 data/logs/reports 目录可由容器内 UID 1000 写入,"
153+
"例如执行 `sudo chown -R 1000:1000 data logs reports` 后重启容器。",
154+
log_dir,
155+
exc,
156+
)
157+
return False
158+
159+
144160
def _get_stock_analysis_pipeline():
145161
"""Lazily import StockAnalysisPipeline for external consumers.
146162
@@ -767,7 +783,7 @@ def main() -> int:
767783

768784
# 配置日志(输出到控制台和文件)
769785
try:
770-
setup_logging(log_prefix="stock_analysis", debug=args.debug, log_dir=config.log_dir)
786+
_setup_runtime_logging(config.log_dir, debug=args.debug)
771787
except Exception as exc:
772788
logger.exception("切换到配置日志目录失败: %s", exc)
773789
return 1

tests/test_main_schedule_mode.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,40 @@ def test_bootstrap_logging_failure_does_not_block_startup(self) -> None:
491491
self.assertEqual(exit_code, 0)
492492
run_mock.assert_called_once()
493493

494+
def test_runtime_file_logging_permission_error_falls_back_to_console(self) -> None:
495+
"""Configured file logging failures should not prevent Docker startup."""
496+
import io
497+
498+
args = self._make_args()
499+
config = self._make_config(log_dir="/app/logs")
500+
capture_stream = io.StringIO()
501+
capture_handler = logging.StreamHandler(capture_stream)
502+
capture_handler.setLevel(logging.DEBUG)
503+
capture_handler.setFormatter(logging.Formatter("%(message)s"))
504+
505+
root_logger = logging.getLogger()
506+
507+
with patch("main.parse_arguments", return_value=args), \
508+
patch("main.get_config", return_value=config), \
509+
patch(
510+
"main.setup_logging",
511+
side_effect=PermissionError("/app/logs/stock_analysis_20260511.log"),
512+
), \
513+
patch("main.run_full_analysis") as run_mock:
514+
root_logger.addHandler(capture_handler)
515+
try:
516+
exit_code = main.main()
517+
finally:
518+
root_logger.removeHandler(capture_handler)
519+
capture_handler.close()
520+
521+
self.assertEqual(exit_code, 0)
522+
run_mock.assert_called_once()
523+
output = capture_stream.getvalue()
524+
self.assertIn("文件日志初始化失败,已降级为控制台日志输出", output)
525+
self.assertIn("/app/logs", output)
526+
self.assertIn("sudo chown -R 1000:1000 data logs reports", output)
527+
494528
def test_run_full_analysis_import_failure_propagates(self) -> None:
495529
"""P1: import failures in run_full_analysis must propagate, not be swallowed."""
496530
args = self._make_args()

0 commit comments

Comments
 (0)