This is a set of scripts, along with a LaunchAgent and a LaunchDaemon, that tracks when users login and logout of a macOS systtem, as well as when the system starts up or shuts down.
Short answer: last
is probably fine in most cases.
Long answer: last
is three versions out of date, and all the other options are worse.
man last
gives no indication of this. However, if we read man getutxent
(from the "See Also" section of man last
), we can see the following section:
Backward compatibility
Successful calls to
pututxline()
will automatically write equivalent entries into theutmp
,wtmp
andlastlog
files. Programs that read these old files should work as expected. However, directly writing to these files does not make corresponding entries inutmpx
and thewtmpx
andlastlogx
equivalent files, so such write-access is deprecated.
And indeed, man lastlog
(or man utmp
or man wtmp
) are all described as "login records (DEPRECATED)". And although the C interfaces remain, the man page also notes that the relevant files do not exist on macOS 10.5 or later. Their sources of truth are their replacements, utmpx
, wtmpx
, and lastlogx
.
The man page for utmpx
, however, includes this text:
Traditionally, separate files would be used to store the running log of the logins and logouts (
wtmpx
), and the last login of each user (lastlogx
). With the availability of the Apple system log facilityasl(3)
, these separate files can be replace with log entries, which are automatically generated whenutmpx
entries are written. The API to access the logins and logouts is described inendutxent_wtmp(3)
while the last login info is accessible withgetlastlogx(3)
.For compatibility, changes to
utmpx
are reflected inutmp(3)
(in theutmp
,wtmp
andlastlog
files), but not the other way around.
However, if we check man asl
, we see that the first line of the description is "This interface is obsoleted by os_log(3)
."
So the actual source of truth for utmpx
(and transitively utmp
and last
) is the Unified System Log. However, as Apple has converted more and more systems to use the system log, it has started to fill up extremely quickly. On some systems, the log will fill up within 24 hours.
So the options are:
- Use
last
and hope that a) it does not roll over; b) that the chain of deprecated API to deprecated API that Apple has built continues to work. I don't actually know how often the lastlog rolls over, but it does seem to just happen sometimes. And to be fair, the APIs that Apple has set up will probably continue to work, as I imagine they incur little to no maintenance burden. - Read from the system log multiple times per day, to ensure that you don't miss anything. I actually tried to write code that would read logins from the system log, and it was a pain.
- Roll your own solution, like this one.
This project is made up of a LaunchAgent (a user-scoped service) and a LaunchDaemon (a system-scoped service), which each run a script.
On macOS, LaunchAgents can be installed to two directories1: /Library/LaunchAgents
, and ~/Library/LaunchAgents
. An agent in ~/Library/LaunchAgents
will run only for that user, while one in the system /Library/LaunchAgents
will run for every user. Regardless of which directory the agent is in, it will run as the current user, starting up on a graphical login and receiving SIGTERM
on logout.
The script attached to the LaunchAgent appends "$(whoami),login,$(date)"
to a CSV file at /var/login-data/file.csv
(that is assumed to exist) when the script starts, and then it simply waits forever. When it receives SIGTERM
, it appends "$(whoami),logout,$(date)"
to the same CSV file, and then exits cleanly.
LaunchDaemons, on the other hand, must be put in /Library/LaunchDaemons
2, start at boot, and stop at shutdown, running as root
. They also receive SIGTERM
when shutting down.
The script attached to the LaunchDaemon is a little more complicated. It starts by ensuring that the CSV file:
- exists
- is owned by root (
chown root:wheel
) - is world-writable (
chmod 666
) - is append-only (
chflags sappend
)
Then it puts an entry in the file indicating startup, and waits for SIGTERM
, upon which it puts an entry in the file indicating shutdown.
- Put the scripts in
/usr/local/bin
and make them executable (sudo chmod +x /usr/local/bin/login-data-*.sh
) - Put
com.samasaur.login-data.system.plist
in/Library/LaunchDaemons
- Put
com.samasaur.login-data.user.plist
in/Library/LaunchAgents
- For each plist file
- Remove any quarantine flags (
xattr -c /path/to/plist
) - Ensure the file is owned by user
root
and groupwheel
(chown root:wheel /path/to/plist
) - Ensure the file permissions are 644 (
chmod 644 /path/to/plist
)
- Remove any quarantine flags (
- Reboot
There is probably a way to activate these services without a reboot (
launchctl enable
?) but it is always a pain. Rebooting will start the system daemon and logging back in will start the user agent.
Footnotes
-
There are also pre-installed LaunchAgents in
/System/Library/LaunchAgents
, but you cannot add or remove LaunchAgents from this directory, so it is irrelevant in this case. ↩ -
Again, LaunchDaemons can be in
/System/Library/LaunchDaemons
, but as these are part of the Signed System Volume, you cannot add or remove them from this directory. ↩