*
* @param string $prefix
*
- * @return Log
+ * @return Log_file
*/
public static function createDebugLogger($prefix = '') {
self::generateLogFileName($prefix);
*/
class CRM_Core_Error_Log extends \Psr\Log\AbstractLogger {
+ /**
+ * @var array
+ */
+ public $map;
+
/**
* CRM_Core_Error_Log constructor.
*/
}
/**
- * @return \Psr\Log\LoggerInterface
+ * @return \CRM_Core_Error_Log
*/
public static function log() {
return Civi\Core\Container::singleton()->get('psr_log');
--- /dev/null
+<?php
+
+
+namespace Civi\API;
+
+/**
+ * API Error Log Observer
+ *
+ * @see \CRM_Core_Error_Log
+ * @see \Civi\API\Subscriber\DebugSubscriber
+ *
+ * @package Civi\API
+ */
+class LogObserver extends \Log_observer {
+
+ /**
+ * @var array
+ */
+ private static $messages = [];
+
+ /**
+ * @see \Log::_announce
+ * @param array $event
+ */
+ public function notify($event) {
+ $levels = \Civi::log()->map;
+ $event['level'] = array_search($event['priority'], $levels);
+ // Extract [civi.tag] from message string
+ // As noted in \CRM_Core_Error_Log::log() the $context array gets prematurely converted to string with print_r() so we have to un-flatten it here
+ if (preg_match('/^(.*)\s*Array\s*\(\s*\[civi\.(\w+)] => (\w+)\s*\)/', $event['message'], $message)) {
+ $event['message'] = $message[1];
+ $event[$message[2]] = $message[3];
+ }
+ self::$messages[] = $event;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMessages() {
+ return self::$messages;
+ }
+
+}
* Class XDebugSubscriber
* @package Civi\API\Subscriber
*/
-class XDebugSubscriber implements EventSubscriberInterface {
+class DebugSubscriber implements EventSubscriberInterface {
+
+ /**
+ * @var \Civi\API\LogObserver
+ */
+ private $debugLog;
/**
* @return array
*/
public static function getSubscribedEvents() {
return [
- Events::RESPOND => ['onApiRespond', Events::W_LATE],
+ Events::PREPARE => ['onApiPrepare', 999],
+ Events::RESPOND => ['onApiRespond', -999],
];
}
+ public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) {
+ $apiRequest = $event->getApiRequest();
+ if (!isset($this->debugLog)
+ && !empty($apiRequest['params']['debug'])
+ && (empty($apiRequest['params']['check_permissions']) || \CRM_Core_Permission::check('view debug output'))
+ ) {
+ $this->debugLog = new \Civi\API\LogObserver();
+ \CRM_Core_Error::createDebugLogger()->attach($this->debugLog);
+ }
+ }
+
/**
* @param \Civi\API\Event\RespondEvent $event
* API response event.
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
$apiRequest = $event->getApiRequest();
$result = $event->getResponse();
- if (
- function_exists('xdebug_time_index')
- && !empty($apiRequest['params']['debug'])
+ if (!empty($apiRequest['params']['debug'])
&& (empty($apiRequest['params']['check_permissions']) || \CRM_Core_Permission::check('view debug output'))
) {
if (is_a($result, '\Civi\Api4\Generic\Result')) {
else {
return;
}
- $debug['peakMemory'] = xdebug_peak_memory_usage();
- $debug['memory'] = xdebug_memory_usage();
- $debug['timeIndex'] = xdebug_time_index();
+ if (isset($this->debugLog) && $this->debugLog->getMessages()) {
+ $debug['log'] = $this->debugLog->getMessages();
+ }
+ if (function_exists('xdebug_time_index')) {
+ $debug['peakMemory'] = xdebug_peak_memory_usage();
+ $debug['memory'] = xdebug_memory_usage();
+ $debug['timeIndex'] = xdebug_time_index();
+ }
$event->setResponse($result);
}
}
\CRM_Utils_API_ReloadOption::singleton(),
\CRM_Utils_API_MatchOption::singleton(),
]));
- $dispatcher->addSubscriber(new \Civi\API\Subscriber\XDebugSubscriber());
+ $dispatcher->addSubscriber(new \Civi\API\Subscriber\DebugSubscriber());
$kernel = new \Civi\API\Kernel($dispatcher);
$reflectionProvider = new \Civi\API\Provider\ReflectionProvider($kernel);
</div>
</div>
<div class="api4-explorer-row">
- <div class="panel panel-warning explorer-code-panel">
+ <div class="panel panel-info explorer-code-panel">
<ul class="panel-heading nav nav-tabs">
<li role="presentation" ng-repeat="lang in ::langs" ng-class="{active: selectedTab.code === lang}">
<a href ng-click="selectLang(lang)">
<span ng-switch="status">
<i class="fa fa-fw fa-circle-o" ng-switch-when="default"></i>
<i class="fa fa-fw fa-check-circle" ng-switch-when="success"></i>
+ <i class="fa fa-fw fa-check-circle" ng-switch-when="warning"></i>
<i class="fa fa-fw fa-minus-circle" ng-switch-when="danger"></i>
- <i class="fa fa-fw fa-spinner fa-pulse" ng-switch-when="warning"></i>
+ <i class="fa fa-fw fa-spinner fa-pulse" ng-switch-when="info"></i>
</span>
<span>{{:: ts('Result') }}</span>
</a>
</li>
<li role="presentation" ng-if="::perm.accessDebugOutput" ng-class="{active: selectedTab.result === 'debug'}">
<a href ng-click="selectedTab.result = 'debug'">
- <i class="fa fa-fw fa-{{ debug ? 'bug' : 'circle-o' }}"></i>
+ <i class="fa fa-fw fa-{{ debug ? (status === 'warning' || status === 'danger' ? 'warning' : 'bug') : 'circle-o' }}"></i>
<span>{{:: ts('Debug') }}</span>
</a>
</li>
}
$scope.execute = function() {
- $scope.status = 'warning';
+ $scope.status = 'info';
$scope.loading = true;
$http.post(CRM.url('civicrm/ajax/api4/' + $scope.entity + '/' + $scope.action, {
params: angular.toJson(getParams()),
}
}).then(function(resp) {
$scope.loading = false;
- $scope.status = 'success';
+ $scope.status = resp.data && resp.data.debug && resp.data.debug.log ? 'warning' : 'success';
$scope.debug = debugFormat(resp.data);
$scope.result = [formatMeta(resp.data), prettyPrintOne(_.escape(JSON.stringify(resp.data.values, null, 2)), 'js', 1)];
}, function(resp) {