繁體中文

新功能

日誌會輪替,測試也不再污染真實紀錄

電腦常駐幾個月時,日誌檔(log,記錄程式運作過程的文字檔)若只由 launchd 轉存,就會像永遠不換頁的筆記本,只增不減。這個選單列小程式原本由 launchd 轉存輸出到 ~/Library/Logs/usage/usage.log,沒有大小限制。launchd(macOS 用來在背景啟動程式的服務)只負責轉送。現在 Usage 在旁邊自己管理日誌:每份最多 2 MB,保留三份舊檔;就算建立目錄失敗,也照常啟動並保留主控台輸出。macOS 寫進 ~/Library/Logs/usage/,其他平台則寫 ~/.usage/logs/。連測試也改把日誌導去暫存目錄,不會再把測試警告混進真正用來診斷的 usage-app.log;Windows 系統匣測試曾在前一版修過同類問題。

新功能

除錯可只看指定區塊,不必淹沒所有訊息

想查價格載入出了什麼事,卻被四十多處 debug 輸出淹沒嗎?USAGE_DEBUG(除錯開關,用來要求程式多印診斷訊息)不再只是全開或全關。在啟動前的環境變數(給程式的外部設定值)填 USAGE_DEBUG=codex_loader,pricing,就只看這兩個子系統;填 1、all 或 *,仍維持原本全部開啟。若填 a.b,它也會涵蓋 a.b.c,因為 Python 的 logger hierarchy(記錄器會由上層名稱管到下層名稱的結構)本來就這樣繼承。像開演唱會的控台,現在可只打亮兩位表演者,不必把整座舞台的燈全開。

新功能

健康檢查可交給程式比對,不只剩截圖

有人請你交出 Usage 的健康檢查結果時,usage --doctor --json 現在給的是可讀取的資料,而不只是一段得靠截圖保存的文字。每項檢查都有固定代碼(stable code,讓程式每次都能辨認同一項檢查的名稱),例如 hook_state、codex_sessions,並標成三種狀態;只要有 error,指令就用非零結束碼(告訴其他程式這次執行失敗的數字)結束,其他工具便能比較兩次結果或自動判斷哪項失敗。邊界不清時它選 warn,不誤報成 error。像健檢表換上固定欄位,電腦和人都能對照。

新功能

選單項目集中組裝,綁定的方法仍留在原地

維護 macOS 選單列時,麻煩的往往不是加新選項,而是十幾個選項都重複同一套組裝動作。switchPanel_ 現在交給 menubar_menu.build_menu_item() 幫手函式(helper function,把重複步驟集中在一處的小函式)建立項目,建立項目、設定目標、狀態、提示,再加入選單的步驟只寫一次。switchPanel_ 本身沒有搬家,因為 AppDelegate(macOS 應用程式的協調者)會用 selector 綁定它。selector(依方法名稱連接功能的機制)若失去原方法就會斷掉。這次省下的是 24 行重複內容,不是看起來像 80 行的數字遊戲;舊測試也改為在幫手模組補上 NSMenuItem 的替換,不再加模組層級轉接。像把十幾張便條收進同一本簿子,項目仍在原處,只有抄寫不再重複。

English

FEATURE

Logs now rotate, and tests stop polluting the real record

When a computer stays on for months, a log file can become a notebook that never turns a page: it only grows. launchd, macOS's background service launcher, had been redirecting output to ~/Library/Logs/usage/usage.log with no size limit. Usage now manages its own nearby log: each file is capped at 2 MB and three old copies are retained; even if it cannot create the directory, the app still starts and keeps console output. macOS uses ~/Library/Logs/usage/; other platforms use ~/.usage/logs/. Tests now write logs to a temporary directory too, so their warnings no longer mingle with the real usage-app.log used for diagnosis.

FEATURE

Debugging can now target selected parts instead of flooding everything

Trying to find out why pricing loaded oddly used to mean drowning in output from more than forty debug sites. USAGE_DEBUG, the switch that asks the program to print diagnostic messages, is no longer only all on or all off. Set the environment variable before launch to USAGE_DEBUG=codex_loader,pricing and only those two subsystems speak up; 1, all, and * still enable everything. Setting a.b also covers a.b.c, because Python's logger hierarchy already passes settings from a parent name to its children. It is like a concert board that can light only two performers instead of every lamp on the stage.

FEATURE

Health checks can now be compared by programs, not just screenshots

