Skip to content

Telemetry & Monitoring

Ghost includes several self-monitoring systems that let developers and operators understand what’s happening inside the application. Think of these as Ghost’s vital signs — memory usage, database size, uptime, and optional anonymous usage tracking. None of these systems affect Ghost’s core functionality; they’re observability layers that can be enabled or disabled independently.


The resource monitor provides a real-time snapshot of Ghost’s internal health — how much memory it’s using, how large the database has grown, how many goroutines (Go’s lightweight threads) are active, and how long the server has been running.

GET /api/v1/monitor

Requires authentication (bearer token). Returns a JSON object with both machine-readable values (bytes, seconds) and human-readable versions (formatted strings like “52.3 MB” or “3d 12h”).

{
"db_size_bytes": 52428800,
"db_size_human": "50 MB",
"mem_alloc_bytes": 134217728,
"mem_alloc_human": "128 MB",
"mem_sys_bytes": 268435456,
"goroutines": 42,
"total_flows": 15000,
"uptime_seconds": 7200,
"uptime_human": "2h 0m",
"num_gc": 156
}
FieldTypeDescription
db_size_bytesint64SQLite database size in bytes, calculated as page_count × page_size using SQLite PRAGMAs. This is the logical database size, not the file system size — it does not include the WAL (Write-Ahead Log) file, which can temporarily add to the on-disk footprint.
db_size_humanstringHuman-readable database size (e.g., “52.3 MB”, “1.2 GB”). Uses 1024-based units (KiB/MiB/GiB) with 1 decimal place. Integer values drop the decimal (e.g., “64 MB” not “64.0 MB”).
mem_alloc_bytesuint64Bytes of heap memory currently allocated by the Go runtime. This is the “active” memory — objects that are currently in use. It does not include memory that was allocated and then freed (that’s tracked by the garbage collector).
mem_alloc_humanstringHuman-readable allocated memory (same formatting as db_size_human).
mem_sys_bytesuint64Total bytes of memory obtained from the operating system by the Go runtime. This is always larger than mem_alloc_bytes because it includes memory held by the garbage collector, goroutine stacks, and internal runtime structures.
goroutinesintNumber of active goroutines. In a healthy Ghost instance, this is typically 30-60 (background workers, WebSocket clients, active agent tasks). A rapidly growing number could indicate a goroutine leak.
total_flowsint64Total number of captured HTTP flows across all sessions, from SELECT COUNT(*) FROM flows.
uptime_secondsint64Seconds since the API server was created. Resets when Ghost restarts.
uptime_humanstringHuman-readable uptime using three tiers: over 24 hours shows "Xd Yh" (days and hours), 1-24 hours shows "Xh Ym" (hours and minutes), under 1 hour shows "Xm" (minutes only).
num_gcuint32Total garbage collection cycles completed since startup. Go’s garbage collector runs automatically when heap pressure increases. A high number is normal and doesn’t indicate a problem.

Error handling: If the database size or flow count queries fail, the monitor endpoint still returns HTTP 200 with zero values for the failed fields and logs a warning. The endpoint never fails entirely.

The frontend polls the monitor endpoint every 30 seconds and displays a compact summary in the bottom status bar:

Visible text: {db_size_human} / {mem_alloc_human} — shows only database size and allocated memory, separated by a slash.

Tooltip (hover): Shows 4 fields: DB: {db_size_human} | Mem: {mem_alloc_human} | Goroutines: {goroutines} | Uptime: {uptime_human}.

The indicator is hidden on small screens and styled in monospace font at 10px to be unobtrusive. If the monitor fetch fails, the indicator silently shows nothing (no error displayed).


GET /health

Returns {"status": "ok"} with HTTP 200. No authentication required — this endpoint is intentionally public so it can be used by external monitoring systems.

Purpose: This is a simple liveness check. It confirms that the Go HTTP server is running and responsive. It does not check database connectivity, proxy status, or any other subsystem.

Consumers:

  • External monitoring tools and load balancers that need a simple up/down check
  • The frontend has an api.health() method available but doesn’t actively call it in current code

Note: The Tauri desktop shell’s safety net polls /api/v1/proxy/status (not /health) every 3 seconds to detect sidecar crashes. The proxy status endpoint provides richer information (proxy state, system proxy state) that the safety net needs for its system proxy recovery logic.


Ghost can send anonymous usage data to help the development team understand how the tool is being used — which features are popular, how long sessions last, and what platforms people run it on. This is completely optional and disabled by default (no endpoint is configured).

SettingDefaultConfig KeyDescription
Enabledtrue (when not set)telemetry.telemetry_enabledMaster switch. This is a pointer type (*bool) in the config — nil (not present in TOML) is treated as true. Set explicitly to false to disable.
Endpoint"" (empty = disabled)telemetry.telemetry_endpointURL to send heartbeats to. Must be a valid HTTP or HTTPS URL. When empty, no heartbeats are sent regardless of the enabled setting.

