Springtale writes structured JSON logs to stdout. What happens after that depends on how you run it.
| Run mode | Where logs land | Rotation |
|---|---|---|
cargo run / interactive |
Terminal stdout | None — your terminal is your buffer |
| systemd (Linux) | journald | Configured via journald.conf's SystemMaxUse / SystemKeepFree |
| launchd (macOS) | The path you set in StandardOutPath / StandardErrorPath in the plist |
None — set up newsyslog or rotate manually |
| Docker Compose | The Docker logging driver (default: json-file) |
Driver-specific — set max-size and max-file in compose |
| Tauri desktop | Inside the desktop app's data dir, plus the OS-native log surface | Tauri rotates per session |
Idle: a handful of lines per hour (heartbeat, dead-man tick).
Active: scales linearly with action dispatches. A bot handling 1 message/second writes ~10–20 log lines/second across rule eval + sentinel verdict + dispatch + audit.
A long-running daemon writes 100–500 MB of logs per week under moderate load. Multiply by your audit-retention setting and your trigger volume.
RUST_LOG=info springtaled # default
RUST_LOG=debug springtaled # everything
RUST_LOG=info,springtale_bot=debug springtaled # per-crate
RUST_LOG=info,springtale_sentinel=trace springtaled # sentinel only
RUST_LOG=warn springtaled # quiet mode
The format is the same as tracing-subscriber::EnvFilter.
We don't recommend trace in production — log volume becomes
unmanageable and sentinel verdicts go several lines deep.
{
"timestamp": "2026-05-10T23:14:07.123Z",
"level": "INFO",
"target": "springtale_bot::runtime::tick_steps::update_momentum",
"fields": {
"formation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"tier": "Hot",
"successes": 9
},
"message": "momentum tier transition"
}Key conventions:
formation_id,agent_id,connector_name,rule_idappear as structured fields, not interpolated in the message.- Secrets are never logged. If you see anything that looks like a token, key, or passphrase in a log line, that's a bug — please file an issue.
- User-facing strings (chat message content, file paths) are logged
at
debugand above, notinfo. Production should not log them.
Default config keeps a few weeks of logs and caps total size at ~10%
of disk. Tune in /etc/systemd/journald.conf:
[Journal]
SystemMaxUse=500M
SystemKeepFree=2G
SystemMaxFileSize=50M
SystemMaxFiles=10After editing: sudo systemctl restart systemd-journald.
Querying:
journalctl -u springtaled --since "1 hour ago"
journalctl -u springtaled -p err
journalctl -u springtaled -o json | jq . # parse the structured fieldsAdd a newsyslog stanza at /etc/newsyslog.d/springtale.conf:
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
/Users/YOU/Library/Logs/springtaled.out.log YOU:staff 640 7 * @T00 Z
/Users/YOU/Library/Logs/springtaled.err.log YOU:staff 640 7 * @T00 Z
That rotates daily at midnight, keeps 7 archives, gzips old ones.
newsyslog runs automatically.
In docker-compose.yml:
services:
springtaled:
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"Or switch to the journald driver if you're piping into the host's systemd. Or syslog if you have an aggregator.
springtaled does not handle SIGHUP — it logs to stdout/stderr
and leaves file management to whatever captures them. That means the
classic rename-then-HUP pattern does not work; the daemon would keep
writing to the renamed file's open handle.
If you're redirecting stdout to a file yourself, use copy-truncate semantics so the open file handle stays valid:
# In a cron / launchd timer:
cp /var/log/springtaled.log /var/log/springtaled.log.$(date +%Y%m%d)
: > /var/log/springtaled.log # truncate in place, handle stays open
gzip /var/log/springtaled.log.* # eventually(logrotate users: that's copytruncate in the stanza above.) Better
still, run under systemd/journald or Docker's logging driver and let
them own rotation entirely.
If you're already running Loki, Vector, Fluent Bit, or similar:
- journald source — read directly from journal on Linux systemd installs. Zero config beyond the source.
- stdout source — for Docker / Kubernetes, the container runtime surfaces logs in your normal pipeline.
tracing-opentelemetryexporter — not yet wired intospringtaled. Tracked for a future release. If you need OTLP today, run a sidecar that parses the stdout JSON.
The daemon should never log:
- Vault passphrases or any KDF output.
- API tokens or HMAC keys.
- Connector credentials (anything wrapped in
Secret<T>). - Webhook signature headers.
- Mental-model contents (separate retention policy from operational logs).
If something in this list appears in your logs, it's a bug. File it
under SECURITY.md, not as a regular issue.
These are two different things:
- Log retention — what
journald/newsyslog/ Docker do. Operational, transient. Lose it, you lose debugging fidelity. - Audit retention — the
audit_trailSQLite table written byspringtale-sentinel. Persistent, structured, queryable. Lose it, you lose the forensic record of every action the daemon dispatched.
Audit retention is controlled by [sentinel] audit_retention_days in
your config (default 90 days, can be 0 for forever). The retention
purge runs as a background task; rows older than the threshold are
deleted at the next scheduled run.