Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Add a formatter to compactly print stack traces #78

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"homepage": "https://github.com/zendframework/zend-log",
"require": {
"php": "^5.6 || ^7.0",
"php": "^7.0",
"psr/log": "^1.0",
"zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3",
"zendframework/zend-stdlib": "^2.7 || ^3.0"
Expand Down
164 changes: 164 additions & 0 deletions src/Formatter/NativeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace the header with this:

/**
 * @see       https://github.com/zendframework/zend-log for the canonical source repository
 * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   https://github.com/zendframework/zend-log/blob/master/LICENSE.md New BSD License
 */

*
* @link http://github.com/zendframework/zend-log for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace Zend\Log\Formatter;

// @codingStandardsIgnoreStart
/**
* Format an Exception in a similar way PHP does by default when an exception bubbles to the top
*
* This is a snippet of the native PHP format which it resembles
*
* [Wed Oct 11 15:45:18 2017] PHP Fatal error: Uncaught RuntimeException: error message in /module/MyModule/src/Module.php:11
* Stack trace:
* #0 /vendor/zendframework/zend-modulemanager/src/Listener/ConfigListener.php(124): MyModule\Module->getConfig()
* #1 [internal function]: Zend\ModuleManager\Listener\ConfigListener->onLoadModule(Object(Zend\ModuleManager\ModuleEvent))
* #2 /vendor/zendframework/zend-eventmanager/src/EventManager.php(490): call_user_func(Array, Object(Zend\ModuleManager\ModuleEvent))
* #3 /vendor/zendframework/zend-eventmanager/src/EventManager.php(251): Zend\EventManager\EventManager->triggerListeners('loadModule', Object(Zend\ModuleManager\ModuleEvent))
* #4 /vendor/zendframework/zend-modulemanager/src/ModuleManager.php(181): Zend\EventManager\EventManager->triggerEvent(Object(Zend\ModuleManager\ModuleEvent))
*/
// @codingStandardsIgnoreEnd
class NativeException implements FormatterInterface
{
/**
* Format specifier for DateTime objects in event data
*
* @see http://php.net/manual/en/function.date.php
* @var string
*/
protected $dateTimeFormat = self::DEFAULT_DATETIME_FORMAT;

/**
* Transform an event created from a PHP exception to a log entry in human-readable format
*
* @param mixed[] $event
* @return string
*/
public function format($event): string
{
if ($event['timestamp'] instanceof \DateTimeInterface) {
$event['timestamp'] = $event['timestamp']->format($this->getDateTimeFormat());
}

$output = '[' . $event['timestamp'] . '] ' . $event['priorityName'] . ' ('
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines are very hard to read. Please use sprintf.

. $event['priority'] . ') ' . $event['message'] .' in '
. $event['extra']['file'] . ':' . $event['extra']['line']
. "\n";

if (! empty($event['extra']['trace'])) {
$output .= "Stack trace:\n";
foreach ($event['extra']['trace'] as $i => $traceLine) {
$output .= "#$i " . $this->formatTraceLine($traceLine) . "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. (Don't use "#$i ".)

}
}

return trim($output);
}

/**
* {@inheritDoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*/
public function getDateTimeFormat(): string
{
return $this->dateTimeFormat;
}

/**
* {@inheritDoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

*/
public function setDateTimeFormat($dateTimeFormat)
{
$this->dateTimeFormat = (string) $dateTimeFormat;
return $this;
}

/**
* Transform an element of the array Exception::getTrace
* to a string of a single line
*
* @param mixed[] $trace element of Exception::getTrace
* @return string of a single line
*/
private function formatTraceLine(array $trace): string
{
$arguments = $this->formatArguments($trace['args']);
$output = '';

if (isset($trace['file'])) {
$output .= $trace['file'];

if (isset($trace['line'])) {
$output .= '(' . $trace['line'] . ')';
}

$output .= ': ';
}

$output .= $trace['class'] ?? '';
$output .= $trace['type'] ?? '';
$output .= $trace['function'] ?? '';

$arguments = $this->formatArguments($trace['args'] ?? []);
$output .= '(' . $arguments . ')';

return $output;
}

/**
* Convert function arguments of any type to a short readable string
*
* @param mixed[] $arguments
* @return string a summary of the list of arguments
*/
private function formatArguments(array $arguments): string
{
return implode(', ', array_map([$this, 'formatArgument'], $arguments));
}

/**
* Summarize a variable
*
* @param mixed $argument anything at all
* @return string a summary of the argument
*/
private function formatArgument($argument): string
{
if (is_int($argument) || is_float($argument)) {
return $argument;
}

if (is_string($argument)) {
if (strlen($argument) < 80) {
$truncated = $argument;
} else {
$truncated = substr($argument, 0, 30) . '...' . substr($argument, -30, 30);
}

return "'$truncated'";
}

if ($argument === false) {
return 'false';
}

if ($argument === true) {
return 'true';
}

if (is_array($argument)) {
return 'Array(' . count($argument) . ')';
}

if (is_object($argument)) {
return 'Object(' . get_class($argument) . ')';
}

return gettype($argument);
}
}
63 changes: 63 additions & 0 deletions test/Formatter/NativeExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace the header with this:

/**
 * @see       https://github.com/zendframework/zend-log for the canonical source repository
 * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   https://github.com/zendframework/zend-log/blob/master/LICENSE.md New BSD License
 */

*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace ZendTest\Log\Formatter;

use DateTime;
use PHPUnit\Framework\TestCase;
use Zend\Log\Formatter\NativeException;

class NativeExceptionTest extends TestCase
{
public function testFormat()
{
$date = new DateTime('2017-10-11T22:12:13+02:00');

$event = [
'timestamp' => $date,
'message' => 'testmessage',
'priority' => 3,
'priorityName' => 'ERR',
'extra' => [
'file' => 'test.php',
'line' => 12,
'trace' => [
[
'file' => 'test.php',
'line' => 12,
'function' => 'topTestMethod',
'class' => 'Test',
'type' => '::',
'args' => [1]
],
[
'file' => 'test.php',
'line' => 5,
'function' => 'mainTestMethod',
'class' => 'Test',
'type' => '::',
'args' => [2]
]
]
]
];

$expected = trim("
[2017-10-11T22:12:13+02:00] ERR (3) testmessage in test.php:12
Stack trace:
#0 test.php(12): Test::topTestMethod(1)
#1 test.php(5): Test::mainTestMethod(2)
");

$formatter = new NativeException();
$output = $formatter->format($event);

$this->assertEquals($expected, $output);
}
}