Centralizing Rust Dedicated Server Logs with Loki, Promtail, and Grafana

devops-observability

When to Use This

If you are running one Rust dedicated server for a small community, tailing output_log.txt over SSH is workable — barely. The moment you add a second node, start caring about player behavior patterns, or need to correlate server errors with performance events, a local log file becomes a liability rather than an asset.

Centralizing with the Loki stack makes sense in these specific situations:

  • You want to query player connect and disconnect events without grepping through gigabytes of raw text.
  • You are running multiple game servers and need a single pane of glass in Grafana.
  • You need alerting when error rates spike — for example, after a plugin update or map wipe.
  • You want to retain logs beyond what the Rust process keeps in its rolling output file.

This setup is intentionally scoped to a single Linux host running both the Rust dedicated server and the observability stack. It is a practical starting point, not a Kubernetes-native deployment. If you are already running containerized workloads, the infrastructure automation posts on kuryzhev.cloud cover patterns that translate directly.

Setup

The stack has three components: Promtail reads the log file and ships entries to Loki, Loki stores and indexes them, and Grafana provides the query and dashboard interface. We will install all three as systemd-managed binaries on the same host as the Rust server.

Install Loki

Download the latest Loki binary from the Grafana Loki releases page and place it under /usr/local/bin.

curl -O -L "https://github.com/grafana/loki/releases/download/v3.0.0/loki-linux-amd64.zip"
unzip loki-linux-amd64.zip
chmod +x loki-linux-amd64
sudo mv loki-linux-amd64 /usr/local/bin/loki

sudo mkdir -p /etc/loki /var/lib/loki

Install Promtail

curl -O -L "https://github.com/grafana/loki/releases/download/v3.0.0/promtail-linux-amd64.zip"
unzip promtail-linux-amd64.zip
chmod +x promtail-linux-amd64
sudo mv promtail-linux-amd64 /usr/local/bin/promtail

sudo mkdir -p /etc/promtail /var/lib/promtail

Install Grafana

Grafana maintains an official APT repository. On Debian or Ubuntu:

sudo apt-get install -y apt-transport-https software-properties-common
wget -q -O - https://apt.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install grafana

Enable and start Grafana:

sudo systemctl enable grafana-server
sudo systemctl start grafana-server

Grafana listens on port 3000 by default. Loki will listen on 3100. Promtail exposes metrics on 9080. Verify none of these conflict with existing services before proceeding.

Configuration (with Code)

Loki Configuration

Create a minimal but production-aware Loki configuration at /etc/loki/loki-config.yaml. The ingester block values below control how aggressively Loki flushes log chunks to storage — tuning these prevents data loss if the process restarts unexpectedly.

# /etc/loki/loki-config.yaml

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

ingester:
  lifecycler:
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
  chunk_idle_period: 5m
  max_chunk_age: 1h
  chunk_retain_period: 30s

schema_config:
  configs:
    - from: 2024-01-01
      store: boltdb-shipper
      object_store: filesystem
      schema: v12
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /var/lib/loki/index
    cache_location: /var/lib/loki/boltdb-cache
    shared_store: filesystem
  filesystem:
    directory: /var/lib/loki/chunks

limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h

compactor:
  working_directory: /var/lib/loki/compactor
  shared_store: filesystem

Create the systemd unit for Loki:

# /etc/systemd/system/loki.service
[Unit]
Description=Loki log aggregation service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/loki -config.file=/etc/loki/loki-config.yaml
Restart=on-failure
User=loki

[Install]
WantedBy=multi-user.target
sudo useradd --system --no-create-home loki
sudo chown -R loki:loki /var/lib/loki /etc/loki
sudo systemctl daemon-reload
sudo systemctl enable loki
sudo systemctl start loki

Confirm it is running: systemctl status loki. You should see the service active and a line indicating it is listening on 3100.

Promtail Configuration

This is the core of the pipeline. The configuration below targets the Rust dedicated server log file at its default path, extracts a log level label from structured prefixes, drops noisy heartbeat lines, and adds static metadata that makes Grafana filtering straightforward.

# /etc/promtail/promtail-config.yaml
# Promtail configuration for Rust dedicated server log ingestion

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: http://localhost:3100/loki/api/v1/push

scrape_configs:
  - job_name: rust-gameserver
    static_configs:
      - targets:
          - localhost
        labels:
          job: "rust-gameserver"
          host: "game-node-01"
          environment: "production"
          __path__: /home/steam/steamapps/common/rust_dedicated/RustDedicated_Data/output_log.txt

    pipeline_stages:
      # Extract log level from lines that contain [Warning], [Error], [Info]
      - regex:
          expression: '^\[(?P<level>Warning|Error|Info)\]'

      # Promote extracted level to a label (keep cardinality low)
      - labels:
          level:

      # Drop noisy heartbeat lines to reduce ingest volume
      - drop:
          expression: ".*Heartbeat.*"
          drop_counter_reason: "heartbeat_noise"

      # Parse timestamp from Rust log prefix format: MM/DD/YYYY HH:MM:SS
      - timestamp:
          source: time
          format: "01/02/2006 15:04:05"
          fallback_formats:
            - "2006-01-02T15:04:05Z07:00"

      # Add static metadata to every log entry
      - static_labels:
          game: "rust"
          server_region: "eu-west"

      # Output the cleaned log line
      - output:
          source: message

