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); } }