Log Shell Script Messages with postlog
postlog lets a backup or maintenance script log a message through Postfix's own logging path, without you writing a syslog wrapper. This installed command is Postfix 3.8.6, package version 3.8.6-1ubuntu0.1. Budget ten minutes. You need the postfix package and a shell; most of this runs as an ordinary user.
The route
Jump straight to the step you need, or tick off Done means at the end.
Safety boundary
This writes log records, not email. Those records may still be forwarded to a central logger or kept somewhere with different access controls than you expect. Never put passwords, tokens or private message content in a log line.
1. Confirm the installed command
Check which executable the shell will use and note the Postfix version:
$ command -v postlog
/usr/sbin/postlog
$ postconf mail_version
mail_version = 3.8.6
The manual describes postlog as a Postfix-compatible logging interface for shell scripts, installed here by the postfix package. It normally runs without sudo; do not grant it extra permissions or change its ownership to make a script work.
Checkpoint
If command -v postlog comes back empty, stop and install or repair Postfix through your normal package process. Installing packages changes system state, which is a different job from testing logging.
2. Write one record with a useful tag
Pass the message as the remaining arguments, and pick a tag that identifies the script:
$ postlog -t backup-check 'backup pre-flight passed'
postfix/postlog: backup pre-flight passed
$ printf '%s\n' "$?"
0
The text arguments become one record. When standard error is a terminal, the installed command also echoes the record there, and it always sends logging on to syslogd or postlogd. The printed prefix carries the Postfix process name and your message. A non-zero status means the logging call itself failed, so capture it if the script cannot safely carry on.
Keep the tag short and stable: it identifies the record, it is not a second message field. Leave off -t and Postfix supplies a default:
$ postlog 'message with the default tag'
Use -- before text that starts with a hyphen, so it is unambiguously a message and not an option:
$ postlog -t backup-check -- '-temporary file was not found'
That matters in scripts where a variable might start with -. Quote variables so spaces stay inside the record you intended.
3. Set a severity deliberately
Use -p for severity. The accepted values are info, warn, error, fatal and panic, and info is the default:
$ postlog -p warn -t backup-check 'archive is older than expected'
postfix/postlog: archive is older than expected
$ postlog -p error -t backup-check 'archive verification failed'
Choose the level for what actually happened, not for how much output you want to see. An unknown severity is rejected outright:
$ postlog -p nonsense -t backup-check 'not sent'
postfix/postlog: fatal: bad severity: "nonsense"
$ printf '%s\n' "$?"
1
Warning
Do not test fatal or panic casually in a production script. Postfix 3.1 and later pause for one second after logging either. They are failure severities, not extra-loud debug levels. If you must exercise that path, do it in an isolated shell and budget for the deliberate delay.
4. Log a stream one line at a time
Skip the message arguments and postlog reads standard input instead, logging each line as its own record. That makes it a fit for a small loop, or for piping in another command's output:
$ printf '%s\n' 'started' 'checked 12 files' 'finished' | postlog -t backup-check
postfix/postlog: started
postfix/postlog: checked 12 files
postfix/postlog: finished
$ printf '%s\n' "$?"
0
Newlines separate records here. Do not pipe arbitrary command output through this if it might carry secrets or unbounded noise: a chatty producer can bury the useful records in the same logging path. Capture and sanitise the result first, then call postlog once with the summary.
5. Put it in a script and keep errors distinct
A bare call is fine for a best-effort notice. For anything you actually check, keep the logging result separate from the result of the work it describes:
if postlog -p info -t backup-check 'backup pre-flight passed'; then
printf '%s\n' 'log record accepted'
else
status=$?
printf 'could not write backup log, status %s\n' "$status" &2
exit "$status"
fi
Do not treat a successful log call as proof that the backup, deployment or repair it describes actually succeeded. It only confirms this one logging attempt went through. Check the real operation on its own terms.
For a diagnostic run, add -v; repeat it for more verbosity. Take the option back out once you are done troubleshooting, so routine logs stay readable.
6. Understand configuration without editing it
By default the command reads Postfix's main.cf from the configured directory. Ask Postfix where that is on this host:
$ postconf -h config_directory
/etc/postfix
Use -c /path/to/configuration to read main.cf from a named directory for one call. MAIL_CONFIG does the same job as an environment variable. Treat an alternate configuration directory as an administrative change: check its ownership and contents first, and never point a privileged Postfix logging path at somewhere an untrusted user can write to.
Where records end up depends on more than the command line. syslog_facility defaults to mail here, and syslog_name supplies the prefix for the process name in syslog records. Postfix 3.4 and later can also route through the postlogd service, with maillog_file naming its optional logfile and postlog_service_name naming the service entry. Check those before promising anyone a particular logfile:
$ postconf syslog_facility syslog_name maillog_file postlog_service_name
syslog_facility = mail
syslog_name = ${multi_instance_name?{$multi_instance_name}:{postfix}}
maillog_file =
postlog_service_name = postlog
Exact output depends on local configuration. An empty maillog_file here just means that parameter is not selecting a Postfix logfile: it does not mean the record vanishes. Whatever syslog or Postfix logging setup is active still decides where it lands.
7. Avoid obsolete and dangerous assumptions
The -i option is obsolete: it used to request a process ID in the tag, but Postfix 3.4 and later always include the PID, so do not design new scripts around it. Treat the PID as diagnostic context, not a stable job identifier; use an explicit tag or message field for correlation instead.
postlog is built to work with set-group-ID privileges when it talks to postlogd. Do not copy that permission model onto a hand-built wrapper, and do not reach for chmod or chown as a workaround if logging fails. Instead check the command's exit status, the Postfix configuration, and the service or system logger. Any change to Postfix service configuration can affect other mail and logging paths too, so back it up and follow your site's change process before touching it.
Done means
- Command verified.
postlogis the installed Postfix build, version known. - Tags stable. Single messages use a fixed
-ttag with variables quoted. - Severity earned. Only documented values used,
fatalandpanicreserved for real failures. - Stream input bounded. Piped input is suitable, non-sensitive and not unbounded.
- Exit status checked. Scripts distinguish
postlog's own status from the operation it logs. - Destination confirmed. Active configuration and logging route checked before relying on a logfile.