- {% if deprecation_logs is empty %}
-
-
Debug {{ debug_logs|length }}
-
Unimportant log messages generated during the execution of the application.
-
-
- {% if debug_logs is empty %}
-
-
There are no log messages of this level.
+
+
+
+
+ {{ include('@WebProfiler/Icon/filter.svg') }}
+ Channel ({{ filters.channel|length - 1 }})
+
+
+
+
+
+ {% for value in filters.channel %}
+
+
+
- {% else %}
- {{ helper.render_table(debug_logs, 'debug') }}
- {% endif %}
+ {% endfor %}
-
-
-
-
PHP Notices {{ collector.countscreams|default(0) }}
-
Log messages generated by PHP notices silenced with the @ operator.
+
+
-
- {% if silenced_logs is empty %}
-
-
There are no log messages of this level.
-
- {% else %}
- {{ helper.render_table(silenced_logs, 'silenced') }}
- {% endif %}
-
-
+
+
+
+
+
+
+
+ Time |
+ Message |
+
+
+
+ {% for log in collector.processedLogs %}
+ {% set css_class = 'error' == log.type ? 'error'
+ : (log.priorityName == 'WARNING' or 'deprecation' == log.type) ? 'warning'
+ : 'silenced' == log.type ? 'silenced'
+ %}
+
+
+
+
+ {% if log.type in ['error', 'deprecation', 'silenced'] or 'WARNING' == log.priorityName %}
+
+ {% if 'error' == log.type or 'WARNING' == log.priorityName %}
+ {{ log.priorityName|lower }}
+ {% else %}
+ {{ log.type|lower }}
+ {% endif %}
+
+ {% endif %}
+ |
- {% set compilerLogTotal = 0 %}
- {% for logs in collector.compilerLogs %}
- {% set compilerLogTotal = compilerLogTotal + logs|length %}
- {% endfor %}
-
-
Container {{ compilerLogTotal }}
-
Log messages generated during the compilation of the service container.
-
-
- {% if collector.compilerLogs is empty %}
-
-
There are no compiler log messages.
-
- {% else %}
-
-
-
- Class |
- Messages |
-
-
-
-
- {% for class, logs in collector.compilerLogs %}
-
-
- {% set context_id = 'context-compiler-' ~ loop.index %}
-
- {{ class }}
-
-
-
- {% for log in logs %}
- - {{ profiler_dump_log(log.message) }}
- {% endfor %}
-
-
- |
- {{ logs|length }} |
-
- {% endfor %}
-
-
- {% endif %}
-
-
+
+ {{ helper.render_log_message('debug', loop.index, log) }}
+ |
+
+ {% endfor %}
+
+
+
+
There are no log messages.
-
+
{% 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 %}
-
-
-
-
- {% if show_level %}Level | {% else %}Time | {% endif %}
- {% if channel_is_defined %}Channel | {% endif %}
- Message |
-
-
-
-
- {% for log in logs %}
- {% set css_class = is_deprecation ? ''
- : log.priorityName in ['CRITICAL', 'ERROR', 'ALERT', 'EMERGENCY'] ? 'status-error'
- : log.priorityName == 'WARNING' ? 'status-warning'
- %}
-
-
- {% if show_level %}
- {{ log.priorityName }}
- {% endif %}
-
- |
-
- {% if channel_is_defined %}
-
- {% if log.channel is null %}n/a{% else %}{{ log.channel }}{% endif %}
- {% if log.errorCount is defined and log.errorCount > 1 %}
- ({{ log.errorCount }} times)
- {% endif %}
- |
+ {% set compilerLogTotal = 0 %}
+ {% for logs in collector.compilerLogs %}
+ {% set compilerLogTotal = compilerLogTotal + logs|length %}
+ {% endfor %}
- {% endif %}
+
+
+ Container Compilation Logs ({{ compilerLogTotal }})
+ Log messages generated during the compilation of the service container.
+
- {{ helper.render_log_message(category, loop.index, log) }} |
+ {% if collector.compilerLogs is empty %}
+
+
There are no compiler log messages.
+
+ {% else %}
+
+
+
+ Messages |
+ Class |
- {% endfor %}
-
-
-{% endmacro %}
+
+
+
+ {% for class, logs in collector.compilerLogs %}
+
+ {{ logs|length }} |
+
+ {% set context_id = 'context-compiler-' ~ loop.index %}
+
+ {{ class }}
+
+
+
+ {% for log in logs %}
+ - {{ profiler_dump_log(log.message) }}
+ {% endfor %}
+
+
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+
+{% endblock %}
{% macro render_log_message(category, log_index, log) %}
{% set has_context = log.context is defined and log.context is not empty %}
@@ -238,26 +230,41 @@
{{ profiler_dump_log(log.message) }}
{% else %}
{{ profiler_dump_log(log.message, log.context) }}
+ {% endif %}
-
+
+ {% if has_trace %}
+ {% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %}
+
Show trace
-
- {{ profiler_dump(log.context, maxDepth=1) }}
-
+
+ {{ profiler_dump(log.context.exception.trace, maxDepth=1) }}
+
+ {% endif %}
+
+ {% if has_context %}
+
+ {{ profiler_dump(log.context, maxDepth=1) }}
+
+ {% endif %}
{% if has_trace %}
{{ profiler_dump(log.context.exception.trace, maxDepth=1) }}
{% endif %}
- {% endif %}
+
{% endmacro %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/filter.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/filter.svg
new file mode 100644
index 0000000000000..8800f1c05d75c
--- /dev/null
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/filter.svg
@@ -0,0 +1 @@
+
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
index 2dfa26918f420..e18700d15fc68 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
@@ -709,104 +709,78 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') {
}
},
- 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 = '
(none)';
- } 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);
- }
- });
- toggleClass(option, 'active');
- } else if ('level' === type) {
- if (i === this.parentNode.querySelectorAll('.active').length - 1) {
- return;
- }
- 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);
- }
- });
- }
+ initializeLogsTable: function() {
+ Sfjs.updateLogsTable();
+
+ document.querySelectorAll('.log-filter input').forEach((input) => {
+ input.addEventListener('change', () => { Sfjs.updateLogsTable(); });
+ });
+
+ document.querySelectorAll('.filter-select-all-or-none a').forEach((link) => {
+ link.addEventListener('click', () => {
+ const selectAll = link.classList.contains('select-all');
+ link.closest('.log-filter-content').querySelectorAll('input').forEach((input) => {
+ input.checked = selectAll;
});
- 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 {
- filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
- toggleClass(row, 'filter-hidden-'+name);
- });
+
+ Sfjs.updateLogsTable();
+ });
+ });
+
+ document.body.addEventListener('click', (event) => {
+ document.querySelectorAll('details.log-filter').forEach((filterElement) => {
+ if (!filterElement.contains(event.target) && filterElement.open) {
+ filterElement.open = false;
}
- processed[value] = true;
});
+ });
+ },
+
+ updateLogsTable: function() {
+ const selectedType = document.querySelector('#log-filter-type input:checked').value;
+ const priorities = document.querySelectorAll('#log-filter-priority input');
+ const selectedPriorities = Array.from(priorities).filter((input) => input.checked).map((input) => input.value);
+ const channels = document.querySelectorAll('#log-filter-channel input');
+ const selectedChannels = Array.from(channels).filter((input) => input.checked).map((input) => input.value);
+
+ const logs = document.querySelector('table.logs');
+ if (null === logs) {
+ return;
+ }
+
+ // hide rows that don't match the current filters
+ let numVisibleRows = 0;
+ logs.querySelectorAll('tbody tr').forEach((row) => {
+ if ('all' !== selectedType && selectedType !== row.getAttribute('data-type')) {
+ row.style.display = 'none';
+ return;
+ }
- if (1 < list.childNodes.length) {
- filter.appendChild(list);
- filter.dataset.filtered = '';
+ if (false === selectedPriorities.includes(row.getAttribute('data-priority'))) {
+ row.style.display = 'none';
+ return;
}
+
+ if ('' !== row.getAttribute('data-channel') && false === selectedChannels.includes(row.getAttribute('data-channel'))) {
+ row.style.display = 'none';
+ return;
+ }
+
+ row.style.display = 'table-row';
+ numVisibleRows++;
});
- }
+
+ document.querySelector('table.logs').style.display = 0 === numVisibleRows ? 'none' : 'table';
+ document.querySelector('.no-logs-message').style.display = 0 === numVisibleRows ? 'block' : 'none';
+
+ // update the selected totals of all filters
+ document.querySelector('#log-filter-priority .filter-active-num').innerText = (priorities.length === selectedPriorities.length) ? 'All' : selectedPriorities.length;
+ document.querySelector('#log-filter-channel .filter-active-num').innerText = (channels.length === selectedChannels.length) ? 'All' : selectedChannels.length;
+
+ // update the currently selected "log type" tab
+ document.querySelectorAll('#log-filter-type li').forEach((tab) => tab.classList.remove('active'));
+ document.querySelector(`#log-filter-type input[value="${selectedType}"]`).parentElement.classList.add('active');
+ },
};
})();
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
index 6bb39de5beb40..7f7db6cf1239c 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
@@ -10,16 +10,28 @@
--page-background: #f9f9f9;
--color-text: #222;
--color-muted: #999;
+ --color-link: #218BC3;
/* when updating any of these colors, do the same in toolbar.css.twig */
--color-success: #4f805d;
--color-warning: #a46a1f;
--color-error: #b0413e;
+ --badge-background: #f5f5f5;
+ --badge-color: #666;
+ --badge-warning-background: #FEF3C7;
+ --badge-warning-color: #B45309;
+ --badge-danger-background: #FEE2E2;
+ --badge-danger-color: #B91C1C;
--tab-background: #fff;
--tab-color: #444;
--tab-active-background: #666;
--tab-active-color: #fafafa;
--tab-disabled-background: #f5f5f5;
--tab-disabled-color: #999;
+ --log-filter-button-background: #fff;
+ --log-filter-button-border: #999;
+ --log-filter-button-color: #555;
+ --log-filter-active-num-color: #2563EB;
+ --log-timestamp-color: #555;
--metric-value-background: #fff;
--metric-value-color: inherit;
--metric-unit-color: #999;
@@ -54,13 +66,25 @@
--page-background: #36393e;
--color-text: #e0e0e0;
--color-muted: #777;
+ --color-link: #93C5FD;
--color-error: #d43934;
+ --badge-background: #555;
+ --badge-color: #ddd;
+ --badge-warning-background: #B45309;
+ --badge-warning-color: #FEF3C7;
+ --badge-danger-background: #B91C1C;
+ --badge-danger-color: #FEE2E2;
--tab-background: #555;
--tab-color: #ccc;
--tab-active-background: #888;
--tab-active-color: #fafafa;
--tab-disabled-background: var(--page-background);
--tab-disabled-color: #777;
+ --log-filter-button-background: #555;
+ --log-filter-button-border: #999;
+ --log-filter-button-color: #ccc;
+ --log-filter-active-num-color: #93C5FD;
+ --log-timestamp-color: #ccc;
--metric-value-background: #555;
--metric-value-color: inherit;
--metric-unit-color: #999;
@@ -139,7 +163,7 @@ p {
}
a {
- color: #218BC3;
+ color: var(--color-link);
text-decoration: none;
}
a:hover {
@@ -204,7 +228,7 @@ button {
}
.btn-link {
border-color: transparent;
- color: #218BC3;
+ color: var(--color-link);
text-decoration: none;
background-color: transparent;
outline: none;
@@ -1011,6 +1035,118 @@ tr.status-warning td {
{# Logger panel
========================================================================= #}
+.badge {
+ background: var(--badge-background);
+ border-radius: 4px;
+ color: var(--badge-color);
+ font-size: 12px;
+ font-weight: bold;
+ padding: 1px 4px;
+}
+.badge-warning {
+ background: var(--badge-warning-background);
+ color: var(--badge-warning-color);
+}
+
+.log-filters {
+ display: flex;
+}
+.log-filters .log-filter {
+ position: relative;
+}
+.log-filters .log-filter + .log-filter {
+ margin-left: 15px;
+}
+.log-filters .log-filter summary {
+ align-items: center;
+ background: var(--log-filter-button-background);
+ border-radius: 2px;
+ border: 1px solid var(--log-filter-button-border);
+ color: var(--log-filter-button-color);
+ cursor: pointer;
+ display: flex;
+ padding: 5px 8px;
+}
+.log-filters .log-filter summary .icon {
+ height: 18px;
+ width: 18px;
+ margin: 0 7px 0 0;
+}
+.log-filters .log-filter summary svg {
+ height: 18px;
+ width: 18px;
+ opacity: 0.7;
+}
+.log-filters .log-filter summary .filter-active-num {
+ color: var(--log-filter-active-num-color);
+ font-weight: bold;
+ padding: 0 1px;
+}
+.log-filter .tab-navigation {
+ margin-bottom: 0;
+}
+.log-filter .tab-navigation li:first-child {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+.log-filter .tab-navigation li:last-child {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+.log-filter .tab-navigation li {
+ border-color: var(--log-filter-button-border);
+ padding: 0;
+}
+.log-filter .tab-navigation li + li {
+ margin-left: -5px;
+}
+.log-filter .tab-navigation li .badge {
+ font-size: 13px;
+ padding: 0 6px;
+}
+.log-filter .tab-navigation li input {
+ display: none;
+}
+.log-filter .tab-navigation li label {
+ align-items: center;
+ cursor: pointer;
+ padding: 5px 10px;
+ display: inline-flex;
+ font-size: 14px;
+}
+
+.log-filters .log-filter .log-filter-content {
+ background: var(--base-0);
+ border: 1px solid var(--table-border);
+ border-radius: 2px;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ padding: 15px;
+ position: absolute;
+ left: 0;
+ top: 36px;
+ max-width: 400px;
+ min-width: 200px;
+ z-index: 9999;
+}
+.log-filters .log-filter .log-filter-content .log-filter-option {
+ align-items: center;
+ display: flex;
+}
+.log-filter .filter-select-all-or-none {
+ margin-bottom: 10px;
+}
+.log-filter .filter-select-all-or-none a + a {
+ margin-left: 15px;
+}
+.log-filters .log-filter .log-filter-content .log-filter-option + .log-filter-option {
+ margin: 7px 0 0;
+}
+.log-filters .log-filter .log-filter-content .log-filter-option label {
+ cursor: pointer;
+ flex: 1;
+ padding-left: 10px;
+}
+
table.logs .metadata {
display: block;
font-size: 12px;
@@ -1018,6 +1154,75 @@ table.logs .metadata {
.theme-dark tr.status-error td,
.theme-dark tr.status-warning td { border-bottom: unset; border-top: unset; }
+table.logs .log-timestamp {
+ color: var(--log-timestamp-color);
+}
+table.logs .log-metadata {
+ margin: 8px 0 0;
+}
+table.logs .log-metadata span {
+ display: inline-block;
+}
+table.logs .log-metadata span + span {
+ margin-left: 10px;
+}
+table.logs .log-metadata .log-channel {
+ color: var(--base-1);
+ font-size: 13px;
+ font-weight: bold;
+}
+table.logs .log-metadata .log-num-occurrences {
+ color: var(--color-muted);
+ font-size: 13px;
+}
+.log-type-badge {
+ display: inline-block;
+ font-family: var(--font-sans-serif);
+ margin-top: 5px;
+}
+.log-type-badge.badge-deprecation {
+ background: var(--badge-warning-background);
+ color: var(--badge-warning-color);
+}
+.log-type-badge.badge-error {
+ background: var(--badge-danger-background);
+ color: var(--badge-danger-color);
+}
+.log-type-badge.badge-silenced {
+ background: #EDE9FE;
+ color: #6D28D9;
+}
+.theme-dark .log-type-badge.badge-silenced {
+ background: #5B21B6;
+ color: #EDE9FE;
+}
+
+tr.log-status-warning {
+ border-left: 4px solid #F59E0B;
+}
+tr.log-status-error {
+ border-left: 4px solid #EF4444;
+}
+tr.log-status-silenced {
+ border-left: 4px solid #A78BFA;
+}
+
+.container-compilation-logs {
+ background: var(--table-background);
+ border: 1px solid var(--base-2);
+ margin-top: 30px;
+ padding: 15px;
+}
+.container-compilation-logs summary {
+ cursor: pointer;
+}
+.container-compilation-logs summary h4 {
+ margin: 0 0 5px;
+}
+.container-compilation-logs summary p {
+ margin: 0;
+}
+
{# Doctrine panel
========================================================================= #}
.sql-runnable {
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
index 2e34a5a1f43de..342c502b977dd 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
@@ -28,6 +28,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
private $containerPathPrefix;
private $currentRequest;
private $requestStack;
+ private $processedLogs;
public function __construct(object $logger = null, string $containerPathPrefix = null, RequestStack $requestStack = null)
{
@@ -80,6 +81,81 @@ public function getLogs()
return $this->data['logs'] ?? [];
}
+ public function getProcessedLogs()
+ {
+ if (null !== $this->processedLogs) {
+ return $this->processedLogs;
+ }
+
+ $rawLogs = $this->getLogs();
+ if ([] === $rawLogs) {
+ return $this->processedLogs = $rawLogs;
+ }
+
+ $logs = [];
+ foreach ($this->getLogs()->getValue() as $rawLog) {
+ $rawLogData = $rawLog->getValue();
+
+ if ($rawLogData['priority']->getValue() > 300) {
+ $logType = 'error';
+ } elseif (isset($rawLogData['scream']) && false === $rawLogData['scream']->getValue()) {
+ $logType = 'deprecation';
+ } elseif (isset($rawLogData['scream']) && true === $rawLogData['scream']->getValue()) {
+ $logType = 'silenced';
+ } else {
+ $logType = 'regular';
+ }
+
+ $logs[] = [
+ 'type' => $logType,
+ 'errorCounter' => isset($rawLogData['errorCounter']) ? $rawLogData['errorCounter']->getValue() : 1,
+ 'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(),
+ 'priority' => $rawLogData['priority']->getValue(),
+ 'priorityName' => $rawLogData['priorityName']->getValue(),
+ 'channel' => $rawLogData['channel']->getValue(),
+ 'message' => $rawLogData['message'],
+ 'context' => $rawLogData['context'],
+ ];
+ }
+
+ // sort logs from oldest to newest
+ usort($logs, static function ($logA, $logB) {
+ return $logA['timestamp'] <=> $logB['timestamp'];
+ });
+
+ return $this->processedLogs = $logs;
+ }
+
+ public function getFilters()
+ {
+ $filters = [
+ 'channel' => [],
+ 'priority' => [
+ 'Debug' => 100,
+ 'Info' => 200,
+ 'Warning' => 300,
+ 'Error' => 400,
+ 'Critical' => 500,
+ 'Alert' => 550,
+ 'Emergency' => 600,
+ ],
+ ];
+
+ $allChannels = [];
+ foreach ($this->getProcessedLogs() as $log) {
+ if ('' === trim($log['channel'])) {
+ continue;
+ }
+
+ $allChannels[] = $log['channel'];
+ }
+ $channels = array_unique($allChannels);
+ sort($channels);
+ $filters['channel'] = $channels;
+
+ return $filters;
+ }
+
public function getPriorities()
{
return $this->data['priorities'] ?? [];
@@ -132,7 +208,7 @@ private function getContainerDeprecationLogs(): array
$logs = [];
foreach (unserialize($logContent) as $log) {
$log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])];
- $log['timestamp'] = $bootTime;
+ $log['timestamp'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED);
$log['priority'] = 100;
$log['priorityName'] = 'DEBUG';
$log['channel'] = null;