Skip to content

Loki

If Alloy, Promtail or another shipper already sends your access logs to Grafana Loki, GeoMetrikks can read them from Loki instead of a file. Each LogQL stream selector is one source, and GeoMetrikks parses and geolocates its lines the same way it handles a tailed file.

Terminal window
LOKI_URL=http://loki:3100
LOKI_QUERIES=["{job=\"nginx\"}", "{job=\"traefik\"}"]
LOKI_SERVER_NAMES=["edge-01", "edge-02"]

The app container has to reach LOKI_URL. http://loki:3100 works when Loki runs on the same Docker network.

Without LOKI_SERVER_NAMES, GeoMetrikks stamps every selector with the first LOGPARSER_SERVER_NAME. That is a name you choose for this server, not your nginx server_name, and it defaults to the machine’s hostname. If the selectors are different proxies, give each one its own server name.

  • Loki must hold the raw access-log line. If a pipeline stage rewrites the line, for example into logfmt, GeoMetrikks has nothing it can parse. GeoMetrikks ignores labels and structured metadata.
  • Don’t set the entry timestamp from the line when the line carries the request’s start time, as Traefik’s StartUTC does. In Alloy that is stage.timestamp, in Promtail the timestamp stage. The proxy writes the line when the request ends, so a websocket or a long download ends up with a timestamp minutes or hours older than the moment it reaches Loki. GeoMetrikks reads back from the newest line it has seen minus LOKI_LOOKBACK (60 seconds by default), and never more than an hour, so it skips that line. Without the stage, the shipper stamps each line when it reads it, and the lookback only has to cover shipping delay. nginx and Caddy stamp lines when they write them, so this doesn’t affect them. If you keep the stage, set LOKI_LOOKBACK above your longest request, up to 3600. Every poll re-reads that whole window.
  • Loki can’t page inside a single timestamp, and GeoMetrikks reads 1000 lines per request for the whole selector. With second-precision timestamps, a selector that logs more than 1000 lines in one second loses the rest of that second. GeoMetrikks logs a warning once per such timestamp.
  • A selector can match several streams, one per label set. GeoMetrikks merges their lines in timestamp order under the selector’s server name. Each selector has one log format, detected from its first lines or pinned with LOKI_FORMATS, and GeoMetrikks counts lines in any other format as malformed. Use one selector per log format.
  • GeoMetrikks polls every LOKI_POLL_INTERVAL seconds, 5 by default. Reading starts when the app starts, as with a tailed file, so lines from while it was down are not read. To backfill a range from Loki’s history, use import-loki.
  • With LOKI_QUERIES set and LOGPARSER_LOG_PATHS unset, GeoMetrikks tails no file. Set both to read files and Loki side by side. A LOGPARSER_LOG_PATHS line in .env or in a compose environment: block counts as set and keeps the file tail. The shipped .env.example has that line, so for a Loki-only install, remove it or comment it out.
  • LOKI_TENANT_ID sends X-Scope-OrgID for a multi-tenant Loki. LOKI_USERNAME and LOKI_PASSWORD together send basic auth.
  • If Loki can’t be reached or refuses the query, /health lists the source under ingestion.unavailableSources as loki:1, loki:2 and so on, with the reason unreachable or rejected. The Status page shows each selector with its server name, format and line count.

An Alloy configuration that ships a Traefik access log the way GeoMetrikks expects:

loki.source.file "traefik" {
targets = [{
__path__ = "/var/log/traefik/access.log",
job = "traefik",
service_name = "traefik",
}]
tail_from_end = true
forward_to = [loki.write.local.receiver]
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}