ObsiViewer/docs/README-logging.md

271 lines
7.0 KiB
Markdown

# ObsiViewer - Frontend Logging System
## 📋 Overview
ObsiViewer implements a robust frontend → backend logging system that tracks user interactions and application lifecycle events. All logs are sent to the `/api/log` endpoint with structured JSON payloads.
## 🎯 Tracked Events
### Application Lifecycle
- **APP_START**: Emitted when the application initializes
- **APP_STOP**: Emitted before page unload or app termination
- **VISIBILITY_CHANGE**: Emitted when page visibility changes (background/foreground)
### Navigation
- **NAVIGATE**: Emitted on every route change with `from` and `to` URLs
### User Actions
- **SEARCH_EXECUTED**: Emitted when user performs a search
- **BOOKMARKS_OPEN**: Emitted when bookmarks view is opened
- **BOOKMARKS_MODIFY**: Emitted when bookmarks are added/updated/deleted
- **GRAPH_VIEW_OPEN**: Emitted when graph view is opened
- **GRAPH_VIEW_CLOSE**: Emitted when graph view is closed
- **GRAPH_VIEW_SETTINGS_CHANGE**: Emitted when graph settings are modified
- **CALENDAR_SEARCH_EXECUTED**: Emitted when calendar search is performed
- **THEME_CHANGE**: Emitted when theme is toggled
## 📐 Log Record Structure
Each log record follows this schema:
```json
{
"ts": "2025-10-05T14:21:33.123Z",
"level": "info",
"app": "ObsiViewer",
"sessionId": "9b2c8f1f-7e2f-4d6f-9f5b-0e3e1c9f7c3a",
"userAgent": "Mozilla/5.0...",
"context": {
"route": "/search?q=test",
"vault": "Main",
"theme": "dark",
"version": "0.0.0"
},
"event": "SEARCH_EXECUTED",
"data": {
"query": "test",
"queryLength": 4
}
}
```
### Fields
- **ts**: ISO 8601 timestamp
- **level**: Log level (`info`, `warn`, `error`)
- **app**: Always `"ObsiViewer"`
- **sessionId**: UUID v4 persisted in sessionStorage
- **userAgent**: Browser user agent string
- **context**: Automatic context (route, theme, vault, version)
- **event**: Event type (see list above)
- **data**: Event-specific metadata (optional)
## ⚙️ Configuration
Configuration is in `src/core/logging/environment.ts`:
```typescript
export const environment = {
production: false,
appVersion: '0.0.0',
logging: {
enabled: true, // Enable/disable logging
endpoint: '/api/log', // Backend endpoint
batchSize: 5, // Batch size (records)
debounceMs: 2000, // Debounce delay (ms)
maxRetries: 5, // Max retry attempts
circuitBreakerThreshold: 5, // Failures before circuit opens
circuitBreakerResetMs: 30000, // Circuit breaker reset time (ms)
},
};
```
## 🔧 Features
### Batching & Debouncing
- Logs are batched (default: 5 records or 2 seconds, whichever comes first)
- Reduces network overhead and backend load
### Retry with Exponential Backoff
- Failed requests are retried up to 5 times
- Backoff delays: 500ms, 1s, 2s, 4s, 8s
### Circuit Breaker
- After 5 consecutive failures, logging pauses for 30 seconds
- Prevents overwhelming the backend during outages
### Offline Support
- Logs are queued in memory and localStorage
- Automatically flushed when connection is restored
- Uses `sendBeacon` for reliable delivery on page unload
### Performance Optimizations
- Uses `requestIdleCallback` when available
- Non-blocking: doesn't interfere with UI interactions
- Data size limit: 5 KB per record (truncated if exceeded)
## 🧪 Testing
### Manual Testing
1. **Check Network Tab**:
- Open DevTools → Network
- Filter by `/api/log`
- Perform actions (search, navigate, toggle theme)
- Verify POST requests with correct payloads
2. **Test Offline Behavior**:
- Open DevTools → Network
- Set throttling to "Offline"
- Perform actions
- Re-enable network
- Verify queued logs are sent
3. **Test with curl**:
```bash
curl -X POST http://localhost:4200/api/log \
-H "Content-Type: application/json" \
-d '{
"ts": "2025-10-05T14:21:33.123Z",
"level": "info",
"app": "ObsiViewer",
"sessionId": "test-session",
"event": "APP_START",
"data": {}
}'
```
### Unit Tests
Run tests with:
```bash
npm test
```
Tests cover:
- Log record construction
- Batch and debounce logic
- Retry mechanism
- Circuit breaker
- Router and visibility listeners
### E2E Tests
Run E2E tests with:
```bash
npm run test:e2e
```
E2E tests verify:
- APP_START on page load
- NAVIGATE on route changes
- SEARCH_EXECUTED on search
- GRAPH_VIEW_OPEN on graph view
- Offline queue and flush
## 🛠️ Architecture
```
src/core/logging/
├── log.model.ts # Types and interfaces
├── log.service.ts # Main logging service
├── log.sender.ts # HTTP sender (pure function)
├── log.router-listener.ts # Router event listener
├── log.visibility-listener.ts # Visibility event listener
├── environment.ts # Configuration
└── index.ts # Public API
```
### Key Components
- **LogService**: Singleton service managing queue, batching, retry, and circuit breaker
- **sendBatch**: Pure function for HTTP POST to backend
- **Router Listener**: Tracks navigation events
- **Visibility Listener**: Tracks app lifecycle (visibility, beforeunload, pagehide)
## 🔒 Security & Privacy
- **No note content**: Only metadata (paths, titles, counts) is logged
- **Data sanitization**: Large objects are truncated
- **Safe serialization**: Prevents circular references and functions
## 📊 Backend Requirements
The backend must implement:
**Endpoint**: `POST /api/log`
**Request**:
- Content-Type: `application/json`
- Body: Single `LogRecord` or array of `LogRecord[]`
**Response**:
- Status: `2xx` (any 2xx status indicates success)
- Body: `{"ok": true}` (optional, ignored by client)
**Error Handling**:
- Non-2xx responses trigger retry logic
- Network errors trigger retry logic
## 🚀 Usage
### Logging Custom Events
```typescript
import { LogService } from './core/logging/log.service';
@Component({...})
export class MyComponent {
private logService = inject(LogService);
onCustomAction(): void {
this.logService.log('CUSTOM_EVENT', {
action: 'button_click',
buttonId: 'submit',
});
}
}
```
### Disabling Logging
Set `logging.enabled` to `false` in `environment.ts`:
```typescript
export const environment = {
logging: {
enabled: false, // Disable all logging
// ...
},
};
```
## 📈 Monitoring
Monitor logs on the backend to:
- Track user engagement (searches, navigation patterns)
- Identify popular features (graph view, bookmarks)
- Detect errors and performance issues
- Analyze session duration and activity
## 🐛 Troubleshooting
### Logs not appearing in Network tab
- Check `environment.logging.enabled` is `true`
- Verify `/api/log` endpoint exists and returns 2xx
- Check browser console for errors
### Circuit breaker opened
- Check backend availability
- Verify endpoint returns 2xx for valid requests
- Check network connectivity
### Logs not sent on page unload
- Modern browsers may block async requests on unload
- `sendBeacon` is used as fallback (best effort)
- Some logs may be lost on hard refresh or forced close
## 📝 License
Same as ObsiViewer project.