mirror of
https://github.com/friendica/friendica
synced 2025-04-30 09:44:22 +02:00
Merge remote-tracking branch 'upstream/develop' into ap-delivery-failure
This commit is contained in:
commit
55325f191b
122 changed files with 5068 additions and 2006 deletions
220
src/Util/Config/ConfigFileLoader.php
Normal file
220
src/Util/Config/ConfigFileLoader.php
Normal file
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Config;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Cache\IConfigCache;
|
||||
|
||||
/**
|
||||
* The ConfigFileLoader loads config-files and stores them in a IConfigCache ( @see IConfigCache )
|
||||
*
|
||||
* It is capable of loading the following config files:
|
||||
* - *.config.php (current)
|
||||
* - *.ini.php (deprecated)
|
||||
* - *.htconfig.php (deprecated)
|
||||
*/
|
||||
class ConfigFileLoader extends ConfigFileManager
|
||||
{
|
||||
/**
|
||||
* @var App\Mode
|
||||
*/
|
||||
private $appMode;
|
||||
|
||||
public function __construct($baseDir, App\Mode $mode)
|
||||
{
|
||||
parent::__construct($baseDir);
|
||||
$this->appMode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration files into an configuration cache
|
||||
*
|
||||
* First loads the default value for all the configuration keys, then the legacy configuration files, then the
|
||||
* expected local.config.php
|
||||
*
|
||||
* @param IConfigCache $config The config cache to load to
|
||||
* @param bool $raw Setup the raw config format
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setupCache(IConfigCache $config, $raw = false)
|
||||
{
|
||||
$config->load($this->loadCoreConfig('defaults'));
|
||||
$config->load($this->loadCoreConfig('settings'));
|
||||
|
||||
$config->load($this->loadLegacyConfig('htpreconfig'), true);
|
||||
$config->load($this->loadLegacyConfig('htconfig'), true);
|
||||
|
||||
$config->load($this->loadCoreConfig('local'), true);
|
||||
|
||||
// In case of install mode, add the found basepath (because there isn't a basepath set yet
|
||||
if (!$raw && ($this->appMode->isInstall() || empty($config->get('system', 'basepath')))) {
|
||||
// Setting at least the basepath we know
|
||||
$config->set('system', 'basepath', $this->baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the specified core-configuration and returns the config array.
|
||||
*
|
||||
* @param string $name The name of the configuration (default is empty, which means 'local')
|
||||
*
|
||||
* @return array The config array (empty if no config found)
|
||||
*
|
||||
* @throws \Exception if the configuration file isn't readable
|
||||
*/
|
||||
public function loadCoreConfig($name = '')
|
||||
{
|
||||
if (!empty($this->getConfigFullName($name))) {
|
||||
return $this->loadConfigFile($this->getConfigFullName($name));
|
||||
} elseif (!empty($this->getIniFullName($name))) {
|
||||
return $this->loadINIConfigFile($this->getIniFullName($name));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the specified addon-configuration and returns the config array.
|
||||
*
|
||||
* @param string $name The name of the configuration
|
||||
*
|
||||
* @return array The config array (empty if no config found)
|
||||
*
|
||||
* @throws \Exception if the configuration file isn't readable
|
||||
*/
|
||||
public function loadAddonConfig($name)
|
||||
{
|
||||
$filepath = $this->baseDir . DIRECTORY_SEPARATOR . // /var/www/html/
|
||||
Addon::DIRECTORY . DIRECTORY_SEPARATOR . // addon/
|
||||
$name . DIRECTORY_SEPARATOR . // openstreetmap/
|
||||
self::SUBDIRECTORY . DIRECTORY_SEPARATOR . // config/
|
||||
$name . ".config.php"; // openstreetmap.config.php
|
||||
|
||||
if (file_exists($filepath)) {
|
||||
return $this->loadConfigFile($filepath);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the legacy config files (.htconfig.php, .htpreconfig.php) and returns the config array.
|
||||
*
|
||||
* @param string $name The name of the config file (default is empty, which means .htconfig.php)
|
||||
*
|
||||
* @return array The configuration array (empty if no config found)
|
||||
*
|
||||
* @deprecated since version 2018.09
|
||||
*/
|
||||
private function loadLegacyConfig($name = '')
|
||||
{
|
||||
$config = [];
|
||||
if (!empty($this->getHtConfigFullName($name))) {
|
||||
$a = new \stdClass();
|
||||
$a->config = [];
|
||||
include $this->getHtConfigFullName($name);
|
||||
|
||||
$htConfigCategories = array_keys($a->config);
|
||||
|
||||
// map the legacy configuration structure to the current structure
|
||||
foreach ($htConfigCategories as $htConfigCategory) {
|
||||
if (is_array($a->config[$htConfigCategory])) {
|
||||
$keys = array_keys($a->config[$htConfigCategory]);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$config[$htConfigCategory][$key] = $a->config[$htConfigCategory][$key];
|
||||
}
|
||||
} else {
|
||||
$config['config'][$htConfigCategory] = $a->config[$htConfigCategory];
|
||||
}
|
||||
}
|
||||
|
||||
unset($a);
|
||||
|
||||
if (isset($db_host)) {
|
||||
$config['database']['hostname'] = $db_host;
|
||||
unset($db_host);
|
||||
}
|
||||
if (isset($db_user)) {
|
||||
$config['database']['username'] = $db_user;
|
||||
unset($db_user);
|
||||
}
|
||||
if (isset($db_pass)) {
|
||||
$config['database']['password'] = $db_pass;
|
||||
unset($db_pass);
|
||||
}
|
||||
if (isset($db_data)) {
|
||||
$config['database']['database'] = $db_data;
|
||||
unset($db_data);
|
||||
}
|
||||
if (isset($config['system']['db_charset'])) {
|
||||
$config['database']['charset'] = $config['system']['db_charset'];
|
||||
}
|
||||
if (isset($pidfile)) {
|
||||
$config['system']['pidfile'] = $pidfile;
|
||||
unset($pidfile);
|
||||
}
|
||||
if (isset($default_timezone)) {
|
||||
$config['system']['default_timezone'] = $default_timezone;
|
||||
unset($default_timezone);
|
||||
}
|
||||
if (isset($lang)) {
|
||||
$config['system']['language'] = $lang;
|
||||
unset($lang);
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the specified legacy configuration file and returns the config array.
|
||||
*
|
||||
* @deprecated since version 2018.12
|
||||
* @param string $filepath
|
||||
*
|
||||
* @return array The configuration array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadINIConfigFile($filepath)
|
||||
{
|
||||
$contents = include($filepath);
|
||||
|
||||
$config = parse_ini_string($contents, true, INI_SCANNER_TYPED);
|
||||
|
||||
if ($config === false) {
|
||||
throw new \Exception('Error parsing INI config file ' . $filepath);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the specified configuration file and returns the config array.
|
||||
*
|
||||
* The config format is PHP array and the template for configuration files is the following:
|
||||
*
|
||||
* <?php return [
|
||||
* 'section' => [
|
||||
* 'key' => 'value',
|
||||
* ],
|
||||
* ];
|
||||
*
|
||||
* @param string $filepath The filepath of the
|
||||
* @return array The config array0
|
||||
*
|
||||
* @throws \Exception if the config cannot get loaded.
|
||||
*/
|
||||
private function loadConfigFile($filepath)
|
||||
{
|
||||
$config = include($filepath);
|
||||
|
||||
if (!is_array($config)) {
|
||||
throw new \Exception('Error loading config file ' . $filepath);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
90
src/Util/Config/ConfigFileManager.php
Normal file
90
src/Util/Config/ConfigFileManager.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Config;
|
||||
|
||||
/**
|
||||
* An abstract class in case of handling with config files
|
||||
*/
|
||||
abstract class ConfigFileManager
|
||||
{
|
||||
/**
|
||||
* The Sub directory of the config-files
|
||||
* @var string
|
||||
*/
|
||||
const SUBDIRECTORY = 'config';
|
||||
|
||||
/**
|
||||
* The default name of the user defined config file
|
||||
* @var string
|
||||
*/
|
||||
const CONFIG_LOCAL = 'local';
|
||||
|
||||
/**
|
||||
* The default name of the user defined ini file
|
||||
* @var string
|
||||
*/
|
||||
const CONFIG_INI = 'local';
|
||||
|
||||
/**
|
||||
* The default name of the user defined legacy config file
|
||||
* @var string
|
||||
*/
|
||||
const CONFIG_HTCONFIG = 'htconfig';
|
||||
|
||||
protected $baseDir;
|
||||
protected $configDir;
|
||||
|
||||
/**
|
||||
* @param string $baseDir The base directory of Friendica
|
||||
*/
|
||||
public function __construct($baseDir)
|
||||
{
|
||||
$this->baseDir = $baseDir;
|
||||
$this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full name (including the path) for a *.config.php (default is local.config.php)
|
||||
*
|
||||
* @param string $name The config name (default is empty, which means local.config.php)
|
||||
*
|
||||
* @return string The full name or empty if not found
|
||||
*/
|
||||
protected function getConfigFullName($name = '')
|
||||
{
|
||||
$name = !empty($name) ? $name : self::CONFIG_LOCAL;
|
||||
|
||||
$fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php';
|
||||
return file_exists($fullName) ? $fullName : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full name (including the path) for a *.ini.php (default is local.ini.php)
|
||||
*
|
||||
* @param string $name The config name (default is empty, which means local.ini.php)
|
||||
*
|
||||
* @return string The full name or empty if not found
|
||||
*/
|
||||
protected function getIniFullName($name = '')
|
||||
{
|
||||
$name = !empty($name) ? $name : self::CONFIG_INI;
|
||||
|
||||
$fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php';
|
||||
return file_exists($fullName) ? $fullName : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full name (including the path) for a .*.php (default is .htconfig.php)
|
||||
*
|
||||
* @param string $name The config name (default is empty, which means .htconfig.php)
|
||||
*
|
||||
* @return string The full name or empty if not found
|
||||
*/
|
||||
protected function getHtConfigFullName($name = '')
|
||||
{
|
||||
$name = !empty($name) ? $name : self::CONFIG_HTCONFIG;
|
||||
|
||||
$fullName = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php';
|
||||
return file_exists($fullName) ? $fullName : '';
|
||||
}
|
||||
}
|
341
src/Util/Config/ConfigFileSaver.php
Normal file
341
src/Util/Config/ConfigFileSaver.php
Normal file
|
@ -0,0 +1,341 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Config;
|
||||
|
||||
/**
|
||||
* The ConfigFileSaver saves specific variables into the config-files
|
||||
*
|
||||
* It is capable of loading the following config files:
|
||||
* - *.config.php (current)
|
||||
* - *.ini.php (deprecated)
|
||||
* - *.htconfig.php (deprecated)
|
||||
*/
|
||||
class ConfigFileSaver extends ConfigFileManager
|
||||
{
|
||||
/**
|
||||
* The standard indentation for config files
|
||||
* @var string
|
||||
*/
|
||||
const INDENT = "\t";
|
||||
|
||||
/**
|
||||
* The settings array to save to
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [];
|
||||
|
||||
/**
|
||||
* Adds a given value to the config file
|
||||
* Either it replaces the current value or it will get added
|
||||
*
|
||||
* @param string $cat The configuration category
|
||||
* @param string $key The configuration key
|
||||
* @param string $value The new value
|
||||
*/
|
||||
public function addConfigValue($cat, $key, $value)
|
||||
{
|
||||
$settingsCount = count(array_keys($this->settings));
|
||||
|
||||
for ($i = 0; $i < $settingsCount; $i++) {
|
||||
// if already set, overwrite the value
|
||||
if ($this->settings[$i]['cat'] === $cat &&
|
||||
$this->settings[$i]['key'] === $key) {
|
||||
$this->settings[$i] = ['cat' => $cat, 'key' => $key, 'value' => $value];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->settings[] = ['cat' => $cat, 'key' => $key, 'value' => $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resetting all added configuration entries so far
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->settings = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all added configuration entries to the given config files
|
||||
* After updating the config entries, all configuration entries will be reseted
|
||||
*
|
||||
* @param string $name The name of the configuration file (default is empty, which means the default name each type)
|
||||
*
|
||||
* @return bool true, if at least one configuration file was successfully updated or nothing to do
|
||||
*/
|
||||
public function saveToConfigFile($name = '')
|
||||
{
|
||||
// If no settings et, return true
|
||||
if (count(array_keys($this->settings)) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$saved = false;
|
||||
|
||||
// Check for the *.config.php file inside the /config/ path
|
||||
list($reading, $writing) = $this->openFile($this->getConfigFullName($name));
|
||||
if (isset($reading) && isset($writing)) {
|
||||
$this->saveConfigFile($reading, $writing);
|
||||
// Close the current file handler and rename them
|
||||
if ($this->closeFile($this->getConfigFullName($name), $reading, $writing)) {
|
||||
// just return true, if everything went fine
|
||||
$saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the *.ini.php file inside the /config/ path
|
||||
list($reading, $writing) = $this->openFile($this->getIniFullName($name));
|
||||
if (isset($reading) && isset($writing)) {
|
||||
$this->saveINIConfigFile($reading, $writing);
|
||||
// Close the current file handler and rename them
|
||||
if ($this->closeFile($this->getIniFullName($name), $reading, $writing)) {
|
||||
// just return true, if everything went fine
|
||||
$saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the *.php file (normally .htconfig.php) inside the / path
|
||||
list($reading, $writing) = $this->openFile($this->getHtConfigFullName($name));
|
||||
if (isset($reading) && isset($writing)) {
|
||||
$this->saveToLegacyConfig($reading, $writing);
|
||||
// Close the current file handler and rename them
|
||||
if ($this->closeFile($this->getHtConfigFullName($name), $reading, $writing)) {
|
||||
// just return true, if everything went fine
|
||||
$saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a config file and returns two handler for reading and writing
|
||||
*
|
||||
* @param string $fullName The full name of the current config
|
||||
*
|
||||
* @return array An array containing the two reading and writing handler
|
||||
*/
|
||||
private function openFile($fullName)
|
||||
{
|
||||
if (empty($fullName)) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
try {
|
||||
$reading = fopen($fullName, 'r');
|
||||
} catch (\Exception $exception) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
if (!$reading) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
try {
|
||||
$writing = fopen($fullName . '.tmp', 'w');
|
||||
} catch (\Exception $exception) {
|
||||
fclose($reading);
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
if (!$writing) {
|
||||
fclose($reading);
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
return [$reading, $writing];
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and rename the config file
|
||||
*
|
||||
* @param string $fullName The full name of the current config
|
||||
* @param resource $reading The reading resource handler
|
||||
* @param resource $writing The writing resource handler
|
||||
*
|
||||
* @return bool True, if the close was successful
|
||||
*/
|
||||
private function closeFile($fullName, $reading, $writing)
|
||||
{
|
||||
fclose($reading);
|
||||
fclose($writing);
|
||||
|
||||
try {
|
||||
$renamed = rename($fullName, $fullName . '.old');
|
||||
} catch (\Exception $exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$renamed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$renamed = rename($fullName . '.tmp', $fullName);
|
||||
} catch (\Exception $exception) {
|
||||
// revert the move of the current config file to have at least the old config
|
||||
rename($fullName . '.old', $fullName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$renamed) {
|
||||
// revert the move of the current config file to have at least the old config
|
||||
rename($fullName . '.old', $fullName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all configuration values to a config file
|
||||
*
|
||||
* @param resource $reading The reading handler
|
||||
* @param resource $writing The writing handler
|
||||
*/
|
||||
private function saveConfigFile($reading, $writing)
|
||||
{
|
||||
$settingsCount = count(array_keys($this->settings));
|
||||
$categoryFound = array_fill(0, $settingsCount, false);
|
||||
$categoryBracketFound = array_fill(0, $settingsCount, false);;
|
||||
$lineFound = array_fill(0, $settingsCount, false);;
|
||||
$lineArrowFound = array_fill(0, $settingsCount, false);;
|
||||
|
||||
while (!feof($reading)) {
|
||||
|
||||
$line = fgets($reading);
|
||||
|
||||
// check for each added setting if we have to replace a config line
|
||||
for ($i = 0; $i < $settingsCount; $i++) {
|
||||
|
||||
// find the first line like "'system' =>"
|
||||
if (!$categoryFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['cat']))) {
|
||||
$categoryFound[$i] = true;
|
||||
}
|
||||
|
||||
// find the first line with a starting bracket ( "[" )
|
||||
if ($categoryFound[$i] && !$categoryBracketFound[$i] && stristr($line, '[')) {
|
||||
$categoryBracketFound[$i] = true;
|
||||
}
|
||||
|
||||
// find the first line with the key like "'value'"
|
||||
if ($categoryBracketFound[$i] && !$lineFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['key']))) {
|
||||
$lineFound[$i] = true;
|
||||
}
|
||||
|
||||
// find the first line with an arrow ("=>") after finding the key
|
||||
if ($lineFound[$i] && !$lineArrowFound[$i] && stristr($line, '=>')) {
|
||||
$lineArrowFound[$i] = true;
|
||||
}
|
||||
|
||||
// find the current value and replace it
|
||||
if ($lineArrowFound[$i] && preg_match_all('/\'(.*?)\'/', $line, $matches, PREG_SET_ORDER)) {
|
||||
$lineVal = end($matches)[0];
|
||||
$line = str_replace($lineVal, '\'' . $this->settings[$i]['value'] . '\'', $line);
|
||||
$categoryFound[$i] = false;
|
||||
$categoryBracketFound[$i] = false;
|
||||
$lineFound[$i] = false;
|
||||
$lineArrowFound[$i] = false;
|
||||
// if a line contains a closing bracket for the category ( "]" ) and we didn't find the key/value pair,
|
||||
// add it as a new line before the closing bracket
|
||||
} elseif ($categoryBracketFound[$i] && !$lineArrowFound[$i] && stristr($line, ']')) {
|
||||
$categoryFound[$i] = false;
|
||||
$categoryBracketFound[$i] = false;
|
||||
$lineFound[$i] = false;
|
||||
$lineArrowFound[$i] = false;
|
||||
$newLine = sprintf(self::INDENT . self::INDENT . '\'%s\' => \'%s\',' . PHP_EOL, $this->settings[$i]['key'], $this->settings[$i]['value']);
|
||||
$line = $newLine . $line;
|
||||
}
|
||||
}
|
||||
|
||||
fputs($writing, $line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a value to a ini file
|
||||
*
|
||||
* @param resource $reading The reading handler
|
||||
* @param resource $writing The writing handler
|
||||
*/
|
||||
private function saveINIConfigFile($reading, $writing)
|
||||
{
|
||||
$settingsCount = count(array_keys($this->settings));
|
||||
$categoryFound = array_fill(0, $settingsCount, false);
|
||||
|
||||
while (!feof($reading)) {
|
||||
|
||||
$line = fgets($reading);
|
||||
|
||||
// check for each added setting if we have to replace a config line
|
||||
for ($i = 0; $i < $settingsCount; $i++) {
|
||||
|
||||
// find the category of the current setting
|
||||
if (!$categoryFound[$i] && stristr($line, sprintf('[%s]', $this->settings[$i]['cat']))) {
|
||||
$categoryFound[$i] = true;
|
||||
|
||||
// check the current value
|
||||
} elseif ($categoryFound[$i] && preg_match_all('/^' . $this->settings[$i]['key'] . '\s*=\s*(.*?)$/', $line, $matches, PREG_SET_ORDER)) {
|
||||
$line = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL;
|
||||
$categoryFound[$i] = false;
|
||||
|
||||
// If end of INI file, add the line before the INI end
|
||||
} elseif ($categoryFound[$i] && (preg_match_all('/^\[.*?\]$/', $line) || preg_match_all('/^INI;.*$/', $line))) {
|
||||
$categoryFound[$i] = false;
|
||||
$newLine = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL;
|
||||
$line = $newLine . $line;
|
||||
}
|
||||
}
|
||||
|
||||
fputs($writing, $line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a value to a .php file (normally .htconfig.php)
|
||||
*
|
||||
* @param resource $reading The reading handler
|
||||
* @param resource $writing The writing handler
|
||||
*/
|
||||
private function saveToLegacyConfig($reading, $writing)
|
||||
{
|
||||
$settingsCount = count(array_keys($this->settings));
|
||||
$found = array_fill(0, $settingsCount, false);
|
||||
while (!feof($reading)) {
|
||||
|
||||
$line = fgets($reading);
|
||||
|
||||
// check for each added setting if we have to replace a config line
|
||||
for ($i = 0; $i < $settingsCount; $i++) {
|
||||
|
||||
// check for a non plain config setting (use category too)
|
||||
if ($this->settings[$i]['cat'] !== 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['cat'] . '\'\]\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) {
|
||||
$line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
|
||||
$found[$i] = true;
|
||||
|
||||
// check for a plain config setting (don't use a category)
|
||||
} elseif ($this->settings[$i]['cat'] === 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) {
|
||||
$line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
|
||||
$found[$i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
fputs($writing, $line);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $settingsCount; $i++) {
|
||||
if (!$found[$i]) {
|
||||
if ($this->settings[$i]['cat'] !== 'config') {
|
||||
$line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
|
||||
} else {
|
||||
$line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
|
||||
}
|
||||
|
||||
fputs($writing, $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
src/Util/Introspection.php
Normal file
88
src/Util/Introspection.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util;
|
||||
|
||||
/**
|
||||
* Get Introspection information about the current call
|
||||
*/
|
||||
class Introspection
|
||||
{
|
||||
private $skipStackFramesCount;
|
||||
|
||||
private $skipClassesPartials;
|
||||
|
||||
private $skipFunctions = [
|
||||
'call_user_func',
|
||||
'call_user_func_array',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array $skipClassesPartials An array of classes to skip during logging
|
||||
* @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
|
||||
*/
|
||||
public function __construct($skipClassesPartials = array(), $skipStackFramesCount = 0)
|
||||
{
|
||||
$this->skipClassesPartials = $skipClassesPartials;
|
||||
$this->skipStackFramesCount = $skipStackFramesCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new classes to get skipped
|
||||
* @param array $classNames
|
||||
*/
|
||||
public function addClasses(array $classNames)
|
||||
{
|
||||
$this->skipClassesPartials = array_merge($this->skipClassesPartials, $classNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the introspection record of the current call
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRecord()
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
$i = 1;
|
||||
|
||||
while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
|
||||
$i++;
|
||||
}
|
||||
|
||||
$i += $this->skipStackFramesCount;
|
||||
|
||||
return [
|
||||
'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
|
||||
'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
|
||||
'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current trace class or function has to be skipped
|
||||
*
|
||||
* @param array $trace The current trace array
|
||||
* @param int $index The index of the current hierarchy level
|
||||
*
|
||||
* @return bool True if the class or function should get skipped, otherwise false
|
||||
*/
|
||||
private function isTraceClassOrSkippedFunction(array $trace, $index)
|
||||
{
|
||||
if (!isset($trace[$index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($trace[$index]['class'])) {
|
||||
foreach ($this->skipClassesPartials as $part) {
|
||||
if (strpos($trace[$index]['class'], $part) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
158
src/Util/Logger/AbstractLogger.php
Normal file
158
src/Util/Logger/AbstractLogger.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Friendica\Util\Introspection;
|
||||
use Friendica\Util\Strings;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* This class contains all necessary dependencies and calls for Friendica
|
||||
* Every new Logger should extend this class and define, how addEntry() works
|
||||
*
|
||||
* Additional information for each Logger, who extends this class:
|
||||
* - Introspection
|
||||
* - UID for each call
|
||||
* - Channel of the current call (i.e. index, worker, daemon, ...)
|
||||
*/
|
||||
abstract class AbstractLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* The output channel of this logger
|
||||
* @var string
|
||||
*/
|
||||
protected $channel;
|
||||
|
||||
/**
|
||||
* The Introspection for the current call
|
||||
* @var Introspection
|
||||
*/
|
||||
protected $introspection;
|
||||
|
||||
/**
|
||||
* The UID of the current call
|
||||
* @var string
|
||||
*/
|
||||
protected $logUid;
|
||||
|
||||
/**
|
||||
* Adds a new entry to the log
|
||||
*
|
||||
* @param int $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function addEntry($level, $message, $context = []);
|
||||
|
||||
/**
|
||||
* @param string $channel The output channel
|
||||
* @param Introspection $introspection The introspection of the current call
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($channel, Introspection $introspection)
|
||||
{
|
||||
$this->channel = $channel;
|
||||
$this->introspection = $introspection;
|
||||
$this->logUid = Strings::getRandomHex(6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
|
||||
* @see https://www.php-fig.org/psr/psr-3/#12-message
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return string the interpolated message
|
||||
*/
|
||||
protected function psrInterpolate($message, array $context = array())
|
||||
{
|
||||
$replace = [];
|
||||
foreach ($context as $key => $value) {
|
||||
// check that the value can be casted to string
|
||||
if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
|
||||
$replace['{' . $key . '}'] = $value;
|
||||
} elseif (is_array($value)) {
|
||||
$replace['{' . $key . '}'] = @json_encode($value);
|
||||
}
|
||||
}
|
||||
|
||||
return strtr($message, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function emergency($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::EMERGENCY, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alert($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::ALERT, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function critical($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::CRITICAL, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function error($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::ERROR, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function warning($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::WARNING, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function notice($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::NOTICE, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function info($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::INFO, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function debug($message, array $context = array())
|
||||
{
|
||||
$this->addEntry(LogLevel::DEBUG, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
$this->addEntry($level, (string) $message, $context);
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Processor\ProcessorInterface;
|
||||
|
||||
/**
|
||||
* Injects line/file//function where the log message came from
|
||||
*
|
||||
* Based on the class IntrospectionProcessor without the "class" information
|
||||
* @see IntrospectionProcessor
|
||||
*/
|
||||
class FriendicaIntrospectionProcessor implements ProcessorInterface
|
||||
{
|
||||
private $level;
|
||||
|
||||
private $skipStackFramesCount;
|
||||
|
||||
private $skipClassesPartials;
|
||||
|
||||
private $skipFunctions = [
|
||||
'call_user_func',
|
||||
'call_user_func_array',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string|int $level The minimum logging level at which this Processor will be triggered
|
||||
* @param array $skipClassesPartials An array of classes to skip during logging
|
||||
* @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
|
||||
*/
|
||||
public function __construct($level = Logger::DEBUG, $skipClassesPartials = array(), $skipStackFramesCount = 0)
|
||||
{
|
||||
$this->level = Logger::toMonologLevel($level);
|
||||
$this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
|
||||
$this->skipStackFramesCount = $skipStackFramesCount;
|
||||
}
|
||||
|
||||
public function __invoke(array $record)
|
||||
{
|
||||
// return if the level is not high enough
|
||||
if ($record['level'] < $this->level) {
|
||||
return $record;
|
||||
}
|
||||
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
$i = 1;
|
||||
|
||||
while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
|
||||
$i++;
|
||||
}
|
||||
|
||||
$i += $this->skipStackFramesCount;
|
||||
|
||||
// we should have the call source now
|
||||
$record['extra'] = array_merge(
|
||||
$record['extra'],
|
||||
[
|
||||
'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
|
||||
'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
|
||||
'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
|
||||
]
|
||||
);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current trace class or function has to be skipped
|
||||
*
|
||||
* @param array $trace The current trace array
|
||||
* @param int $index The index of the current hierarchy level
|
||||
* @return bool True if the class or function should get skipped, otherwise false
|
||||
*/
|
||||
private function isTraceClassOrSkippedFunction(array $trace, $index)
|
||||
{
|
||||
if (!isset($trace[$index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($trace[$index]['class'])) {
|
||||
foreach ($this->skipClassesPartials as $part) {
|
||||
if (strpos($trace[$index]['class'], $part) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
namespace Friendica\Util\Logger\Monolog;
|
||||
|
||||
use Monolog\Handler;
|
||||
use Monolog\Logger;
|
||||
|
@ -11,7 +11,7 @@ use Monolog\Logger;
|
|||
* If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
|
||||
* you'll use Logger::develop() for the duration of your work, and you clean it up when you're done before submitting your PR.
|
||||
*/
|
||||
class FriendicaDevelopHandler extends Handler\AbstractHandler
|
||||
class DevelopHandler extends Handler\AbstractHandler
|
||||
{
|
||||
/**
|
||||
* @var string The IP of the developer who wants to debug
|
43
src/Util/Logger/Monolog/IntrospectionProcessor.php
Normal file
43
src/Util/Logger/Monolog/IntrospectionProcessor.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger\Monolog;
|
||||
|
||||
use Friendica\Util\Introspection;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Processor\ProcessorInterface;
|
||||
|
||||
/**
|
||||
* Injects line/file//function where the log message came from
|
||||
*/
|
||||
class IntrospectionProcessor implements ProcessorInterface
|
||||
{
|
||||
private $level;
|
||||
|
||||
private $introspection;
|
||||
|
||||
/**
|
||||
* @param Introspection $introspection Holds the Introspection of the current call
|
||||
* @param string|int $level The minimum logging level at which this Processor will be triggered
|
||||
*/
|
||||
public function __construct(Introspection $introspection, $level = Logger::DEBUG)
|
||||
{
|
||||
$this->level = Logger::toMonologLevel($level);
|
||||
$introspection->addClasses(array('Monolog\\'));
|
||||
$this->introspection = $introspection;
|
||||
}
|
||||
|
||||
public function __invoke(array $record)
|
||||
{
|
||||
// return if the level is not high enough
|
||||
if ($record['level'] < $this->level) {
|
||||
return $record;
|
||||
}
|
||||
// we should have the call source now
|
||||
$record['extra'] = array_merge(
|
||||
$record['extra'],
|
||||
$this->introspection->getRecord()
|
||||
);
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
127
src/Util/Logger/ProfilerLogger.php
Normal file
127
src/Util/Logger/ProfilerLogger.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* This Logger adds additional profiling data in case profiling is enabled.
|
||||
* It uses a predefined logger.
|
||||
*/
|
||||
class ProfilerLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* The Logger of the current call
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* The Profiler for the current call
|
||||
* @var Profiler
|
||||
*/
|
||||
protected $profiler;
|
||||
|
||||
/**
|
||||
* ProfilerLogger constructor.
|
||||
* @param LoggerInterface $logger The Logger of the current call
|
||||
* @param Profiler $profiler The profiler of the current call
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger, Profiler $profiler)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->profiler = $profiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function emergency($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->emergency($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alert($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->alert($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function critical($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->critical($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function error($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->error($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function warning($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->warning($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function notice($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->notice($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function info($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->info($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function debug($message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->debug($message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$this->logger->log($level, $message, $context);
|
||||
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
|
||||
}
|
||||
}
|
27
src/Util/Logger/README.md
Normal file
27
src/Util/Logger/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
## Friendica\Util\Logger
|
||||
|
||||
This namespace contains the different implementations of a Logger.
|
||||
|
||||
### Configuration guideline
|
||||
|
||||
The following settings are possible for `logger_config`:
|
||||
- `monolog`: A Logging framework with lots of additions (see [Monolog](https://github.com/Seldaek/monolog/)). There are just Friendica additions inside the Monolog directory
|
||||
- [`stream`](StreamLogger.php): A small logger for files or streams
|
||||
- [`syslog`](SyslogLogger.php): Prints the logging output into the syslog
|
||||
|
||||
[`VoidLogger`](VoidLogger.php) is a fallback logger without any function if no debugging is enabled.
|
||||
|
||||
[`ProfilerLogger`](ProfilerLogger.php) is a wrapper around an existing logger in case profiling is enabled for Friendica.
|
||||
Every log call will be saved to the `Profiler` with a timestamp.
|
||||
|
||||
### Implementation guideline
|
||||
|
||||
Each logging implementation should pe capable of printing at least the following information:
|
||||
- An unique ID for each Request/Call
|
||||
- The process ID (PID)
|
||||
- A timestamp of the logging entry
|
||||
- The critically of the log entry
|
||||
- A log message
|
||||
- A context of the log message (f.e which user)
|
||||
|
||||
If possible, a Logger should extend [`AbstractLogger`](AbstractLogger.php), because it contains additional, Friendica specific business logic for each logging call.
|
197
src/Util/Logger/StreamLogger.php
Normal file
197
src/Util/Logger/StreamLogger.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Introspection;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* A Logger instance for logging into a stream (file, stdout, stderr)
|
||||
*/
|
||||
class StreamLogger extends AbstractLogger
|
||||
{
|
||||
/**
|
||||
* The minimum loglevel at which this logger will be triggered
|
||||
* @var string
|
||||
*/
|
||||
private $logLevel;
|
||||
|
||||
/**
|
||||
* The file URL of the stream (if needed)
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* The stream, where the current logger is writing into
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* The current process ID
|
||||
* @var int
|
||||
*/
|
||||
private $pid;
|
||||
|
||||
/**
|
||||
* An error message
|
||||
* @var string
|
||||
*/
|
||||
private $errorMessage;
|
||||
|
||||
/**
|
||||
* Translates LogLevel log levels to integer values
|
||||
* @var array
|
||||
*/
|
||||
private $levelToInt = [
|
||||
LogLevel::EMERGENCY => 0,
|
||||
LogLevel::ALERT => 1,
|
||||
LogLevel::CRITICAL => 2,
|
||||
LogLevel::ERROR => 3,
|
||||
LogLevel::WARNING => 4,
|
||||
LogLevel::NOTICE => 5,
|
||||
LogLevel::INFO => 6,
|
||||
LogLevel::DEBUG => 7,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param string|resource $stream The stream to write with this logger (either a file or a stream, i.e. stdout)
|
||||
* @param string $level The minimum loglevel at which this logger will be triggered
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($channel, $stream, Introspection $introspection, $level = LogLevel::DEBUG)
|
||||
{
|
||||
parent::__construct($channel, $introspection);
|
||||
|
||||
if (is_resource($stream)) {
|
||||
$this->stream = $stream;
|
||||
} elseif (is_string($stream)) {
|
||||
$this->url = $stream;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('A stream must either be a resource or a string.');
|
||||
}
|
||||
|
||||
$this->pid = getmypid();
|
||||
if (array_key_exists($level, $this->levelToInt)) {
|
||||
$this->logLevel = $this->levelToInt[$level];
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->url && is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
|
||||
$this->stream = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new entry to the log
|
||||
*
|
||||
* @param int $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addEntry($level, $message, $context = [])
|
||||
{
|
||||
if (!array_key_exists($level, $this->levelToInt)) {
|
||||
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
|
||||
}
|
||||
|
||||
$logLevel = $this->levelToInt[$level];
|
||||
|
||||
if ($logLevel > $this->logLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->checkStream();
|
||||
|
||||
$formattedLog = $this->formatLog($level, $message, $context);
|
||||
fwrite($this->stream, $formattedLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a log record for the syslog output
|
||||
*
|
||||
* @param int $level The loglevel/priority
|
||||
* @param string $message The message
|
||||
* @param array $context The context of this call
|
||||
*
|
||||
* @return string the formatted syslog output
|
||||
*/
|
||||
private function formatLog($level, $message, $context = [])
|
||||
{
|
||||
$record = $this->introspection->getRecord();
|
||||
$record = array_merge($record, ['uid' => $this->logUid, 'process_id' => $this->pid]);
|
||||
$logMessage = '';
|
||||
|
||||
$logMessage .= DateTimeFormat::utcNow() . ' ';
|
||||
$logMessage .= $this->channel . ' ';
|
||||
$logMessage .= '[' . strtoupper($level) . ']: ';
|
||||
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
|
||||
$logMessage .= @json_encode($context) . ' - ';
|
||||
$logMessage .= @json_encode($record);
|
||||
$logMessage .= PHP_EOL;
|
||||
|
||||
return $logMessage;
|
||||
}
|
||||
|
||||
private function checkStream()
|
||||
{
|
||||
if (is_resource($this->stream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($this->url)) {
|
||||
throw new \LogicException('Missing stream URL.');
|
||||
}
|
||||
|
||||
$this->createDir();
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
$this->stream = fopen($this->url, 'ab');
|
||||
restore_error_handler();
|
||||
|
||||
if (!is_resource($this->stream)) {
|
||||
$this->stream = null;
|
||||
|
||||
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $this->url));
|
||||
}
|
||||
}
|
||||
|
||||
private function createDir()
|
||||
{
|
||||
$dirname = null;
|
||||
$pos = strpos($this->url, '://');
|
||||
if (!$pos) {
|
||||
$dirname = dirname($this->url);
|
||||
}
|
||||
|
||||
if (substr($this->url, 0, 7) === 'file://') {
|
||||
$dirname = dirname(substr($this->url, 7));
|
||||
}
|
||||
|
||||
if (isset($dirname) && !is_dir($dirname)) {
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
$status = mkdir($dirname, 0777, true);
|
||||
restore_error_handler();
|
||||
|
||||
if (!$status && !is_dir($dirname)) {
|
||||
throw new \UnexpectedValueException(sprintf('Directory "%s" cannot get created: ' . $this->errorMessage, $dirname));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function customErrorHandler($code, $msg)
|
||||
{
|
||||
$this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
|
||||
}
|
||||
}
|
206
src/Util/Logger/SyslogLogger.php
Normal file
206
src/Util/Logger/SyslogLogger.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Util\Introspection;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* A Logger instance for syslogging (fast, but simple)
|
||||
* @see http://php.net/manual/en/function.syslog.php
|
||||
*/
|
||||
class SyslogLogger extends AbstractLogger
|
||||
{
|
||||
const IDENT = 'Friendica';
|
||||
|
||||
/**
|
||||
* Translates LogLevel log levels to syslog log priorities.
|
||||
* @var array
|
||||
*/
|
||||
private $logLevels = [
|
||||
LogLevel::DEBUG => LOG_DEBUG,
|
||||
LogLevel::INFO => LOG_INFO,
|
||||
LogLevel::NOTICE => LOG_NOTICE,
|
||||
LogLevel::WARNING => LOG_WARNING,
|
||||
LogLevel::ERROR => LOG_ERR,
|
||||
LogLevel::CRITICAL => LOG_CRIT,
|
||||
LogLevel::ALERT => LOG_ALERT,
|
||||
LogLevel::EMERGENCY => LOG_EMERG,
|
||||
];
|
||||
|
||||
/**
|
||||
* Translates log priorities to string outputs
|
||||
* @var array
|
||||
*/
|
||||
private $logToString = [
|
||||
LOG_DEBUG => 'DEBUG',
|
||||
LOG_INFO => 'INFO',
|
||||
LOG_NOTICE => 'NOTICE',
|
||||
LOG_WARNING => 'WARNING',
|
||||
LOG_ERR => 'ERROR',
|
||||
LOG_CRIT => 'CRITICAL',
|
||||
LOG_ALERT => 'ALERT',
|
||||
LOG_EMERG => 'EMERGENCY'
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates what logging options will be used when generating a log message
|
||||
* @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $logOpts;
|
||||
|
||||
/**
|
||||
* Used to specify what type of program is logging the message
|
||||
* @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $logFacility;
|
||||
|
||||
/**
|
||||
* The minimum loglevel at which this logger will be triggered
|
||||
* @var int
|
||||
*/
|
||||
private $logLevel;
|
||||
|
||||
/**
|
||||
* A error message of the current operation
|
||||
* @var string
|
||||
*/
|
||||
private $errorMessage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param string $level The minimum loglevel at which this logger will be triggered
|
||||
* @param int $logOpts Indicates what logging options will be used when generating a log message
|
||||
* @param int $logFacility Used to specify what type of program is logging the message
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
|
||||
{
|
||||
parent::__construct($channel, $introspection);
|
||||
$this->logOpts = $logOpts;
|
||||
$this->logFacility = $logFacility;
|
||||
$this->logLevel = $this->mapLevelToPriority($level);
|
||||
$this->introspection->addClasses(array(self::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new entry to the syslog
|
||||
*
|
||||
* @param int $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @throws InternalServerErrorException if the syslog isn't available
|
||||
*/
|
||||
protected function addEntry($level, $message, $context = [])
|
||||
{
|
||||
$logLevel = $this->mapLevelToPriority($level);
|
||||
|
||||
if ($logLevel > $this->logLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formattedLog = $this->formatLog($logLevel, $message, $context);
|
||||
$this->write($logLevel, $formattedLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the LogLevel (@see LogLevel ) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters )
|
||||
*
|
||||
* @param string $level A LogLevel
|
||||
*
|
||||
* @return int The SysLog priority
|
||||
*
|
||||
* @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
|
||||
*/
|
||||
public function mapLevelToPriority($level)
|
||||
{
|
||||
if (!array_key_exists($level, $this->logLevels)) {
|
||||
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
|
||||
}
|
||||
|
||||
return $this->logLevels[$level];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Syslog
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
closelog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a message to the syslog
|
||||
* @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
|
||||
*
|
||||
* @param int $priority The Priority
|
||||
* @param string $message The message of the log
|
||||
*
|
||||
* @throws InternalServerErrorException if syslog cannot be used
|
||||
*/
|
||||
private function write($priority, $message)
|
||||
{
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
$opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
|
||||
restore_error_handler();
|
||||
|
||||
if (!$opened) {
|
||||
throw new \UnexpectedValueException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
|
||||
}
|
||||
|
||||
$this->syslogWrapper($priority, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a log record for the syslog output
|
||||
*
|
||||
* @param int $level The loglevel/priority
|
||||
* @param string $message The message
|
||||
* @param array $context The context of this call
|
||||
*
|
||||
* @return string the formatted syslog output
|
||||
*/
|
||||
private function formatLog($level, $message, $context = [])
|
||||
{
|
||||
$record = $this->introspection->getRecord();
|
||||
$record = array_merge($record, ['uid' => $this->logUid]);
|
||||
$logMessage = '';
|
||||
|
||||
$logMessage .= $this->channel . ' ';
|
||||
$logMessage .= '[' . $this->logToString[$level] . ']: ';
|
||||
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
|
||||
$logMessage .= @json_encode($context) . ' - ';
|
||||
$logMessage .= @json_encode($record);
|
||||
|
||||
return $logMessage;
|
||||
}
|
||||
|
||||
private function customErrorHandler($code, $msg)
|
||||
{
|
||||
$this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* A syslog wrapper to make syslog functionality testable
|
||||
*
|
||||
* @param int $level The syslog priority
|
||||
* @param string $entry The message to send to the syslog function
|
||||
*/
|
||||
protected function syslogWrapper($level, $entry)
|
||||
{
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
$written = syslog($level, $entry);
|
||||
restore_error_handler();
|
||||
|
||||
if (!$written) {
|
||||
throw new \UnexpectedValueException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
|
||||
}
|
||||
}
|
||||
}
|
140
src/Util/Logger/VoidLogger.php
Normal file
140
src/Util/Logger/VoidLogger.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Util\Logger;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* A Logger instance to not log
|
||||
*/
|
||||
class VoidLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emergency($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action must be taken immediately.
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should
|
||||
* trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function alert($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical conditions.
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function critical($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||
* that are not necessarily wrong.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function warning($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function info($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function debug($message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue