class Debugger in Acquia Purge 8
Provides a centralized debugger for Acquia purger plugins.
Hierarchy
- class \Drupal\acquia_purge\Plugin\Purge\Purger\Debugger implements DebuggerInterface uses PurgeLoggerAwareTrait
Expanded class hierarchy of Debugger
File
- src/
Plugin/ Purge/ Purger/ Debugger.php, line 15
Namespace
Drupal\acquia_purge\Plugin\Purge\PurgerView source
class Debugger implements DebuggerInterface {
use PurgeLoggerAwareTrait;
/**
* Int: the assumed maximum buffer length for debugg output.
*/
const OUTPUT_BUFLEN = 80;
/**
* Int: the length of indentation padding.
*/
const OUTPUT_INDENTLEN = 2;
/**
* Supporting variable for ::callerAdd() and ::callerRemove.
*
* @var string[]
*/
protected $callGraph = [];
/**
* Boolean indicating whether debugging is enabled in the passed logger.
*
* @var bool
*/
protected $enabled = FALSE;
/**
* {@inheritdoc}
*/
public function __construct(LoggerChannelPartInterface $logger) {
$this
->setLogger($logger);
// Only enable the debugger when running in CLI context (e.g. Drush).
if (php_sapi_name() == 'cli') {
$this->enabled = $logger
->isDebuggingEnabled();
}
$this
->callerAdd(__CLASS__);
}
/**
* {@inheritdoc}
*/
public function callerAdd($caller) {
if (!$this->enabled) {
return;
}
// Normalize the caller name and add it to the call graph.
$caller = $this
->extractClassName($caller);
$this
->write('<' . $caller . '>');
$this->callGraph[] = $caller;
}
/**
* {@inheritdoc}
*/
public function callerRemove($caller) {
if (!$this->enabled) {
return;
}
// Normalize the caller name and remove it from the call graph.
$caller = $this
->extractClassName($caller);
if (is_int($key = array_search($caller, $this->callGraph))) {
unset($this->callGraph[$key]);
}
$this
->write('</' . $caller . '>');
}
/**
* {@inheritdoc}
*/
public function enabled() {
return $this->enabled;
}
/**
* {@inheritdoc}
*/
public function extractClassName($caller) {
if (is_object($caller)) {
$caller = get_class($caller);
}
// Strip out a couple of common class paths to make output more readable.
$caller = str_replace('Drupal\\acquia_purge\\', '', $caller);
$caller = str_replace('Plugin\\Purge\\Purger\\', '', $caller);
return $caller;
}
/**
* {@inheritdoc}
*/
public function extractRequestInfo(RequestInterface $request, $body_title = FALSE) {
$info = [];
$info['REQ'] = sprintf("%s %s HTTP/%s", $request
->getMethod(), $request
->getRequestTarget(), $request
->getProtocolVersion());
foreach ($request
->getHeaders() as $header => $value) {
$info['REQ ' . $header] = implode(',', $value);
}
if ($body = $request
->getBody()
->getContents()) {
if ($body_title) {
$info['REQ BODY'] = $body;
}
else {
$info[] = '';
$info[] = $body;
}
}
return $info;
}
/**
* {@inheritdoc}
*/
public function extractResponseInfo(ResponseInterface $response, $body_title = FALSE) {
$info = [];
$info['RSP'] = sprintf("HTTP/%s %d %s", $response
->getProtocolVersion(), $response
->getStatusCode(), $response
->getReasonPhrase());
foreach ($response
->getHeaders() as $header => $value) {
$info['RSP ' . $header] = implode(',', $value);
}
if ($body = (string) $response
->getBody()) {
$content_type = $response
->getHeaderLine('content-type');
if (strpos($content_type, 'application/json') !== FALSE) {
$body = json_decode($body);
$body = json_encode($body, JSON_PRETTY_PRINT);
}
elseif (strpos($content_type, 'text/html') !== FALSE) {
if (count($lines = explode("\n", $body)) >= 4) {
$lines = array_slice($lines, 0, 4);
$body = implode("\n", $lines) . "\n...";
}
}
if ($body_title) {
$info['RSP BODY'] = $body;
}
else {
$info[] = '';
$info[] = $body;
}
}
return $info;
}
/**
* Calculate the indentation depth in number of characters.
*
* @return int
* The number of characters to be used for indentation.
*/
protected function indentationDepth() {
return count($this->callGraph) * self::OUTPUT_INDENTLEN;
}
/**
* {@inheritdoc}
*/
public function logFailedRequest(\Exception $exception) {
$debug = [];
$vars = [
'@excmsg' => $exception
->getMessage(),
];
// ConnectException's are frequent, rewrite its message somewhat.
if ($exception instanceof ConnectException) {
$vars['@excmsg'] = str_replace('cURL error 6: ', '', $vars['@excmsg']);
$vars['@excmsg'] = str_replace('(see http://curl.haxx.se/libcurl/c/libcurl-errors.html)', '(this is allowed to happen incidentally when servers are slow, not structurally)', $vars['@excmsg']);
}
// Enrich debugging data with the request and response, if available.
if ($exception instanceof RequestException) {
$req = $exception
->getRequest();
foreach ($this
->extractRequestInfo($req, TRUE) as $key => $value) {
$debug[$key] = $value;
}
if ($exception
->hasResponse() && ($rsp = $exception
->getResponse())) {
foreach ($this
->extractResponseInfo($rsp, TRUE) as $key => $value) {
$debug[$key] = $value;
}
}
}
// Add exception and backtrace data.
$debug = array_merge([
'EXC' => $this
->extractClassName($exception),
], $debug);
$debug['BACKTRACE'] = [];
$path_strip = getcwd() . DIRECTORY_SEPARATOR;
foreach (explode("\n", $exception
->getTraceAsString()) as $i => $frame) {
if (in_array($i, [
0,
1,
2,
])) {
$debug['BACKTRACE'][$i] = str_replace($path_strip, '', $frame);
}
}
// Log the normal message to the emergency output stream.
$vars['%debug'] = json_encode($debug, JSON_PRETTY_PRINT);
$this
->logger()
->error("@excmsg\n\n%debug", $vars);
}
/**
* Write a line to the logger's debug output stream.
*
* Content will be prefixed depending on its depth in the call graph. Output
* will also be right-side padded up to $padlen, to improve readability when
* executed using 'drush -d', which adds timestamps at the end of each line.
*
* {@inheritdoc}
*/
public function write($line) {
if (!$this->enabled) {
throw new \LogicException("Cannot call ::write().");
}
$depth = $this
->indentationDepth();
$prefix = str_repeat(' ', $depth);
$suffix = '';
if (($suffixlen = self::OUTPUT_BUFLEN - $depth - strlen($line)) > 0) {
$suffix = str_repeat(' ', $suffixlen);
}
$this
->logger()
->debug($prefix . $line . $suffix);
}
/**
* {@inheritdoc}
*/
public function writeSeparator($separator = '-') {
if (!$this->enabled) {
throw new \LogicException("Cannot call ::writeSeparator().");
}
$depth = $this
->indentationDepth();
$this
->write(str_repeat($separator, self::OUTPUT_BUFLEN - $depth));
}
/**
* {@inheritdoc}
*/
public function writeTable(array $table, $title = NULL) {
if (!$this->enabled) {
throw new \LogicException("Cannot call ::writeTable().");
}
if ($title) {
$this
->writeTitle($title, TRUE, FALSE);
}
$this
->writeSeparator('-');
$longest_key = max(array_map('strlen', array_keys($table)));
foreach ($table as $key => $value) {
$spacing = '';
// Determine how the left-side of the table looks like.
$left = '| ';
if (!is_int($key)) {
$spacing = str_repeat(' ', $longest_key - strlen($key));
$left = '| ' . $key . $spacing . ' | ';
}
// Treat all values as potential multiline and render accordingly.
if (!is_string($value)) {
$value = json_encode($value);
}
foreach (explode("\n", $value) as $line) {
$this
->write($left . $line);
// Render empty columns on the left after rendering the key once.
if (!is_int($key)) {
$left = '| ' . str_repeat(' ', strlen($key)) . $spacing . ' | ';
}
}
}
$this
->writeSeparator('-');
}
/**
* {@inheritdoc}
*/
public function writeTitle($title, $top = TRUE, $bottom = TRUE) {
if (!$this->enabled) {
throw new \LogicException("Cannot call ::writeTitle().");
}
if ($top) {
$this
->writeSeparator('-');
}
$workspace = self::OUTPUT_BUFLEN - $this
->indentationDepth();
$left = intval(floor($workspace / 2) - ceil(strlen($title) / 2) - 1);
$right = $workspace - $left - strlen($title) - 2;
$padleft = str_repeat(' ', $left);
$padright = str_repeat(' ', $right);
$this
->write('|' . $padleft . $title . $padright . '|');
if ($bottom) {
$this
->writeSeparator('-');
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Debugger:: |
protected | property | Supporting variable for ::callerAdd() and ::callerRemove. | |
Debugger:: |
protected | property | Boolean indicating whether debugging is enabled in the passed logger. | |
Debugger:: |
public | function |
Register the current caller to the callgraph. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Remove the current caller from the callgraph. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Check whether the debugger is enabled or not. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Generate a short and readable class name. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Extract information from a request. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Extract information from a response. Overrides DebuggerInterface:: |
|
Debugger:: |
protected | function | Calculate the indentation depth in number of characters. | |
Debugger:: |
public | function |
Log the given failure with as much info as possible. Overrides DebuggerInterface:: |
|
Debugger:: |
constant | Int: the assumed maximum buffer length for debugg output. | ||
Debugger:: |
constant | Int: the length of indentation padding. | ||
Debugger:: |
public | function |
Write a line to the logger's debug output stream. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Write out a separator line to Drupal's debug output. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Write tabular data rendered as table to Drupal's debug output. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Write a header title. Overrides DebuggerInterface:: |
|
Debugger:: |
public | function |
Construct a debugger. Overrides DebuggerInterface:: |
|
PurgeLoggerAwareTrait:: |
protected | property | Channel logger. | |
PurgeLoggerAwareTrait:: |
public | function |