Skip to content

Console Output, Input, and Logging

When you run a game server, you’re running a headless copy of your game. There is no screen showing what’s happening inside the world. The only way for humans and automated systems to know the state of the server is through its output.

Your console output is not just debug information. It is an API that hosting platforms, monitoring tools, and server admins consume in real time.

Your server should write to two standard output streams:

  • STDOUT: Normal operational output. Server startup, player events, state changes, informational messages.
  • STDERR: Errors and warnings. Crashes, failed operations, configuration problems.

Every hosting platform and log aggregation tool distinguishes between these two streams. If you write everything to STDOUT, errors get buried. If you write everything to STDERR, normal operation looks like a stream of problems.

Hosting platforms parse your console output to detect important events. These events drive UI updates, notifications, and automation. At minimum, your server should produce clear, parseable output for:

EventDescriptionRequired Data
Server OnlineServer has started and is accepting connections(none)
Server StoppingServer is shutting down, no longer accepting connections(none)
Player JoinA player connected to the serverPlatform ID (e.g., Steam ID), Username
Player LeaveA player disconnected from the serverPlatform ID, Username

Player events must include both the platform ID (such as a Steam ID) and the username. The platform ID is the stable identifier; the username can change at any time.

If your game allows players to create characters with different names, log the character name alongside the platform ID so players can be tracked across all events. If events log only the character name without a platform ID, it becomes impossible for hosting platforms to correlate “ShadowKnight” with the Steam user who is actually playing.

Example output format:

[2026-01-15T14:32:01Z] [INFO] Server online. Listening on 0.0.0.0:27015
[2026-01-15T14:33:15Z] [INFO] Player joined: steam:76561198012345678 "PlayerName"
[2026-01-15T14:45:02Z] [INFO] Player left: steam:76561198012345678 "PlayerName"
[2026-01-15T14:50:00Z] [INFO] Server stopping.

Consistent formatting makes your logs parseable. Every log line should include:

  • Timestamp in ISO 8601 format (e.g., 2026-01-15T14:32:01Z)
  • Log level (INFO, WARN, ERROR, DEBUG)
  • Message with relevant context

Avoid unstructured output like bare print() statements that produce lines like player connected with no timestamp, no level, and no identifying information.

If your engine has its own logging format, that’s fine as a starting point. Just make sure it includes timestamps and log levels.

Support at minimum these levels:

LevelWhen to Use
ERRORSomething failed. Data may be lost, a player may be affected.
WARNSomething unexpected happened but the server recovered.
INFONormal operations. Server start, player join/leave, map changes.
DEBUGDetailed diagnostic information. Disabled by default in production.

Make the log level configurable via startup argument or config file (e.g., -loglevel info).

Production servers should run at INFO level. DEBUG output should be available for troubleshooting but off by default. A server that floods STDOUT with frame-by-frame debug data at 60 ticks per second makes the console unusable for admins and generates enormous log files.

In addition to writing to STDOUT, your server should write logs to a file. Console output is ephemeral and gets lost when the terminal session ends. File logs persist for post-mortem analysis.

  • Write to a predictable location (e.g., ./logs/server.log)
  • Rotate logs by date or size so they don’t fill the disk
  • Make the log file path configurable

STDOUT is the primary interface for hosting platforms. File logs are the backup for admins and developers investigating issues after the fact.

A good server console is interactive. Server admins should be able to type commands into the console to control the server without restarting it.

Accept STDIN commands using the same syntax as in-game admin commands. This keeps the command language consistent between the console and in-game usage.

Every server should support at minimum:

CommandPurpose
stop or quit or exitGracefully shut down the server
saveForce a world save
statusPrint current server status (player count, uptime, map)
list or playersList connected players with their platform IDs
kick <player>Disconnect a player
ban <player>Ban a player from the server
say <message>Send a message to all connected players
  • Use consistent syntax. Don’t mix /kick, !kick, and kick.
  • Return feedback for every command. Silence means the admin doesn’t know if it worked.
  • Handle bad input gracefully. An invalid command should not crash the server.
  • Support a help command that lists available commands.

RCON allows admins to send console commands to the server over the network, without direct access to the server’s terminal.

If you want to support remote administration, implementing a basic RCON protocol gives hosting platforms the ability to build admin UIs that issue commands on behalf of server owners. Valve’s Source RCON protocol is widely supported by existing tools, but any TCP-based command protocol works.

RCON should be:

  • Disabled by default
  • Password-protected
  • On a configurable port
  • Rate-limited to prevent abuse

Avoid TTYs or Custom Console Implementations

Section titled “Avoid TTYs or Custom Console Implementations”

Some engines provide a built-in console window or TTY interface. This can be useful for local development, but it is not suitable for production servers running in headless environments. If your engine has a built-in console, make sure it also supports writing to STDOUT and reading from STDIN so that hosting platforms can interact with it. Do not rely solely on a custom console implementation that may not work in all environments.

If you must use a custom console for development, consider adding a “batch mode” that bypasses the custom console and uses standard input/output instead via a -batchmode option or similar. This gives you the best of both worlds: a rich console experience during development and a compatible interface for hosting platforms in production. STDOUT/STDERR streams should not be littered with ANSI escapes or cursor control codes that make them unreadable by log parsers.