Read back what a game server or a sandbox printed. Every line a game server logs, and every line a sandbox's container writes, is kept and searchable by time and by text. ListLogLines reads one server's or one sandbox's output, newest first by default; ListAgentRuns shows each time it started, so you can see exactly what happened either side of a restart.
Returns the lines one game server, or one sandbox, logged.
Newest first, which is the opposite of how a log reads and the right
default for how one is opened: the first question is almost always "what
just happened", and a chronological first page of a server that has been up
for a week is a week old. Ask for ORDER_OLDEST_FIRST to read forwards.
parentstringRequiredprojects/{project}/locations/{location}/gameServers/{game_server}, or
projects/{project}/locations/{location}/sandboxes/{sandbox}.
One server, or one sandbox across all of its instances. There is deliberately no wildcard: this is the highest-volume
data on the platform, and projects/- over it is a query that would answer
slowly, hold a connection open and tell nobody anything they asked.
page_sizeint32page_tokenstringagent_run_idstringRestrict to one boot of the agent. See ListAgentRuns.
streamStreamRestrict to one stream. Unset returns all three, interleaved, which is how they are worth reading — "the server died" and "the agent asked it to" are far easier to correlate in one stream than in two.
STREAM_STDOUTSTREAM_STDERRSTREAM_AGENTtime_rangeIntervalRestrict by observation time — when the server printed the line, not when Plany received it. Lines a server buffered while it could not reach Plany are placed at the time they happened.
containsstringRestrict to lines containing this substring, case-insensitively. A plain substring, not a regular expression.
ORDER_NEWEST_FIRSTORDER_OLDEST_FIRSTgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations//gameServers/", "page_size": 50 }' \ api.plany.cloud:443 plany.gamelog.v1.LogService/ListLogLines{ "records": [ { "game_server": "projects//locations//gameServers/", "agent_run_id": "my-agent-run", "line": { "observe_time": "2026-10-04T12:00:00Z", "stream": "STREAM_STDOUT", "text": "", "sequence": "0" }, "ingest_time": "2026-10-04T12:00:00Z", "sandbox": "projects//locations//sandboxes/" } ], "next_page_token": "", "gaps": [ { "agent_run_id": "my-agent-run", "report_time": "2026-10-04T12:00:00Z", "dropped_lines": "0" } ]}Returns each run that shipped lines for a server or a sandbox.
A run is one start. For a sandbox, a run belongs to one instance and its id begins with the instance id — so this is also "which instances have run, and when".
Line sequence numbers restart at 1 on every start, so two lines either side of a restart are ordered by the run they belong to. This is what turns a wall of lines into "the four times this server has started".
parentstringRequiredA game server or a sandbox, exactly as ListLogLinesRequest.parent.
page_sizeint32page_tokenstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations//gameServers/", "page_size": 50 }' \ api.plany.cloud:443 plany.gamelog.v1.LogService/ListAgentRuns{ "agent_runs": [ { "agent_run_id": "my-agent-run", "first_line_time": "2026-10-04T12:00:00Z", "last_line_time": "2026-10-04T12:00:00Z", "line_count": "0", "dropped_lines": "0" } ], "next_page_token": ""}AgentRun is one start of a server or sandbox instance, as seen from the lines it shipped.
Worked out from the lines themselves rather than reported. A run that is still going has a last_line_time that keeps moving.
{ "agent_run_id": "my-agent-run", "first_line_time": "2026-10-04T12:00:00Z", "last_line_time": "2026-10-04T12:00:00Z", "line_count": "0", "dropped_lines": "0"}LogGap is a stretch of lines the agent threw away rather than block the game.
Reported rather than hidden, and surfaced beside the lines rather than in a metric. A gap in a log is invisible in the data: a session that appears to last four hours because the disconnect fell in the gap is indistinguishable from a real one, so a reader who cannot see the gap has no way to distrust what surrounds it.
agent_run_idstringreport_timeTimestampWhen the agent admitted to the loss, which is the batch after the lines went missing. The lines themselves have no time — they were never sent.
dropped_linesint64{ "agent_run_id": "my-agent-run", "report_time": "2026-10-04T12:00:00Z", "dropped_lines": "0"}LogRecord is one stored line and what is known about how it arrived.
game_serverstringprojects/{project}/locations/{location}/gameServers/{game_server}.
Empty for a sandbox's line, which sets sandbox instead.
agent_run_idstringThe agent run that shipped it. Sequence numbers restart at 1 when the agent
does, so (agent_run_id, line.sequence) is what actually orders two lines
across a restart.
lineLogLineWhen the server printed the line. Not when Plany received it: a server that could not reach Plany for ten minutes buffers its lines, and they keep their original times.
textstringRequiredThe line, verbatim and without its trailing newline.
Verbatim matters: a template is a regular expression over exactly these bytes, so any normalisation here would silently invalidate every template learned before it was introduced.
streamStreamWhich stream it came from.
Kept distinct because a game server that logs everything to stderr is common, and collapsing the two loses the only signal that something was actually wrong.
STREAM_STDOUTSTREAM_STDERRSTREAM_AGENTsequenceint64Monotonic sequence within one agent run, starting at 1.
Two lines can share a timestamp — most servers log to the second — and ordering between a connect and the disconnect that follows it is the whole point. This is what breaks that tie.
ingest_timeTimestampWhen Plany received the line, as distinct from when the server printed it.
A large gap between the two means the server was buffering — for example, because it could not reach Plany for a while.
sandboxstringprojects/{project}/locations/{location}/sandboxes/{sandbox}, for a line a
sandbox's container printed. Exactly one of this and game_server is set.
{ "game_server": "projects//locations//gameServers/", "agent_run_id": "my-agent-run", "line": { "observe_time": "2026-10-04T12:00:00Z", "stream": "STREAM_STDOUT", "text": "", "sequence": "0" }, "ingest_time": "2026-10-04T12:00:00Z", "sandbox": "projects//locations//sandboxes/"}agent_runsrepeated AgentRunMost recent first.
next_page_tokenstringrecordsrepeated LogRecordgame_serverstringprojects/{project}/locations/{location}/gameServers/{game_server}.
Empty for a sandbox's line, which sets sandbox instead.
agent_run_idstringThe agent run that shipped it. Sequence numbers restart at 1 when the agent
does, so (agent_run_id, line.sequence) is what actually orders two lines
across a restart.
lineLogLineWhen the server printed the line. Not when Plany received it: a server that could not reach Plany for ten minutes buffers its lines, and they keep their original times.
textstringRequiredThe line, verbatim and without its trailing newline.
Verbatim matters: a template is a regular expression over exactly these bytes, so any normalisation here would silently invalidate every template learned before it was introduced.
streamStreamWhich stream it came from.
Kept distinct because a game server that logs everything to stderr is common, and collapsing the two loses the only signal that something was actually wrong.
STREAM_STDOUTSTREAM_STDERRSTREAM_AGENTsequenceint64Monotonic sequence within one agent run, starting at 1.
Two lines can share a timestamp — most servers log to the second — and ordering between a connect and the disconnect that follows it is the whole point. This is what breaks that tie.
ingest_timeTimestampWhen Plany received the line, as distinct from when the server printed it.
A large gap between the two means the server was buffering — for example, because it could not reach Plany for a while.
sandboxstringprojects/{project}/locations/{location}/sandboxes/{sandbox}, for a line a
sandbox's container printed. Exactly one of this and game_server is set.
next_page_tokenstringgapsrepeated LogGapGaps overlapping the window these records came from.
Returned with the lines rather than behind another call, because a caller who has to ask separately is a caller who will not, and a gap that is not shown beside the lines might as well not be recorded.
agent_run_idstringreport_timeTimestampWhen the agent admitted to the loss, which is the batch after the lines went missing. The lines themselves have no time — they were never sent.
dropped_linesint64Order results are returned in.
| Value | Description |
|---|---|
| ORDER_UNSPECIFIED | Newest first. |
| ORDER_NEWEST_FIRST | Newest first. The default, because a log is usually opened to find out what just happened. |
| ORDER_OLDEST_FIRST | Oldest first — reading a boot forwards from the start. |