Skip to content

Remote Logging

Remote Logging lets you send debug, info, warning, and error logs from your game to Quest Data. View and search them in the dashboard — no need to ask players for log files.

  • Debug production issues without asking players to send log files
  • Filter by log level to focus on errors or warnings
  • Search by player to investigate specific user reports
  • 30-day retention with automatic cleanup

The SDK provides helper functions for each log level:

# Error — crashes, exceptions, critical failures
QuestData.log_error("Failed to load save data", {
"save_slot": 2,
"error": "corrupted JSON"
})
# Warning — performance issues, degraded experience
QuestData.log_warning("Frame drop detected: 8 FPS", {
"scene": "castle_boss",
"avg_fps": 8,
"duration_ms": 3000
})
# Info — player milestones, important state changes
QuestData.log_info("Player reached level 5", {
"level": 5,
"time_played": 1200
})
# Debug — verbose diagnostics (disabled in production by default)
QuestData.log_debug("Physics tick: 142 active bodies", {
"bodies": 142,
"sleeping": 58
})

The SDK logging system works similarly to event tracking:

  • Batching: Logs are queued in memory and sent every 10 seconds or when 50 logs are queued
  • Offline persistence: If the server is unreachable, logs are saved to user://quest_log_queue.save and retried on next flush
  • Level filtering: On init, the SDK fetches the game’s minimum log level from the server (GET /v1/logs/config). Logs below this level are silently discarded without sending
  • Auto-enrichment: player_id and session_id are automatically added to each log
  • Crash-safe: Logs are saved to disk on game exit (_exit_tree)
# Check current configured log level
var level = QuestData.get_log_level() # e.g. "warning"

Each log entry contains:

FieldTypeRequiredDescription
levelstringYesdebug, info, warning, or error
messagestringYesHuman-readable log message
contextobjectNoArbitrary structured data (scene, stack trace, device info)
player_idstringNoPlayer identifier (auto-set by SDK if available)
session_idstringNoSession identifier (auto-set by SDK)

If you’re not using the Godot SDK, send logs via the REST API directly.

Terminal window
curl -X POST https://api.questdata.io/v1/logs \
-H "Content-Type: application/json" \
-H "x-game-api-key: YOUR_API_KEY" \
-d '{
"level": "error",
"message": "NullReferenceException in PlayerController",
"context": {"scene": "forest_1", "line": 142},
"player_id": "player-123",
"session_id": "sess-abc"
}'

Response: 202 Accepted

{ "received": 1 }

Send up to 100 logs in a single request for better performance:

Terminal window
curl -X POST https://api.questdata.io/v1/logs \
-H "Content-Type: application/json" \
-H "x-game-api-key: YOUR_API_KEY" \
-d '[
{"level": "error", "message": "Crash in scene forest_1"},
{"level": "warning", "message": "Low memory: 45MB free"},
{"level": "info", "message": "Checkpoint saved"}
]'

Response: 202 Accepted

{ "received": 2, "filtered": 1 }

The filtered field shows how many logs were dropped because they were below the game’s configured minimum log level.

Terminal window
curl "https://api.questdata.io/v1/logs?level=error&limit=20" \
-H "x-game-api-key: YOUR_API_KEY"

Query Parameters:

ParameterTypeDefaultDescription
levelstringFilter by level: debug, info, warning, error
player_idstringFilter by player
session_idstringFilter by session
searchstringFull-text search in message (uses PostgreSQL tsvector)
context_searchJSON stringFilter by context fields (e.g. {"scene":"forest"})
fromISO dateStart date
toISO dateEnd date
limitnumber50Results per page (max 200)
offsetnumber0Pagination offset

Response:

{
"logs": [
{
"id": "a1b2c3d4-...",
"level": "error",
"message": "NullReferenceException in PlayerController",
"context": {"scene": "forest_1", "line": 142},
"player_id": "player-123",
"session_id": "sess-abc",
"server_timestamp": "2026-04-07T12:30:00.000Z"
}
],
"total": 42
}

The SDK fetches this on init to know which logs to send:

Terminal window
curl "https://api.questdata.io/v1/logs/config" \
-H "x-game-api-key: YOUR_API_KEY"

Response:

{ "log_level": "warning" }

Only logs at or above this level are accepted. Debug and info logs would be filtered out in this example.

Terminal window
curl -X PATCH "https://api.questdata.io/v1/games/GAME_ID/log-level" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{"log_level": "info"}'

Response:

{ "log_level": "info" }

Requires Admin or Owner role.

Terminal window
curl "https://api.questdata.io/v1/logs/stats" \
-H "x-game-api-key: YOUR_API_KEY"

Response:

{
"debug": 150,
"info": 89,
"warning": 34,
"error": 12,
"total": 285
}

The Logs page in the dashboard (under Configuration > Logs) provides:

  • Min Level Config — Set the minimum log level for the game (Debug/Info/Warning/Error). Logs below this level are rejected by the backend, saving bandwidth and storage. Default: Warning.
  • Level Stats Cards — Quick overview of error, warning, info, and debug counts (last 7 days)
  • Filter Controls — Dropdown for log level, text inputs for player ID and message search
  • Log List — Color-coded entries sorted by time, click to expand context JSON
  • Pagination — Navigate through large log sets
  • Refresh — Manual refresh button (WebSocket live-tail coming soon)
LevelColorUse Case
errorRedCrashes, exceptions, data corruption
warningYellowFPS drops, memory pressure, network issues
infoBluePlayer milestones, state changes
debugGrayVerbose diagnostics, physics ticks
  1. Use context objects liberally — Include scene name, device info, stack traces. This is what makes remote debugging possible.

  2. Don’t log on every frame — Batch periodic metrics (e.g. average FPS every 10 seconds) instead of per-frame logging.

  3. Configure log level in the dashboard — Set the minimum level to warning or error in production. The backend filters out logs below this threshold automatically, so you don’t need to change SDK code between environments.

  4. Include player_id — Without it, you can’t correlate logs to specific player reports.

  5. Keep messages short and searchable — Use the context object for details, keep the message field as a one-line summary. Messages are limited to 10,000 characters.

  6. Keep context objects small — Maximum 50 properties per context object. Use flat structures instead of deeply nested objects.

LimitValue
Message length10,000 characters
Context properties50 per log entry
Player/Session ID255 characters
Batch size500 logs per request
Rate limit1,000 requests/min per API key
Effective throughputUp to 500,000 logs/min (500 per batch x 1,000 req/min)
Data retention30 days (automatic cleanup)
Stats time windowLast 7 days

Logs have per-level retention policies, cleaned up daily:

LevelRetention
Debug7 days
Info14 days
Warning30 days
Error90 days

TimescaleDB’s 30-day retention policy acts as a safety net. The level statistics (error/warning/info/debug counts) cover the last 7 days.

HTTP StatusCause
202Logs accepted successfully
400Invalid log level, missing message, or invalid schema
401Missing or invalid x-game-api-key header
413Batch too large (over 500 logs)
429Rate limit exceeded (1,000 req/min per API key)