Skip to content

Commit

Permalink
allow log error renderers
Browse files Browse the repository at this point in the history
  • Loading branch information
juliangut committed Apr 4, 2020
1 parent 6613bb0 commit d6623e7
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 58 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ trigger_error('This is embarrasing', \E_USER_ERROR);

Overall usage has been drastically simplified due to Slim 4 migration to exception based error handling, basically what slim-exception was doing in 1.x.

* Minimum Slim version is now 4.0
* Minimum Slim version is now 4.2
* ExceptionManager has been removed as its functionality is now integrated into Slim
* Exceptions no longer uses juliangut/http-exception and thus they have no identifier
* Global error/exception handling has been moved from a trait (meant for App) to its own class ExceptionHandler
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"require": {
"php": "^7.1",
"psr/log": "^1.0",
"slim/slim": "^4.0",
"slim/slim": "^4.2",
"willdurand/negotiation": "^2.0"
},
"require-dev": {
Expand Down
50 changes: 19 additions & 31 deletions src/Handler/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use Jgut\Slim\Exception\Renderer\JsonRenderer;
use Jgut\Slim\Exception\Renderer\PlainTextRenderer;
use Jgut\Slim\Exception\Renderer\XmlRenderer;
use Jgut\Slim\Exception\Whoops\Renderer\PlainTextRenderer as WhoopsTextRenderer;
use Negotiation\BaseAccept;
use Negotiation\Negotiator;
use Psr\Http\Message\ResponseFactoryInterface;
Expand All @@ -35,6 +34,13 @@ class ErrorHandler extends SlimErrorHandler
{
use LoggerAwareTrait;

/**
* Default error renderer for logs.
*
* @var ErrorRendererInterface|string|callable
*/
protected $logErrorRenderer = PlainTextRenderer::class;

/**
* PHP to PSR3 error map.
*
Expand Down Expand Up @@ -184,10 +190,10 @@ protected function writeToErrorLog(): void
'http_method' => $this->request->getMethod(),
'request_uri' => (string) $this->request->getUri(),
'level_name' => \strtoupper($logLevel),
'stacktrace' => $this->getStackTrace(),
];

$message = $this->exception->getMessage();
$renderer = $this->determineLogRenderer();
$message = $renderer($this->exception, $this->logErrorDetails);
if (!$this->displayErrorDetails) {
$message .= "\nTips: To display error details in HTTP response ";
$message .= 'set "displayErrorDetails" to true in the ErrorHandler constructor.';
Expand All @@ -196,6 +202,16 @@ protected function writeToErrorLog(): void
$this->logger->log($logLevel, $message, $logContext);
}

/**
* Determine log renderer.
*
* @return callable
*/
protected function determineLogRenderer(): callable
{
return $this->callableResolver->resolve($this->logErrorRenderer);
}

/**
* Get log level.
*
Expand All @@ -211,32 +227,4 @@ final protected function getLogLevel(): string

return LogLevel::ERROR;
}

/**
* Get exception stack trace.
*
* @return string
*/
protected function getStackTrace(): string
{
if (!$this->logErrorDetails) {
return '';
}

if (!\interface_exists('\Whoops\RunInterface')) {
// @codeCoverageIgnoreStart
return $this->exception->getTraceAsString();
// @codeCoverageIgnoreEnd
}

$renderer = new WhoopsTextRenderer();
$renderer->setException($this->exception);
$exceptionParts = \explode("\n", \rtrim($renderer->generateResponse(), "\n"));

if (\count($exceptionParts) !== 1) {
$exceptionParts = \array_filter(\array_splice($exceptionParts, 2));
}

return \implode("\n", $exceptionParts);
}
}
31 changes: 29 additions & 2 deletions src/Whoops/Handler/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class ErrorHandler extends BaseErrorHandler
{
protected const REQUEST_DATA_TABLE_LABEL = 'Slim Application (Request)';

/**
* Default error renderer for logs.
*
* @var WhoopsHandler|string|callable
*/
protected $logErrorRenderer = PlainTextRenderer::class;

/**
* @var string[]
*/
Expand Down Expand Up @@ -87,9 +94,29 @@ public function __construct(
*/
protected function determineRenderer(): callable
{
/** @var mixed $renderer */
$renderer = parent::determineRenderer();
return $this->getRenderer(parent::determineRenderer());
}

/**
* {@inheritdoc}
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
protected function determineLogRenderer(): callable
{
return $this->getRenderer(parent::determineLogRenderer());
}

/**
* Get Whoops aware renderer.
*
* @param mixed $renderer
*
* @return callable
*/
protected function getRenderer($renderer): callable
{
if (!$renderer instanceof WhoopsHandler) {
throw new \InvalidArgumentException(\sprintf(
'Renderer "%s" for Whoops error handler does not implement %s',
Expand Down
63 changes: 46 additions & 17 deletions src/Whoops/Renderer/PlainTextRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,19 @@ public function generateResponse(): string
*/
protected function getStackTraceOutput(array $stackFrames): string
{
$argsOutputLimit = $this->getTraceFunctionArgsOutputLimit();

$line = 1;
$stackTrace = \array_map(
function (array $stack) use (&$line): string {
$template = "\n%3d. %s->%s() %s:%d%s";
if (!$stack['class']) {
$template = "\n%3d. %s%s() %s:%d%s";
}

function (array $stack) use ($argsOutputLimit, &$line): string {
$trace = \sprintf(
$template,
!$stack['class'] ? "\n%3d. %s%s() %s:%d%s" : "\n%3d. %s->%s() %s:%d%s",
$line,
$stack['class'],
$stack['function'],
$stack['file'],
$stack['line'],
$this->getArguments($stack['args'], $line)
$this->getArguments($stack['args'], $line, $argsOutputLimit)
);

$line++;
Expand All @@ -104,10 +101,11 @@ function (array $stack) use (&$line): string {
*
* @param mixed[] $args
* @param int $line
* @param int $argsOutputLimit
*
* @return string
*/
protected function getArguments(array $args, int $line): string
protected function getArguments(array $args, int $line, int $argsOutputLimit): string
{
$addArgs = $this->addTraceFunctionArgsToOutput();
if ($addArgs === false || $addArgs < $line) {
Expand All @@ -116,27 +114,58 @@ protected function getArguments(array $args, int $line): string
// @codeCoverageIgnoreEnd
}

$argsOutputLimit = $this->getTraceFunctionArgsOutputLimit();

\ob_start();

\var_dump($args);
foreach ($this->flattenArguments($args) as $arg) {
$this->dump($arg);
}

if (\ob_get_length() > $argsOutputLimit) {
// The argument var_dump is to big.
// The argument var_dump is too big.
// Discarded to limit memory usage.
\ob_end_clean();

return \sprintf(
"\n%sArguments dump length greater than %d Bytes. Discarded.",
"\n%sArguments list dump length greater than %d Bytes. Discarded.",
parent::VAR_DUMP_PREFIX,
$argsOutputLimit
);
}

return \sprintf(
"\n%s",
\preg_replace('/^/m', parent::VAR_DUMP_PREFIX, (string) \ob_get_clean())
$argumentsTrace = (string) \ob_get_clean();
return $argumentsTrace === ''
? ''
: \sprintf("\n%s", \preg_replace('/^/m', parent::VAR_DUMP_PREFIX, $argumentsTrace));
}

/**
* Simplify argument list.
*
* @param mixed[] $args
*
* @return mixed[]
*/
protected function flattenArguments(array $args): array
{
return \array_map(
function ($arg) {
if (\is_object($arg)) {
$class = \get_class($arg);

return $class . (\strpos($class, 'class@anonymous') !== 0 ? '::class' : '');
}

if (\is_resource($arg)) {
return 'resource';
}

if (\is_array($arg)) {
return $this->flattenArguments($arg);
}

return $arg;
},
$args
);
}
}
12 changes: 6 additions & 6 deletions tests/Exception/Handler/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ public function testLoggingError(): void
$callableResolver = $this->getMockBuilder(CallableResolverInterface::class)
->disableOriginalConstructor()
->getMock();
$callableResolver->expects(static::once())
$callableResolver->expects(static::any())
->method('resolve')
->with(HtmlRenderer::class)
->will($this->returnValue(new HtmlRenderer()));
->withConsecutive([PlainTextRenderer::class], [HtmlRenderer::class])
->willReturnOnConsecutiveCalls(new PlainTextRenderer(), new HtmlRenderer());
/* @var CallableResolverInterface $callableResolver */
$handler = new ErrorHandlerStub($callableResolver, new ResponseFactory(), new Negotiator());

Expand All @@ -106,10 +106,10 @@ public function testLoggingHttpError(): void
$callableResolver = $this->getMockBuilder(CallableResolverInterface::class)
->disableOriginalConstructor()
->getMock();
$callableResolver->expects(static::once())
$callableResolver->expects(static::any())
->method('resolve')
->with(HtmlRenderer::class)
->will($this->returnValue(new HtmlRenderer()));
->withConsecutive([PlainTextRenderer::class], [HtmlRenderer::class])
->willReturnOnConsecutiveCalls(new PlainTextRenderer(), new HtmlRenderer());
/* @var CallableResolverInterface $callableResolver */
$handler = new ErrorHandlerStub($callableResolver, new ResponseFactory(), new Negotiator());

Expand Down

0 comments on commit d6623e7

Please sign in to comment.