Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions LLMD/resources/live-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type { CounterDemo as _Client } from '@client/src/live/CounterDemo'
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
static componentName = 'LiveCounter'
static publicActions = ['increment', 'decrement', 'reset'] as const // 🔒 REQUIRED
static logging = ['lifecycle', 'messages'] as const // Per-component logging (optional)
// static logging = ['lifecycle', 'messages'] as const // Console logging (optional, prefer DEBUG_LIVE)
static defaultState = {
count: 0
}
Expand Down Expand Up @@ -609,7 +609,7 @@ Each server file contains:
- `static componentName` - Component identifier
- `static publicActions` - **REQUIRED** whitelist of client-callable methods
- `static defaultState` - Initial state object
- `static logging` - Per-component log control (optional, see [Live Logging](./live-logging.md))
- `static logging` - Per-component console log control (optional, prefer `DEBUG_LIVE=true` for debug panel — see [Live Logging](./live-logging.md))
- Component class extending `LiveComponent`
- Client link via `import type { Demo as _Client }`

Expand Down
128 changes: 95 additions & 33 deletions LLMD/resources/live-logging.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,100 @@
# Live Logging

**Version:** 1.12.0 | **Updated:** 2025-02-12
**Version:** 1.12.1 | **Updated:** 2025-02-22

## Quick Facts

- Per-component logging control — silent by default
- Opt-in via `static logging` property on LiveComponent subclasses
- Two output channels: **console** (`LIVE_LOGGING`) and **debug panel** (`DEBUG_LIVE`)
- Both off by default — opt-in only
- 6 categories: `lifecycle`, `messages`, `state`, `performance`, `rooms`, `websocket`
- Global (non-component) logs controlled by `LIVE_LOGGING` env var
- `console.error` always visible regardless of config
- All `liveLog`/`liveWarn` calls are forwarded to the Live Debugger as `LOG` events when `DEBUG_LIVE=true`

## Usage
## Two Logging Channels

### Enable Logging on a Component
| Channel | Env Var | Default | Purpose |
|---------|---------|---------|---------|
| **Console** | `LIVE_LOGGING` | `false` | Server terminal output |
| **Debug Panel** | `DEBUG_LIVE` | `false` | Live Debugger WebSocket stream |

The debug panel receives **all** `liveLog`/`liveWarn` calls as `LOG` events (with `category`, `level`, `message`, and `details`) regardless of the `LIVE_LOGGING` console setting. This keeps the console clean while making everything visible in the debug panel.

### Recommended Workflow

- **Normal development**: both off — clean console, no debug overhead
- **Debugging live components**: `DEBUG_LIVE=true` — open the debug panel at `/api/live/debug/ws`
- **Quick console debugging**: `LIVE_LOGGING=lifecycle,state` — targeted categories to console
- **Per-component debugging**: `static logging = true` on the specific component class

## Console Logging

### Per-Component (static logging)

```typescript
// app/server/live/LiveCounter.ts
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
static componentName = 'LiveCounter'
// app/server/live/LiveChat.ts
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
static componentName = 'LiveChat'

// ✅ All categories
// ✅ All categories to console
static logging = true

// ✅ Specific categories only
static logging = ['lifecycle', 'messages', 'state', 'rooms'] as const
static logging = ['lifecycle', 'rooms'] as const

// ✅ Silent (default — omit property or set false)
// static logging = false
// No static logging needed
}
```

### Global Logs (Non-Component)
### Global (LIVE_LOGGING env var)

Logs not tied to a specific component (room cleanup, key rotation, etc.) are controlled by the `LIVE_LOGGING` env var:
Logs not tied to a specific component (connection cleanup, key rotation, etc.):

```bash
# .env
LIVE_LOGGING=true # All global logs
LIVE_LOGGING=true # All global logs to console
LIVE_LOGGING=lifecycle,rooms # Specific categories only
# (unset or 'false') # Silent (default)
```

## Debug Panel (DEBUG_LIVE)

When `DEBUG_LIVE=true`, all `liveLog`/`liveWarn` calls emit `LOG` events to the Live Debugger, regardless of `LIVE_LOGGING` or `static logging` settings.

