Skip to content

Observability & Telemetry

Local logs for debugging plus a switchable, content-free telemetry stream for product metrics, launch diagnostics, and sanitized errors.

Logging

Use createLogger(scope) from electron-log everywhere in the desktop app:

ts
import { createLogger } from '@/lib/logger'
const log = createLogger('Sync')
log.info('pull complete', { count, durationMs })
log.error('pull failed', err)
  • Never use console.*. A pre-commit hook flags it.
  • Logs land in the OS-standard log directory and rotate automatically.
  • Renderer and main process logs are separate files.
  • Important launch, renderer, and main-process errors are mirrored as telemetry events when product telemetry is enabled.

Log Locations

PlatformPath
macOS~/Library/Logs/memrynote/
Windows%USERPROFILE%/AppData/Roaming/memrynote/logs/
Linux~/.config/memrynote/logs/

Telemetry

Telemetry is enabled by default in production builds and off by default in development builds. Users can turn it off via Settings → General → Privacy.

What Ships

Only enums and event metadata:

ts
trackTelemetry('page_viewed', { surface: 'notes', action: 'viewed' })

Recognized surfaces (TelemetrySurface in packages/contracts/telemetry-api):

app, onboarding, vault, notes, journal, tasks, inbox, calendar, search, graph, settings, sync, ai, voice, updater.

What Never Ships

  • Note content
  • Note titles
  • Identifiers (note IDs, task IDs, project IDs)
  • Search queries
  • Tag names
  • File paths

The contract uses string-typed enums for surfaces and actions; arbitrary strings can't sneak through.

Tracking Pattern

All telemetry calls are fire-and-forget — never await:

ts
void trackTelemetry('onboarding_completed', {
  surface: 'onboarding',
  action: 'completed',
  result: 'success'
})

The void makes the call non-blocking and unfailable from the UI's point of view.

Event Categories

CategoryEvents
Surface viewspage_viewed — one per tab open
App lifecycleapp_started, app_backgrounded, app_active_heartbeat, app_update_installed
Onboardingonboarding_started, onboarding_completed
Notesnote_created, note_opened, note_updated (throttled 1/doc/5 min), note_deleted
Journaljournal_opened, journal_updated (throttled 1/doc/5 min)
Searchsearch_opened, search_performed, search_result_opened
Command palettecommand_palette_opened, search_result_opened (palette context)
Settingssetting_changed — surface only, never the value
Agent chatagent_chat_started, agent_chat_message_sent
Sync healthsync_enabled, sync_run_completed, sync_error (counts/status only)
Authsignin_started, signin_succeeded
Diagnosticsapp_log_recorded, app_error_seen, app_launch_phase_completed

PostHog Export

The sync server always writes accepted desktop telemetry batches to Cloudflare Analytics Engine. When POSTHOG_API_KEY and POSTHOG_HOST are configured on the sync server, the same content-free events are mirrored to PostHog's batch endpoint.

PostHog mirroring does not use raw install IDs or session IDs. Desktop telemetry uses a server-HMAC install hash as the PostHog distinct ID so active-install, funnel, and retention insights work without exposing local identifiers. Session IDs and session hashes stay out of PostHog.

Additional PostHog events:

EventSource
app_launch_phase_completedElectron main/renderer startup milestones
app_log_recordedSanitized desktop diagnostic breadcrumbs
app_error_seenRenderer, React boundary, and main errors
server_error_seenSync-server request/background failures
server_log_recordedStructured sync-server diagnostic logs

Account-Linked Identity

When a user is signed in, the desktop telemetry flush includes a verified bearer token. The sync server verifies the token's signature, extracts the account ID, and passes it to PostHog as the distinct_id for the batch. An $identify merge event is also sent to link the anonymous install-hash identity to the account ID so funnels and retention insights stay coherent across sign-in.

  • Signed-out / anonymous: uses the server-HMAC install hash as before.
  • Signed-in: uses the verified account ID. Token signatures are checked but revocation is not (the token is accepted as long as the signature is valid and it is not expired).
  • Auth never blocks telemetry: a missing or invalid bearer falls back to anonymous. Batches are never rejected for auth reasons.

Autosave Event Throttling

note_updated and journal_updated events fired by the autosave path are throttled to at most one emission per document per 5-minute window (in-memory, resets on restart). This prevents high-frequency editor flushes from inflating event counts.

Error Reporting

Desktop error reporting follows the same product telemetry setting. Captured errors include process area, component/source, action, phase, and stable error codes. They do not include note content, titles, file paths, search text, stack traces, or raw exception messages.

Sync-server error reporting is server-side and uses only sanitized routing metadata: method, normalized path, route area, source, action, status code, error type, and error code. Dynamic path segments and query strings are removed before export.

Server Configuration

Set these sync-server variables to enable PostHog mirroring:

bash
POSTHOG_API_KEY=phc_...
POSTHOG_HOST=https://us.i.posthog.com

Use https://eu.i.posthog.com for EU Cloud projects.

Performance

trackTelemetry is debounced and batched. Calls during the first second of startup are deferred until after the vault is open so they never delay first paint. On the sync server, Analytics Engine writes finish before /telemetry/batch responds, while optional PostHog mirroring runs in waitUntil so third-party telemetry cannot block the request path.

Released under the GNU GPL v3.0.