When someone asks for Usage's health-check result, usage --doctor --json now returns data a program can read rather than a block of text that needs a screenshot. Each check has a stable code, such as hook_state or codex_sessions, and one of three states. If any check is an error, the command ends with a non-zero exit code, so other tools can compare two reports or determine which check failed automatically. When the boundary is unclear, it chooses warn instead of falsely calling it an error. It is like a medical form with fixed columns: people and computers can both line up the results.

FEATURE

Menu items are assembled in one place while the bound method stays put

When maintaining a macOS menu bar, the trouble is often not adding a new option but repeating the same assembly steps for a dozen of them. switchPanel_ now uses menubar_menu.build_menu_item(), a helper function that gathers repeated steps in one place, to create the items; target, state, tooltip, and adding the item are written once. switchPanel_ itself stays where it is because AppDelegate binds it through a selector, the mechanism that connects a feature by method name. Move the original method and that connection breaks. This removes 24 repeated lines, not the 80-line illusion suggested by the count. The old test now also replaces NSMenuItem in the helper module instead of adding module-level indirection. It is like gathering a dozen sticky notes into one notebook: the items stay put, but copying stops repeating.

原始 CHANGELOG

Original changelog日誌會輪替,測試也不再污染真實紀錄
**The file log is bounded and rotates.** The only log on disk was whatever launchd redirected into `~/Library/Logs/usage/usage.log`, and a redirect has no size limit — for a menu bar app that stays open for months, that file only ever grows. Python now owns a log of its own beside it, capped at 2 MB with three archives kept. Building the handler is best-effort: if the directory cannot be created the app keeps its console output and starts normally, because a logging subsystem must never be the thing that stops the program from running. macOS keeps the `~/Library/Logs/usage/` convention; every other platform writes under `~/.usage/logs/` next to the disk caches, since `~/Library/Logs` means nothing on Windows and `_setup_logging()` runs before the platform branch.
**The test suite wrote into the developer's real log file.** `tests/test_main.py` exercises `main()`, whose first action is `_setup_logging()` — so the run attached a rotating file handler pointed at the actual `~/Library/Logs/usage/usage-app.log`, and every warning logged by every later test landed in it. One full run deposited 8 KB containing pytest's temporary directory paths, which is worse than noise: it makes the log untrustworthy the day someone reads it for a real diagnosis. An autouse fixture now redirects the log directory to `tmp_path`, matching the existing rule for `cache_quarantine.QUARANTINE_DIR`. Curiously the same class of bug was fixed one version earlier in the Windows tray tests.
Original changelog除錯可只看指定區塊,不必淹沒所有訊息
**`USAGE_DEBUG` selects subsystems instead of switching everything on.** It was a boolean, so diagnosing one loader meant turning on all forty-odd debug sites and reading the result through the noise. It now takes a comma-separated list — `USAGE_DEBUG=codex_loader,pricing` raises only those two. Prefix matching comes free from Python's logger hierarchy rather than a hand-written comparison: setting `a.b` covers `a.b.c` because that is how logger inheritance already works. `1`, `all` and `*` keep the previous global behaviour.
Original changelog健康檢查可交給程式比對,不只剩截圖
**`--doctor --json` emits the report as data with stable codes.** The report was a block of text, so a user could only screenshot it and the reader had to scan by eye — nothing could compare two reports or decide programmatically which check failed. Each check now carries a fixed code (`hook_state`, `codex_sessions`, …) and one of three states, the process exits non-zero when any check is in error, and the human-readable output is unchanged to the character. Ambiguous cases resolve to `warn` rather than `error`: a false alarm that fails a health check costs more than a missed warning.
Original changelog選單項目集中組裝,綁定的方法仍留在原地
**`switchPanel_` builds its items through a helper.** A dozen menu entries each repeated the same five to seven lines — allocate the item, set the target, set the state, set the tooltip, add it — and `menubar.py` had two lines left under its ceiling. `menubar_menu.build_menu_item()` absorbs that shape. It lives in a new module rather than in `menubar_state.py`, which stays free of PyObjC. `switchPanel_` itself does not move: an AppDelegate method is bound by selector, and relocating one breaks the binding. The saving is smaller than it looks — 24 lines, not the 80 the line count suggested, because keyword arguments wrap across lines and give much of it back. The gain is the removed repetition, not the arithmetic. Moving the helper out also broke a test that patched `NSMenuItem` on the `menubar` module only; the fix was to patch the helper module too, never to add module-level indirection so the old patch keeps reaching.