Skip to content

feat(desktop-main): rotate Winston logs in userData with daily rotation#226

Merged
CoderCoco merged 19 commits into
mainfrom
claude/issue-149-winston-logger-userdata-logs
May 24, 2026
Merged

feat(desktop-main): rotate Winston logs in userData with daily rotation#226
CoderCoco merged 19 commits into
mainfrom
claude/issue-149-winston-logger-userdata-logs

Conversation

@CoderCoco
Copy link
Copy Markdown
Owner

Closes #149

Summary

  • Installs winston-daily-rotate-file ^5.0.0 in @hyveon/desktop-main and refactors logger.ts to a createLogger(logDir) factory that adds a DailyRotateFile transport writing main-YYYY-MM-DD.log under a caller-supplied directory (14-day retention, daily rotation at midnight)
  • main.ts resolves app.getPath('userData') from Electron and calls createLogger(path.join(userData, 'logs')) before bootstrap(), ensuring log files land in the correct OS-specific user data directory on every platform
  • Adds DiagnosticsService (reads last N lines from today's log file, ENOENT-safe) and DiagnosticsController (GET /api/diagnostics/tail, GET /api/diagnostics/path) wired into AppModule with a DIAGNOSTICS_LOG_DIR injection token
  • Adds DiagnosticsPanel React component that fetches api.diagnosticsTail() on mount, polls every 5 seconds, autoscrolls to the bottom, and surfaces loading/error states; mounted in a new "Diagnostics" section on SettingsPage

Implementation notes

  • The logger.ts singleton is exported as export let logger (ESM live binding) so existing import sites (LogsService, etc.) receive the updated instance after main.ts calls createLogger — no refactor of call sites needed
  • winston-daily-rotate-file v5.0.0 is CJS but importable from ESM hosts in Node.js; v5 also ships bundled TypeScript declarations, so no @types/ package is required
  • The DIAGNOSTICS_LOG_DIR AppModule provider uses useFactory: () => path.join(electronApp.getPath('userData'), 'logs') — this call is safe because the factory only runs during Nest bootstrap, which main.ts defers until after createLogger has already resolved the path
  • The preload extension (window.gsd.diagnostics) was intentionally omitted — the web layer uses the same HTTP GET /api/* pattern as every other feature; no IPC bridge is needed
  • diagnosticsLogPath is fetched once on mount only (stable across the component lifetime); diagnosticsTail is re-fetched every 5 seconds

Test plan

  • npm run app:test — 525 tests pass (50 test files, 0 failures)
  • npm run app:lint — 0 errors
  • On macOS/Linux: packaged app writes ~/Library/Application Support/<app>/logs/main-YYYY-MM-DD.log (macOS) or ~/.config/<app>/logs/... (Linux)
  • Settings → Diagnostics page shows last log lines and auto-refreshes every 5 seconds

CoderCoco and others added 9 commits May 23, 2026 14:24
…ency

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…th DailyRotateFile

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fore bootstrap

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h resolution

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…path endpoints

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n AppModule

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vice

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…toscroll

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…icsPanel

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 19:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds daily-rotating file logging to the Electron main process (writing under the OS-specific userData/logs/ directory) and surfaces basic log diagnostics through new /api/diagnostics/* endpoints plus a Settings → Diagnostics UI panel in the web frontend.

Changes:

  • Add winston-daily-rotate-file and refactor the desktop-main logger into a createLogger(logDir) factory that installs a daily-rotating file transport (14-day retention).
  • Initialize the desktop-main logger using Electron’s app.getPath('userData') and wire a new diagnostics controller/service to tail today’s log + expose its path.
  • Add a web DiagnosticsPanel (polling tail every 5s) and mount it in Settings, with associated tests and API client methods.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
package-lock.json Locks winston-daily-rotate-file and its transitive deps.
app/packages/desktop-main/package.json Adds winston-daily-rotate-file dependency to desktop-main.
app/packages/desktop-main/src/logger.ts Refactors logger into console fallback + createLogger(logDir) with DailyRotateFile transport.
app/packages/desktop-main/src/logger.test.ts Tests createLogger transport setup and live-binding singleton reassignment.
app/packages/desktop-main/src/main.ts Initializes logger under userData/logs before bootstrapping Nest microservice.
app/packages/desktop-main/src/main.test.ts Verifies createLogger is called with derived userData/logs path during startup.
app/packages/desktop-main/src/app.module.ts Wires diagnostics controller/service and provides DIAGNOSTICS_LOG_DIR token.
app/packages/desktop-main/src/services/DiagnosticsService.ts Implements today’s log path resolution and tail reading (ENOENT-safe).
app/packages/desktop-main/src/services/DiagnosticsService.test.ts Unit tests for log path and tail behavior.
app/packages/desktop-main/src/controllers/diagnostics.controller.ts Adds /diagnostics/tail and /diagnostics/path endpoints.
app/packages/desktop-main/src/controllers/diagnostics.controller.test.ts Unit tests for diagnostics controller behavior.
app/packages/web/src/api.service.ts Adds api.diagnosticsTail() and api.diagnosticsLogPath() client helpers.
app/packages/web/src/components/DiagnosticsPanel.tsx Adds polling diagnostics UI panel with loading/error states + autoscroll.
app/packages/web/src/components/DiagnosticsPanel.test.tsx Tests DiagnosticsPanel loading, success, error, and polling behavior.
app/packages/web/src/pages/settings.page.tsx Adds a new “Diagnostics” section and mounts DiagnosticsPanel.
app/packages/web/src/pages/settings.page.test.tsx Updates SettingsPage tests to include Diagnostics section/panel.
Comments suppressed due to low confidence (1)

app/packages/desktop-main/src/main.ts:21

  • The Electron runtime guard runs after createLogger(path.join(app.getPath('userData'), 'logs')). In a non-Electron execution (e.g. node dist/main.js), app may be undefined/incorrect and this will throw before the intended readable error is raised. Move the process.versions['electron'] check above any electron API usage (and only call createLogger once the guard passes).
applyFixPath();
createLogger(path.join(app.getPath('userData'), 'logs'));

// ElectronIPCTransport requires ipcMain, which is only available inside an
// Electron main process. Fail fast with a readable message rather than a
// cryptic module-resolution error when someone runs `node dist/main.js`.
if (!process.versions['electron']) {
  throw new Error(
    'desktop-main must run inside an Electron main process. ' +
      'Launch via Electron — running with plain Node is not supported.',
  );

Comment thread app/packages/desktop-main/src/services/DiagnosticsService.ts
Comment thread app/packages/desktop-main/package.json
…ach poll

readTail() previously read the entire log file with readFile() on every
5-second poll. Replaced with fs.open/FileHandle.read to seek directly to
max(0, size - 200 KB) and read only the tail window, capping memory and
I/O regardless of how large the daily log grows.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CoderCoco CoderCoco changed the title feat(desktop-main): Winston logger writing to userData/logs/ with daily rotation feat(desktop-main): rotate Winston logs in userData with daily rotation May 23, 2026
@CoderCoco CoderCoco requested a review from Copilot May 23, 2026 20:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.

Comment thread app/packages/desktop-main/src/app.module.ts Outdated
Comment thread app/packages/desktop-main/src/main.ts Outdated
Comment thread app/packages/desktop-main/src/services/DiagnosticsService.ts Outdated
…tron import

- main.ts: move process.versions['electron'] guard above app.getPath() so a
  friendly error is raised before any Electron API is touched in non-Electron envs
- app.module.ts: remove static `import { app } from 'electron'`; DIAGNOSTICS_LOG_DIR
  factory now uses createRequire lazily, guarded by process.versions['electron'],
  with DIAGNOSTICS_LOG_DIR env-var fallback for non-Electron contexts (test-main)
- DiagnosticsService: use bytesRead from FileHandle.read() to slice the buffer
  before toString(), preventing null-byte corruption on short reads

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Comment thread app/packages/desktop-main/src/logger.ts Outdated
Comment thread app/packages/web/src/components/DiagnosticsPanel.tsx
Split logger formats so Console gets colorized output in dev while
DailyRotateFile uses a plain format with no ANSI escape codes, keeping
log files safe for parsers and the diagnostics tail endpoint.

Guard DiagnosticsPanel fetchData state updates behind the cancellation
flag so in-flight requests resolved after unmount do not attempt to
set state on an unmounted component.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/desktop-main/src/main.ts
Replace the static `import { app } from 'electron'` with a top-level
`await import('electron')` placed after the `process.versions['electron']`
guard. The npm electron package does not export `app` in plain Node
environments, so the static import would silently give `undefined`. The
dynamic import runs only when the guard has confirmed we are inside an
Electron main process, preventing any evaluation failure before the
friendly error message can be thrown.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/web/src/components/DiagnosticsPanel.test.tsx Outdated
…Panel

vi.clearAllTimers() must only be called while fake timers are active.
The polling test already calls vi.useRealTimers() at the end of the
test body, and RTL cleanup handles component unmount for all other
tests, so the global afterEach call is unnecessary.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/desktop-main/src/app.module.ts
…Electron

Falling back to '' caused getTodayLogPath() to return a relative path,
which could accidentally open a same-named file from the process CWD
instead of returning ENOENT. os.tmpdir() is an absolute, writable
directory that is unlikely to contain a matching log file, so readTail()
safely returns [] in non-Electron / test environments.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/web/src/components/DiagnosticsPanel.test.tsx
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/desktop-main/src/services/DiagnosticsService.ts Outdated
…lete first line

When offset > 0, readTail() now reads from offset-1 rather than offset.
The extra byte ensures the first split element is always either '' (offset
landed on a newline) or a partial fragment (offset landed mid-line) —
safe to drop in both cases. Previously, if the offset coincided exactly
with the start of a complete line, that line was incorrectly discarded.

Adds a test asserting the first line at an exact boundary survives.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/desktop-main/src/services/DiagnosticsService.test.ts
Replace hard-coded POSIX separators with node:path so the assertions
are platform-independent on Windows as well as POSIX systems.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread app/packages/desktop-main/src/logger.test.ts
The block had a stale comment claiming it reset module state but its
body was empty. `initialLogger` is captured at static import time
(before any test runs) so isolation is already guaranteed; no reset
hook is needed.

Refs #149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment on lines +76 to +78
it('should poll for new lines every 5 seconds', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });

@CoderCoco CoderCoco merged commit e671f8d into main May 24, 2026
12 checks passed
@CoderCoco CoderCoco deleted the claude/issue-149-winston-logger-userdata-logs branch May 24, 2026 04:38
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.

feat(desktop-main): Winston logger writing to userData/logs/ with daily rotation

2 participants