Skip to content
Snippets Groups Projects
cronlog.class.php 10.07 KiB
<?php
/**
 * central cronlog viewer
 * 
 * cronlog contains non visual methods
 * @see cronlog-renderer.class.php
 *
 * @author hahn
 */

class cronlog {
    
    
    protected $_sDataDir = "__APPDIR__/data";
    protected $_iTtlCache = 60; // in sec

    /**
     * when show an error for expired jobs (latency to execute job and sync logs)
     * @var integer
     */
    protected $_iExpiredJobsFailAfter = 60*30; // in sec
    protected $_aSkipJoblogs = array();
    
    protected $_aServers = array();
    protected $_sActiveServer = false;
    
    protected $_sFileFilter_serverjoblog = '*joblog*.done';
    protected $_sFileFilter_serverlog = '*.log';

    // ----------------------------------------------------------------------
    // MAIN
    // ----------------------------------------------------------------------

    /**
     * init
     * @return boolean
     */
    public function __construct() {

        // read config
        if (file_exists(__DIR__.'/../config/inc_cronlog.php')){
            $aCfgTemp=include(__DIR__.'/../config/inc_cronlog.php');
            $this->_sDataDir = isset($aCfgTemp['sDatadir']) ? $aCfgTemp['sDatadir'] : $this->_sDataDir;
            $this->_iTtlCache = isset($aCfgTemp['iTtlCache']) ? (int)$aCfgTemp['iTtlCache'] : $this->_iTtlCache;
            $this->_iExpiredJobsFailAfter = isset($aCfgTemp['iExpiredJobsFailAfter']) ? (int)$aCfgTemp['iExpiredJobsFailAfter'] : $this->_iExpiredJobsFailAfter;
            $this->_aSkipJoblogs = isset($aCfgTemp['aHidelogs']) && is_array($aCfgTemp['aHidelogs']) ? $aCfgTemp['aHidelogs'] : $this->_aSkipJoblogs;        
        }
        $this->_sDataDir = str_replace("__APPDIR__", dirname(dirname(__FILE__)), $this->_sDataDir);
        $this->_sDataDir = str_replace('\\', '/', $this->_sDataDir);
        
        $this->getServers();
                
        return true;
    }
    
    // ----------------------------------------------------------------------
    // private
    // ----------------------------------------------------------------------

    
    /**
     * chaching: get the full path of directory for caching
     * @return type
     */
    protected function _getCacheDir(){
        return $this->_sDataDir.'/__cache';
    }

    /**
     * caching: get full path of a caching item
     * @param type $sTaskId
     * @return type
     */
    protected function _getCacheFile($sTaskId){
        return $this->_getCacheDir().'/'.$sTaskId;
    }

    /**
     * read logs: get full path to a servers cronjob logdata
     * @return type
     */
    protected function _getServerlogDir(){
        return $this->_sDataDir.'/'.$this->_sActiveServer;
    }

    /**
     * caching: get cached data if they exist and aren't expired
     * @param  string  $sTaskId
     * @return boolean|array
     */
    protected function _getCacheData($sTaskId){
        // DISABLE CACHE return false;
        $sFile=$this->_getCacheFile($sTaskId);
        if(file_exists($sFile)){
            if (filemtime($sFile)>(date('U')-$this->_iTtlCache)){
                // echo "USE cache $sFile<br>";
                return unserialize(file_get_contents($sFile));
            } else {
                // echo "DELETE cache $sFile<br>";
                unlink($sFile);
            }
        }
        return false;
    }
    /**
     * read logs: parse a single line in the joblog and return has with all key value items
     * @param  string  $sLine  single line in the log
     * @return array
     */
    protected function _parseJoblogLine($sLine){
        $aReturn=array();
        // echo "DEBUG $sLine<br>";
        // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0
        $sLine=str_replace("\n", '', $sLine);
        $sLine=str_replace(':', '", "', $sLine);
        $sLine=str_replace('=', '": "', $sLine);
        $sLine='{"'.$sLine.'"}';
        // echo "DEBUG $sLine<br><br>";
        $aReturn=json_decode($sLine, 1);
        if(!is_array($aReturn)){
            echo "not a JSON string<br>";
            echo "DEBUG $sLine<br><br>";
            die();
        }
        return $aReturn;
    }