Create a systemd unit for Promtail, then enable it:

# /etc/systemd/system/promtail.service
[Unit]
Description=Promtail log shipping agent
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/promtail-config.yaml
Restart=on-failure
User=promtail

[Install]
WantedBy=multi-user.target
sudo useradd --system --no-create-home promtail
# Promtail needs read access to the Rust log file
sudo usermod -aG steam promtail
sudo chown -R promtail:promtail /var/lib/promtail /etc/promtail
sudo systemctl daemon-reload
sudo systemctl enable promtail
sudo systemctl start promtail

Grafana Datasource

You can provision the Loki datasource declaratively rather than clicking through the UI. Create the following file and restart Grafana:

# /etc/grafana/provisioning/datasources/loki.yaml
apiVersion: 1

datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://localhost:3100
    isDefault: true
    version: 1
    editable: false
sudo systemctl restart grafana-server

Navigate to http://<your-host>:3000, log in with the default credentials (admin / admin), and open Explore. Select the Loki datasource and confirm you can see the rust-gameserver job label.

Examples

The following LogQL queries are written against the label set defined in the Promtail configuration above. Refer to the Loki LogQL documentation for the full query language reference.

Filter Player Connection Events

Rust logs player joins in the format PlayerName joined [steamid]. This query isolates those lines:

{job="rust-gameserver"} |= "joined ["

Filter by Log Level

Because we promoted level to a label in the pipeline, you can filter at the stream selector level rather than scanning every line — which is significantly more efficient at scale:

{job="rust-gameserver", level="Error"}

Kill Feed Events

Rust logs PvP kills with the pattern was killed by. Combine with a rate to build a kills-per-minute panel:

rate({job="rust-gameserver"} |= "was killed by" [5m])

Error Rate Over Time

This metric query is suitable for a Grafana time series panel tracking server stability after plugin deployments:

sum(rate({job="rust-gameserver", level="Error"}[5m]))

Player Disconnect Events

{job="rust-gameserver"} |= "disconnecting"

Pair this with the connection query in a stat panel to show concurrent session deltas over your wipe cycle.

Common Mistakes

High-Cardinality Labels

The most damaging mistake is adding dynamic values — Steam IDs, player names, or IP addresses — as Loki labels inside a pipeline_stages labels block. Loki’s index is designed for low-cardinality label sets. A server with 200 unique players will create 200 distinct streams if you label by player name, which causes index bloat, slow queries, and eventually ingester memory pressure. Extract those fields as structured metadata or query them with |= "pattern" instead.

Deleting positions.yaml

The file at /var/lib/promtail/positions.yaml tracks how far Promtail has read into each log file. If you delete it — perhaps while troubleshooting — Promtail will re-ingest the entire log file from the beginning on next start. On an active server with weeks of logs, this will flood Loki with duplicate entries and may trigger rate limiting. If you need to reset ingestion for a specific file, edit the positions file and remove only the relevant entry.

Wrong Log File Path

The __path__ label in Promtail must point to the exact file Rust is writing to. If you installed the server under a different Steam user or changed the install directory, the default path /home/steam/steamapps/common/rust_dedicated/RustDedicated_Data/output_log.txt will not exist and Promtail will silently produce no logs. Verify with ls -lh before starting the service.

Grafana Datasource URL Mismatch

The datasource URL must be http://localhost:3100 when Grafana and Loki are on the same host. Using 127.0.0.1 usually works, but using a public IP or hostname can introduce DNS resolution delays or firewall blocks. If the Explore panel shows “Bad Gateway” or “connection refused,” this is the first thing to check.

Timestamp Parsing Failures

If the timestamp pipeline stage cannot parse the log line’s timestamp, Promtail falls back to the current ingestion time. This means your Grafana panels will show accurate wall-clock timing but historical log lines loaded after a restart will all appear at the same timestamp. Validate your format string against actual log output using promtail --dry-run before deploying to production.

Loki Not Flushing Chunks

If recent log lines are missing from Grafana queries, the ingester may be holding them in an open chunk. The chunk_idle_period: 5m and max_chunk_age: 1h values in the Loki config control this behavior. For a game server where you want near-real-time visibility, keep chunk_idle_period at five minutes or below. Setting it too high means logs are queryable only after the chunk closes.

official docs

Leave a Reply

Your email address will not be published. Required fields are marked *

Support us · 💳 Monobank