A super easy PHP Framework for web development!
https://github.com/exacti/phacil-framework
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
11 KiB
482 lines
11 KiB
<?php
|
|
/*
|
|
* Copyright © 2021 ExacTI Technology Solutions. All rights reserved.
|
|
* GPLv3 General License.
|
|
* https://exacti.com.br
|
|
* Phacil PHP Framework - https://github.com/exacti/phacil-framework
|
|
*/
|
|
|
|
namespace Phacil\Framework;
|
|
|
|
/**
|
|
* The principal log class for this framework
|
|
*
|
|
* @param string $filename (optional) The name of log file. The path is automatic defined in the DIR_LOGS constant in config file. Isn't not possible to change the path. The default name is error.log.
|
|
* @package Phacil\Framework
|
|
*/
|
|
class Log implements \Phacil\Framework\Api\Log {
|
|
|
|
/**
|
|
* Storage the object of log file
|
|
*
|
|
* @var resource|false
|
|
*/
|
|
private $fileobj;
|
|
|
|
/**
|
|
* Storage the filename
|
|
*
|
|
* @var string
|
|
*/
|
|
private $filename;
|
|
|
|
/**
|
|
* Storage the path
|
|
*
|
|
* @var string
|
|
*/
|
|
private $filepath;
|
|
|
|
/**
|
|
* @var \Phacil\Framework\Api\Log[]
|
|
*/
|
|
private $extraLoggers = [];
|
|
|
|
/** @var bool */
|
|
protected $removeUsedContextFields = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $allowInlineLineBreaks = true;
|
|
|
|
/**
|
|
* Log output format
|
|
* @var string
|
|
*/
|
|
protected $format = "[%datetime%] %channel%.%level_name%: %message% %context% %extra% | %route%";
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function __construct($filename = \Phacil\Framework\Api\Log::DEFAULT_FILE_NAME) {
|
|
if(!defined(self::CONFIGURATION_LOGS_CONST)){
|
|
if(!defined('DIR_APPLICATION'))
|
|
trigger_error('The '.self::CONFIGURATION_LOGS_CONST.' folder configuration is required!', E_USER_ERROR);
|
|
|
|
define(self::CONFIGURATION_LOGS_CONST, DIR_APPLICATION . 'logs/');
|
|
}
|
|
$this->filename = $filename;
|
|
$this->filepath = constant(self::CONFIGURATION_LOGS_CONST) . $filename;
|
|
if(!is_dir(constant(self::CONFIGURATION_LOGS_CONST))){
|
|
$old = umask(0);
|
|
mkdir(constant(self::CONFIGURATION_LOGS_CONST), self::DIR_LOGS_PERMISSIONS, true);
|
|
umask($old);
|
|
}
|
|
if(!is_writable(constant(self::CONFIGURATION_LOGS_CONST))){
|
|
trigger_error('The '. constant(self::CONFIGURATION_LOGS_CONST).' folder must to be writeable!', E_USER_ERROR);
|
|
}
|
|
//$this->fileobj = fopen($this->filepath, 'a+');
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function setLog($name) {
|
|
if(($name.self::LOG_FILE_EXTENSION) == $this->filename){
|
|
return $this;
|
|
}
|
|
|
|
if(isset($this->extraLoggers[$name])){
|
|
return $this->extraLoggers[$name];
|
|
}
|
|
|
|
return $this->extraLoggers[$name] = new self($name . self::LOG_FILE_EXTENSION);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function write($message) {
|
|
return $this->writeToFile('[' . $this->getDateTime() . '] - ' . print_r($message, true)." | ".$_SERVER['REQUEST_URI'] . " | " . \Phacil\Framework\startEngineExacTI::getRoute());
|
|
}
|
|
|
|
/**
|
|
* @param string $message
|
|
* @return int|false
|
|
*/
|
|
protected function writeToFile($message) {
|
|
if(!$this->fileobj){
|
|
$this->fileobj = fopen($this->filepath, 'a+');
|
|
}
|
|
return fwrite($this->fileobj, $message . PHP_EOL);
|
|
}
|
|
|
|
/** @return string */
|
|
protected function getDateTime() {
|
|
if (version_compare(phpversion(), "7.2.0", "<")) {
|
|
// Obtém o timestamp atual com precisão de microssegundos
|
|
$microtime = microtime(true);
|
|
|
|
// Arredonda os milissegundos para três casas decimais
|
|
$milissegundos = round(($microtime - floor($microtime)) * 1000);
|
|
|
|
return sprintf(date(self::DATE_TIME_FORMAT_PHP5), (string)$milissegundos);
|
|
}
|
|
|
|
return (new \DateTimeImmutable())->format(self::DATE_TIME_FORMAT);
|
|
}
|
|
|
|
/**
|
|
* Remove object from memory
|
|
*
|
|
* @since 1.0.0
|
|
* @return void
|
|
*/
|
|
public function __destruct() {
|
|
if ($this->fileobj) {
|
|
fclose($this->fileobj);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFileName()
|
|
{
|
|
return $this->filename;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFilePath()
|
|
{
|
|
return $this->filepath;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function setLogformat($logformat) {
|
|
$this->format = $logformat;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function setRemoveUsedContextFields($remove = false) {
|
|
$this->removeUsedContextFields = $remove;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function setAllowInlineLineBreaks($allow = true){
|
|
$this->allowInlineLineBreaks = $allow;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function tail($filepath = null, $lines = 10, $adaptive = true)
|
|
{
|
|
|
|
// Open file
|
|
$f = @fopen(($filepath ?: $this->filepath), "rb");
|
|
if ($f === false) return false;
|
|
|
|
// Sets buffer size
|
|
if (!$adaptive)
|
|
$buffer = 4096;
|
|
else
|
|
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
|
|
|
|
// Jump to last character
|
|
fseek($f, -1, SEEK_END);
|
|
|
|
// Read it and adjust line number if necessary
|
|
// (Otherwise the result would be wrong if file doesn't end with a blank line)
|
|
if (fread($f, 1) != "\n") $lines -= 1;
|
|
|
|
// Start reading
|
|
$output = '';
|
|
$chunk = '';
|
|
|
|
// While we would like more
|
|
while (ftell($f) > 0 && $lines >= 0) {
|
|
|
|
// Figure out how far back we should jump
|
|
$seek = min(ftell($f), $buffer);
|
|
|
|
// Do the jump (backwards, relative to where we are)
|
|
fseek($f, -$seek, SEEK_CUR);
|
|
|
|
// Read a chunk and prepend it to our output
|
|
$output = ($chunk = fread($f, $seek)) . $output;
|
|
|
|
// Jump back to where we started reading
|
|
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
|
|
|
|
// Decrease our line counter
|
|
$lines -= substr_count($chunk, "\n");
|
|
}
|
|
|
|
// While we have too many lines
|
|
// (Because of buffer size we might have read too many)
|
|
while ($lines++ < 0) {
|
|
|
|
// Find first newline and remove all text before that
|
|
$output = substr($output, strpos($output, "\n") + 1);
|
|
}
|
|
|
|
// Close file and return
|
|
fclose($f);
|
|
return trim($output);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function head($filepath = null, $lines = 10, $adaptive = true)
|
|
{
|
|
|
|
// Open file
|
|
$f = @fopen(($filepath ?: $this->filepath), "rb");
|
|
if ($f === false) return false;
|
|
|
|
// Sets buffer size
|
|
/* if (!$adaptive)
|
|
$buffer = 4096;
|
|
else
|
|
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096)); */
|
|
|
|
// Start reading
|
|
$output = '';
|
|
while (($line = fgets($f)) !== false) {
|
|
if (feof($f)) break;
|
|
if ($lines-- == 0) break;
|
|
$output .= $line . "";
|
|
}
|
|
|
|
// Close file and return
|
|
fclose($f);
|
|
return trim($output);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function emergency($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::EMERGENCY, $context);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function alert($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::ALERT, $context);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function critical($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::CRITICAL, $context);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function error($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::ERROR, $context);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function warning($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::WARNING, $context);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function notice($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::NOTICE, $context);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function info($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::INFO, $context);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function debug($message, array $context = array())
|
|
{
|
|
return $this->log($message, self::DEBUG, $context);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function log($message = null, $level = self::INFO, array $context = array()) {
|
|
$record = [
|
|
'message' => $message,
|
|
'context' => $context,
|
|
'level' => $level,
|
|
'level_name' => $level,
|
|
'channel' => str_replace(self::LOG_FILE_EXTENSION, '', $this->filename),
|
|
'datetime' => $this->getDateTime(),
|
|
'extra' => [],
|
|
'route' => \Phacil\Framework\startEngineExacTI::getRoute(),
|
|
];
|
|
|
|
$trated = $this->logTreatment($record);
|
|
|
|
$log = $this->interpolate($this->format, $trated);
|
|
|
|
return $this->writeToFile($log);
|
|
}
|
|
|
|
/**
|
|
* @param array $record
|
|
* @return array
|
|
* @throws \RuntimeException
|
|
*/
|
|
protected function logTreatment(array $record)
|
|
{
|
|
if (false === strpos($record['message'], '{')) {
|
|
return $record;
|
|
}
|
|
|
|
try {
|
|
$replacements = [];
|
|
foreach ($record['context'] as $key => $val) {
|
|
$placeholder = '{' . $key . '}';
|
|
if (strpos($record['message'], $placeholder) === false) {
|
|
continue;
|
|
}
|
|
|
|
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
|
|
$replacements[$placeholder] = $val;
|
|
} elseif ($val instanceof \DateTimeInterface) {
|
|
$replacements[$placeholder] = $val->format(self::DATE_TIME_FORMAT);
|
|
} elseif ($val instanceof \UnitEnum) {
|
|
$replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name;
|
|
} elseif (is_object($val)) {
|
|
$replacements[$placeholder] = '[object ' . \get_class($val) . ']';
|
|
} elseif (is_array($val)) {
|
|
$replacements[$placeholder] = 'array' . $this->toJson($val);
|
|
} else {
|
|
$replacements[$placeholder] = '[' . gettype($val) . ']';
|
|
}
|
|
|
|
if ($this->removeUsedContextFields) {
|
|
unset($record['context'][$key]);
|
|
}
|
|
}
|
|
|
|
$record['message'] = strtr($record['message'], $replacements);
|
|
|
|
} catch (\Exception $th) {
|
|
//throw $th;
|
|
$this->warning($th->getMessage());
|
|
}
|
|
|
|
return $record;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $data
|
|
*/
|
|
protected function convertToString($data)
|
|
{
|
|
if (null === $data || is_bool($data)) {
|
|
return var_export($data, true);
|
|
}
|
|
|
|
if (is_scalar($data)) {
|
|
return (string) $data;
|
|
}
|
|
|
|
return $this->toJson($data);
|
|
}
|
|
|
|
/**
|
|
* Return the JSON representation of a value
|
|
*
|
|
* @param mixed $data
|
|
* @throws \RuntimeException if encoding fails and errors are not ignored
|
|
* @return string if encoding fails and ignoreErrors is true 'null' is returned
|
|
*/
|
|
protected function toJson($data)
|
|
{
|
|
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_PARTIAL_OUTPUT_ON_ERROR);;
|
|
}
|
|
|
|
/**
|
|
* @param string $str
|
|
* @return string
|
|
* @throws \Phacil\Framework\Exception\RuntimeException
|
|
*/
|
|
protected function replaceNewlines($str)
|
|
{
|
|
if ($this->allowInlineLineBreaks) {
|
|
if (0 === strpos($str, '{')) {
|
|
$str = preg_replace('/(?<!\\\\)\\\\[rn]/', "\n", $str);
|
|
if (null === $str) {
|
|
$pcreErrorCode = preg_last_error();
|
|
throw new \Phacil\Framework\Exception\RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode );
|
|
}
|
|
}
|
|
|
|
return $str;
|
|
}
|
|
|
|
return str_replace(["\r\n", "\r", "\n"], ' ', $str);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
*/
|
|
protected function stringify($value)
|
|
{
|
|
return $this->replaceNewlines($this->convertToString($value));
|
|
}
|
|
|
|
/**
|
|
* @param mixed $format
|
|
* @param mixed $record
|
|
* @return string
|
|
* @throws \RuntimeException
|
|
*/
|
|
protected function interpolate($format, $record)
|
|
{
|
|
$replace = [];
|
|
|
|
foreach ($record as $key => $value) {
|
|
if (is_array($value)) {
|
|
$value = $this->convertToString($value);
|
|
}
|
|
$replace['%' . $key . '%'] = $value;
|
|
}
|
|
|
|
return strtr($format, $replace);
|
|
}
|
|
}
|
|
|