```bash
# .env
DEBUG_LIVE=true # Enable debug panel events
```

Each `LOG` event contains:

```typescript
{
type: 'LOG',
componentId: string | null,
componentName: null,
data: {
category: 'lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket',
level: 'info' | 'warn',
message: string,
details?: unknown // Extra args passed to liveLog/liveWarn
}
}
```

The debug panel also receives all other debug events (`COMPONENT_MOUNT`, `STATE_CHANGE`, `ACTION_CALL`, etc.) — see [Live Components](./live-components.md) for the full event list.

## Categories

| Category | What It Logs |
|----------|-------------|
| `lifecycle` | Mount, unmount, rehydration, recovery, migration |
| `messages` | Received/sent WebSocket messages, file uploads |
| `messages` | Received/sent WebSocket messages, file uploads, queue operations |
| `state` | Signing, backup, compression, encryption, validation |
| `performance` | Monitoring init, alerts, optimization suggestions |
| `rooms` | Room create/join/leave, emit, broadcast |
| `websocket` | Connection open/close, auth |
| `websocket` | Connection open/close/cleanup, pool management, auth |

## Type Definition

Expand All @@ -69,12 +113,12 @@ static logging = ['lifecycle', 'messages'] as const

## API (Framework Internal)

These functions are used by the framework — app developers only need `static logging`:
These functions are used by the framework — app developers only need `static logging` or env vars:

```typescript
import { liveLog, liveWarn, registerComponentLogging, unregisterComponentLogging } from '@core/server/live'

// Log gated by component config
// Log gated by component config (console) + always forwarded to debug panel
liveLog('lifecycle', componentId, '🚀 Mounted component')
liveLog('rooms', componentId, `📡 Joined room '${roomId}'`)

Expand All @@ -89,70 +133,88 @@ unregisterComponentLogging(componentId)
## How It Works

1. **Mount**: `ComponentRegistry` reads `static logging` from the class and calls `registerComponentLogging(componentId, config)`
2. **Runtime**: All `liveLog()`/`liveWarn()` calls check the registry before emitting
2. **Runtime**: All `liveLog()`/`liveWarn()` calls:
- Forward to the Live Debugger as `LOG` events (when `DEBUG_LIVE=true`)
- Check the registry before emitting to console (when `LIVE_LOGGING` or `static logging` is active)
3. **Unmount**: `unregisterComponentLogging(componentId)` removes the entry
4. **Global logs**: Fall back to `LIVE_LOGGING` env var when `componentId` is `null`

## Examples

### Debug a Specific Component
### Debug via Panel (Recommended)

```bash
# .env
DEBUG_LIVE=true
# No LIVE_LOGGING needed — console stays clean
```

Open the debug panel WebSocket at `/api/live/debug/ws` to see all events in real-time.

### Debug a Specific Component (Console)

```typescript
// Only this component will show logs
// Only this component will show console logs
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
static componentName = 'LiveChat'
static logging = true // See everything for this component
static logging = true // See everything for this component in console
}

