Skip to content

Match Listeners

The page at /admin/match-listeners (nav: Advanced → Match Listeners) lists match_listeners lifecycle rows grouped by match. Layout matches the Matches board: a left filter sidebar and card groups in the main column.

Filters (sidebar)

  • Scheduled dayadmin_day_range / AdminDayRange (presets: Today, Yesterday, Last 5 days, Custom)
  • League — server filter (reloads list)
  • Search — client-side text filter on loaded groups
  • Min runs — server filter (min_runs)
  • Test matches — Any / Test only / Live only (Match.is_test, client-side)

Changing day, league, or min runs reloads via GET /api/match-listeners?league=&scheduled_from=&scheduled_to=&min_runs=. Search and test filters only hide already-loaded groups.

Deep-link from Matches (listener badges / Listener state): ?match_id=&day=&league=. Day and league apply before the first load; Search is set to the match id when that group is present. If the match has no listener runs for the selected day/league (min_runs ≥ 1), a toast explains that and the page stays in its normal loaded state.

Match naming

Same convention as Matches:

Field Form Use
display_title Full market + team name Card headline
display_label Team aliases (AWAY @ HOME) Subtitle, pickers

On each card, internal match id and Sportradar match id show the first 8 characters (full value on hover; copy icon copies the full UUID). Shortcode has a copy icon as well.

Operators can load a run’s text log (copy to clipboard), download the per-run .log or the match event .json recording, and open Actions → Celery task… for Postgres-stored Celery ids (run match_listeners.celery_task_id and match listener_start_settings.celery_task_id) with copy and Flower deep links when Flower is configured.

This page lists match_listeners lifecycle rows. Start listener enqueues start_match_listener with force=true (fresh Celery task id; prefer Stop first if a run is live). Test-match starts follow the clone/listen env gate (hidden in main); live matches can start in any environment. Stop listener revokes Celery tasks, closes open runs, clears Redis start barriers, and turns auto-dispatch off for that match. Clone flows remain on Clone Past Match.

For badges, exit codes, hard fail vs stop, and “will Beat restart?”: Lifecycle cases.

Operator workflow

  1. Open /admin/match-listeners.
  2. Set day / league / min runs in the sidebar, then review groups.
  3. Optionally use Search or Test matches to narrow in the browser.
  4. On a match card: Start listener (confirm) to force-enqueue a new start, or Stop listener (confirm) to revoke/close and turn auto-dispatch off. On a run: Load log / Copy log, Download log, Download events, or Celery task… (stored task ids + Flower).

Data sources

Surface Source
List GET /api/match-listeners?league=&scheduled_from=&scheduled_to=&match_id=&min_runs=
Start POST /api/matches/{match_id}/start-listener with { "force": true }
Stop POST /api/matches/{match_id}/stop-listener
Log preview GET /api/match-listeners/{id}/log
Download log GET /api/match-listeners/{id}/log/download
Download events GET /api/match-listeners/{id}/events/download
Live write (listener host) {FILESYSTEM_CONTAINER_DATA_DIR}/listener/{league}/{shortcode}.json (+ optional .log)
Archive (downloads / replay) S3 AWS_S3_MATCH_ARCHIVE_BUCKET keys {shortcode}.json and {shortcode}.{listener_id[:8]}.log when Match.listener_events_archived

shortcode is {sr_id[:8]}-{internal_match_id[:8]} (Sportradar match id + internal match id). Many runs have no text log: listener_write_logs_to_file defaults to false unless overridden at start (Clone Past Match turns it on). Event recordings are per match (shared across runs), not per listener row.

Admin Download log / events and log preview read from the S3 gamedata archive only (no local filesystem fallback). They require Match.listener_events_archived=true after a successful archive upload (on listener exit and via the sweep_pending_listener_archives beat task, which runs every 5 minutes and which enqueues per-match archive_match_listener_data jobs for terminal matches scheduled within a lookback window (default 48 hours; override via lookback_hours on the sweep task). The sweep only considers matches that had at least one closed listener run with event recording enabled (listener_write_events_to_file true or unset; Clone Past Match defaults it off, so those runs are not sticky-retried). Only staging/main archive to S3 — develop/test keep recordings on disk; download/replay of archived matches is a staging/main path. Incomplete / unclosed JSON arrays are never uploaded.

Admin day presets convert browser-local calendar days to UTC scheduled_from/scheduled_to.

Status badges and lifecycle cases

Status Meaning
Running Open row with a recent heartbeat
Stale Open row with a stale heartbeat (until Beat sweeper / dispatch abandon)
Abandoned Closed exit 14 after heartbeat timeout (hard fail; barriers cleared if no active sibling; dispatch may re-enqueue)
Stopped Closed exit 15 (operator Stop; auto-dispatch off)
Ended Closed for any other reason (graceful end, failure exit, unexpected cancel 16)

Stop turns off auto_dispatch_listener. Abandoned leaves it on and clears Redis barriers so Beat can recover. Start is disabled while a run is Running (stop first); test-match starts remain env-gated.

Full case list (start, skip, failure, hard kill, stop, dispatch off): Match listener startup — Lifecycle cases.

Use the ? next to each run’s exit code (and on the expanded Exit code row) for the operator catalog of codes (0 / 916). Same tables live in Exit codes.

Hard stop vs Abandoned

See the lifecycle case catalog (hard kill → Stale → Abandoned vs operator Stop). Short form: abandon recovers via dispatch; stop blocks auto re-enqueue.

Details: Correcting a hard-failed listener.