# 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:

```bash
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-Session` groups everything one run sends, so each night's backup is one session in the dashboard.
- `X-JustLog-Seq` numbers the batches of a session. `--retry 5` resends a batch after a timeout, a `5xx` or a `429`; if the first attempt did arrive, the server sees the same session and Seq, answers `202` with `X-JustLog-Repeat: 1` and stores nothing twice. Since curl 7.66, `--retry` also waits as long as the `Retry-After` header asks.
- `X-JustLog-UTC-Offset` lets 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:

```bash
#!/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:

```bash
echo 'JUSTLOG3_TOKEN=your-token' > ~/.justlog3 && chmod 600 ~/.justlog3
```

and load it in the cron line:

```bash
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](/docs/http-api/).