// All other components remain silent
// All other components remain silent in console
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
static componentName = 'LiveCounter'
// No static logging → silent
// No static logging → silent in console
}
```

### Monitor Only Room Activity
### Monitor Only Room Activity (Console)

```typescript
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
static componentName = 'LiveChat'
static logging = ['rooms'] as const // Only room events
static logging = ['rooms'] as const // Only room events in console
}
```

### Production: Silent Everywhere

```bash
# .env (no LIVE_LOGGING set)
# All components without static logging → silent
# Components with static logging still log (remove for production)
# .env (no LIVE_LOGGING, no DEBUG_LIVE)
# Console: silent
# Debug panel: disabled
```

## Files Reference

| File | Purpose |
|------|---------|
| `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic |
| `core/server/live/ComponentRegistry.ts` | Reads `static logging` on mount/unmount |
| `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic, debugger forwarding |
| `core/server/live/LiveDebugger.ts` | Debug event bus, `LOG` event type, debug client management |
| `core/server/live/ComponentRegistry.ts` | Reads `static logging` on mount/unmount, uses `liveLog` |
| `core/server/live/websocket-plugin.ts` | Uses `liveLog` for WebSocket events |
| `core/server/live/WebSocketConnectionManager.ts` | Uses `liveLog`/`liveWarn` for connection pool management |
| `core/server/live/FileUploadManager.ts` | Uses `liveLog`/`liveWarn` for upload operations |
| `core/server/live/StateSignature.ts` | Uses `liveLog`/`liveWarn` for state operations |
| `core/server/live/LiveRoomManager.ts` | Uses `liveLog` for room lifecycle |
| `core/server/live/LiveComponentPerformanceMonitor.ts` | Uses `liveLog`/`liveWarn` for perf |
| `config/system/runtime.config.ts` | `DEBUG_LIVE` env var config |
| `core/types/types.ts` | `LiveComponent` base class with `static logging` property |

## Critical Rules

**ALWAYS:**
- Use `as const` on logging arrays for type safety
- Keep components silent by default in production
- Keep components silent by default (no `static logging`)
- Use `DEBUG_LIVE=true` for debugging instead of `static logging` on components
- Use specific categories instead of `true` when possible

**NEVER:**
- Use `console.log` directly in Live Component code — use `liveLog()`
- Forget that `console.error` is always visible (not gated)
- Enable `LIVE_LOGGING` or `DEBUG_LIVE` in production

## Related

- [Live Components](./live-components.md) - Base component system
- [Live Rooms](./live-rooms.md) - Room system (logged under `rooms` category)
- [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` reference
- [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` and `DEBUG_LIVE` reference
10 changes: 5 additions & 5 deletions app/client/src/live/CounterDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function CounterDemo() {
}

return (
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full flex flex-col">
<h2 className="text-2xl font-bold text-white mb-2 text-center">
{title}
</h2>
Expand All @@ -74,7 +74,7 @@ export function CounterDemo() {
</div>
</div>

<div className="text-center mb-8">
<div className="text-center mb-8 flex-1 flex flex-col justify-center">
<div className="text-8xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
{counter.$state.count}
</div>
Expand Down Expand Up @@ -139,15 +139,15 @@ export function CounterDemo() {
}

return (
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full flex flex-col">
<h2 className="text-2xl font-bold text-white mb-2 text-center">
Contador Local (sem Room)
</h2>
<p className="text-gray-400 text-sm text-center mb-6">
Estado local do componente, sem eventos de sala.
</p>

<div className="text-center mb-8">
<div className="text-center mb-8 flex-1 flex flex-col justify-center">
<div className="text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
{localCounter.$state.count}
</div>
Expand Down Expand Up @@ -189,7 +189,7 @@ export function CounterDemo() {
}

return (
<div className="flex flex-col lg:flex-row gap-6 items-start justify-center">
<div className="flex flex-col lg:flex-row gap-6 items-stretch justify-center">
{renderLocalCounter()}
{renderCounter(
'Contador Isolado',
Expand Down
1 change: 0 additions & 1 deletion app/server/live/LiveCounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type { CounterDemo as _Client } from '@client/src/live/CounterDemo'
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
static componentName = 'LiveCounter'
static publicActions = ['increment', 'decrement', 'reset'] as const
static logging = ['lifecycle', 'messages', 'state', 'rooms'] as const
static defaultState = {
count: 0,
lastUpdatedBy: null as string | null,
Expand Down
7 changes: 2 additions & 5 deletions config/system/runtime.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ export const appRuntimeConfig = defineReactiveConfig({
enableDebugMode: config.boolean('DEBUG', false),

// Live Component Debugger
// Defaults to true in development, false otherwise
debugLive: config.boolean(
'DEBUG_LIVE',
(process.env.NODE_ENV || 'development') === 'development'
),
// Defaults to false — opt-in via DEBUG_LIVE=true
debugLive: config.boolean('DEBUG_LIVE', false),

// Rate limiting
rateLimitEnabled: config.boolean('RATE_LIMIT_ENABLED', true),
Expand Down
Loading
Loading