Blog / Guide
Send logs from a Bash script or cron job with curl
Ship the output of shell scripts and cron jobs to JustLog3 Cloud with one curl call per run, with retries that never store a batch twice and Telegram alerts for runaway jobs.
A nightly backup or a cron job usually logs to a file nobody opens until something has been broken for a week. This guide sends the whole output of a shell job to JustLog3 Cloud with one curl call per run, so you can read it from anywhere and get a Telegram message when a run goes wrong.
All it needs is bash, gzip and curl 7.66 or newer.
The sender function
Put your token in the JUSTLOG3_TOKEN environment variable (see the end of this guide for cron). Then add this function to your script:
JL3_URL="https://jl3-cloud.site/api/logs/"
JL3_SESSION="$(basename "$0")-$(date -u +%Y%m%dT%H%M%SZ)"
JL3_SEQ=0
# Seconds east of UTC, e.g. 18000 for +0500.
utc_offset() {
local zone seconds
zone=$(date +%z)
seconds=$(( 10#${zone:1:2} * 3600 + 10#${zone:3:2} * 60 ))
[ "${zone:0:1}" = "-" ] && seconds=$(( -seconds ))
echo "$seconds"
}
# jl3_send FILE: send the lines of FILE as one gzip-compressed batch.
jl3_send() {
JL3_SEQ=$(( JL3_SEQ + 1 ))
gzip -c "$1" > "$1.gz"
curl --silent --show-error --fail --retry 5 \
-X POST "$JL3_URL" \
-H "Authorization: Bearer $JUSTLOG3_TOKEN" \
-H "Content-Type: text/plain" \
-H "Content-Encoding: gzip" \
-H "X-JustLog-Logger: $(basename "$0")" \
-H "X-JustLog-Session: $JL3_SESSION" \
-H "X-JustLog-Seq: $JL3_SEQ" \
-H "X-JustLog-UTC-Offset: $(utc_offset)" \
--data-binary @"$1.gz"
local status=$?
rm -f "$1.gz"
return $status
}
What the headers do:
X-JustLog-Sessiongroups everything one run sends, so each night's backup is one session in the dashboard.X-JustLog-Seqnumbers the batches of a session.--retry 5resends a batch after a timeout, a5xxor a429; if the first attempt did arrive, the server sees the same session and Seq, answers202withX-JustLog-Repeat: 1and stores nothing twice. Since curl 7.66,--retryalso waits as long as theRetry-Afterheader asks.X-JustLog-UTC-Offsetlets the dashboard show the times your server saw.
The body is the file as it is: every line becomes one log line.
Wrap the job
Run the real work in a function, collect its output in a temporary file and send the file at the end:
#!/usr/bin/env bash
set -o pipefail
# ... the jl3_send function from above ...
OUT="/backups/app-$(date +%F).sql.gz"
main() {
echo "backup started"
pg_dump app | gzip > "$OUT" || { echo "ERROR pg_dump failed"; return 1; }
echo "backup finished: $(du -h "$OUT" | cut -f1)"
}
LOG=$(mktemp)
status=0
main > "$LOG" 2>&1 || status=$?
echo "exit status: $status" >> "$LOG"
jl3_send "$LOG" || echo "could not upload the log" >&2
rm -f "$LOG"
exit "$status"
A failed upload never fails the job: the backup's own exit status is what cron sees.
For a long job, call jl3_send on parts of the output as you go. A request may carry 1 MB as sent and 4 MB of text after decompression; Seq goes up with every call, so the parts keep their order.
Get a message when a run goes wrong
Link Telegram from the dashboard (Telegram notifications → Create secure link). Every time one session grows past another 1,000 lines, the bot sends you a message. A healthy backup writes three lines; a job stuck in a retry loop writes thousands, and you hear about it the same night.
When something did break, open the AI assistant with Logs switched on and ask "Why did last night's backup fail?". It reads the session and answers with the lines that matter.
Run it from cron
Keep the token out of the crontab itself. Put it in a file only the job's user can read:
echo 'JUSTLOG3_TOKEN=your-token' > ~/.justlog3 && chmod 600 ~/.justlog3
and load it in the cron line:
0 3 * * * . "$HOME/.justlog3" && export JUSTLOG3_TOKEN && /opt/jobs/backup.sh
The Free plan takes 5,000 lines a day, which is plenty for a few jobs. The full request format is in the HTTP API docs.
Try it on your own logs
The Free plan takes 5,000 lines a day, with Telegram alerts and the AI assistant included. No card.
Start free