Permalink
Browse files

feature #28934 [WebProfilerBundle] Add channel log filter (ro0NL)

This PR was squashed before being merged into the 4.2-dev branch (closes #28934).

Discussion
----------

[WebProfilerBundle] Add channel log filter

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #...   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

Continuation of #28906

The JS is revised to be more generic;

- support 2 filter types: `level` and `choice` (respectively `Log level` and `Log channel` here)
- remove default filter value support (not used yet, but opportunity kept open) - it requires a bit more work to genericify it.
- filters refines the resultset (e.g. show all logs in the app channel with priority higher than alert)

![image](https://user-images.githubusercontent.com/1047696/47257162-b01bfe00-d48a-11e8-8364-d1eca69c9182.png)

Level filter (works the same as shown in #28906 )

![image](https://user-images.githubusercontent.com/1047696/47257699-78648480-d491-11e8-8c55-1dccda980de4.png)

Choice filter

![image](https://user-images.githubusercontent.com/1047696/47257205-3c2e2580-d48b-11e8-821b-e95bfed36331.png)

![image](https://user-images.githubusercontent.com/1047696/47257209-4bad6e80-d48b-11e8-8fcc-e868aa556ff8.png)

We forgot to update TwigBundle previously, that's still needed after review here.

Commits
-------

e1bd82e [WebProfilerBundle] Add channel log filter
  • Loading branch information...
fabpot committed Oct 24, 2018
2 parents e0c6049 + e1bd82e commit 01dfca1590270899bab900ef57780c349c830ea3
@@ -1,9 +1,9 @@
{% set channel_is_defined = (logs|first).channel is defined %}
<table class="logs" data-log-levels="Emergency,Alert,Critical,Error,Warning,Notice,Info,Debug" data-default-log-level="Info">
<table class="logs" data-filter-level="Emergency,Alert,Critical,Error,Warning,Notice,Info,Debug" data-filters>
<thead>
<tr>
<th>Level</th>
{% if channel_is_defined %}<th>Channel</th>{% endif %}
<th data-filter="level">Level</th>
{% if channel_is_defined %}<th data-filter="channel">Channel</th>{% endif %}
<th class="full-width">Message</th>
</tr>
</thead>
@@ -18,7 +18,7 @@
{% set severity = log.context.exception.severity|default(false) %}
{% set status = severity is constant('E_DEPRECATED') or severity is constant('E_USER_DEPRECATED') ? 'warning' : 'normal' %}
{% endif %}
<tr class="status-{{ status }}" data-log-level="{{ log.priorityName|lower }}">
<tr class="status-{{ status }}" data-filter-level="{{ log.priorityName|lower }}"{% if channel_is_defined %} data-filter-channel="{{ log.channel }}"{% endif %}>
<td class="text-small" nowrap>
<span class="colored text-bold">{{ log.priorityName }}</span>
<span class="text-muted newline">{{ log.timestamp|date('H:i:s') }}</span>
@@ -173,50 +173,102 @@
}
},
createLogLevels: function() {
document.querySelectorAll('.logs[data-log-levels]').forEach(function (el) {
var bullets = document.createElement('ul'),
levels = el.getAttribute('data-log-levels').toLowerCase().split(','),
defaultLevel = el.hasAttribute('data-default-log-level') ? levels.indexOf(el.getAttribute('data-default-log-level').toLowerCase()) : levels.length - 1;
addClass(bullets, 'log-levels');
el.getAttribute('data-log-levels').split(',').forEach(function (level, i) {
var bullet = document.createElement('li');
bullet.innerText = level;
bullet.setAttribute('data-log-level', String(i));
bullets.appendChild(bullet);
addEventListener(bullet, 'click', function() {
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
return;
}
this.parentNode.querySelectorAll('li').forEach(function (bullet, j) {
if (parseInt(bullet.getAttribute('data-log-level')) <= levels.indexOf(level.toLowerCase())) {
addClass(bullet, 'active');
if (i === j) {
addClass(bullet, 'last-active');
} else {
removeClass(bullet, 'last-active');
createFilters: function() {
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
var filters = filter.closest('[data-filters]'),
type = 'choice',
name = filter.dataset.filter,
ucName = name.charAt(0).toUpperCase()+name.slice(1),
list = document.createElement('ul'),
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
labels = {},
defaults = null,
indexed = {},
processed = {};
if (typeof values === 'string') {
type = 'level';
labels = values.split(',');
values = values.toLowerCase().split(',');
defaults = values.length - 1;
}
addClass(list, 'filter-list');
addClass(list, 'filter-list-'+type);
values.forEach(function (value, i) {
if (value instanceof HTMLElement) {
value = value.dataset['filter'+ucName];
}
if (value in processed) {
return;
}
var option = document.createElement('li'),
label = i in labels ? labels[i] : value,
active = false,
matches;
if ('' === label) {
option.innerHTML = '<em>(none)</em>';
} else {
option.innerText = label;
}
option.dataset.filter = value;
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
indexed[value] = i;
list.appendChild(option);
addEventListener(option, 'click', function () {
if ('choice' === type) {
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (option.dataset.filter === row.dataset['filter'+ucName]) {
toggleClass(row, 'filter-hidden-'+name);
}
} else {
removeClass(bullet, 'active');
removeClass(bullet, 'last-active');
});
toggleClass(option, 'active');
} else if ('level' === type) {
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
return;
}
});
el.querySelectorAll('tr[data-log-level]').forEach(function (row) {
row.style.display = i < levels.indexOf(row.getAttribute('data-log-level')) ? 'none' : '';
});
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
if (j <= i) {
addClass(currentOption, 'active');
if (i === j) {
addClass(currentOption, 'last-active');
} else {
removeClass(currentOption, 'last-active');
}
} else {
removeClass(currentOption, 'active');
removeClass(currentOption, 'last-active');
}
});
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (i < indexed[row.dataset['filter'+ucName]]) {
addClass(row, 'filter-hidden-'+name);
} else {
removeClass(row, 'filter-hidden-'+name);
}
});
}
});
if (i <= defaultLevel) {
addClass(bullet, 'active');
if (i === defaultLevel) {
addClass(bullet, 'last-active');
if ('choice' === type) {
active = null === defaults || 0 <= defaults.indexOf(value);
} else if ('level' === type) {
active = i <= defaults;
if (active && i === defaults) {
addClass(option, 'last-active');
}
}
if (active) {
addClass(option, 'active');
} else {
el.querySelectorAll('tr[data-log-level="'+level.toLowerCase()+'"]').forEach(function (row) {
row.style.display = 'none';
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
toggleClass(row, 'filter-hidden-'+name);
});
}
processed[value] = true;
});
el.parentNode.insertBefore(bullets, el);
if (1 < list.childNodes.length) {
filter.appendChild(list);
filter.dataset.filtered = '';
}
});
}
};
@@ -225,7 +277,7 @@
Sfjs.addEventListener(document, 'DOMContentLoaded', function() {
Sfjs.createTabs();
Sfjs.createToggles();
Sfjs.createLogLevels();
Sfjs.createFilters();
});
/*]]>*/</script>
@@ -1,3 +1,39 @@
{# This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig.
If you make any change in this file, verify the same change is needed in the other file. #}
:root {
--font-sans-serif: 'Helvetica, Arial, sans-serif';
--page-background: #f9f9f9;
--color-text: #222;
/* when updating any of these colors, do the same in toolbar.css.twig */
--color-success: #4f805d;
--color-warning: #a46a1f;
--color-error: #b0413e;
--color-muted: #999;
--tab-background: #fff;
--tab-color: #444;
--tab-active-background: #666;
--tab-active-color: #fafafa;
--tab-disabled-background: #f5f5f5;
--tab-disabled-color: #999;
--metric-value-background: #fff;
--metric-value-color: inherit;
--metric-unit-color: #999;
--metric-label-background: #e0e0e0;
--metric-label-color: inherit;
--table-border: #e0e0e0;
--table-background: #fff;
--table-header: #e0e0e0;
--shadow: 0px 0px 1px rgba(128, 128, 128, .2);
--border: 1px solid #e0e0e0;
--base-0: #fff;
--base-1: #f5f5f5;
--base-2: #e0e0e0;
--base-3: #ccc;
--base-4: #666;
--base-5: #444;
--base-6: #222;
}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
html {
@@ -20,6 +56,7 @@ table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }
.m-t-5 { margin-top: 5px; }
.hidden-xs-down { display: none; }
.block { display: block; }
.full-width { width: 100%; }
.hidden { display: none; }
.prewrap { white-space: pre-wrap; }
.nowrap { white-space: nowrap; }
@@ -58,6 +95,40 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis
.tab-navigation li .badge.status-error { background: #B0413E; color: #FFF; }
.tab-content > *:first-child { margin-top: 0; }
[data-filters] { position: relative; }
[data-filtered] { cursor: pointer; }
[data-filtered]:after { content: '\00a0\25BE'; }
[data-filtered]:hover .filter-list li { display: inline-flex; }
[class*="filter-hidden-"] { display: none; }
.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; }
.filter-list :after { content: ''; }
.filter-list li {
background: var(--tab-disabled-background);
border-bottom: var(--border);
color: var(--tab-disabled-color);
display: none;
list-style: none;
margin: 0;
padding: 5px 10px;
text-align: left;
font-weight: normal;
}
.filter-list li.active {
background: var(--tab-background);
color: var(--tab-color);
}
.filter-list li.last-active {
background: var(--tab-active-background);
color: var(--tab-active-color);
}
.filter-list-level li { cursor: s-resize; }
.filter-list-level li.active { cursor: n-resize; }
.filter-list-level li.last-active { cursor: default; }
.filter-list-level li.last-active:before { content: '\2714\00a0'; }
.filter-list-choice li:before { content: '\2714\00a0'; color: var(--tab-background); }
.filter-list-choice li.active:before { color: unset; }
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
.container::after { content: ""; display: table; clear: both; }
@@ -125,14 +196,6 @@ header .container { display: flex; justify-content: space-between; }
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }
table.logs tr td:last-child { width: 100%; }
.log-levels { width: 100%; margin: 0; padding: 0; display: flex; align-items: center; list-style: none; }
.log-levels li { width: 100%; padding: 3px; margin: 0; cursor: pointer; text-align: center; border: 2px dashed #e0e0e0; border-radius: 5px; color: #888; }
.log-levels li + li { margin-left: 10px; }
.log-levels li.active { background: #eee; color: #666; border-style: solid; border-width: 1px; padding: 4px; border-color: #aaa; }
.log-levels li.last-active { cursor: not-allowed; }
@media (min-width: 575px) {
.hidden-xs-down { display: initial; }
.help-link { margin-left: 30px; }
@@ -180,18 +180,21 @@
</div>
</div>
<script>Sfjs.createFilters();</script>
{% endif %}
{% endblock %}
{% macro render_table(logs, category = '', show_level = false, is_deprecation = false) %}
{% import _self as helper %}
{% set channel_is_defined = (logs|first).channel is defined %}
{% set filter = show_level or channel_is_defined %}
<table class="logs"{% if show_level %} data-log-levels="Emergency,Alert,Critical,Error,Warning,Notice,Info"{% endif %}>
<table class="logs"{% if show_level %} data-filter-level="Emergency,Alert,Critical,Error,Warning,Notice,Info"{% endif %}{% if filter %} data-filters{% endif %}>
<thead>
<tr>
<th>{{ show_level ? 'Level' : 'Time' }}</th>
{% if channel_is_defined %}<th>Channel</th>{% endif %}
{% if show_level %}<th data-filter="level">Level</th>{% else %}<th>Time</th>{% endif %}
{% if channel_is_defined %}<th data-filter="channel">Channel</th>{% endif %}
<th class="full-width">Message</th>
</tr>
</thead>
@@ -202,7 +205,7 @@
: log.priorityName in ['CRITICAL', 'ERROR', 'ALERT', 'EMERGENCY'] ? 'status-error'
: log.priorityName == 'WARNING' ? 'status-warning'
%}
<tr class="{{ css_class }}"{% if show_level %} data-log-level="{{ log.priorityName|lower }}"{% endif %}>
<tr class="{{ css_class }}"{% if show_level %} data-filter-level="{{ log.priorityName|lower }}"{% endif %}{% if channel_is_defined %} data-filter-channel="{{ log.channel is not null ? log.channel : '' }}"{% endif %}>
<td class="font-normal text-small" nowrap>
{% if show_level %}
<span class="colored text-bold">{{ log.priorityName }}</span>
@@ -212,7 +215,7 @@
{% if channel_is_defined %}
<td class="font-normal text-small text-bold" nowrap>
{{ log.channel }}
{% if log.channel is null %}<em>n/a</em>{% else %}{{ log.channel }}{% endif %}
{% if log.errorCount is defined and log.errorCount > 1 %}
<span class="text-muted">({{ log.errorCount }} times)</span>
{% endif %}
@@ -225,10 +228,6 @@
{% endfor %}
</tbody>
</table>
{% if show_level %}
<script>Sfjs.createLogLevels();</script>
{% endif %}
{% endmacro %}
{% macro render_log_message(category, log_index, log) %}
Oops, something went wrong.

0 comments on commit 01dfca1

Please sign in to comment.