cli-logging-ux
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCLI Logging UX expert persona
CLI日志UX专家角色
CLI Logging & Developer Experience
CLI日志与开发者体验
Decision framework
决策框架
Apply these three tests to every piece of user-facing output. If a message fails any test, redesign it.
对每一条面向用户的输出应用以下三项测试。如果消息未通过任何一项测试,请重新设计。
1. The "So What?" Test
1. “那又如何?”测试
Every warning must answer: what should the user do about this?
undefined每条警告必须回答:用户应该对此采取什么行动?
undefinedFails — not actionable, user can't do anything
未通过——无操作性,用户无法采取任何措施
Sub-skill 'my-skill' from 'my-package' overwrites existing skill
Sub-skill 'my-skill' from 'my-package' overwrites existing skill
Passes — tells the user exactly what to do
通过——明确告知用户该怎么做
Skipping my-skill — local file exists (not managed by APM). Use 'apm install --force' to overwrite.
If the user can't act on it, it's not a warning — it's noise. Demote to `--verbose` or remove.Skipping my-skill — local file exists (not managed by APM). Use 'apm install --force' to overwrite.
如果用户无法采取行动,那它就不是警告——而是噪音。降级为`--verbose`输出或直接移除。2. The Traffic Light Rule
2. 交通灯规则
Use color semantics consistently. Never use a warning color for an informational state.
| Color | Helper | Meaning | When to use |
|---|---|---|---|
| Green | | Success / completed | Operation finished as expected |
| Yellow | | User action needed | Something requires user decision |
| Red | | Error / failure | Operation failed, cannot continue |
| Blue | | Informational | Status updates, progress, summaries |
| Dim | | Secondary detail | Verbose-mode details, grouping headers |
始终一致地使用颜色语义。绝不能用警告色表示信息性状态。
| 颜色 | 辅助函数 | 含义 | 适用场景 |
|---|---|---|---|
| 绿色 | | 成功/已完成 | 操作按预期完成 |
| 黄色 | | 需要用户操作 | 某些内容需要用户决策 |
| 红色 | | 错误/失败 | 操作失败,无法继续 |
| 蓝色 | | 信息性内容 | 状态更新、进度、摘要 |
| 暗淡色 | | 次要细节 | 详细模式下的细节、分组标题 |
3. The Newspaper Test
3. 报纸测试
Can the user scan output like headlines? Top-level = what happened. Details = drill down.
undefined用户能否像浏览新闻标题一样快速扫描输出?顶层内容=发生了什么,细节内容=深入查看。
undefinedBad — warnings break the visual flow between status and summary
不佳——警告打断了状态与摘要之间的视觉流程
[checkmark] package-name
[warning] something happened
[warning] something else happened
[tree] 3 skill(s) integrated
[checkmark] package-name
[warning] something happened
[warning] something else happened
[tree] 3 skill(s) integrated
Good — clean tree, diagnostics at the end
良好——清晰的树状结构,诊断信息放在末尾
[checkmark] package-name
[tree] 3 skill(s) integrated
── Diagnostics ──
[warning] 2 skills replaced by a different package (last installed wins)
Run with --verbose to see details
undefined[checkmark] package-name
[tree] 3 skill(s) integrated
── Diagnostics ──
[warning] 2 skills replaced by a different package (last installed wins)
Run with --verbose to see details
undefinedInline output vs deferred diagnostics
内联输出 vs 延迟诊断
Use inline output for:
内联输出适用于:
- Success confirmations ()
_rich_success - Progress updates (with indented
_rich_infoprefix)└─ - Errors that halt the current operation ()
_rich_error
- 成功确认()
_rich_success - 进度更新(带缩进前缀的
└─)_rich_info - 终止当前操作的错误()
_rich_error
Use DiagnosticCollector for:
DiagnosticCollector适用于:
- Warnings that apply across multiple packages (collisions, overwrites)
- Issues the user should know about but that don't stop the operation
- Anything that would repeat N times in a loop
python
undefined- 跨多个包的警告(冲突、覆盖)
- 用户需要知晓但不会终止操作的问题
- 循环中会重复N次的内容
python
undefinedBad — inline warning repeated per file, clutters output
不佳——内联警告随文件重复输出,造成混乱
for file in files:
if collision:
_rich_warning(f"Skipping {file}...")
for file in files:
if collision:
_rich_warning(f"Skipping {file}...")
Good — collect during loop, render grouped summary at the end
良好——循环期间收集,最后渲染分组摘要
for file in files:
if collision:
diagnostics.skip(file, package=pkg_name)
for file in files:
if collision:
diagnostics.skip(file, package=pkg_name)
Later, after the loop:
循环结束后:
if diagnostics.has_diagnostics:
diagnostics.render_summary()
DiagnosticCollector categories: `skip()` for collisions, `overwrite()` for cross-package replacements, `warn()` for general warnings, `error()` for failures.if diagnostics.has_diagnostics:
diagnostics.render_summary()
DiagnosticCollector分类:`skip()`用于冲突,`overwrite()`用于跨包替换,`warn()`用于常规警告,`error()`用于失败。Console helper conventions
控制台辅助函数约定
Always use the helpers from — never raw or bare .
apm_cli.utils.consoleprint()click.echo()Emojis are banned. Never use emoji characters anywhere in CLI output — not in messages, symbols, help text, or status indicators. Use ASCII text symbols exclusively via .
STATUS_SYMBOLSpython
from apm_cli.utils.console import (
_rich_success, _rich_error, _rich_warning, _rich_info, _rich_echo
)
_rich_success("Installed 3 APM dependencies") # green, bold
_rich_info(" └─ 2 prompts integrated → .github/prompts/") # blue
_rich_warning("Config drift detected — re-run apm install") # yellow
_rich_error("Failed to download package") # red
_rich_echo(" [pkg-name]", color="dim") # dim, for verbose detailsUse dict with parameter for consistent ASCII prefixes:
STATUS_SYMBOLSsymbol=python
_rich_info("Starting operation...", symbol="gear") # renders as "[*] Starting operation..."始终使用中的辅助函数——绝不要使用原生或直接。
apm_cli.utils.consoleprint()click.echo()禁止使用表情符号。在CLI输出的任何位置都不要使用表情符号字符——包括消息、符号、帮助文本或状态指示器。仅通过使用ASCII文本符号。
STATUS_SYMBOLSpython
from apm_cli.utils.console import (
_rich_success, _rich_error, _rich_warning, _rich_info, _rich_echo
)
_rich_success("Installed 3 APM dependencies") # 绿色、加粗
_rich_info(" └─ 2 prompts integrated → .github/prompts/") # 蓝色
_rich_warning("Config drift detected — re-run apm install") # 黄色
_rich_error("Failed to download package") # 红色
_rich_echo(" [pkg-name]", color="dim") # 暗淡色,用于详细细节使用带参数的字典以保持一致的ASCII前缀:
symbol=STATUS_SYMBOLSpython
_rich_info("Starting operation...", symbol="gear") # 渲染为 "[*] Starting operation..."Output structure pattern
输出结构模式
Follow this visual hierarchy for multi-package operations:
[checkmark] package-name-1 # _rich_success — download/copy ok
[tree] 2 prompts integrated → .github/prompts/ # _rich_info — indented summary
[tree] 1 skill(s) integrated → .github/skills/
[checkmark] package-name-2
[tree] 1 instruction(s) integrated → .github/instructions/
── Diagnostics ── # Only if diagnostics.has_diagnostics
[warning] N files skipped — ... # Grouped by category
Run with --verbose to see details
Installed 2 APM dependencies # _rich_success — final summary多包操作遵循以下视觉层级:
[checkmark] package-name-1 # _rich_success — 下载/复制完成
[tree] 2 prompts integrated → .github/prompts/ # _rich_info — 缩进摘要
[tree] 1 skill(s) integrated → .github/skills/
[checkmark] package-name-2
[tree] 1 instruction(s) integrated → .github/instructions/
── Diagnostics ── # 仅当diagnostics.has_diagnostics时显示
[warning] N files skipped — ... # 按分类分组
Run with --verbose to see details
Installed 2 APM dependencies # _rich_success — 最终摘要Content-awareness principle
内容感知原则
Before reporting changes, check if anything actually changed. Don't report no-ops.
python
undefined在报告更改前,检查是否真的有内容变更。不要报告无操作(no-ops)。
python
undefinedBad — always copies and reports, even when content is identical
不佳——无论内容是否相同都复制并报告
shutil.rmtree(target)
shutil.copytree(source, target)
_rich_info(f" └─ Skill updated")
shutil.rmtree(target)
shutil.copytree(source, target)
_rich_info(f" └─ Skill updated")
Good — skip when content matches
良好——内容匹配时跳过
if SkillIntegrator._dirs_equal(source, target):
continue # Nothing changed, nothing to report
undefinedif SkillIntegrator._dirs_equal(source, target):
continue # 无变更,无需报告
undefinedCommandLogger Architecture
CommandLogger架构
APM is a large and growing CLI with 10+ commands, 8+ integrators, and dozens of output sites. The logging architecture enforces Separation of Concerns: commands declare what happened; the logger decides how to render it. This keeps output consistent, testable, and evolvable without shotgun surgery across command files.
APM是一个规模不断扩大的CLI,拥有10+命令、8+集成器和数十个输出点。日志架构遵循关注点分离原则:命令层声明发生了什么;日志层决定如何渲染。这确保了输出的一致性、可测试性,且无需在命令文件中进行零散修改即可演进。
The three layers
三层结构
┌─────────────────────────────────────────────────────┐
│ Command layer (install.py, pack.py, audit.py …) │
│ Calls: logger.success(), logger.tree_item(), … │
│ NEVER calls: _rich_*, click.echo(), print() │
├─────────────────────────────────────────────────────┤
│ Logger layer (command_logger.py) │
│ CommandLogger ← InstallLogger, future subclasses │
│ Owns: verbose gating, symbol choice, indentation │
│ Delegates to: _rich_* helpers │
├─────────────────────────────────────────────────────┤
│ Rendering layer (console.py) │
│ _rich_echo, _rich_success, _rich_error, … │
│ Owns: Rich/colorama fallback, color, STATUS_SYMBOLS │
└─────────────────────────────────────────────────────┘Changes to output style (colors, symbols, indentation) happen in the logger or rendering layer only — command code is untouched. New output patterns (e.g. a tree sub-item, a package metadata line) become new logger methods, not ad-hoc format strings in commands.
┌─────────────────────────────────────────────────────┐
│ 命令层 (install.py, pack.py, audit.py …) │
│ 调用:logger.success(), logger.tree_item(), … │
│ 禁止调用:_rich_*, click.echo(), print() │
├─────────────────────────────────────────────────────┤
│ 日志层 (command_logger.py) │
│ CommandLogger ← InstallLogger,未来子类 │
│ 负责:详细模式控制、符号选择、缩进 │
│ 委托给:_rich_* 辅助函数 │
├─────────────────────────────────────────────────────┤
│ 渲染层 (console.py) │
│ _rich_echo, _rich_success, _rich_error, … │
│ 负责:Rich/colorama降级处理、颜色、STATUS_SYMBOLS │
└─────────────────────────────────────────────────────┘输出样式的变更(颜色、符号、缩进)仅在日志层或渲染层进行——命令代码无需修改。新的输出模式(如树状子项、包元数据行)应成为新的日志方法,而非命令中的临时格式化字符串。
Base class: CommandLogger
CommandLogger基类:CommandLogger
CommandLoggersrc/apm_cli/core/command_logger.py| Method | Purpose | When to use |
|---|---|---|
| Operation start | Beginning of a command |
| Status update with | Mid-operation phase changes |
| Green success | Operation completed |
| Yellow warning | User action needed |
| Red error | Operation failed |
| Dim text, verbose-only | Internal details (paths, hashes) |
| Green text, no symbol prefix | |
| Yellow text, verbose-only | Per-package diagnostic hints |
| | Dry-run explanation |
| Auth resolution step | Verbose auth tracing |
| Render DiagnosticCollector | End of command |
src/apm_cli/core/command_logger.py| 方法 | 用途 | 适用场景 |
|---|---|---|
| 操作启动 | 命令开始时 |
| 带 | 操作中期状态变更 |
| 绿色成功提示 | 操作完成时 |
| 黄色警告提示 | 需要用户操作时 |
| 红色错误提示 | 操作失败时 |
| 暗淡色文本,仅详细模式显示 | 内部细节(路径、哈希) |
| 绿色文本,无符号前缀 | 包下的 |
| 黄色文本,仅详细模式显示 | 每个包的诊断提示 |
| 带 | 试运行说明 |
| 认证步骤追踪 | 详细模式下的认证追踪 |
| 渲染DiagnosticCollector | 命令结束时 |
Subclass: InstallLogger(CommandLogger)
InstallLogger(CommandLogger)子类:InstallLogger(CommandLogger)
InstallLogger(CommandLogger)Install-specific phases. Commands that don't need these use directly.
CommandLogger| Method | Purpose | Output |
|---|---|---|
| Start validation | |
| Package OK | |
| Package bad | |
| Start resolution | Context-aware install/update message |
| Package installed | |
| Download error | |
| Lockfile verbose line | |
| Auth source verbose | |
| Package type verbose | |
| Final summary | |
安装流程专属阶段。不需要这些阶段的命令直接使用。
CommandLogger| 方法 | 用途 | 输出 |
|---|---|---|
| 开始验证 | |
| 包验证通过 | |
| 包验证失败 | |
| 开始依赖解析 | 上下文感知的安装/更新消息 |
| 包安装完成 | |
| 下载失败 | |
| 锁文件详细行 | |
| 认证源详细信息 | |
| 包类型详细信息 | |
| 最终摘要 | |
When to add a new logger method
何时添加新的日志方法
If a command needs a new output pattern (new indentation level, new semantic meaning, new verbose gate), add a method to CommandLogger or a subclass. Signs you need a new method:
- You're writing in a command file
_rich_echo(f" Something: {value}", color="dim") - You're checking before calling
if logger.verbose:in a command_rich_echo - You're formatting a string with specific indentation that other commands might reuse
- Multiple commands emit the same kind of line (e.g., file lists, auth info)
如果命令需要新的输出模式(新的缩进级别、新的语义、新的详细模式控制),请向CommandLogger或其子类添加方法。需要添加新方法的信号:
- 你在命令文件中编写
_rich_echo(f" Something: {value}", color="dim") - 你在命令中调用前检查
_rich_echoif logger.verbose: - 你正在格式化带有特定缩进的字符串,而其他命令可能会复用
- 多个命令输出相同类型的行(如文件列表、认证信息)
Rule: No direct _rich_*
in commands
_rich_*规则:命令中禁止直接调用_rich_*
_rich_*Command functions must NOT call , , etc. directly. Use , , etc. instead. The helpers are internal to the logger and rendering layers.
_rich_info()_rich_error()logger.progress()logger.error()_rich_*Exception: Rich tables and panels for display (not lifecycle logging) may use directly — these are data presentation, not status reporting.
console.print()命令函数绝不能直接调用、等。请改用、等方法。辅助函数是日志层和渲染层的内部函数。
_rich_info()_rich_error()logger.progress()logger.error()_rich_*例外: 用于展示的Rich表格和面板(非生命周期日志)可直接使用——这些属于数据展示,而非状态报告。
console.print()Rule: Every command gets a CommandLogger
CommandLogger规则:每个命令都要实例化CommandLogger
CommandLoggerEvery Click command function must instantiate a (or subclass) and pass it to helpers:
CommandLoggerpython
@cli.command()
@click.option("--verbose", "-v", is_flag=True)
@click.option("--dry-run", is_flag=True)
def my_command(verbose, dry_run):
logger = CommandLogger("my-command", verbose=verbose, dry_run=dry_run)
logger.start("Starting operation...")
_do_work(logger=logger)
logger.render_summary()每个Click命令函数必须实例化(或其子类)并传递给辅助函数:
CommandLoggerpython
@cli.command()
@click.option("--verbose", "-v", is_flag=True)
@click.option("--dry-run", is_flag=True)
def my_command(verbose, dry_run):
logger = CommandLogger("my-command", verbose=verbose, dry_run=dry_run)
logger.start("Starting operation...")
_do_work(logger=logger)
logger.render_summary()Rule: Verbose gating lives in the logger
规则:详细模式控制由日志层负责
Never check in command code. Use methods that gate internally:
if verbose:python
undefined绝不要在命令代码中检查。请使用内部已实现控制的方法:
if verbose:python
undefinedBad — manual verbose check in command
不佳——命令中手动检查详细模式
if verbose:
_rich_echo(f" Auth: {source}", color="dim")
if verbose:
_rich_echo(f" Auth: {source}", color="dim")
Good — logger handles the gate
良好——日志层处理控制
logger.package_auth(source, token_type) # No-ops when not verbose
logger.verbose_detail(f" Path: {path}") # No-ops when not verbose
undefinedlogger.package_auth(source, token_type) # 非详细模式下无操作
logger.verbose_detail(f" Path: {path}") # 非详细模式下无操作
undefinedDiagnosticCollector integration
DiagnosticCollector集成
Access via (lazy-initialized). The collector owns the collect-then-render lifecycle:
logger.diagnosticspython
undefined通过访问(延迟初始化)。收集器负责“先收集后渲染”的生命周期:
logger.diagnosticspython
undefinedDuring operation — collect
操作期间——收集
diagnostics.skip(file, package=pkg_name) # Collision
diagnostics.overwrite(file, package=pkg_name) # Cross-package replacement
diagnostics.error(msg, package=pkg_name) # Failure
diagnostics.auth(msg, package=pkg_name) # Auth issue
diagnostics.skip(file, package=pkg_name) # 冲突
diagnostics.overwrite(file, package=pkg_name) # 跨包替换
diagnostics.error(msg, package=pkg_name) # 失败
diagnostics.auth(msg, package=pkg_name) # 认证问题
Query during operation (e.g., for inline verbose hints)
操作期间查询(如内联详细提示)
count = diagnostics.count_for_package(pkg_name, category="collision")
if count > 0:
logger.package_inline_warning(f" [!] {count} files skipped")
count = diagnostics.count_for_package(pkg_name, category="collision")
if count > 0:
logger.package_inline_warning(f" [!] {count} files skipped")
After operation — render grouped summary
操作结束后——渲染分组摘要
logger.render_summary() # Delegates to diagnostics.render_summary()
undefinedlogger.render_summary() # 委托给diagnostics.render_summary()
undefinedVisual hierarchy contract
视觉层级约定
Multi-package operations follow this tree structure:
[+] package-name #v1.0 @b0cbd3df # download_complete
Auth: git-credential-fill (oauth) # package_auth (verbose)
Package type: Skill (SKILL.md detected) # package_type_info (verbose)
└─ 3 skill(s) integrated -> .github/skills/ # tree_item
└─ 1 prompt integrated -> .github/prompts/ # tree_item
[!] 2 files skipped (local files exist) # package_inline_warning (verbose)
[+] another-package (cached) # download_complete
── Diagnostics ── # render_summary
[!] 2 files skipped -- local files exist # Grouped by category
Use 'apm install --force' to overwrite
[*] Installed 2 APM dependencies. # install_summaryKey rules:
- package lines are the top-level anchors (green, no indent beyond 2-space)
[+] - Verbose metadata (Auth, Package type) uses 4-space indent, dim color
- Tree items () use 4-space indent, green color, no symbol prefix
└─ - Inline warnings use 4-space indent, yellow color, verbose-only
- Diagnostics summary appears AFTER all packages, not inline (except verbose hints)
多包操作遵循以下树状结构:
[+] package-name #v1.0 @b0cbd3df # download_complete
Auth: git-credential-fill (oauth) # package_auth (详细模式)
Package type: Skill (SKILL.md detected) # package_type_info (详细模式)
└─ 3 skill(s) integrated -> .github/skills/ # tree_item
└─ 1 prompt integrated -> .github/prompts/ # tree_item
[!] 2 files skipped (local files exist) # package_inline_warning (详细模式)
[+] another-package (cached) # download_complete
── Diagnostics ── # render_summary
[!] 2 files skipped -- local files exist # 按分类分组
Use 'apm install --force' to overwrite
[*] Installed 2 APM dependencies. # install_summary关键规则:
- 包行是顶层锚点(绿色,缩进不超过2空格)
[+] - 详细元数据(认证、包类型)使用4空格缩进、暗淡色
- 树状项()使用4空格缩进、绿色、无符号前缀
└─ - 内联警告使用4空格缩进、黄色、仅详细模式显示
- 诊断摘要出现在所有包之后,而非内联(详细提示除外)
Scaling guidance
扩展指导
As the CLI grows, this architecture scales by:
- New commands: Instantiate , use existing methods. Add subclass only if the command has distinct phases (like
CommandLogger).InstallLogger - New output patterns: Add methods to . Every command benefits.
CommandLogger - New integrators: Accept param, push to collector. No direct output.
diagnostics= - Theme changes: Modify rendering layer (). Zero command changes.
console.py - Testing: Mock in tests to assert semantic calls without parsing output strings.
CommandLogger
随着CLI规模增长,此架构通过以下方式扩展:
- 新命令:实例化,使用现有方法。仅当命令有独特阶段时才添加子类(如
CommandLogger)。InstallLogger - 新输出模式:向添加方法。所有命令均可受益。
CommandLogger - 新集成器:接收参数,将警告推送到收集器。禁止直接输出。
diagnostics= - 主题变更:修改渲染层()。无需修改任何命令。
console.py - 测试:在测试中模拟,断言语义调用,无需解析输出字符串。
CommandLogger
Anti-patterns
反模式
-
Warning for non-actionable state — If the user can't do anything about it, useor defer to
_rich_info, not--verbose._rich_warning -
Inline warnings in loops — Useto collect, then render a grouped summary after the loop.
DiagnosticCollector -
Missingparameter — When calling integrators, always pass
diagnosticsso warnings route to the deferred summary.diagnostics=diagnostics -
No emojis, ever — Emojis are completely banned from all CLI output. Use ASCII text symbols fromexclusively. This applies to messages, help text, status indicators, and table titles.
STATUS_SYMBOLS -
Inconsistent symbols — Always usedict with
STATUS_SYMBOLSparam, not inline characters.symbol= -
Walls of text — Use Rich tables for structured data, panels for grouped content. Break up long output with visual hierarchy (indentation,tree connectors).
└─ -
Directcalls in commands — Use
_rich_*,logger.start(),logger.progress()etc. Thelogger.tree_item()helpers are internal to CommandLogger and console.py. Adding a_rich_*call in a command file is a SoC violation._rich_echo -
Manualchecks — Use
if verbose:,logger.verbose_detail(), or other verbose-gated methods. The logger owns the gate.logger.package_auth() -
Manualchecks — Use
if dry_run:orlogger.should_execute.logger.dry_run_notice() -
Format strings for indentation in commands — Don't writein command code. Use
f" Auth: {source}"which owns the indent level. When a new indentation pattern is needed, add a method to CommandLogger.logger.package_auth(source) -
Re-creating shared objects per iteration — Expensive objects likeshould be created once before loops and reused per-package. The logger and diagnostics collector are already singletons per command invocation.
AuthResolver -
Usingfor tree sub-items —
logger.progress()adds aprogress()symbol prefix. Tree continuation lines ([i]) should use└─which renders with no symbol.logger.tree_item()
-
对非可操作状态发出警告——如果用户无法采取任何行动,请使用或降级到
_rich_info,而非--verbose。_rich_warning -
循环中的内联警告——使用收集,然后在循环结束后渲染分组摘要。
DiagnosticCollector -
缺少参数——调用集成器时,始终传递
diagnostics,以便警告路由到延迟摘要。diagnostics=diagnostics -
绝对禁止使用表情符号——CLI输出的所有位置完全禁止使用表情符号。仅使用中的ASCII文本符号。这适用于消息、帮助文本、状态指示器和表格标题。
STATUS_SYMBOLS -
符号不一致——始终使用带参数的
symbol=字典,而非内联字符。STATUS_SYMBOLS -
文本墙——使用Rich表格展示结构化数据,使用面板分组内容。通过视觉层级(缩进、树状连接器)拆分长输出。
└─ -
命令中直接调用——使用
_rich_*、logger.start()、logger.progress()等方法。logger.tree_item()辅助函数是CommandLogger和console.py的内部函数。在命令文件中添加_rich_*调用违反关注点分离原则。_rich_echo -
手动检查——使用
if verbose:、logger.verbose_detail()或其他已实现详细模式控制的方法。日志层负责控制逻辑。logger.package_auth() -
手动检查——使用
if dry_run:或logger.should_execute。logger.dry_run_notice() -
命令中使用格式化字符串控制缩进——不要在命令代码中编写。使用
f" Auth: {source}",它负责缩进级别。当需要新的缩进模式时,向CommandLogger添加方法。logger.package_auth(source) -
每次迭代重新创建共享对象——像这样的昂贵对象应在循环前创建一次,并在每个包中复用。日志器和诊断收集器在每次命令调用中已是单例。
AuthResolver -
使用处理树状子项——
logger.progress()会添加progress()符号前缀。树状延续行([i])应使用└─,它渲染时无符号。logger.tree_item()