Both conditions must be met for telemetry to actually send data: telemetry_enabled must be true AND telemetry_endpoint must be non-empty.

Ghost sends exactly two events per session:

EventWhenPurpose
session_startWhen the app loads and settings are fetchedTells the team a user opened Ghost
session_endWhen the browser tab is closing (beforeunload event)Tells the team the session ended, includes duration

There is no periodic heartbeat — only these two events per app session.

{
"install_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ghost_version": "0.1.0",
"os": "macOS",
"event": "session_start",
"session_duration_min": 0,
"timestamp": "2024-06-15T14:30:00.000Z"
}
FieldDescription
install_idA random UUID v4 generated via crypto.randomUUID() on first use and stored in localStorage permanently. This is completely anonymous — there is no way to map this ID to a person, machine, or organization. It exists solely to distinguish “one user opened the app 5 times” from “5 different users opened the app once.”
ghost_versionThe app version (e.g., "0.1.0").
osThe operating system, detected via navigator.userAgentData.platform (modern browsers) or navigator.platform (fallback).
eventEither "session_start" or "session_end".
session_duration_minSession length in minutes (rounded). Only populated for session_end events.
timestampISO 8601 timestamp of when the event was sent.

What is NOT sent: No traffic data, URLs, hostnames, request/response content, API keys, usernames, IP addresses, or any personally identifiable information.

Feature Usage Tracking (Defined but Not Active)

Section titled “Feature Usage Tracking (Defined but Not Active)”

The telemetry system defines a FeatureUsage interface with 12 boolean flags tracking which Ghost features were used during a session:

proxy, system_proxy, agent_chat, inspector, security_mode, injection_rules, extension_connected, mobile_device, session_comparison, bug_report, breakpoints, addons

These fields exist in the TypeScript interface but are not currently populated — the sendHeartbeat calls in the app don’t pass any feature data. The infrastructure is ready for when feature tracking is implemented.

Users can disable telemetry through Settings → About → “Usage Analytics” toggle, which calls api.updateSettings({ telemetry: { telemetry_enabled: false } }). The toggle shows a description: “Send anonymous usage data to help improve Ghost. No traffic data, URLs, or personal information is ever sent.”

Disabling telemetry is immediate — no more events are sent after the setting is saved.

Telemetry is designed to be completely invisible to the user:

  • If the settings fetch fails on app load, telemetry is silently disabled for the session
  • If a heartbeat send fails (network error, endpoint down), it’s logged to console.debug and swallowed — no retry, no queue, no user-visible error
  • The session_end event uses beforeunload, which is best-effort — the browser may not wait for the request to complete

SQLite uses a Write-Ahead Log (WAL) file for concurrent read/write performance. The WAL file grows as writes happen and needs periodic checkpointing to transfer data back to the main database file and keep disk usage under control.

A background goroutine runs every 30 minutes and executes:

PRAGMA wal_checkpoint(PASSIVE)

PASSIVE mode means “checkpoint whatever you can without blocking any readers.” If another goroutine is actively reading the database, the checkpoint skips those pages and tries again next cycle. This is the safest option — it never causes any visible delay or lock contention.

The goroutine is wrapped in safeGo for panic recovery and Sentry reporting. If the checkpoint fails, it logs a warning but does not retry immediately — it waits for the next 30-minute tick.

When Ghost shuts down gracefully, the database Close() method runs:

PRAGMA wal_checkpoint(TRUNCATE)

TRUNCATE mode is more aggressive — it flushes all pending WAL data to the main database file and then truncates the WAL file to zero bytes. This is safe at shutdown because there are no more readers or writers. The result is a clean single-file database with no leftover WAL file.

If the shutdown checkpoint fails, it logs a warning but still proceeds to close the database connection — the WAL file will be automatically recovered by SQLite on the next startup.


Ghost runs as two processes when used as a desktop application:

Tauri Shell (Rust process)
└── Ghost Engine (Go sidecar subprocess)

Tauri Shell: A lightweight Rust binary that provides the native window, system tray, native menus, and WebView. It doesn’t contain any business logic — its job is to launch the Go sidecar, display the web UI, and provide native OS integration (file dialogs, system proxy control, certificate AirDrop).

Ghost Engine: The Go binary that contains everything else — the MITM proxy, REST API, WebSocket hub, SQLite database, AI agent, addon engine, security interceptor, and all other features. It runs as a child process of the Tauri shell.

Communication: The Tauri shell launches the Go sidecar with --sidecar flag. The Go binary prints a JSON line to stdout with api_port, proxy_port, and token. The Tauri shell reads this JSON and navigates the WebView to the Go server’s URL.

Safety net: The Tauri shell polls the Go sidecar every 3 seconds via /api/v1/proxy/status. If 3 consecutive polls fail (9 seconds of downtime) and the system proxy was configured to route through Ghost, the Tauri shell natively disables the system proxy using platform-specific code. This prevents internet loss if the Go sidecar crashes while system proxy is enabled.