Logging with launchd

Apple switched from cron to launchd a while ago, but the fact that launchd allows you to log the output of your program or script quite easily seems to be shrouded in mystery for no apparent reason. Do you know how to create/write logfiles using launchd-plists? If not, read on.

Every program/script should write its messages to a logfile, period. It just makes sense to be able to figure out what happened or went wrong after the fact. So far, so obvious. A program or script usually takes advantage of, either some kind of framework to write the logs, or relays the work — by using STDOUT and STDERR — to the executing shell. (I know I’m simplifying here, but bear with me.) Let’s assume, that we are using a bash-script, controlled by launchd, to do some work and relay the logging to the shell. Meaning we use echo inside the script to write out diagnostic messages like so:

echo "`date +"%b %d %Y %H:%M"` $0: Everything is peachy."

We write the date and time, the name of the program, and the message we want to convey. When run on the command-line, the message shows up in the terminal. When run via launchd there is no STDOUT to write to; the string probably ends up in the system.log and we have to search for it. Enter the magic of launchd.

The PropertyList-1.0.dtd for launchd luckily contains two keys to make our life easier, the first one is called StandardOutPath and is used inside a launchd-plist like so:

<key>StandardOutPath</key>
<string>/var/log/progname.log</string>

launchd interprets this key and string as “if there is something written to STDOUT, then write it to the file pointed to by the string.” In the example above to the file /var/log/progname.log. Great, but what about the errors? Glad you asked.

The second key is called StandardErrorPath and will process everything written to STDERR.

<key>StandardErrorPath</key>
<string>/var/log/progname_err.log</string>

You could use the same file for both by using the same filename, should you be inclined to do so. I prefer to separate the messages.

Happy logging everyone.

Don’t you hate it when posts like this do not contain a full example? ;-) I do. So here it goes:

The bash-script (named sillyscript.sh) doesn’t do much, but you get the idea:

#!/usr/bin/env bash
MYLOGLINE="`date +"%b %d %Y %H:%M"` $0:"
echo "$MYLOGLINE I’m alive!"
#uncomment to create an error
#cp /testfoobar /tmp/`

And here is a simple launchd-plist to put the script in action:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>label</key>
<string>com.erikslab.sillyscript</string>
<key>ProgramArguments</key>
<array>
<string>/Users/Shared/sillyscript/sillyscript.sh</string>
</array>
<key>StandardErrorPath</key>
<string>/Users/Shared/sillyscript/sillyscript_err.log</string>
<key>StandardOutPath</key>
<string>/Users/Shared/sillyscript/sillyscript.log</string>
<key>StartInterval</key>
<integer>120</integer>
</dict>
</plist>

Note that everything takes place in a folder named sillyscript located in /Users/Shared, meaning if you want to start trying using the example shown, you would have to create that folder.

Now back to work. What? Still not satisfied? OK.

Use

launchctl load ~/Library/LaunchAgents/com.erikslab.sillyscript.plist

(after copying the file com.erikslab.sillyscript.plist to ~/Library/LaunchAgents of course) and watch your logfile in /Users/Shared/sillyscript/ grow in two-minute intervals.

Again, happy logging everyone. ;-) (And don’t forget to launchctl unload and remove the plist after you are done playing with it. )

Post a Comment

Your email is never published nor shared. Required fields are marked *, comments are moderated.

*
*