Commit 5a3dba60 by Carsten Brandt

created debug panel for elasticsearch

this panel shows all logged elasticsearch queries and allows to execute them to see the results.
parent 4565df93
No related merge requests found
......@@ -61,7 +61,8 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
});
foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) {
$this->panels = array_merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) {
$config['module'] = $this;
$config['id'] = $id;
$this->panels[$id] = Yii::createObject($config);
......
......@@ -31,6 +31,12 @@ class Panel extends Component
*/
public $module;
public $data;
/**
* @var array array of actions to add to the debug modules default controller.
* This array will be merged with all other panels actions property.
* See [[yii\base\Controller::actions()]] for the format.
*/
public $actions = [];
/**
* @return string name of the panel
......
......@@ -27,6 +27,15 @@ class DefaultController extends Controller
*/
public $summary;
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function actionIndex()
{
return $this->render('index', ['manifest' => $this->getManifest()]);
......@@ -82,7 +91,7 @@ class DefaultController extends Controller
return $this->_manifest;
}
protected function loadData($tag)
public function loadData($tag)
{
$manifest = $this->getManifest();
if (isset($manifest[$tag])) {
......
......@@ -94,7 +94,7 @@ class Command extends Component
*/
public function get($index, $type, $id, $options = [])
{
return $this->db->get([$index, $type, $id], $options, null, [200, 404]);
return $this->db->get([$index, $type, $id], $options, null);
}
/**
......
......@@ -177,10 +177,10 @@ class Connection extends Component
return new QueryBuilder($this);
}
public function get($url, $options = [], $body = null)
public function get($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('GET', $this->createUrl($url, $options), $body);
return $this->httpRequest('GET', $this->createUrl($url, $options), $body, $raw);
}
public function head($url, $options = [], $body = null)
......@@ -189,37 +189,43 @@ class Connection extends Component
return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body);
}
public function post($url, $options = [], $body = null)
public function post($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('POST', $this->createUrl($url, $options), $body);
return $this->httpRequest('POST', $this->createUrl($url, $options), $body, $raw);
}
public function put($url, $options = [], $body = null)
public function put($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body);
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body, $raw);
}
public function delete($url, $options = [], $body = null)
public function delete($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body);
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body, $raw);
}
private function createUrl($path, $options = [])
{
$url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($options)) {
$url .= '?' . http_build_query($options);
if (!is_string($path)) {
$url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($options)) {
$url .= '?' . http_build_query($options);
}
} else {
$url = $path;
if (!empty($options)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options);
}
}
return [$this->nodes[$this->activeNode]['http_address'], $url];
}
protected function httpRequest($method, $url, $requestBody = null)
protected function httpRequest($method, $url, $requestBody = null, $raw = false)
{
$method = strtoupper($method);
......@@ -228,7 +234,7 @@ class Connection extends Component
$body = '';
$options = [
CURLOPT_USERAGENT => 'Yii2 Framework ' . __CLASS__,
CURLOPT_USERAGENT => 'Yii Framework 2 ' . __CLASS__,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
// http://www.php.net/manual/en/function.curl-setopt.php#82418
......@@ -264,8 +270,11 @@ class Connection extends Component
if (is_array($url)) {
list($host, $q) = $url;
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
if (strncmp($host, 'inet[', 5) == 0) {
$host = substr($host, 5, -1);
if (($pos = strpos($host, '/')) !== false) {
$host = substr($host, $pos + 1);
}
}
$profile = $method . ' ' . $q . '#' . $requestBody;
$url = 'http://' . $host . '/' . $q;
......@@ -312,7 +321,7 @@ class Connection extends Component
]);
}
if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) {
return Json::decode($body);
return $raw ? $body : Json::decode($body);
}
throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [
'requestMethod' => $method,
......
<?php
/**
* @author Carsten Brandt <mail@cebe.cc>
*/
namespace yii\elasticsearch;
use yii\base\Action;
use yii\base\NotSupportedException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use Yii;
use yii\web\Response;
class DebugAction extends Action
{
/**
* @var string the connection id to use
*/
public $db;
/**
* @var Panel
*/
public $panel;
public function run($logId, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
if (!isset($timings[$logId])) {
throw new HttpException(404, 'Log message not found.');
}
$message = $timings[$logId][1];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$method = mb_substr($url, 0, $pos = mb_strpos($url, ' '));
$url = mb_substr($url, $pos + 1);
$options = ['pretty' => true];
/** @var Connection $db */
$db = \Yii::$app->getComponent($this->db);
$time = microtime(true);
switch($method) {
case 'GET': $result = $db->get($url, $options, $body, true); break;
case 'POST': $result = $db->post($url, $options, $body, true); break;
case 'PUT': $result = $db->put($url, $options, $body, true); break;
case 'DELETE': $result = $db->delete($url, $options, $body, true); break;
case 'HEAD': $result = $db->head($url, $options, $body); break;
default:
throw new NotSupportedException("Request method '$method' is not supported by elasticsearch.");
}
$time = microtime(true) - $time;
if ($result === true) {
$result = '<span class="label label-success">success</span>';
} elseif ($result === false) {
$result = '<span class="label label-danger">no success</span>';
}
Yii::$app->response->format = Response::FORMAT_JSON;
return [
'time' => sprintf('%.1f ms', $time * 1000),
'result' => $result,
];
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@
namespace yii\elasticsearch;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger;
use yii\helpers\Html;
use yii\web\View;
......@@ -20,6 +21,17 @@ use yii\web\View;
*/
class DebugPanel extends Panel
{
public $db = 'elasticsearch';
public function init()
{
$this->actions['elasticsearch-query'] = [
'class' => 'yii\\elasticsearch\\DebugAction',
'panel' => $this,
'db' => $this->db,
];
}
public function getName()
{
return 'Elasticsearch';
......@@ -47,13 +59,14 @@ EOD;
public function getDetail()
{
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = [];
$i = 0;
foreach ($this->data['messages'] as $log) {
list ($message, $level, $category, $time, $traces) = $log;
if ($level == Logger::LEVEL_PROFILE_BEGIN) {
continue;
}
foreach ($timings as $logId => $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);
$message = $timing[1];
$traces = $timing[4];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
......@@ -66,48 +79,43 @@ EOD;
$traceString .= Html::ul($traces, [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
$runLinks = '';
$c = 0;
\Yii::$app->elasticsearch->open();
foreach(\Yii::$app->elasticsearch->nodes as $node) {
$pos = mb_strpos($url, ' ');
$type = mb_substr($url, 0, $pos);
if ($type == 'GET' && !empty($body)) {
$type = 'POST';
}
$host = $node['http_address'];
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
}
$nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1);
$nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true';
$nodeBody = json_encode($body);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i-$c').on('click', function() {
$('#elastic-result-$i').html('Sending $type request to $nodeUrl...');
$('#elastic-result-$i').parent('tr').show();
$ajaxUrl = Html::url(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i').on('click', function() {
var result = $('#elastic-result-$i');
result.html('Sending request...');
result.parent('tr').show();
$.ajax({
type: "$type",
url: "$nodeUrl",
body: $nodeBody,
type: "POST",
url: "$ajaxUrl",
success: function( data ) {
$('#elastic-result-$i').html(data);
$('#elastic-time-$i').html(data.time);
$('#elastic-result-$i').html(data.result);
},
dataType: "text"
error: function(jqXHR, textStatus, errorThrown) {
$('#elastic-time-$i').html('');
$('#elastic-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
},
dataType: "json"
});
return false;
});
JS
, View::POS_READY);
$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>';
$c++;
}
$rows[] = "<tr><td style=\"width: 80%;\"><div><b>$url</b><br/><p>$body</p>$traceString</div></td><td style=\"width: 20%;\">$runLinks</td></tr><tr style=\"display: none;\"><td colspan=\"2\" id=\"elastic-result-$i\"></td></tr>";
$runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$rows[] = <<<HTML
<tr>
<td style="width: 10%;">$duration</td>
<td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
<td style="width: 15%;">$runLink</td>
</tr>
<tr style="display: none;"><td id="elastic-time-$i"></td><td colspan="3" id="elastic-result-$i"></td></tr>
HTML;
$i++;
}
$rows = implode("\n", $rows);
......@@ -117,8 +125,9 @@ JS
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 80%;">Url / Query</th>
<th style="width: 20%;">Run Query on node</th>
<th style="width: 10%;">Time</th>
<th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr>
</thead>
<tbody>
......@@ -130,7 +139,7 @@ HTML;
private $_timings;
protected function calculateTimings()
public function calculateTimings()
{
if ($this->_timings !== null) {
return $this->_timings;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment