OpenXE/classes/Components/Logger/Context/ContextHelper.php
2021-05-21 08:49:41 +02:00

287 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Xentral\Components\Logger\Context;
use DateTime;
use Throwable;
use Xentral\Components\Http\Request;
use Xentral\Components\Logger\LoggerInterface;
use Xentral\Components\Util\StringUtil;
final class ContextHelper
{
/** @var Request $request */
private $request;
/**
* @param Request $request
*/
public function __construct(
Request $request
) {
$this->request = $request;
}
/**
* @param array $contextArray
*
* @return ContextInterface
*/
public function createContext(array $contextArray): ContextInterface
{
//time context
$time = new DateTime('now');
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
//calling code line
$invocation = $this->findInvocation($backtrace);
//origin of the call
$instigation = $this->createOrigin($backtrace);
//possible exception
$exceptionEntry = null;
if (array_key_exists('exception', $contextArray)) {
$exceptionEntry = $contextArray['exception'];
unset($contextArray['exception']);
}
$exception = null;
if (is_subclass_of($exceptionEntry, Throwable::class)) {
$exception = $exceptionEntry;
}
//possible dump
if (count($contextArray) === 0) {
$dump = null;
} else {
$dump = $contextArray;
}
return new LoggerContext(
$time,
$invocation,
$instigation,
$exception,
$dump
);
}
/**
* @param string $message
* @param array $context
*
* @return string
*/
public function interpolateMessage(string $message, array &$context): string
{
$keys = array_keys($context);
$interpolated = $message;
foreach ($keys as $key) {
if ($key === 'exception') {
continue;
}
if (!preg_match('/^\w+$/', (string)$key)) {
continue;
}
if (preg_match(sprintf('/\{%s\}/', $key), $interpolated)) {
$pattern = sprintf('{%s}', $key);
try {
$interpolated = str_replace($pattern, (string)$context[$key], $interpolated);
} catch (Throwable $e) {
continue;
}
unset($context[$key]);
}
}
return $interpolated;
}
/**
* @param array $backtrace
*
* @return Invocation
*/
public function findInvocation(array $backtrace): Invocation
{
$lastLine = 0;
$lastFile = '';
foreach ($backtrace as $trace) {
if (isset($trace['class'])) {
$class = $trace['class'];
if (
$class !== self::class
&& !in_array(LoggerInterface::class, class_implements($class), true)
) {
return new Invocation(
$trace['class'],
$trace['function'],
$lastLine,
$lastFile
);
}
if (array_key_exists('line', $trace)) {
$lastLine = $trace['line'];
$lastFile = $trace['file'];
}
}
}
return new Invocation('unknown', 'unknown', 0, 'unknown');
}
/**
* @param array $backtrace
*
* @return OriginInterface
*/
public function createOrigin(array $backtrace): OriginInterface
{
try {
$instigation = $this->tryCreateFrontendOrigin();
if ($instigation !== null) {
return $instigation;
}
$instigation = $this->tryCreateSchedulerJobOrigin($backtrace);
if ($instigation !== null) {
return $instigation;
}
$instigation = $this->tryCreateRestApiOrigin();
if ($instigation !== null) {
return $instigation;
}
$instigation = $this->tryCreateLegacyApiOrigin();
if ($instigation !== null) {
return $instigation;
}
$instigation = $this->tryCreateCliOrigin();
if ($instigation !== null) {
return $instigation;
}
} catch (Throwable $e) {
}
return new Origin(OriginInterface::TYPE_UNKNOWN, 'unknown');
}
/**
* @return OriginInterface|null
*/
private function tryCreateFrontendOrigin(): ?OriginInterface
{
$module = $this->request->get->get('module', null);
if ($this->request === null || $module === null || $module === 'api') {
return null;
}
$action = $this->request->get->get('action', null);
$cmd = $this->request->get->get('cmd', null);
$payload = [strtoupper($this->request->getMethod())];
if ($module !== null) {
$payload[] = sprintf('module=%s', $module);
}
if ($action !== null) {
$payload[] = sprintf('action=%s', $action);
}
if ($cmd !== null) {
$payload[] = sprintf('cmd=%s', $cmd);
}
return new Origin(OriginInterface::TYPE_FRONTEND, implode(' ', $payload));
}
/**
* @return OriginInterface|null
*/
private function tryCreateLegacyApiOrigin(): ?OriginInterface
{
if ($this->request->get->get('module', '') === 'api') {
return new Origin(
OriginInterface::TYPE_LEGACY_API,
sprintf(
'%s action=%s',
$this->request->getMethod(),
$this->request->get->get('action', '')
)
);
}
return null;
}
/**
* @return OriginInterface|null
*/
private function tryCreateRestApiOrigin(): ?OriginInterface
{
$url = $this->request->getFullUrl();
$match = preg_match('/api\/(v\d.?\/[^?]+)\??/', $url, $matches);
if ($match && count($matches) > 1) {
return new Origin(
OriginInterface::TYPE_REST_API,
sprintf(
'%s endpoint=%s',
$this->request->getMethod(),
$matches[1]
)
);
}
$path = $this->request->get->get('path', '');
if (preg_match('/(v\d.?\/[^?]+)/', $path)
&& preg_match('/api\/index.php\?/', $url)
) {
return new Origin(
OriginInterface::TYPE_REST_API,
sprintf(
'%s path=%s',
$this->request->getMethod(),
$path
)
);
}
return null;
}
/**
* @param array $backtrace
*
* @return OriginInterface|null
*/
private function tryCreateSchedulerJobOrigin(array $backtrace): ?OriginInterface
{
if (count($backtrace) === 0) {
return null;
}
$trace = $backtrace[count($backtrace) - 1];
$file = $trace['file'] ?? '';
if (!StringUtil::endsWith($file, '/cronjobs/command.php')) {
return null;
}
$jobFile = $trace['args'][0] ?? '';
$payload = $jobFile;
$matchresult = [];
preg_match('/^.+\/cronjobs\/(.+)$/', $jobFile, $matchresult);
if (count($matchresult) > 1) {
$payload = sprintf('job=%s', $matchresult[1]);
}
return new Origin(OriginInterface::TYPE_SCHEDULER_JOB, $payload);
}
/**
* @return OriginInterface|null
*/
private function tryCreateCliOrigin(): ?OriginInterface
{
if ($this->request->server->getInt('argc', 0) < 1) {
return null;
}
$file = $this->request->server->get('argv', [''])[0];
$payload = sprintf('script=%s', $file);
return new Origin(OriginInterface::TYPE_CLI, $payload);
}
}