<?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;
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
/**
* 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 {
/**
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
* Storage the object of log file
*
* @var resource|false
*/
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
private $fileobj;
/**
* Storage the filename
*
* @var string
*/
private $filename;
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
/**
* 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/');
}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
$this->filename = $filename;
$this->filepath = constant(self::CONFIGURATION_LOGS_CONST) . $filename;
if(!is_dir(constant(self::CONFIGURATION_LOGS_CONST))){
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
$old = umask(0);
mkdir(constant(self::CONFIGURATION_LOGS_CONST), self::DIR_LOGS_PERMISSIONS, true);
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
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);
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
}
//$this->fileobj = fopen($this->filepath, 'a+');
return;
}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
/**
* {@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);
}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
/**
* Remove object from memory
*
* @since 1.0.0
* @return void
*/
public function __destruct() {
if ($this->fileobj) {
fclose($this->fileobj);
}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
}
/**
* {@inheritdoc}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
*/
public function getFileName()
{
return $this->filename;
}
/**
* {@inheritdoc}
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
*/
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
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
*/
public function tail($filepath = null, $lines = 10, $adaptive = true)
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
{
// 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
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
*/
public function head($filepath = null, $lines = 10, $adaptive = true)
Controller child exception, Log, Response and Login
Action PHPDoc improvements, Controller child exception, Log new functions (getpath, getfilename, head and tail), log class check dir and creates if not exists, Response redirect improvement, Controller redirect function use the response redirect class, login interface documentation, login doc
3 years ago
{
// 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);
}
}