MacOS – App or script to monitor/log processes starting and stopping

macosscript

I want to be able to start an app or script, leave it running for a few hours, then come back and see a log of what processes started and stopped in that time. This is not the same as Activity Monitor or Top which will only show me a "live" view.

E.g. something like this:

<timestamp> <pid> Safari started
<timestamp> <pid> ssh started
<timestamp> <pid> ssh stopped
<timestamp> <pid> Safari stopped

Any recommendations?

Edit to clarify – I don't already know the names of the processes I want to monitor – I want to find out what processes are starting/stopping.

Best Answer

The following script builds an array of Applications/Processes dynamically. This is an advantage in the sense that you do not need to "know" every process prior to its launch. However, it is important to note that due to this design as the number of processes grow the longer it will take to add a new process to the array. To compensate for this the script keeps the contents of the array cached so as to use them later preventing the need to re-add them to the array.

ALSO: If the Application/Process has not been dynamically loaded into the array prior to the process stopping the logger will leave the "Command" column empty. This will become less and less likely as your dynamically built array builds upon its cached information. Adding to the previous point. At times a "Command" may not be shown in the column yet it has already been added to the array. This is likely caused by the fact that certain process's like "sort" have been removed as they are used in the script and would be constantly starting and stopping (adding false positives to the logger). Currently the data will be put into the file /command_logger. This of course can be changed as you see fit. The config file /tmp/command_logger.plist can also be renamed/moved but remember that it holds the data to the growing App/Process array.

Please feel free to use and modify the following script as you see fit. Please remember to utilize a testing environment prior to running on your primary environment. Enjoy.

Tested on:

•   10.5.x
•   10.6.x
•   10.7.4

The output will include the following:

  • STARTED/STOPPED
  • Epoch (timestamp)
  • Date/Time
  • PID
  • Application/Process

command_logger

NOTE: The following script includes an internal loop.

DYNAMIC APP/PROCESS LOGGER

#!/bin/bash

On="true"

TMP="/tmp/command_logger"
LOG_CONFIG="/command_logger.plist"
LOG="/command_logger"

if [[ ! -e ${TMP} ]]; then

    mkdir ${TMP}

    if [[ ! -e ${TMP}${LOG_CONFIG} ]]; then
        /usr/libexec/PlistBuddy -c "Add:Commands Dict" ${TMP}${LOG_CONFIG} > /dev/null 2>&1
    fi

elif [[ -e ${TMP} ]]; then

    if [[ ! -e ${TMP}${LOG_CONFIG} ]]; then
        /usr/libexec/PlistBuddy -c "Add:Commands Dict" ${TMP}${LOG_CONFIG} > /dev/null 2>&1
        find ${TMP} -type f ! -name '*.plist' -exec rm -f {} \;
    elif [[ -e ${TMP}${LOG_CONFIG} ]]; then
        find ${TMP} -type f ! -name '*.plist' -exec rm -f {} \;
    fi

fi

if [[ ! -e ${LOG} ]]; then
    echo -e "Status\tTimestamp\tDate/Time\t\tPID\tCommand\n" > ${LOG}
elif [[ -e ${LOG} ]]; then

    TITLE=$(head -1 ${LOG} | awk '{print $1}')

    if [[ ${TITLE} != "Status" ]]; then
        echo -e "Status\tTimestamp\tDate/Time\t\tPID\tCommand\n" > ${LOG}
    fi

fi


