Files
Cloud-CMS/lib/Helper/DatabaseLogHandler.php

187 lines
5.4 KiB
PHP
Raw Normal View History

2025-12-02 10:32:59 -05:00
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Helper;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Xibo\Storage\PdoStorageService;
/**
* Class DatabaseLogHandler
* @package Xibo\Helper
*/
class DatabaseLogHandler extends AbstractProcessingHandler
{
/** @var \PDO */
private static $pdo;
/** @var \PDOStatement|null */
private static $statement;
/** @var int Log Level */
protected $level = Logger::ERROR;
/** @var int Track the number of failures since a success */
private $failureCount = 0;
/**
* @param int $level The minimum logging level at which this handler will be triggered
*/
public function __construct($level = Logger::ERROR)
{
parent::__construct($level);
}
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return int
*/
public function getLevel(): int
{
return $this->level;
}
/**
* @param int|string $level
* @return $this|\Monolog\Handler\AbstractHandler
*/
public function setLevel($level): \Monolog\Handler\AbstractHandler
{
$this->level = Logger::toMonologLevel($level);
return $this;
}
/**
* @inheritDoc
* @throws \Exception
*/
protected function write(array $record): void
{
if (self::$statement == null) {
self::$pdo = PdoStorageService::newConnection('log');
$SQL = '
INSERT INTO `log` (
`runNo`,
`logdate`,
`channel`,
`type`,
`page`,
`function`,
`message`,
`userid`,
`displayid`,
`sessionHistoryId`,
`requestId`
) VALUES (
:runNo,
:logdate,
:channel,
:type,
:page,
:function,
:message,
:userid,
:displayid,
:sessionHistoryId,
:requestId
)
';
self::$statement = self::$pdo->prepare($SQL);
}
$params = [
'runNo' => $record['extra']['uid'] ?? '',
'logdate' => $record['datetime']->format('Y-m-d H:i:s'),
'type' => $record['level_name'],
'channel' => $record['channel'],
'page' => $record['extra']['route'] ?? '',
'function' => $record['extra']['method'] ?? '',
'message' => $record['message'],
'userid' => $record['extra']['userId'] ?? 0,
'displayid' => $record['extra']['displayId'] ?? 0,
'sessionHistoryId' => $record['extra']['sessionHistoryId'] ?? 0,
'requestId' => $record['extra']['requestId'] ?? 0,
];
try {
// Insert
self::$statement->execute($params);
// Reset failure count
$this->failureCount = 0;
// Successful write
PdoStorageService::incrementStat('log', 'insert');
} catch (\Exception $e) {
// Increment failure count
$this->failureCount++;
// Try to create a new statement
if ($this->failureCount <= 1) {
// Clear the stored statement, and try again
// this will rebuild the connection
self::$statement = null;
// Try again.
$this->write($record);
}
// If the failureCount is > 1, then we ignore the error.
}
}
/**
* Deleting logs must happen on the same DB connection as the log handler writes logs
* otherwise we can end up with a deadlock where the log handler has written things, locked the table
* and, we're then trying to get the same lock.
* @param string $cutOff
*/
public static function tidyLogs(string $cutOff): void
{
try {
if (self::$pdo === null) {
self::$pdo = PdoStorageService::newConnection('log');
}
$statement = self::$pdo->prepare('DELETE FROM `log` WHERE logdate < :maxage LIMIT 10000');
do {
// Execute statement
$statement->execute(['maxage' => $cutOff]);
// initialize number of rows deleted
$rowsDeleted = $statement->rowCount();
PdoStorageService::incrementStat('log', 'delete');
// pause for a second
sleep(2);
} while ($rowsDeleted > 0);
} catch (\PDOException) {
}
}
}