Skip to content

feat: implement structured logging with Pino#1

Open
LucasRoesler wants to merge 4 commits intomainfrom
feat/structured-logging-pino
Open

feat: implement structured logging with Pino#1
LucasRoesler wants to merge 4 commits intomainfrom
feat/structured-logging-pino

Conversation

@LucasRoesler
Copy link
Member

Summary

Replaces console.log calls with Pino structured logging across the server codebase.

Key Features

  • Runtime log level changes: HTTP endpoint (/api/admin/log-level) and Unix signals (SIGUSR1/SIGUSR2)
  • Structured JSON logs: Better for parsing and querying in production
  • Child loggers: Each component has contextual metadata (sessionId, machineId, etc.)
  • Environment-aware output: Pretty-printed in dev, JSON in production
  • Sensitive data redaction: Automatically redacts passwords, tokens, API keys

Changes

  • Migrated 90+ console.log calls across 18 server files to Pino
  • Added server/src/lib/logger.ts - main logger module with child logger tracking
  • Added server/src/web/routes/admin.ts - admin endpoints for log level control
  • Preserved intentional console.log calls for QR code/URL display

Benefits

  • Debug production issues without restart
  • 5-10x faster than Winston/Bunyan
  • journald-friendly JSON output
  • Better observability with structured fields

Add structured JSON logging with runtime log level control to replace
plain console.log throughout the codebase.

## Features

- **Pino logger**: Fast, structured JSON logging (5-10x faster than alternatives)
- **Runtime log level changes**: HTTP endpoint + Unix signal support
  - POST /api/admin/log-level to change level without restart
  - SIGUSR1/SIGUSR2 signals for emergency debugging
- **Child logger tracking**: Global level changes propagate to all children
- **Development mode**: Pretty-printed colorized output
- **Production mode**: JSON to stdout (journald-friendly)
- **Sensitive data redaction**: Automatic removal of passwords/tokens
- **Performance**: Async architecture, non-blocking event loop

## Files Added

- `server/src/lib/logger.ts`: Main logger configuration with child tracking
- `server/src/web/routes/admin.ts`: Admin endpoints for log level control

## Files Modified

- `server/src/sync/syncEngine.ts`: Migrated performResume/spawnWithResume to pino
- `server/src/sync/rpcGateway.ts`: Migrated spawnResumedSession to pino
- `server/src/web/server.ts`: Register admin routes
- `server/package.json`: Add pino + pino-pretty dependencies

## Usage

```typescript
import { logger, createChildLogger } from '@/lib/logger'

// Basic logging
logger.info({ port: 3006 }, 'Server started')
logger.debug({ sessionId }, 'Processing session')

// Child logger with context
const sessionLogger = createChildLogger({ sessionId: 'abc-123' })
sessionLogger.info('Session created')  // Includes sessionId automatically
```

## Runtime Control

```bash
# Change log level via HTTP
curl -X POST http://localhost:3006/api/admin/log-level \
  -H "Authorization: Bearer $CLI_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"level":"debug"}'

# Or via signals (production emergency debugging)
kill -SIGUSR1 $(pidof hapi)  # Enable debug
kill -SIGUSR2 $(pidof hapi)  # Back to info
```

## Migration Status

Phase 1 complete: Critical paths migrated (resume flow)
- ✅ SyncEngine.performResume()
- ✅ SyncEngine.spawnWithResume()
- ✅ RpcGateway.spawnResumedSession()

Next: Migrate remaining console.log calls incrementally

## Dependencies

- pino@10.3.0 (production)
- pino-pretty@13.1.3 (dev dependency for pretty printing)
The pino-pretty transport cannot be dynamically loaded when the code is
compiled into a binary with bun's --compile flag, causing the server to
fail on startup with:

  Error: unable to determine transport target for "pino-pretty"

Solution: Check if running from compiled binary using isBunCompiled() and
disable the transport in that case. In production (compiled), logs will be
plain JSON to stdout which journald captures perfectly.

- Development (not compiled): Pretty-printed colorized logs
- Production (compiled): Raw JSON logs for journald

This ensures the logger works in both development and production
environments.
Migrated 90+ console.log/error/warn calls across 18 server files to use
Pino structured logging with contextual information:

Core components:
- index.ts: Server startup, configuration, tunnel, and shutdown
- syncEngine, rpcGateway: Resume and session management
- messageService: Message pagination and sending

Infrastructure:
- tunnelManager: Tunnel lifecycle and TLS gate
- telegram/bot: Telegram bot lifecycle and notifications
- notificationHub: Permission and ready notifications
- eventPublisher: Event listener error handling

Web routes:
- sessions: Session management and resume endpoints
- messages: Message CRUD operations
- voice: ElevenLabs integration
- version: Version info loading

Configuration:
- cliApiToken: Token generation and validation warnings
- settings: Settings file parsing
- configuration: Config loading and persistence

Storage:
- store/sessions: Session CRUD with debug/info levels
- push/pushService: Web push notification errors

Features:
- Child loggers with component context for better log organization
- Structured data fields for easier log parsing and querying
- Preserved intentional console.log calls for QR code/URL display
- Used appropriate log levels (debug, info, warn, error, fatal)

All server logging now goes through Pino, enabling:
- Runtime log level changes via HTTP endpoint or signals
- JSON output in production (journald-friendly)
- Pretty printing in development
- Automatic sensitive data redaction
@LucasRoesler LucasRoesler force-pushed the feat/structured-logging-pino branch from 8b09b44 to 7649a08 Compare January 26, 2026 13:07
- Remove createChildLogger and childLoggers array to fix memory leak
- Use global logger with inline component context at each call site
- Fix token leakage by using console.log for intentional token display
- Fix TypeScript error in admin routes type signature
- Add validation to setGlobalLogLevel with proper error handling
- Fix previousLevel capture bug (now captured before level change)
- Enable signal handlers (SIGUSR1/2) in all environments
- Fix namespace.test.ts to use valid UUIDs
LucasRoesler added a commit that referenced this pull request Feb 11, 2026
- Fix symlink traversal by using realpath() and lstat() before validation
- Add input sanitization for prefix parameter (reject .. and null bytes)
- Fix case-sensitivity handling to be platform-aware (Windows vs Linux)
- Add early termination to prevent unbounded memory usage (max 100 results)
- Validate base paths configuration on startup (must be absolute, must exist)
- Add maxDepth bounds checking (min 1, max 10)

Addresses security issues #1-7 from code review.

TODO: Path validation still uses homedir instead of configured base paths.
This requires passing allowedBasePaths from server to CLI in RPC params.
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