<?php
namespace Modules\Admin\Server\Logs; class ModelGenerateReportCoreAppError { public function generate($outputFile, $listingParams) { $model = new Model(); $listingParams->setCurrentPage(0); $listingParams->setResultsPerPage(PHP_INT_MAX); $model->list_execute($listingParams); $listRows = $model->list_getRecordsList(); $fileHandle = fopen($outputFile, 'w'); fwrite($fileHandle, <<<'HTML'
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>TINA core error logs</title>

	<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js"></script>

	<style>
		pre.output {
			margin-top: 2em;
		}
		span.id {
			float: right;
			color: #88f;
			padding: 0 0.5em;
		}
		span.addTime {
			float: right;
			color: #888;
			padding: 0 0.5em;
		}
		div.line {
			line-height: 1;
			border-bottom: 1px solid #444;
			padding-bottom: 0.5em;
		}
		div.line.E_NOTICE >div.message >span.type {
			background-color: hsl(90, 75%, 70%);
		}
		div.line.E_WARNING >div.message >span.type {
			background-color: hsl(30, 75%, 80%);
		}
		div.line.E_ERROR >div.message >span.type {
			background-color: hsl(5, 75%, 80%);
		}

		div.line div.file {
			margin-left: 2em;
			margin-top: 1em;
			color: hsl(120, 50%, 25%);
		}
		div.line div.stacktrace {
			margin-left: 2em;
			margin-top: 1em;
			color: hsl(180, 50%, 25%);
		}

		section.log-header {
			position: fixed;
			width: 100%;
			top: 0;
			left: 0;
			background-color: hsl(180,  25%, 95%);
			border-bottom: 2px solid hsl(180,  25%, 90%);
			z-index: 1;
		}

		.log-header >#stats {
			float: right;
			margin-left: 0.5em;
			padding-left: 0.5em;
			border-left: 1px solid #ccc;

			font-size: 0.75em;
			color: hsl(180,  25%, 33%);
		}
	</style>
</head>
<body>

<section class="log-header">
	<span 	id="stats">loading ...</span>
	<button onClick="deleteDuplicates()">delete duplicates</button>
	<button onClick="sortByDate()">sort everything by date</button>
	<button onClick="sortByMessage()">sort everything by message</button>
	<button onClick="sortByFile()">sort everything by file</button>
	<button onClick="sortByStacktrace()">sort everything by stacktrace</button>

	<script>
		function deleteDuplicates() {
			let uniqueHashes = [...new Set($('pre.output >div.line').map((i, a) => {
				return $(a).attr('data-hash');
			}))];

			let newOutput = '';
			uniqueHashes.forEach((h) => {
				newOutput+= $(`pre.output >div.line[data-hash=${h}]`)[0].outerHTML;
			});
			// seems it's faster to remove the whole element and add it back
			$(`pre.output`).remove();
			$('.log-header').after(`<pre class="output">${newOutput}</pre>`);

			generateStats();
		}

		function sortByDate() {
			let rows = $('pre.output >div.line')
				.sort( (a, b) => {
					let a1 = $(a).find('span.addTime').html();
					let b1 = $(b).find('span.addTime').html();

					if (a1==null) {
						a1 = '';
					}

					if (b1==null) {
						b1 = '';
					}

					return a1.localeCompare(b1);
				}
			);

			$('pre.output').append(rows);
		}

		function sortByMessage() {
			let rows = $('pre.output >div.line')
				.sort( (a, b) => {
					let a1 = $(a).find('div.message').html();
					let b1 = $(b).find('div.message').html();

					if (a1==null) {
						a1 = '';
					}

					if (b1==null) {
						b1 = '';
					}

					return a1.localeCompare(b1);
				}
			);

			$('pre.output').append(rows);
		}

		function sortByFile() {
			let rows = $('pre.output >div.line')
				.sort( (a, b) => {
					let a1 = $(a).find('div.file').html();
					let b1 = $(b).find('div.file').html();

					if (a1==null) {
						a1 = '';
					}

					if (b1==null) {
						b1 = '';
					}

					return a1.localeCompare(b1);
				}
			);

			$('pre.output').append(rows);
		}

		function sortByStacktrace() {
			let rows = $('pre.output >div.line')
				.sort( (a, b) => {
					let a1 = $(a).find('div.stacktrace').html();
					let b1 = $(b).find('div.stacktrace').html();

					if (a1==null) {
						a1 = '';
					}

					if (b1==null) {
						b1 = '';
					}

					return a1.localeCompare(b1);
				}
			);

			$('pre.output').append(rows);
		}

		function generateStats() {
			let totalRows = new Intl.NumberFormat().format($('pre.output >div.line').length);
			$('#stats').html(`got ${totalRows} rows`);
		}

		$(window).on( "load", function() {
			generateStats();
		});
	</script>
</section>

<pre class="output">
HTML
); $rowIndex = 0; $reportedMessages = []; foreach ($listRows as $listRow) { if (!preg_match("/^\[MAIN::__logCoreAppError\].+/", $listRow['message'])) { continue; } $logRow = $model->getFullLogDetails($listRow['id']); if (!$logRow) { continue; } if (!$logRow['context']) { continue; } $messageHash = md5($listRow['message'] . $logRow['context']['$errfile'] . $logRow['context']['$stacktrace']); if (!$this->shouldReportRow($messageHash)) { continue; } $reportedMessages[$messageHash] = true; $colorIndex = strlen($listRow['message']??'') + strlen($logRow['context']['$errfile']??'') + strlen($logRow['context']['$stacktrace']??''); $colorIndex = (round($colorIndex/10) * 10) % 360; $fileContent = sprintf("<div style='background-color: hsl(%d, %d%%, %d%%);' class='line %s' data-hash='%s' ><span class='addTime'>%s</span><span class='id'>%s</span><div class='message'>%s</div><div class='file'>%s</div><div class='stacktrace'>%s</div></div>", $colorIndex, 66, 92, $logRow['context']['$errorLevel'], $messageHash, $listRow['addTime'], $listRow['id'], $this->cleanupMessage($listRow['message']), $logRow['context']['$errfile'], implode("\n", $logRow['stacktrace']), ); fwrite($fileHandle, $fileContent); $rowIndex++; } fwrite($fileHandle, <<<HTML
</pre>
</body>
</html>
HTML
); fclose($fileHandle); return $this; } protected function cleanupMessage($message) { $message = preg_replace("/^\[MAIN::__logCoreAppError\][\s]*(PHP message:[\s]*)?/", "", $message); $message = preg_replace("/^(E_[A-Z]+)/", "<span class='type'>$1</span>", $message); return $message; } protected function shouldReportRow(string $messageHash) { return true; } } 