while [[ ${On} == "true" ]]; do
    IFS=""

    Live_Array=$(ps -Ac | sed 's/  /|/g' | sed 's/ /_/g' | sed 's/[[:digit:]]_.*[[:digit:]]_/ /g' | sed 's/:/!/' | sed 's/$/.comm/' | sed 's/^_//g' | sed 's/^|//g' | sed 's/^_//g' | sed 's/^|.*//g' | sed 's/ $//g' | sed 's/_$//g' | sed 's/|$//g' | sed 's/PID_TTY||.*//g' | awk '{print $2}' | sed 's/.*-sh.*//' | sed 's/.*CMD.*//' | sed 's/.*PID.*//' | sort -u | grep "[[:graph:]]" | tr -s "[\n]" "[,]")
    Live_Array=$(echo ${Live_Array%\,})
    IFS=","
    Live_Array_2=( $Live_Array )
    livarray=${#Live_Array_2[@]}    
    for (( liv=0; liv<${livarray}; liv++ ));
    do  


        /usr/libexec/PlistBuddy -c "Add:Commands:${Live_Array_2[$liv]} bool true" ${TMP}${LOG_CONFIG} 2> /dev/null

        Config_Array=$(/usr/libexec/PlistBuddy -c "Print:Commands" ${TMP}${LOG_CONFIG} | grep "=" | sed 's/=.*//' | sed 's/  //g' | sed 's/^.comm//' | sed 's/ $//g' | grep "[[:graph:]]" | tr -s "[\n]" "[,]")
        Config_Array_2=( $Config_Array )
        conarray=${#Config_Array_2[@]}  


        for (( con=0; con<${conarray}; con++ ));
        do  

            STRING=$(ps -Ac | sed 's/  /|/g' | sed 's/ /_/g' | sed 's/[[:digit:]]_.*[[:digit:]]_/ /g' | sed 's/:/!/'  | sed 's/$/.comm/' | sed 's/^_//g' | sed 's/^|//g' | sed 's/^_//g' | sed 's/^|.*//g' | sed 's/|_.comm/.comm/g' | sed 's/PID_TTY||.*//g' | awk '{print $2,$1}' | sed 's/.*-sh.*//' | sed 's/.*CMD.*//' | sed 's/.*PID.*//' | sort -u)
            Launched_Command=$(echo ${STRING} | awk '{print $1}' | sort -u | awk "/${Config_Array_2[$con]}/")
            PID=$(echo ${STRING} | sort -u | awk '{print $2,$1}' | awk "/${Config_Array_2[$con]}/" | sed 's/ .*//')

            if [[ ${Launched_Command} != "" ]] && [[ ${PID} != "" ]]; then

                DATE=$(date "+%m-%d-%Y %T")
                EPOCH=$(date "+%s") 

                if [[ ${Launched_Command} == ${Config_Array_2[$con]} ]] && [[ ! -e ${TMP}/${Config_Array_2[$con]}-RUNNING ]]; then          
                    echo -e "STARTED\t${EPOCH}\t${DATE}\t${PID}\t${Config_Array_2[$con]}" | sed 's/.comm$//g' | sed 's/_/ /g' | sed 's/.*sort.*//g' | sed 's/.*sed.*//g' | sed 's/awk//g'| awk '/STARTED/' >> ${LOG}
                    rm -f ${TMP}/${Config_Array_2[$con]}-STOPPED
                    touch ${TMP}/${Config_Array_2[$con]}-RUNNING
                elif [[ ${Launched_Command} == ${Config_Array_2[$con]} ]] && [[ -e ${TMP}/${Config_Array_2[$con]}-RUNNING ]]; then
                    :       
                elif [[ ${Launched_Command} == ${Config_Array_2[$con]} ]] && [[ -e ${TMP}/${Config_Array_2[$con]}-RUNNING ]]; then

                    if [[ -e ${TMP}/${Config_Array_2[$con]}-STOPPED ]]; then
                        :
                    elif [[ ! -e ${TMP}/${Config_Array_2[$con]}-STOPPED ]]; then
                        echo -e "STOPPED\t${EPOCH}\t${DATE}\t${PID}\t${Config_Array_2[$con]}" | sed 's/.comm$//g' | sed 's/_/ /g' | sed 's/.*sort.*//g' | sed 's/.*sed.*//g' | sed 's/awk//g' | awk '/STOPPED/' >> ${LOG}
                        rm -f ${TMP}/${Config_Array_2[$con]}-RUNNING
                        touch ${TMP}/${Config_Array_2[$con]}-STOPPED                    
                    fi  

                fi

            elif [[ ${Launched_Command} == "" ]] && [[ -e ${TMP}/${Config_Array_2[$con]}-RUNNING ]]; then

                if [[ -e ${TMP}/${Config_Array_2[$con]}-STOPPED ]]; then
                    :
                elif [[ ! -e ${TMP}/${Config_Array_2[$con]}-STOPPED ]]; then
                    echo -e "STOPPED\t${EPOCH}\t${DATE}\t${PID}\t${Config_Array_2[$con]}" | sed 's/.comm$//g' | sed 's/_/ /g' | sed 's/.*sort.*//g' | sed 's/.*sed.*//g' | sed 's/awk//g' | awk '/STOPPED/' >> ${LOG}
                    rm -f ${TMP}/${Config_Array_2[$con]}-RUNNING
                    touch ${TMP}/${Config_Array_2[$con]}-STOPPED                    
                fi              
            fi      
        done
    done    
done