diff --git a/web/FRONTEND_LOGGING_UPDATE_PROMPT.md b/web/FRONTEND_LOGGING_UPDATE_PROMPT.md new file mode 100644 index 00000000..67774925 --- /dev/null +++ b/web/FRONTEND_LOGGING_UPDATE_PROMPT.md @@ -0,0 +1,87 @@ +# Frontend Logging Update Prompt + +## Context +We've just implemented a structured logging system for the server-side code and created endpoints for frontend logging. Now we need to update all frontend components to use this system instead of console.log/error/warn. + +## What's Already Done +1. **Server-side logging**: All server files now use the structured logger with proper log levels +2. **Log endpoints created**: + - `POST /api/logs/client` - Frontend can send logs to this endpoint + - The endpoint expects: `{ level: 'log'|'warn'|'error'|'debug', module: string, args: unknown[] }` +3. **Log viewer component**: Available at `/logs.html` to view all logs (both server and client) +4. **Style guide**: Created in `LOGGING_STYLE_GUIDE.md` with rules: + - No colors in error/warn + - Colors only in logger.log (green=success, yellow=warning, blue=info, gray=metadata) + - No prefixes or tags + - Lowercase start, no periods + - Always include error objects + +## Your Task +Replace all `console.log`, `console.error`, and `console.warn` calls in frontend code (`src/client/`) with API calls to the logging endpoint. + +### Step 1: Create a Frontend Logger Utility +Create `/src/client/utils/logger.ts` with: +```typescript +export function createLogger(moduleName: string) { + const sendLog = async (level: string, ...args: unknown[]) => { + try { + await fetch('/api/logs/client', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ level, module: moduleName, args }) + }); + } catch { + // Fallback to console on network error + console[level]?.(...args) || console.log(...args); + } + }; + + return { + log: (...args: unknown[]) => sendLog('log', ...args), + warn: (...args: unknown[]) => sendLog('warn', ...args), + error: (...args: unknown[]) => sendLog('error', ...args), + debug: (...args: unknown[]) => sendLog('debug', ...args) + }; +} +``` + +### Step 2: Find All Console Calls +Use ripgrep to find all console.log/error/warn in frontend: +```bash +rg "console\.(log|error|warn)" src/client/ --type ts +``` + +### Step 3: Update Each File +For each file with console calls: +1. Import the logger: `import { createLogger } from '../utils/logger.js';` +2. Create logger instance: `const logger = createLogger('component-name');` +3. Replace console calls following the style guide +4. Remove superfluous logs (like "View transition ready") +5. Ensure essential logs use appropriate levels + +### Step 4: Special Cases +- For error boundaries and critical failures, you may keep console.error as fallback +- WebSocket/SSE error handling should use logger but can keep console as fallback +- Remove all debug console.logs that were for development (like view transition logs) + +### Step 5: Test +After updates, verify: +1. Navigate to `/logs.html` +2. Perform actions in the app +3. Confirm client logs appear with `CLIENT:` prefix +4. Check that log levels and messages follow the style guide + +## Files to Update (based on previous analysis) +Key files that likely have console calls: +- `/src/client/app.ts` +- `/src/client/services/terminal-connection.ts` +- `/src/client/services/sse-client.ts` +- `/src/client/services/websocket-client.ts` +- `/src/client/components/*.ts` (all component files) +- Any other files in `/src/client/` + +## Remember +- Module names should be descriptive (e.g., 'terminal-connection', 'session-list', 'app') +- Follow the same style guide as server logs (no prefixes, lowercase start) +- Essential logs only - remove debugging/development logs +- Include error objects when logging errors \ No newline at end of file diff --git a/web/spec.md b/web/spec.md index 5f012f75..537648d0 100644 --- a/web/spec.md +++ b/web/spec.md @@ -112,6 +112,14 @@ web/ - `POST /api/remotes/register` (30-52): Remote registration - `DELETE /api/remotes/:id` (55-69): Unregister remote +#### Logs (`logs.ts`) +- `POST /api/logs/client` (24-56): Client-side log submission + - Accepts: `{ level, module, args }` + - Prefixes module with `CLIENT:` for identification +- `GET /api/logs/raw` (59-76): Stream raw log file +- `GET /api/logs/info` (79-104): Log file metadata +- `DELETE /api/logs/clear` (107-121): Clear log file + ### Binary Buffer Protocol **Note**: "Buffer" refers to the current terminal display state (visible viewport) without scrollback history - just what's currently shown at the bottom of the terminal. This is used for rendering terminal previews in the session list. @@ -205,6 +213,23 @@ Monitors all terminal sessions for activity by watching `stream-out` file change - Registration with HQ (29-58) - Unregister on shutdown (60-72) +### Logging Infrastructure (`utils/logger.ts`) + +#### Server Logger +- Unified logging with file and console output +- Log levels: log, warn, error, debug +- File output to `~/.vibetunnel/log.txt` +- Formatted timestamps and module names +- Debug mode toggle +- Style guide compliance (see LOGGING_STYLE_GUIDE.md) + +#### Client Logger (`client/utils/logger.ts`) +- Mirrors server logger interface +- Logs to browser console +- Sends logs to `/api/logs/client` endpoint +- Objects formatted as JSON before sending +- Integrates with server logging system + ## Client Architecture (`src/client/`) ### Core Components @@ -318,6 +343,22 @@ Modal file browser for navigating the filesystem and selecting files/directories - `directory-selected` - When a directory is selected in 'select' mode (detail: string) - `browser-cancel` - When the browser is cancelled or closed +##### `log-viewer.ts` - System log viewer (1-432) +Real-time log viewer with filtering and search capabilities. +- SSE-style polling every 2 seconds +- Client/server log distinction +- Log level filtering +- Relative timestamps +- Mobile-responsive layout +- Mac-style auto-hiding scrollbars +- **Features**: + - Filter by log level (error, warn, log, debug) + - Toggle client/server logs + - Search/filter by text + - Auto-scroll (smart - only when near bottom) + - Download logs + - Clear logs + ##### Icon Components - `vibe-logo.ts` - Application logo - `terminal-icon.ts` - Terminal icon @@ -396,6 +437,7 @@ npx tsx src/fwd.ts [--session-id ] [args...] ### Configuration - Environment: `PORT`, `VIBETUNNEL_USERNAME`, `VIBETUNNEL_PASSWORD` - CLI: `--port`, `--username`, `--password`, `--hq`, `--hq-url`, `--name` +- Express static: `.html` extension handling for clean URLs ### Protocols - REST API: Session CRUD, terminal I/O, activity status @@ -419,6 +461,13 @@ Each session has a directory in `~/.vibetunnel/control/[sessionId]/` containing: - Simplified fwd.ts - control pipe and stdin forwarding handled by PTY Manager - Added proper TypeScript types throughout (removed all "as any" assertions) - Cleaned up logging and added colorful output messages using chalk +- **Unified logging infrastructure**: + - Server-wide adoption of structured logger + - Client-side logger with server integration + - Centralized log viewer at `/logs` + - Consistent style guide (LOGGING_STYLE_GUIDE.md) +- **Express enhancements**: + - Auto `.html` extension resolution for static files ### Build System - `npm run dev`: Auto-rebuilds TypeScript diff --git a/web/src/client/app.ts b/web/src/client/app.ts index 9bbfd4e7..2feee8cc 100644 --- a/web/src/client/app.ts +++ b/web/src/client/app.ts @@ -5,6 +5,9 @@ import { keyed } from 'lit/directives/keyed.js'; // Import shared types import type { Session } from '../shared/types.js'; +// Import logger +import { createLogger } from './utils/logger.js'; + // Import components import './components/app-header.js'; import './components/session-create-form.js'; @@ -12,9 +15,12 @@ import './components/session-list.js'; import './components/session-view.js'; import './components/session-card.js'; import './components/file-browser.js'; +import './components/log-viewer.js'; import type { SessionCard } from './components/session-card.js'; +const logger = createLogger('app'); + @customElement('vibetunnel-app') export class VibeTunnelApp extends LitElement { // Disable shadow DOM to use Tailwind @@ -129,7 +135,7 @@ export class VibeTunnelApp extends LitElement { this.showError('Failed to load sessions'); } } catch (error) { - console.error('Error loading sessions:', error); + logger.error('error loading sessions:', error); this.showError('Failed to load sessions'); } finally { this.loading = false; @@ -189,12 +195,12 @@ export class VibeTunnelApp extends LitElement { } // If we get here, session creation might have failed - console.log('Session not found after all attempts'); + logger.log('session not found after all attempts'); this.showError('Session created but could not be found. Please refresh.'); } private handleSessionKilled(e: CustomEvent) { - console.log('Session killed:', e.detail); + logger.log(`session ${e.detail} killed`); this.loadSessions(); // Refresh the list } @@ -239,9 +245,9 @@ export class VibeTunnelApp extends LitElement { // Check if View Transitions API is supported if ('startViewTransition' in document && typeof document.startViewTransition === 'function') { // Debug: Check what elements have view-transition-name before transition - console.log('Before transition - elements with view-transition-name:'); + logger.debug('before transition - elements with view-transition-name:'); document.querySelectorAll('[style*="view-transition-name"]').forEach((el) => { - console.log('Element:', el, 'Style:', el.getAttribute('style')); + logger.debug('element:', el, 'style:', el.getAttribute('style')); }); // Use View Transitions API for smooth animation @@ -255,19 +261,19 @@ export class VibeTunnelApp extends LitElement { await this.updateComplete; // Debug: Check what elements have view-transition-name after transition - console.log('After transition - elements with view-transition-name:'); + logger.debug('after transition - elements with view-transition-name:'); document.querySelectorAll('[style*="view-transition-name"]').forEach((el) => { - console.log('Element:', el, 'Style:', el.getAttribute('style')); + logger.debug('element:', el, 'style:', el.getAttribute('style')); }); }); // Log if transition is ready transition.ready .then(() => { - console.log('View transition ready'); + logger.debug('view transition ready'); }) .catch((err) => { - console.error('View transition failed:', err); + logger.error('view transition failed:', err); }); } else { // Fallback for browsers without View Transitions support @@ -349,7 +355,7 @@ export class VibeTunnelApp extends LitElement { const saved = localStorage.getItem('hideExitedSessions'); return saved !== null ? saved === 'true' : true; // Default to true if not set } catch (error) { - console.error('Error loading hideExited state:', error); + logger.error('error loading hideExited state:', error); return true; // Default to true on error } } @@ -358,7 +364,7 @@ export class VibeTunnelApp extends LitElement { try { localStorage.setItem('hideExitedSessions', String(value)); } catch (error) { - console.error('Error saving hideExited state:', error); + logger.error('error saving hideExited state:', error); } } @@ -416,7 +422,7 @@ export class VibeTunnelApp extends LitElement { } }; } catch (error) { - console.log('Error setting up hot reload:', error); + logger.log('error setting up hot reload:', error); } } } diff --git a/web/src/client/components/app-header.ts b/web/src/client/components/app-header.ts index 00616c25..549358e3 100644 --- a/web/src/client/components/app-header.ts +++ b/web/src/client/components/app-header.ts @@ -78,10 +78,14 @@ export class AppHeader extends LitElement {
-

+ - VibeTunnel -

+ VibeTunnel +

${runningSessions.length} ${runningSessions.length === 1 ? 'session' : 'sessions'} ${exitedSessions.length > 0 ? `• ${exitedSessions.length} exited` : ''} @@ -153,16 +157,22 @@ export class AppHeader extends LitElement {