feat: Discord alert relay for Locomotive log shipping - #70
Draft
joemurrell wants to merge 1 commit into
Draft
Conversation
Locomotive (ghcr.io/brody192/locomotive) has no Discord webhook mode -- it only ships raw JSON/JSONL, which Discord's webhook API rejects. This adds a small standalone service that sits between the two: receives Locomotive's JSON payload, keeps only WARNING+ log lines, and posts Discord-shaped embeds to the existing webhook. Filtering reads the app-reported `level` field (populated by JSONFormatter when LOG_FORMAT=json), not Railway's own `severity` field, which defaults to "info" for any app emitting plain-text logs regardless of real severity -- confirmed by comparing Locomotive's own structured warn/error log lines (correctly tagged by Railway) against the bot's current plain-text output (uniformly "info").
|
🚅 Deployed to the brevitybot-pr-70 environment in BrevityBot
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restores production log alerting to Discord. Investigation while following up on the scheduled-posting fix (#68/#69) found the
locomotive loggingRailway service — meant to forward critical bot logs to Discord — had been crash-looping and dead since 2026-05-20 (the tool it runs,ferretcode/locomotive, was archived upstream on 2025-08-06). It was dead for the entire lifetime of the scheduled-posting bug, which is very likely why no one caught it earlier.Migrating to the maintained fork (
ghcr.io/brody192/locomotive) surfaced a second gap: v2 has no Discord webhook mode at all (confirmed in source — onlyjson/jsonl/datadog/axiom/betterstack/loki/sentry/signoz/victorialogs/otel_http). Pointing it straight at a Discord webhook URL fails outright — confirmed live in the newLocomotiveservice logs:dispatch failed, retrying→dropping item after exhausting all retries, because Discord's webhook API rejects Locomotive's raw JSON (it expects a specific{content, embeds}schema).alert_relay.pyis the missing piece: a small standalone service that sits between Locomotive and Discord, translating one into the other.What it does
POST /webhookreceives Locomotive's JSON array or JSONL payload.levelfield.DISCORD_WEBHOOK_URL.X-Relay-Secretheader check againstRELAY_SHARED_SECRETso the endpoint isn't open to anyone who finds the URL.GET /healthfor Railway's health check.Important: requires
LOG_FORMAT=jsonon the bot to actually filter correctlyConfirmed by comparing two data points from this investigation: Locomotive's own Go structured logs came through Railway correctly tagged (
"dispatch failed, retrying"→severity: "warn","dropping item..."→severity: "error"), while every log line pulled from the bot all session — including ones logged vialogger.warning()/logger.error()in the code — showedseverity: "info"uniformly. Railway only extracts a real severity from apps emitting structured/leveled log output; the bot's current plain-text format (LOG_FORMATunset) means every line looks identical to Railway regardless of actual Python log level.extract_level()in this relay deliberately reads the app's ownlevelfield (populated byJSONFormatterwhenLOG_FORMAT=json) rather than Railway'sseverity— falling back toseveritywould have reproduced the exact blind spot that let the original bug go unnoticed. This PR alone does not fix alerting —LOG_FORMAT=jsonstill needs to be set on thediscord botRailway service, and this relay needs to be deployed with Locomotive pointed at it, for alerts to actually flow. Those are infra changes to make after this merges, not part of the diff.Test plan
pytest tests/— 120 passed (96 existing + 24 new)Generated by Claude Code