    /**
     * read logs: parse the whole cronwrapper logfile and return a hash
     * @param  string  $sFile  filename with full path
     */
    protected function _parseLogfile($sFile) {
        $aReturn=array(
            'SCRIPTNAME'=>false,
            'SCRIPTTTL'=>false,
            'SCRIPTSTARTTIME'=>false,
            'SCRIPTLABEL'=>false,
            'SCRIPTENDTIME'=>false,
            'SCRIPTEXECTIME'=>false,
            'SCRIPTRC'=>false,
            // 'SCRIPTOUT'=>array(),
        );
        $fileHandle = fopen($sFile, "r");
        while (($line = fgets($fileHandle)) !== false) {
            // get key ... the part before "="
            $sKey=trim(preg_replace('/=.*/', '', $line));
            if($sKey && isset($aReturn[$sKey])){
                // add value ... the part behind "="
                $aReturn[$sKey]=preg_replace('/^([A-Z]*\=)/', '', $line);
            }
        }
        fclose($fileHandle);
        
        // fetch unit timestamp from date values (they are like "2018-09-30 03:40:05, 1538278805")
        $aReturn['SCRIPTSTARTTIME']=(int)preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']);
        $aReturn['SCRIPTENDTIME']=(int)preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']);
        
        // remove " s" from exec time value
        $aReturn['SCRIPTEXECTIME']=preg_replace('/\ s$/', '', $aReturn['SCRIPTEXECTIME']);
        
        return $aReturn;
        
    }
    
    /**
     * caching: write new data; it returns the success of write operation as bool
     * @param  string  $sTaskId
     * @param  [any]   $data      data to store; can be any serializable value
     * @return boolean
     */
    protected function _writeCacheData($sTaskId, $data){
        $sFile=$this->_getCacheFile($sTaskId);
        // echo "WRITE cache $sFile<br>";
        return file_put_contents($sFile, serialize($data));
    }

    // ----------------------------------------------------------------------
    // public getter
    // ----------------------------------------------------------------------
    
    /**
     * get currently selected server
     * @return string
     */
    public function getServer(){
        return $this->_sActiveServer;
    }
    
    /**
     * get array with existing servers in data dir
     * @return array
     */
    public function getServers(){
        
        if(is_array($this->_aServers) && count($this->_aServers)){
            return $this->_aServers;
        }
        
        $this->_aServers=array();
        // echo "DEBUG DATADIR: " . $this->_sDataDir."<br>";
        if (!is_dir($this->_sDataDir)){
            echo "WARNING: no data. Check sDatadir in the config and set it to an existing directory.<br>";
            die();
        }

        if ($handle = opendir($this->_sDataDir)) {
            while (false !== ($entry = readdir($handle))) {
                if ($entry != "." && $entry != ".." && $entry != "__cache" && is_dir($this->_sDataDir.'/'.$entry)) {
                    // echo "DEBUG $entry<br>\n";
                    $this->_aServers[$entry]=array();
                }
            }
            closedir($handle);
        }
        ksort($this->_aServers);
        return $this->_aServers;
    }
    
    /**
     * get logs from jobfilea of the current or given server
     * @param  boolean  $bUseSkip  hide jobs if their label matches the skip list; default: true
     * @return array
     */
    public function getServerJobHistory($bUseSkip=true){
        $aReturn=array();
        $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer;

        $aData=$this->_getCacheData($sTaskId);
        if($aData){
            return $aData;
        }

        $aData=array();
            
        foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_serverjoblog) as $sMyJobfile){
            // echo "DEBUG: $sMyJobfile<br>";
            $fileHandle = fopen($sMyJobfile, "r");
            while (($line = fgets($fileHandle)) !== false) {
                // send the current file part to the browser
                $aData=$this->_parseJoblogLine($line);
                if($bUseSkip && array_search($aData['job'], $this->_aSkipJoblogs)===false){
                    $aReturn[$aData['start']]=$aData;
                }
            }
            fclose($fileHandle);
        }
        krsort($aReturn);
        $this->_writeCacheData($sTaskId, $aReturn);
        return $aReturn;
    }
    

    /**
     * get logs from jobfilea of the current or given server
     * @return array
     */
    public function getServersLastLog(){
        $aReturn=array();
        $aData=array();
        foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_serverlog) as $sMyJobfile){
            // echo "DEBUG: log file $sMyJobfile<br>";
            $aData=$this->_parseLogfile($sMyJobfile);
            $aData['server']=$this->_sActiveServer;
            $aData['logfile']= $this->_sActiveServer.'/'.basename($sMyJobfile);
            $aReturn[$aData['SCRIPTSTARTTIME'].$sMyJobfile]=$aData;
        }
        rsort($aReturn);
        return $aReturn;
    }
    
    // ----------------------------------------------------------------------
    // public setter
    // ----------------------------------------------------------------------
   
    /**
     * set which server is selected
     * The given server must exist as directory (that contains its logs)
     * @param  string  $sServer  server name
     * @return string
     */
    public function setServer($sServer){
        $this->_sActiveServer=false;
        if($sServer==='ALL'){
            return false;
        }
        if($sServer && !array_key_exists($sServer, $this->_aServers)){
            echo "WARNING: server [$sServer] does not exist<br>";
            return false;
        }
        $this->_sActiveServer=$sServer;
        
        return $this->_sActiveServer;
    }
    
    
}