ObsiViewer/docs/LOGGING/README-logging.md

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 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:

{
  "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

  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:

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 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

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 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.