Project

General

Profile

1
<?php
2

    
3
/*
4
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19

    
20
/**
21
 * LogRotation.php
22
 *
23
 * @category     Core
24
 * @package      Core_logging
25
 * @copyright    Manuela v.d.Decken <manuela@isteam.de>
26
 * @author       Manuela v.d.Decken <manuela@isteam.de>
27
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
28
 * @version      0.0.1
29
 * @revision     $Revision: $
30
 * @lastmodified $Date: $
31
 * @since        File available since 29.06.2015
32
 * @description  handles logfiles and compress it if they are too big(like server logs)
33
 */
34
class LogRotation
35
{
36
    protected $iMaxLogfileSize = 20;   // size as MB
37
    protected $iMaxLogfiles    = 10;   //
38
    protected $iScanFrequency  = 3600; // scan each hour
39
    protected $sLogPath        = '';   // AppPath + 'var/log'
40
    protected $aLogFiles       = null; // like 'error.log'
41
    protected $aLogFilesGz     = null; // like 'error.log.1.gz'
42

    
43
/**
44
 * constructor
45
 * @param WbAdaptor $oReg
46
 * @throws AppException
47
 */
48
    public function __construct(WbAdaptor $oReg)
49
    {
50
        $this->sLogPath = $oReg->VarPath.'log/';
51
        if (!file_exists($this->sLogPath)) {
52
            $iOldUmask = umask(0);
53
            if (!mkdir($this->sLogPath, $oReg->FileMode, true)) {
54
                throw new AppException('unable to create LogPath ['.$this->sLogPath.']');
55
            }
56
            umask($iOldUmask);
57
        }
58
    }
59
/**
60
 * proceed compression if needed
61
 */
62
    public function execute()
63
    {
64
        if ($this->checkSemaphore($this->sLogPath.'LOCK')) {
65
            // scan for uncompressed log files
66
            $this->aLogFiles = $this->scanForFiles($this->sLogPath);
67
            foreach($this->aLogFiles as $sFile) {
68
            // iterate matching log files
69
                if ($this->isLogFileFull($this->sLogPath.$sFile)) {
70
                // start processing if uncompressed file exceeds limit
71
                    $aGzFiles = $this->scanForFiles($this->sLogPath, $sFile.'.*.gz');
72
                    // rename existing gz files
73
                    $this->renameExistingGzFiles($aGzFiles);
74
                    // compress logfile into gz archive
75
                    $this->compressFile($this->sLogPath.$sFile, $this->sLogPath.$sFile.'.0.gz');
76
                    // remove logfile (next entry will create new one)
77
                    unlink($this->sLogPath.$sFile);
78
                }
79
            }
80
        }
81
    }
82
/**
83
 *
84
 * @param string $sSemaphoreFile file which holds the timestamp from last scan
85
 * @return bool
86
 * @throws AppException
87
 */
88
    protected function checkSemaphore($sSemaphoreFile)
89
    {
90
        // test if readable file exists
91
        if (!is_readable($sSemaphoreFile)) {
92
            // try to create with current timestamp
93
            if (!touch($sSemaphoreFile)) {
94
                throw new AppException('unable to create semaphore ['.$sSemaphoreFile.']');
95
            }
96
        } else {
97
            // return true if it's time for next scan
98
            return ((filemtime($sSemaphoreFile) + $this->iScanFrequency) > time());
99
        }
100
    }
101
/**
102
 * compress source file into target file
103
 * @param string $sSourceFile
104
 * @param string $sTargetFile
105
 */
106
    protected function compressFile($sSourceFile, $sTargetFile)
107
    {
108
        // try to write/create target file
109
        if (touch($sTargetFile)) {
110
            $oSource = gzopen($sSourceFile, 'rb');
111
            $oTarget = gzopen($sTargetFile, 'wb9');
112
            if ($oSource && $oTarget) {
113
                while (!gzeof($oSource)) {
114
                    // read 64k buffer
115
                    $sBuff = gzread($oSource, 64*1024);
116
                    $iBuffLen = strlen($sBuff);
117
                    // write the buffer
118
                    gzwrite($oTarget, $sBuff, $iBuffLen);
119
                }
120
                // close files and clear buffer
121
                gzclose($oSource);
122
                gzclose($oTarget);
123
                unset($sBuff);
124
            }
125
        }
126
    }
127
/**
128
 * rename files to index+1 and delete files with index >= iMaxLogfiles
129
 * @param array $aGzFiles
130
 * @param string $sBasefile
131
 */
132
    protected function renameExistingGzFiles(array $aGzFiles, $sBasefile)
133
    {
134
        natsort($aGzFiles);
135
        $aGzFiles = array_merge(array_reverse($aGzFiles));
136
        $sPattern = '/^'.preg_quote($sBasefile, '/').'\.([0-9]+)\.gz$/s';
137
        foreach ($aGzFiles as $iKey=>$sFile) {
138
            if ($iKey < $this->iMaxLogfiles) {
139
                if (preg_match($sPattern, $sFile, $aMatch)) {
140
                    $sNewFile = $sBasefile.'.'.(string)($aMatch[1]++).'.gz';
141
                    rename($this->sLogPath.$sFile, $this->sLogPath.$sNewFile);
142
                }
143
            } else {
144
                unlink($this->sLogPath.$sFile);
145
            }
146
        }
147
    }
148
/**
149
 * check size of file
150
 * @param string $sFile
151
 * @return bool true if filesize greater than iMaxLogfileSize
152
 */
153
    protected function isLogFileFull($sFile)
154
    {
155
        $iSize = (int)floor(filesize($sFile) / pow(2, 20));
156
        return ($iSize >= $this->iMaxLogfileSize);
157
    }
158
/**
159
 * scan for log files
160
 * @param string $sLogPath
161
 * @param string $sPattern
162
 * @return array  list of files
163
 */
164
    protected function scanForFiles($sLogPath, $sPattern = '*.log')
165
    {
166
        $sOldDir = getcwd();
167
        chdir($sLogPath);
168
        $aRetval = glob($sPattern, GLOB_NOSORT);
169
        chdir($sOldDir);
170
        return $aRetval;
171
    }
172

    
173
} // end of class LogRotation
(4-4/40)