PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,95 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Formatter;
use Monolog\Formatter\FormatterInterface;
/**
* Formats a log message into a line of text using our custom Piwik log format.
*/
class LineMessageFormatter implements FormatterInterface
{
/**
* The log message format string that turns a tag name, date-time and message into
* one string to log.
*
* @var string
*/
private $logMessageFormat;
private $allowInlineLineBreaks;
/**
* @param string $logMessageFormat
* @param bool $allowInlineLineBreaks If disabled, a log message will be created for each line
*/
public function __construct($logMessageFormat, $allowInlineLineBreaks = true)
{
$this->logMessageFormat = $logMessageFormat;
$this->allowInlineLineBreaks = $allowInlineLineBreaks;
}
public function format(array $record)
{
$class = isset($record['extra']['class']) ? $record['extra']['class'] : '';
$date = $record['datetime']->format('Y-m-d H:i:s T');
$message = trim($record['message']);
if ($this->allowInlineLineBreaks) {
$message = str_replace("\n", "\n ", $message); // intend lines
$messages = array($message);
} else {
$messages = explode("\n", $message);
}
$total = '';
foreach ($messages as $message) {
$message = $this->prefixMessageWithRequestId($record, $message);
$total .= $this->formatMessage($class, $message, $date, $record);
}
return $total;
}
private function formatMessage($class, $message, $date, $record)
{
$message = str_replace(
array('%tag%', '%message%', '%datetime%', '%level%'),
array($class, $message, $date, $record['level_name']),
$this->logMessageFormat
);
$message .= "\n";
return $message;
}
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
private function prefixMessageWithRequestId(array $record, $message)
{
$requestId = isset($record['extra']['request_id']) ? $record['extra']['request_id'] : '';
$message = trim($message);
if ($requestId) {
$message = '[' . $requestId . '] ' . $message;
}
return $message;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\AbstractProcessingHandler;
use Piwik\Common;
use Piwik\Db;
/**
* Writes log to database.
*/
class DatabaseHandler extends AbstractProcessingHandler
{
protected function write(array $record)
{
$sql = sprintf(
'INSERT INTO %s (tag, timestamp, level, message) VALUES (?, ?, ?, ?)',
Common::prefixTable('logger_message')
);
$queryLog = Db::isQueryLogEnabled();
Db::enableQueryLog(false);
Db::query($sql, array(
$record['extra']['class'],
$record['datetime']->format('Y-m-d H:i:s'),
$record['level_name'],
trim($record['formatted'])
));
Db::enableQueryLog($queryLog);
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\AbstractProcessingHandler;
/**
* Simply echos all messages.
*/
class EchoHandler extends AbstractProcessingHandler
{
protected function write(array $record)
{
$message = $record['level_name'] . ': ' . $record['message'];
echo $message . "\n";
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\AbstractHandler;
use Monolog\Logger;
/**
* Handler used to detect whether a certain level of log has been emitted.
*/
class FailureLogMessageDetector extends AbstractHandler
{
/**
* @var boolean
*/
private $hasEncounteredImportantLog = false;
public function __construct($level = Logger::WARNING)
{
parent::__construct($level, $bubble = true);
}
public function handle(array $record)
{
$this->hasEncounteredImportantLog = true;
}
/**
* @return bool
*/
public function hasEncounteredImportantLog()
{
return $this->hasEncounteredImportantLog;
}
/**
* for tests
*/
public function reset()
{
$this->hasEncounteredImportantLog = false;
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\StreamHandler;
use Piwik\Exception\MissingFilePermissionException;
use Piwik\Filechecks;
/**
* Writes log to file.
*
* Extends StreamHandler to be able to have a custom exception message.
*/
class FileHandler extends StreamHandler
{
protected function write(array $record)
{
try {
parent::write($record);
} catch (\UnexpectedValueException $e) {
$ex = new MissingFilePermissionException(
Filechecks::getErrorMessageMissingPermissions($this->url)
);
$ex->setIsHtmlMessage();
throw $ex;
}
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\AbstractHandler;
class LogCaptureHandler extends AbstractHandler
{
/**
* @var array
*/
private $allLogs;
public function handle(array $record)
{
$this->allLogs[] = $record;
}
/**
* Returns all records. The records should be processed, so one could just use $record['message'].
*
* @return array[]
*/
public function getAllRecords()
{
return $this->allLogs;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Handler;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Piwik\Common;
use Piwik\Notification;
use Piwik\Notification\Manager;
use Zend_Session_Exception;
/**
* Writes log messages into HTML notification box.
*/
class WebNotificationHandler extends AbstractProcessingHandler
{
public function isHandling(array $record)
{
if (!empty($record['context']['ignoreInScreenWriter'])) {
return false;
}
return parent::isHandling($record);
}
protected function write(array $record)
{
switch ($record['level']) {
case Logger::EMERGENCY:
case Logger::ALERT:
case Logger::CRITICAL:
case Logger::ERROR:
$context = Notification::CONTEXT_ERROR;
break;
case Logger::WARNING:
$context = Notification::CONTEXT_WARNING;
break;
default:
$context = Notification::CONTEXT_INFO;
break;
}
$message = $record['level_name'] . ': ' . htmlentities($record['message'], ENT_COMPAT | ENT_HTML401, 'UTF-8');
$notification = new Notification($message);
$notification->context = $context;
$notification->flags = 0;
try {
Manager::notify(Common::getRandomString(), $notification);
} catch (Zend_Session_Exception $e) {
// Can happen if this handler is enabled in CLI
// Silently ignore the error.
}
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog;
use Piwik\Plugin;
class Monolog extends Plugin
{
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Processor;
use Piwik\Plugin;
/**
* Records the name of the class that logged.
*/
class ClassNameProcessor
{
private $skippedClasses = array(
__CLASS__,
'Piwik\Log',
'Piwik\Piwik',
'Piwik\CronArchive',
'Monolog\Logger',
);
public function __invoke(array $record)
{
$record['extra']['class'] = $this->getLoggingClassName();
return $record;
}
/**
* Returns the name of the plugin/class that triggered the log.
*
* @return string
*/
private function getLoggingClassName()
{
$backtrace = $this->getBacktrace();
$name = Plugin::getPluginNameFromBacktrace($backtrace);
// if we can't determine the plugin, use the name of the calling class
if ($name == false) {
$name = $this->getClassNameThatIsLogging($backtrace);
}
return $name;
}
private function getClassNameThatIsLogging($backtrace)
{
foreach ($backtrace as $line) {
if (isset($line['class'])) {
return $line['class'];
}
}
return '';
}
private function getBacktrace()
{
if (version_compare(phpversion(), '5.3.6', '>=')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
} else {
$backtrace = debug_backtrace();
}
$skippedClasses = $this->skippedClasses;
$backtrace = array_filter($backtrace, function ($item) use ($skippedClasses) {
if (isset($item['class'])) {
return !in_array($item['class'], $skippedClasses);
}
return true;
});
return $backtrace;
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Processor;
use Piwik\ErrorHandler;
use Piwik\Log;
/**
* Process a log record containing an exception to generate a textual message.
*/
class ExceptionToTextProcessor
{
public function __invoke(array $record)
{
if (! $this->contextContainsException($record)) {
return $record;
}
/** @var \Exception $exception */
$exception = $record['context']['exception'];
$exceptionStr = sprintf(
"%s(%d): %s\n%s",
$exception instanceof \Exception ? $exception->getFile() : $exception['file'],
$exception instanceof \Exception ? $exception->getLine() : $exception['line'],
$this->getMessage($exception),
$this->getStackTrace($exception)
);
if (!isset($record['message'])
|| strpos($record['message'], '{exception}') === false
) {
$record['message'] = $exceptionStr;
} else {
$record['message'] = str_replace('{exception}', $exceptionStr, $record['message']);
}
return $record;
}
private function contextContainsException($record)
{
return isset($record['context']['exception'])
&& ($record['context']['exception'] instanceof \Exception
|| $this->isLooksLikeFatalErrorArray($record['context']['exception']));
}
private function isLooksLikeFatalErrorArray($exception)
{
return is_array($exception) && isset($exception['message']) && isset($exception['file']) && isset($exception['line']);
}
private function getMessage($exception)
{
if ($exception instanceof \ErrorException) {
return ErrorHandler::getErrNoString($exception->getSeverity()) . ' - ' . $exception->getMessage();
}
if (is_array($exception) && isset($exception['message'])) {
return $exception['message'];
}
return $exception->getMessage();
}
private function getStackTrace($exception)
{
if (is_array($exception) && isset($exception['backtrace'])) {
return $exception['backtrace'];
}
return Log::$debugBacktraceForTests ?: self::getWholeBacktrace($exception);
}
public static function getWholeBacktrace(\Exception $exception, $shouldPrintBacktrace = true)
{
$message = "";
$e = $exception;
do {
if ($e !== $exception) {
$message .= ",\ncaused by: ";
}
$message .= $e->getMessage();
if ($shouldPrintBacktrace) {
$message .= "\n" . $e->getTraceAsString();
}
} while ($e = $e->getPrevious());
return $message;
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Processor;
use Piwik\Common;
use Piwik\FrontController;
/**
* Adds a unique "request id" to the log message to follow log entries for each HTTP request.
*/
class RequestIdProcessor
{
private $currentRequestKey;
public function __invoke(array $record)
{
if (empty($this->currentRequestKey)) {
if (Common::isPhpCliMode()) {
$this->currentRequestKey = getmypid();
} else {
$this->currentRequestKey = FrontController::getUniqueRequestId();
}
}
$record['extra']['request_id'] = $this->currentRequestKey;
return $record;
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Processor;
/**
* Processes a log message using `sprintf()`.
*/
class SprintfProcessor
{
public function __invoke(array $record)
{
$message = $record['message'];
$parameters = $record['context'];
if (is_string($message) && !empty($parameters) && strpos($message, '%') !== false) {
$parameters = $this->ensureParametersAreStrings($parameters);
$record['message'] = vsprintf($message, $parameters);
}
return $record;
}
private function ensureParametersAreStrings(array $parameters)
{
foreach ($parameters as &$param) {
if (is_array($param)) {
$param = json_encode($param);
} elseif (is_object($param)) {
$param = get_class($param);
}
}
return $parameters;
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Monolog\Processor;
/**
* Removes any token_auth that might appear in the logs.
*
* Ideally the token_auth should never be logged, but...
*/
class TokenProcessor
{
public function __invoke(array $record)
{
$record['message'] = preg_replace('/token_auth=[0-9a-h]+/', 'token_auth=removed', $record['message']);
return $record;
}
}

View File

@ -0,0 +1,31 @@
<?php
use Interop\Container\ContainerInterface;
use Monolog\Logger;
use Piwik\Plugins\Monolog\Handler\FailureLogMessageDetector;
use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
use Symfony\Component\Console\Output\OutputInterface;
return array(
// Log
'log.handlers' => array(
DI\get(FailureLogMessageDetector::class),
DI\get('Symfony\Bridge\Monolog\Handler\ConsoleHandler'),
),
'Symfony\Bridge\Monolog\Handler\ConsoleHandler' => function (ContainerInterface $c) {
// Override the default verbosity map to make it more verbose by default
$verbosityMap = array(
OutputInterface::VERBOSITY_NORMAL => Logger::INFO,
OutputInterface::VERBOSITY_VERBOSE => Logger::DEBUG,
OutputInterface::VERBOSITY_VERY_VERBOSE => Logger::DEBUG,
OutputInterface::VERBOSITY_DEBUG => Logger::DEBUG,
);
$handler = new ConsoleHandler(null, true, $verbosityMap);
$handler->setFormatter(new ConsoleFormatter($c->get('log.console.format'), null, true, true));
return $handler;
},
'log.console.format' => '%start_tag%%level_name% [%datetime%] %extra.request_id% %end_tag% %message%' . PHP_EOL,
);

View File

@ -0,0 +1,196 @@
<?php
use Interop\Container\ContainerInterface;
use Monolog\Logger;
use Piwik\Log;
use Piwik\Plugins\Monolog\Handler\FileHandler;
use Piwik\Plugins\Monolog\Handler\LogCaptureHandler;
return array(
'Monolog\Logger' => DI\object('Monolog\Logger')
->constructor('piwik', DI\get('log.handlers'), DI\get('log.processors')),
'Psr\Log\LoggerInterface' => DI\get('Monolog\Logger'),
'log.handler.classes' => array(
'file' => 'Piwik\Plugins\Monolog\Handler\FileHandler',
'screen' => 'Piwik\Plugins\Monolog\Handler\WebNotificationHandler',
'database' => 'Piwik\Plugins\Monolog\Handler\DatabaseHandler',
),
'log.handlers' => DI\factory(function (\DI\Container $c) {
if ($c->has('ini.log.log_writers')) {
$writerNames = $c->get('ini.log.log_writers');
} else {
return array();
}
$classes = $c->get('log.handler.classes');
$logConfig = $c->get(\Piwik\Config::class)->log;
$enableFingersCrossed = isset($logConfig['enable_fingers_crossed_handler']) && $logConfig['enable_fingers_crossed_handler'] == 1;
$fingersCrossedStopBuffering = isset($logConfig['fingers_crossed_stop_buffering_on_activation']) && $logConfig['fingers_crossed_stop_buffering_on_activation'] == 1;
$enableLogCaptureHandler = isset($logConfig['enable_log_capture_handler']) && $logConfig['enable_log_capture_handler'] == 1;
$isLogBufferingAllowed = !\Piwik\Common::isPhpCliMode()
|| \Piwik\SettingsServer::isArchivePhpTriggered()
|| \Piwik\CliMulti::isCliMultiRequest();
$writerNames = array_map('trim', $writerNames);
$writers = [
// we always add the null handler to make sure there is at least one handler specified. otherwise Monolog will
// add a stream handler to stderr w/ a DEBUG log level, which will cause archiving requests to fail.
$c->get(\Monolog\Handler\NullHandler::class),
];
foreach ($writerNames as $writerName) {
if ($writerName === 'screen' && \Piwik\Common::isPhpCliMode()) {
continue; // screen writer is only valid for web requests
}
if (isset($classes[$writerName])) {
// wrap the handler in FingersCrossedHandler if we can and this isn't the screen handler
/** @var \Monolog\Handler\HandlerInterface $handler */
$handler = $c->make($classes[$writerName]);
if ($enableFingersCrossed
&& $writerName !== 'screen'
&& $handler instanceof \Monolog\Handler\AbstractHandler
&& $isLogBufferingAllowed
) {
$passthruLevel = $handler->getLevel();
$handler->setLevel(Logger::DEBUG);
$handler = new \Monolog\Handler\FingersCrossedHandler($handler, $activationStrategy = null, $bufferSize = 0,
$bubble = true, $fingersCrossedStopBuffering, $passthruLevel);
}
$writers[$writerName] = $handler;
}
}
if ($enableLogCaptureHandler
&& $isLogBufferingAllowed
) {
$writers[] = $c->get(LogCaptureHandler::class);
}
return array_values($writers);
}),
'log.processors' => array(
DI\get('Piwik\Plugins\Monolog\Processor\SprintfProcessor'),
DI\get('Piwik\Plugins\Monolog\Processor\ClassNameProcessor'),
DI\get('Piwik\Plugins\Monolog\Processor\RequestIdProcessor'),
DI\get('Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor'),
DI\get('Monolog\Processor\PsrLogMessageProcessor'),
DI\get('Piwik\Plugins\Monolog\Processor\TokenProcessor'),
),
'Piwik\Plugins\Monolog\Handler\FileHandler' => DI\object()
->constructor(DI\get('log.file.filename'), DI\get('log.level.file'))
->method('setFormatter', DI\get('log.lineMessageFormatter.file')),
'log.lineMessageFormatter.file' => DI\object('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')
->constructorParameter('allowInlineLineBreaks', false),
'Piwik\Plugins\Monolog\Handler\DatabaseHandler' => DI\object()
->constructor(DI\get('log.level.database'))
->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
'Piwik\Plugins\Monolog\Handler\WebNotificationHandler' => DI\object()
->constructor(DI\get('log.level.screen'))
->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
'log.level' => DI\factory(function (ContainerInterface $c) {
if ($c->has('ini.log.log_level')) {
$level = strtoupper($c->get('ini.log.log_level'));
if (!empty($level) && defined('Piwik\Log::'.strtoupper($level))) {
return Log::getMonologLevel(constant('Piwik\Log::'.strtoupper($level)));
}
}
return Logger::WARNING;
}),
'log.level.file' => DI\factory(function (ContainerInterface $c) {
if ($c->has('ini.log.log_level_file')) {
$level = Log::getMonologLevelIfValid($c->get('ini.log.log_level_file'));
if ($level !== null) {
return $level;
}
}
return $c->get('log.level');
}),
'log.level.screen' => DI\factory(function (ContainerInterface $c) {
if ($c->has('ini.log.log_level_screen')) {
$level = Log::getMonologLevelIfValid($c->get('ini.log.log_level_screen'));
if ($level !== null) {
return $level;
}
}
return $c->get('log.level');
}),
'log.level.database' => DI\factory(function (ContainerInterface $c) {
if ($c->has('ini.log.log_level_database')) {
$level = Log::getMonologLevelIfValid($c->get('ini.log.log_level_database'));
if ($level !== null) {
return $level;
}
}
return $c->get('log.level');
}),
'log.file.filename' => DI\factory(function (ContainerInterface $c) {
$logPath = $c->get('ini.log.logger_file_path');
// Absolute path
if (strpos($logPath, '/') === 0) {
return $logPath;
}
// Remove 'tmp/' at the beginning
if (strpos($logPath, 'tmp/') === 0) {
$logPath = substr($logPath, strlen('tmp'));
}
if (empty($logPath)) {
// Default log file
$logPath = '/logs/piwik.log';
}
$logPath = $c->get('path.tmp') . $logPath;
if (is_dir($logPath)) {
$logPath .= '/piwik.log';
}
return $logPath;
}),
'Piwik\Plugins\Monolog\Formatter\LineMessageFormatter' => DI\object()
->constructor(DI\get('log.format')),
'log.format' => DI\factory(function (ContainerInterface $c) {
if ($c->has('ini.log.string_message_format')) {
return $c->get('ini.log.string_message_format');
}
return '%level% %tag%[%datetime%] %message%';
}),
'archiving.performance.handlers' => function (ContainerInterface $c) {
$logFile = trim($c->get('ini.Debug.archive_profiling_log'));
if (empty($logFile)) {
return [new \Monolog\Handler\NullHandler()];
}
$fileHandler = new FileHandler($logFile, \Psr\Log\LogLevel::INFO);
$fileHandler->setFormatter($c->get('log.lineMessageFormatter.file'));
return [$fileHandler];
},
'archiving.performance.logger' => DI\object(Logger::class)
->constructor('matomo.archiving.performance', DI\get('archiving.performance.handlers'), DI\get('log.processors')),
);

View File

@ -0,0 +1,37 @@
<?php
use Interop\Container\ContainerInterface;
function isTrackerDebugEnabled(ContainerInterface $c)
{
$trackerDebug = $c->get("ini.Tracker.debug");
return ($trackerDebug == 1 || !empty($GLOBALS['PIWIK_TRACKER_DEBUG']));
}
return array(
'Psr\Log\LoggerInterface' => \DI\decorate(function ($previous, ContainerInterface $c) {
if (isTrackerDebugEnabled($c)) {
return $previous;
} else {
return new \Psr\Log\NullLogger();
}
}),
'log.handler.classes' => DI\decorate(function ($previous) {
if (isset($previous['screen'])) {
$previous['screen'] = 'Piwik\Plugins\Monolog\Handler\EchoHandler';
}
return $previous;
}),
'log.level' => DI\decorate(function ($previous, ContainerInterface $c) {
if (isTrackerDebugEnabled($c)) {
return \Monolog\Logger::DEBUG;
}
return $previous;
})
);

View File

@ -0,0 +1,3 @@
{
"description": "Adds logging capabilities to Matomo."
}