1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\System;
9:
10: use DateTime;
11: use DateTimeImmutable;
12:
13: /**
14: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
15: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
16: * @copyright Copyright (c) 2019, Afterlogic Corp.
17: *
18: * @category Core
19: */
20: class Logger
21: {
22: /**
23: * @var string
24: */
25: public static $sEventLogPrefix = 'event-';
26:
27: /**
28: * @var string
29: */
30: public static $sErrorLogPrefix = 'error-';
31:
32: /**
33: * @param string $sDesc
34: * @param string $sModuleName
35: */
36: public static function LogEvent($sDesc, $sModuleName = '')
37: {
38: $oSettings = &Api::GetSettings();
39: if ($oSettings && $oSettings->EnableEventLogging) {
40: $sDate = gmdate('H:i:s');
41: $iIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown';
42: $sUserId = Api::getAuthenticatedUserId();
43:
44: self::Log('Event: ' . $sUserId . ' > ' . $sDesc);
45: self::LogOnly('[' . $sDate . '][' . $iIp . '][' . $sUserId . '][' . $sModuleName . '] > ' . $sDesc, self::GetLogFileDir() . self::GetLogFileName(self::$sEventLogPrefix));
46: }
47: }
48:
49: /**
50: * @param mixed $mObject
51: * @param int $iLogLevel = \Aurora\System\Enums\LogLevel::Full
52: * @param string $sFilePrefix = ''
53: */
54: public static function LogObject($mObject, $iLogLevel = Enums\LogLevel::Full, $sFilePrefix = '')
55: {
56: self::Log(print_r($mObject, true), $iLogLevel, $sFilePrefix);
57: }
58:
59: /**
60: * @param Exceptions\Exception $mObject
61: * @param int $iLogLevel = \Aurora\System\Enums\LogLevel::Error
62: * @param string $sFilePrefix = ''
63: */
64: public static function LogException($mObject, $iLogLevel = Enums\LogLevel::Error, $sFilePrefix = '')
65: {
66: $sMessage = '';
67:
68: $oSettings = & Api::GetSettings();
69: // if ($oSettings && $oSettings->GetValue('LogStackTrace', false))
70: // {
71: // $sMessage = (string) $mObject;
72: // }
73: // else
74: // {
75: $sMessage = 'Exception: ' . (string) $mObject . ', Code: ' . $mObject->getCode() . ', Message: ' . $mObject->getMessage();
76: // }
77:
78: if (0 < \count(Api::$aSecretWords)) {
79: $sMessage = \str_replace(Api::$aSecretWords, '*******', $sMessage);
80: }
81:
82: self::Log($sMessage, $iLogLevel, $sFilePrefix);
83: }
84:
85: /**
86: * @param string $sFilePrefix = ''
87: *
88: * @return string
89: */
90: public static function GetLogFileName($sFilePrefix = '', $iTimestamp = 0)
91: {
92: $oSettings = & Api::GetSettings();
93:
94: $sFileName = "log.txt";
95:
96: if ($oSettings && $oSettings->LogFileName) {
97: $fCallback = ($iTimestamp === 0)
98: ? function ($matches) {
99: return date($matches[1]);
100: }
101: : function ($matches) use ($iTimestamp) {
102: return date($matches[1], $iTimestamp);
103: };
104: $sFileName = preg_replace_callback('/\{([\w|-]*)\}/', $fCallback, $oSettings->LogFileName);
105: }
106:
107: return $sFilePrefix . $sFileName;
108: }
109:
110: public static function getLogFileDateFormat()
111: {
112: $result = '';
113: $oSettings = & Api::GetSettings();
114: if ($oSettings && $oSettings->LogFileName) {
115: preg_match('/\{([\w|-]*)\}/', $oSettings->LogFileName, $matches);
116: if (isset($matches[1])) {
117: $result = $matches[1];
118: }
119: }
120: return $result;
121: }
122:
123: public static function GetLogFileDir()
124: {
125: static $bDir = null;
126: static $sLogDir = null;
127:
128: if (null === $sLogDir) {
129: $oSettings = & Api::GetSettings();
130: if ($oSettings) {
131: $sS = $oSettings->GetValue('LogCustomFullPath', '');
132: $sLogDir = empty($sS) ? Api::DataPath() . '/logs/' : rtrim(trim($sS), '\\/') . '/';
133: }
134: }
135:
136: if (null === $bDir) {
137: $bDir = true;
138: if (!@is_dir($sLogDir)) {
139: @mkdir($sLogDir, 0777);
140: }
141: }
142:
143: return $sLogDir;
144: }
145:
146: /**
147: * @return \MailSo\Log\Logger
148: */
149: public static function SystemLogger()
150: {
151: static $oLogger = null;
152: if (null === $oLogger) {
153: $oLogger = \MailSo\Log\Logger::NewInstance();
154: $oLogger->Add(
155: \MailSo\Log\Drivers\Callback::NewInstance(function ($sDesc) {
156: self::Log($sDesc);
157: })->DisableTimePrefix()->DisableGuidPrefix()
158: );
159: $oLogger->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME);
160:
161: $oSettings = & Api::GetSettings();
162: $oLogger->bLogStackTrace = ($oSettings && $oSettings->GetValue('LogStackTrace', false));
163: }
164:
165: return $oLogger;
166: }
167:
168: /**
169: * @param string $sDesc
170: * @param string $sLogFile
171: */
172: private static function dbDebugBacktrace($sDesc, $sLogFile)
173: {
174: static $iDbBacktraceCount = null;
175:
176: if (null === $iDbBacktraceCount) {
177: $oSettings = & Api::GetSettings();
178: $iDbBacktraceCount = (int) $oSettings->GetValue('DBDebugBacktraceLimit', 0);
179: if (!function_exists('debug_backtrace') || version_compare(PHP_VERSION, '5.4.0') < 0) {
180: $iDbBacktraceCount = 0;
181: }
182: }
183:
184: if (0 < $iDbBacktraceCount && is_string($sDesc) &&
185: (false !== strpos($sDesc, 'DB[') || false !== strpos($sDesc, 'DB ERROR'))) {
186: $bSkip = true;
187: $sLogData = '';
188: $iCount = $iDbBacktraceCount;
189:
190: foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 20) as $aData) {
191: if ($aData && isset($aData['function']) && !in_array(strtolower($aData['function']), array(
192: 'log', 'logonly', 'logend', 'logevent', 'logexception', 'logobject', 'dbdebugbacktrace'))) {
193: $bSkip = false;
194: }
195:
196: if (!$bSkip) {
197: $iCount--;
198: if (isset($aData['class'], $aData['type'], $aData['function'])) {
199: $sLogData .= $aData['class'] . $aData['type'] . $aData['function'];
200: } elseif (isset($aData['function'])) {
201: $sLogData .= $aData['function'];
202: }
203:
204: if (isset($aData['file'])) {
205: $sLogData .= ' ../' . basename($aData['file']);
206: }
207: if (isset($aData['line'])) {
208: $sLogData .= ' *' . $aData['line'];
209: }
210:
211: $sLogData .= "\n";
212: }
213:
214: if (0 === $iCount) {
215: break;
216: }
217: }
218:
219: if (0 < strlen($sLogData)) {
220: try {
221: @error_log('[' . \MailSo\Log\Logger::Guid() . '][DB/backtrace]' . AU_API_CRLF . trim($sLogData) . AU_API_CRLF, 3, $sLogFile);
222: } catch (\Exception $oE) {
223: }
224: }
225: }
226: }
227:
228: /**
229: * @param string $sDesc
230: * @param int $iLogLevel = \Aurora\System\Enums\LogLevel::Full
231: * @param string $sFilePrefix = ''
232: */
233: public static function Log($sDesc, $iLogLevel = Enums\LogLevel::Full, $sFilePrefix = '')
234: {
235: static $bIsFirst = true;
236:
237: $oSettings = &Api::GetSettings();
238:
239: if ($oSettings && $oSettings->EnableLogging && $iLogLevel <= $oSettings->LoggingLevel) {
240: try {
241: $oAuthenticatedUser = Api::getAuthenticatedUser();
242: } catch (\Exception $oEx) {
243: $oAuthenticatedUser = false;
244: }
245: $sFirstPrefix = "";
246: if ($oAuthenticatedUser) {
247: $sFirstPrefix = $oAuthenticatedUser->WriteSeparateLog ? $oAuthenticatedUser->PublicId . '-' : '';
248: }
249: $sLogFile = self::GetLogFileDir() . self::GetLogFileName($sFirstPrefix . $sFilePrefix);
250:
251: $sGuid = \MailSo\Log\Logger::Guid();
252: $aMicro = explode('.', microtime(true));
253: $sDate = gmdate('H:i:s.') . str_pad((isset($aMicro[1]) ? substr($aMicro[1], 0, 2) : '0'), 2, '0');
254: if ($bIsFirst) {
255: $sUri = Utils::RequestUri();
256: $bIsFirst = false;
257: $sPost = (is_array($_POST) && count($_POST) > 0) ? '[POST(' . count($_POST) . ')]' : '[GET]';
258:
259: self::LogOnly(AU_API_CRLF . '[' . $sDate . '][' . $sGuid . '] ' . $sPost . '[ip:' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown') . '] ' . $sUri, $sLogFile);
260:
261: if ($oSettings->GetValue('LogPostView', false)) {
262: self::LogOnly('[' . $sDate . '][' . $sGuid . '] POST > ' . print_r($_POST, true), $sLogFile);
263: } else {
264: self::LogOnly('[' . $sDate . '][' . $sGuid . '] POST > [' . implode(', ', array_keys($_POST)) . ']', $sLogFile);
265: }
266:
267: self::LogOnly('[' . $sDate . '][' . $sGuid . ']', $sLogFile);
268: }
269:
270: self::LogOnly('[' . $sDate . '][' . $sGuid . '] ' . (is_string($sDesc) ? $sDesc : print_r($sDesc, true)), $sLogFile);
271: }
272: }
273:
274: /**
275: * @param string $sDesc
276: * @param string $sLogFile
277: */
278: public static function LogOnly($sDesc, $sLogFile)
279: {
280: if (0 < \count(Api::$aSecretWords)) {
281: $sDesc = \str_replace(Api::$aSecretWords, '*******', $sDesc);
282: }
283:
284: try {
285: @error_log($sDesc . AU_API_CRLF, 3, $sLogFile);
286: } catch (\Exception $oE) {
287: }
288:
289: self::dbDebugBacktrace($sDesc, $sLogFile);
290: }
291:
292: public static function LogSql($sDesc, $iLogLevel = Enums\LogLevel::Full)
293: {
294: if (Api::$bUseDbLog) {
295: $oSettings = &Api::GetSettings();
296:
297: if ($oSettings && $oSettings->EnableLogging && $iLogLevel <= $oSettings->LoggingLevel) {
298: $sLogFile = self::GetLogFileDir() . self::GetLogFileName('sql-');
299:
300: $sGuid = \MailSo\Log\Logger::Guid();
301: $aMicro = explode('.', microtime(true));
302: $sDate = gmdate('H:i:s.') . str_pad((isset($aMicro[1]) ? substr($aMicro[1], 0, 2) : '0'), 2, '0');
303:
304: self::LogOnly('[' . $sDate . '][' . $sGuid . '] ' . $sDesc, $sLogFile);
305: }
306: }
307: }
308:
309: public static function ClearLog($sFileFullPath)
310: {
311: return (@file_exists($sFileFullPath)) ? @unlink($sFileFullPath) : true;
312: }
313:
314: public static function RemoveSeparateLogs()
315: {
316: $sLogDir = self::GetLogFileDir();
317: $sLogFile = self::GetLogFileName();
318: if (is_dir($sLogDir)) {
319: $aLogFiles = array_diff(scandir($sLogDir), array('..', '.'));
320: foreach ($aLogFiles as $sFileName) {
321: if ($sFileName !== $sLogFile && $sFileName !== (self::$sEventLogPrefix . $sLogFile) && strpos($sFileName, $sLogFile) !== false) {
322: unlink($sLogDir . $sFileName);
323: }
324: }
325: }
326: }
327:
328: public static function RemoveOldLogs()
329: {
330: $oSettings = &Api::GetSettings();
331:
332: if ($oSettings) {
333: $bRemoveOldLogs = $oSettings->GetValue('RemoveOldLogs', true);
334: $iRemoveOldLogsDays = (int) $oSettings->GetValue('RemoveOldLogsDays', 2);
335:
336: $sLogDir = self::GetLogFileDir();
337: if (is_dir($sLogDir) && $bRemoveOldLogs) {
338: $aLogFiles = glob($sLogDir . '*.txt');
339: $nowDateTime = new \DateTimeImmutable(date(self::getLogFileDateFormat()));
340: $dateTimeToRemove = $nowDateTime->sub(new \DateInterval(sprintf('P%dD', $iRemoveOldLogsDays)));
341:
342: foreach ($aLogFiles as $sFileName) {
343: $aPathInfo = pathinfo($sFileName);
344: preg_match('/\w+\-([\d\-]+)$/', $aPathInfo['filename'], $matches, 0);
345: if (isset($matches[1])) {
346: $fileDateTime = new \DateTimeImmutable($matches[1]);
347: if ($fileDateTime <= $dateTimeToRemove) {
348: if (file_exists($sFileName)) {
349: unlink($sFileName);
350: }
351: }
352: }
353: }
354: }
355: }
356: }
357:
358: public static function GetLoggerGuid()
359: {
360: $oSettings = &Api::GetSettings();
361:
362: if ($oSettings && $oSettings->EnableLogging) {
363: return \MailSo\Log\Logger::Guid();
364: }
365:
366: return '';
367: }
368: }
369: