Skip to content

Watcher: Wire up file + Event Log logging on the Windows service path - #68

Merged
wasimxyz merged 4 commits into
stagingfrom
cursor/watcher-service-logging-gaps
May 13, 2026
Merged

Watcher: Wire up file + Event Log logging on the Windows service path#68
wasimxyz merged 4 commits into
stagingfrom
cursor/watcher-service-logging-gaps

Conversation

@wasimxyz

@wasimxyz wasimxyz commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds watcher/src/data_hub_watcher/logging_setup.py — a single source of truth for watcher logging: setup_file_logging() (rotating watcher.log, idempotent), attach_servicemanager_handler(sm) (mirrors logger.* records to LogErrorMsg / LogWarningMsg / LogInfoMsg with re-entrancy guard, 30 KB truncation under the 32 KB Event Log cap, and never-raise semantics), and a DATA_HUB_WATCHER_LOG_LEVEL env override so operators can flip a stuck lab PC to DEBUG without redeploying.
  • Adds WATCHER_LOG_DIR in watcher/src/data_hub_watcher/constants.py that resolves to C:\ProgramData\DataHubWatcher\ on Windows (a location both the operator user and LocalSystem can write to by default via the inherited ACL on C:\ProgramData) and to ~/.data-hub/ on macOS/Linux for dev parity. Both the CLI watch command and the Windows service write to the same watcher.log and service-bootstrap.log files now, so a single Get-Content -Wait covers all entry points.
  • watcher/src/data_hub_watcher/service.py now calls the setup helpers at the top of _run_service_loop (before the registry read, env load, or API health check) and converts every bare sm.LogInfoMsg / LogWarningMsg / LogErrorMsg call to logger.*. The Handler routes them back to the Event Log AND records them in watcher.log, eliminating the silent-drop of logger.* calls from runtime.py, uploader.py, monitor.py, heartbeat.py, and updater.py when running under the SCM.
  • install_service proactively mkdirs WATCHER_LOG_DIR under the elevated install shell to avoid a first-writer ACL race.
  • Wraps the __main__ dispatcher block in a try/except that captures phase A/B crashes (broken venv, missing pywin32, bytecode issues — anything before SvcDoRun runs) into C:\ProgramData\DataHubWatcher\service-bootstrap.log. Original exception still propagates to the SCM.
  • watcher/src/data_hub_watcher/cli.py _setup_file_logging now delegates to the shared helper so CLI and service can't drift on log location, rotation policy, or format.
  • Docs (docs/guides/installing-a-watcher.md, docs/watcher.md, docs/guides/upgrading-the-watcher.md) updated to point at the unified C:\ProgramData\DataHubWatcher\ path, note the single-writer caveat (don't run data-hub-watcher watch while the service is up), and document the DATA_HUB_WATCHER_LOG_LEVEL knob plus the two read-only triage commands (Get-WinEvent and python -m win32serviceutil debug DataHubWatcher).
  • Bumps watcher to 0.2.8 so auto-update rolls this out to the lab PCs.

Why

Under the current code, three distinct service-startup failure phases each log to a different place — or nowhere:

Phase What runs Where it logged before this PR
A SCM spawns python.exe -m data_hub_watcher.service Nowhere — exit code only
B Module imports + StartServiceCtrlDispatcher Sometimes Event Log under Python Service, otherwise nothing
C SvcDoRun -> _run_service_loop Event Log via sm.LogErrorMsg only. Any logger.* call from library code was dropped on the floor.

The troubleshooting guide already promised operators a ~/.data-hub/watcher.log file, but _setup_file_logging was only ever called from the CLI watch command. And even if it had been called from the service path, the LocalSystem ~ expanded to C:\Windows\System32\config\systemprofile\.data-hub\ — a path most operators don't know to look in. This PR closes both gaps so a service that crashes on start always leaves a usable trail in a discoverable location.

Test plan

  • uv run pytest watcher/tests -q — 386 unit tests pass (332 before, +21 new in test_logging_setup.py, the rest existing — no regressions).
  • make check-all — ruff, pyright, prettier, eslint, tsc all green.
  • On a real lab PC: data-hub-watcher service reinstall and confirm C:\ProgramData\DataHubWatcher\watcher.log accumulates records on a successful boot.
  • On a deliberately broken venv (e.g. delete pywin32 site-packages), confirm service-bootstrap.log captures the import-time traceback in the same directory.
  • Add DATA_HUB_WATCHER_LOG_LEVEL=DEBUG to the service's .env file, restart the service, and confirm DEBUG records appear in both the file log and Event Viewer.
  • Confirm a non-elevated operator account can Get-Content C:\ProgramData\DataHubWatcher\watcher.log -Tail 20 -Wait while the service is writing.

The service entry point never called the CLI's file-logging setup, so
every logger.* call from runtime/uploader/monitor/heartbeat/updater was
silently dropped under the SCM — and any crash before SvcDoRun left
nothing on disk to triage. Extracts a shared logging_setup module with
a rotating file handler and a servicemanager-mirroring Handler, calls
both at the top of _run_service_loop, and wraps the dispatcher entry
point in a bootstrap try/except that captures pre-dispatcher crashes
to service-bootstrap.log. Adds a DATA_HUB_WATCHER_LOG_LEVEL env var so
operators can flip a stuck lab PC to DEBUG without redeploying, and
documents the LocalSystem log path + win32serviceutil/Event Viewer
triage commands in the installing-a-watcher guide.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
data-hub Ready Ready Preview, Comment May 13, 2026 10:07pm

Request Review

@wasimxyz
wasimxyz marked this pull request as ready for review May 13, 2026 21:38
@wasimxyz wasimxyz self-assigned this May 13, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
…cher

Both entry points previously computed DEFAULT_CONFIG_DIR / watcher.log,
but `~` resolves against the calling account — so the CLI under the
operator wrote to C:\Users\<op>\.data-hub\ while the service under
LocalSystem wrote to C:\Windows\System32\config\systemprofile\.data-hub\.
The result was two parallel logs that operators had to know about.

Introduces WATCHER_LOG_DIR that resolves to C:\ProgramData\DataHubWatcher
on Windows (where both accounts have write access by default via the
inherited ACL on ProgramData) and to DEFAULT_CONFIG_DIR on non-Windows
hosts for dev/test parity. logging_setup, the service-path bootstrap
log, and install_service all use the new constant; install_service
proactively mkdirs the location under the elevated install shell to
avoid a first-writer ACL race. Updates docs across watcher.md,
installing-a-watcher.md, and upgrading-the-watcher.md to point at the
unified path, and notes that `data-hub-watcher watch` should not run
alongside the service since they would race on the same rotating file.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Filter SystemExit / KeyboardInterrupt out of the bootstrap log capture
  so clean upgrade restarts don't append misleading tracebacks to
  service-bootstrap.log. Extract the dispatcher startup into a testable
  helper.
- Drop the now-dead `sm` parameter from `_repair_upgrade_worker_if_missing`
  and update its call site + tests.
- Tighten `setup_file_logging` idempotency: compare `os.path.abspath`-shaped
  paths and case-fold only on case-insensitive filesystems (win32, darwin).
- Use a trimmed `EVENT_LOG_FORMAT` for the SCM handler since the Windows
  Event Log already records timestamp + level per entry.
- Guard against an empty `%ProgramData%` env var in `_resolve_watcher_log_dir`
  so the log path never collapses to a relative path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wasimxyz
wasimxyz merged commit f83edb4 into staging May 13, 2026
4 checks passed
@wasimxyz
wasimxyz deleted the cursor/watcher-service-logging-gaps branch May 13, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant