A lightweight syslog aggregator with web UI and email alerting. Single Go binary, SQLite storage, no external dependencies. Accepts both RFC 3164 (BSD) and RFC 5424 syslog messages over UDP and TCP.
Requires Go 1.25+ and CGO (for SQLite).
CGO_ENABLED=1 go build -o logyard ../logyard -config ./config.yamlLogyard looks for config.yaml in the current directory or /etc/logyard/config.yaml.
-config string Path to config.yaml
-alert-interval Alert evaluation interval (default 60s)
See config.yaml.example for a full example.
# db_path: ./logyard.db
# retention: 14 # days
# debug: false
# web_addr: ":8080"
# url: "https://logyard.example.com" # used in alert emails
listen:
udp: ":514"
tcp: ":514"
smtp:
host: smtp.example.com
port: 587
user: alerts@example.com
password: secret
from: alerts@example.com
to: admin@example.com
alerts:
- name: "Many warnings"
count: 10
window_minutes: 5
level: warning
- name: "Any critical"
count: 1
window_minutes: 5
level: crit
- name: "Nginx errors"
count: 5
window_minutes: 10
level: err
tag: nginx
message: "timeout|connection refused"
ignore:
- host: noisy-box.lan
- facility: kern
- tag: CRON
- host: proxmox
level: warning
- message: "CRON|systemd-.*"
- host: noisy-box.lan
discard: true
digest:
enabled: true
initial: "5m"
multiplier: 3
max: "2h"
cooldown: "10m"
severity_rewrite:
- tag: my-docker-app
level: info
message: "ERROR|FATAL"
new_severity: err
- tag: my-docker-app
level: info
message: "WARN"
new_severity: warningEvery alert rule requires count, window_minutes, and level. Optionally narrow the scope with host, facility, tag (regex), or message (regex, RE2 syntax). Empty fields are ignored. The alerter checks every 60s (configurable via -alert-interval) and sends an email when the threshold is reached. Cooldown prevents re-alerting within the same time window.
Alert emails are sent from "Logyard" and include an HTML table of the triggering log entries (up to 50). Set url in your config to include a link to your Logyard instance in the email footer. If not set, it defaults to http://<hostname>:<web_port>.
When multiple alert rules fire continuously (e.g. overnight), individual emails can pile up quickly. Digest mode batches all triggered alerts into a single email and escalates the collection window using a multiplier, reducing noise while preserving all alert information.
digest:
enabled: true
initial: "5m" # first digest window
multiplier: 3 # multiply window each escalation (min 1.5)
max: "2h" # maximum window cap
cooldown: "10m" # quiet period before resetting to initialEscalation example (multiplier=3): 5m → 15m → 45m → 2h (capped). If no alerts fire for the cooldown duration after the last digest, the window resets to initial.
Duration values support human-readable units: s/sec/seconds, m/min/minutes, h/hour/hours. Unitless values default to seconds.
Per-rule cooldowns still apply during digest mode -- each rule only fires once per its window_minutes.
When digest is not configured (or enabled: false), behavior is identical to the default per-rule alerting.
Each rule matches on all specified fields (AND). Multiple rules are OR'd. By default, ignore rules apply to alerting only -- all logs are stored and visible in the UI.
Set discard: true to drop matching messages entirely -- they will not be stored in the database or appear in the UI. This is useful for noisy hosts whose logs you never want to see.
The tag and message fields support regular expressions using Go's RE2 syntax (e.g. CRON|systemd-.*). Use ^exactmatch$ for exact tag matching.
Rewrite rules change the severity of matching messages before they are stored in the database. This is useful when log sources (like Docker's syslog driver) send all messages with the same severity regardless of actual log level.
Rules are evaluated in order -- first match wins. Each rule matches on all specified fields (AND logic, same as ignore rules). At least one match field is required. The new_severity field is required and must be a valid severity level.
severity_rewrite:
- tag: my-docker-app
level: info
message: "ERROR|FATAL"
new_severity: err
- tag: my-docker-app
level: info
message: "WARN"
new_severity: warningDocker's syslog logging driver forwards container output to a syslog server. All messages are sent as severity info regardless of content, which is where severity rewrite rules come in handy.
docker run -d \
--log-driver syslog \
--log-opt syslog-address=udp://logyard-host:514 \
--log-opt tag=my-app \
my-imageWith Docker Compose:
services:
my-app:
image: my-image
logging:
driver: syslog
options:
syslog-address: "udp://logyard-host:514"
tag: my-app
syslog-format: rfc3164You can use syslog-format: rfc5424 for RFC 5424 messages. Logyard auto-detects both formats.
To apply syslog logging to all containers by default, configure Docker's daemon.json (typically /etc/docker/daemon.json):
{
"log-driver": "syslog",
"log-opts": {
"syslog-address": "udp://logyard-host:514",
"syslog-facility": "docker",
"syslog-format": "rfc3164"
}
}Restart the Docker daemon after changing daemon.json (sudo systemctl restart docker). Individual containers can still override these defaults with per-container logging: options.
When using the global log driver, you can still set a custom tag per container in Docker Compose without overriding the driver:
services:
my-app:
image: my-image
logging:
options:
tag: my-appUsing a dedicated facility like local0 for all Docker containers lets you write a single set of severity rewrite rules that cover every container:
severity_rewrite:
- facility: docker
level: info
message: "ERROR|FATAL|PANIC"
new_severity: err
- facility: docker
level: info
message: "WARN"
new_severity: warningAlternatively, use per-container tag matching if you only need rewrite rules for specific containers:
severity_rewrite:
- tag: my-app
level: info
message: "ERROR|FATAL|PANIC"
new_severity: err
- tag: my-app
level: info
message: "WARN"
new_severity: warningOpen http://localhost:8080. Auto-refreshes every 3 seconds. Filter by host, facility, severity, tag, or free-text search. The row limit dropdown controls how many rows are displayed (default 200); selecting a custom date range automatically shows unlimited rows.
Each row has a copy button that copies all fields (timestamp, host, facility, severity, tag, message) to the clipboard. Use the pause button in the filter bar to stop auto-refresh and select text across multiple rows.
Log rows that match any configured alert rule show a bell-off icon. Click it to open the settings modal with a new ignore rule prefilled from that log line's host, facility, tag, severity, and message. Review the fields, adjust the regex if needed, and click Save.
Logyard does not provide authentication or TLS. Use a reverse proxy like Caddy for HTTPS and access control.
Logyard auto-detects both RFC 3164 and RFC 5424 syslog formats. Timestamps are displayed as received -- Logyard does not convert between timezones.
- RFC 3164 (
<PRI>Jun 1 14:30:00 ...): No year, no timezone. The parser fills in the current year and stores the time value as-is. The displayed time matches whatever clock the sender used. Docker's syslog driver defaults to this format. - RFC 5424 (
<PRI>1 2025-06-01T14:30:00+02:00 ...): Full ISO 8601 date with timezone offset. The timestamp is displayed in the sender's timezone (e.g. a message sent with+02:00is displayed at that offset, a message sent withZis displayed as UTC).
docker run -d \
-v ./config.yaml:/data/config.yaml \
-v logyard-data:/data \
-p 514:514/udp \
-p 514:514/tcp \
-p 8080:8080 \
--name logyard \
--restart unless-stopped \
mietzen/logyard:latestOr with docker compose:
services:
logyard:
image: mietzen/logyard:latest
container_name: logyard
volumes:
- ./config.yaml:/data/config.yaml
- logyard-data:/data
ports:
- "514:514/udp"
- "514:514/tcp"
- "8080:8080"
restart: unless-stopped
volumes:
logyard-data:sudo cp logyard /usr/local/bin/
sudo mkdir -p /etc/logyard /var/lib/logyard
sudo cp config.yaml /etc/logyard/
sudo cp logyard.service /etc/systemd/system/
sudo systemctl enable --now logyardPort 514 requires CAP_NET_BIND_SERVICE (included in the service file). During development, use ports above 1024.
A docker-compose setup is included for testing the web UI, alert rules, ignore rules, and digest batching locally. It starts logyard, a mailpit SMTP mock with web UI, and a syslog message generator that sends a continuous stream of realistic messages.
docker compose -f docker-compose.dev.yaml up --build| Service | URL | Description |
|---|---|---|
| Logyard | http://localhost:8080 | Web UI and log console |
| Mailpit | http://localhost:8025 | Email inbox UI (captures alert mails) |
The generator sends a weighted mix of severities (mostly info, some warnings, occasional errors, rare crits) from multiple hosts and tags. Edit dev/config.yaml to test different alert rules, ignore rules, severity rewrites, or digest settings. Changes to the config can be applied live via the settings modal in the Logyard web UI.
Stop the stack with:
docker compose -f docker-compose.dev.yaml down -vIcons by Pictogrammers (Material Design Icons), licensed under the Apache License 2.0.
