7.0 KiB
7.0 KiB
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
andto
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:
{
"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
:
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
-
Check Network Tab:
- Open DevTools → Network
- Filter by
/api/log
- Perform actions (search, navigate, toggle theme)
- Verify POST requests with correct payloads
-
Test Offline Behavior:
- Open DevTools → Network
- Set throttling to "Offline"
- Perform actions
- Re-enable network
- Verify queued logs are sent
-
Test with curl:
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:
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:
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 ofLogRecord[]
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
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
:
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
istrue
- 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.