Skip to content

Instantly share code, notes, and snippets.

@faizanakram99
Created November 18, 2020 12:02
Show Gist options
  • Save faizanakram99/bd6275e14eee075497ec487f60d7307b to your computer and use it in GitHub Desktop.
Save faizanakram99/bd6275e14eee075497ec487f60d7307b to your computer and use it in GitHub Desktop.
View Monolog in browser with HttpFoundation component
<?php
declare(strict_types=1);
namespace Qbil\CommonBundle\Extension;
use Symfony\Component\HttpFoundation\Response;
use Webmozart\Assert\Assert;
final class MonospaceResponse extends Response
{
private const STYLES = [
'text-align' => 'left',
'color' => 'green',
'font-family' => 'monospace',
'font-size' => '16px',
'line-height' => '1.5',
'font-weight' => '500',
'width' => '100%',
'table-layout' => 'fixed',
'border-collapse' => 'collapse',
];
/**
* @param iterable<int, string> $lines
*/
public function __construct(iterable $lines, array $styles = [], array $headers = [])
{
$styles += self::STYLES;
Assert::isMap($styles);
$css = \array_reduce(
\array_keys($styles),
static fn (string $css, string $key) => "$css{$key}:$styles[$key];",
''
);
$rows = '';
foreach ($lines as $line) {
\preg_match(
'/\[(?<date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})]\s(?<channel>\w+)\.(?<level>DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL):\s?(?<message>.+)/',
$line,
$matches
);
[
'date' => $date,
'channel' => $channel,
'level' => $level,
'message' => $message
] = $matches + [
'date' => '',
'channel' => '',
'level' => 'INFO',
'message' => $line,
];
$rows .= ('' === $formattedMessage = self::removeEmptyContextFromMessage($message))
? ''
: <<<"HTML"
<tr class="{$level}">
<td class="date">{$date}</td>
<td class="channel">{$channel}</td>
<td class="level">{$level}</td>
<td class="message"><div>{$formattedMessage}</div></td>
</tr>
HTML;
}
$content = <<<"HTML"
<link rel="stylesheet" href="/bundles/qbilcommon/css/tables.css"/>
<style>
table.pure-table {
$css
}
input[type=search] {
position: sticky;
font-size: 20px;
display: block;
padding: 6px 10px 6px 35px;
border: 1px solid #c4c4c4;
margin: 5px 0;
outline: 0;
width: 100%;
font-weight: normal;
top: 0;
z-index: 10;
background: #fff url(/bundles/qbilcommon/images/mainmenu/svg/search.svg) no-repeat 10px center;
background-size: 20px;
}
table.pure-table th {
position: sticky;
top: 32px;
background-color: #e0e0e0;
}
table.pure-table th[data-sort="channel"],
table.pure-table th[data-sort="level"] {
width: 100px;
}
table.pure-table th[data-sort="date"] {
width: 200px;
}
table.pure-table tbody tr {
border-bottom: 1px solid;
}
table.pure-table tr.DEBUG,
table.pure-table tr.INFO {
color: green;
}
table.pure-table tr.NOTICE {
color: orange;
}
table.pure-table tr.WARNING,
table.pure-table tr.ERROR,
table.pure-table tr.CRITICAL {
color: red;
}
table.pure-table div {
overflow-x: auto;
}
</style>
<div id="logs">
<input type="search" class="search" placeholder="Search">
<table class="pure-table">
<thead>
<tr>
<th class="sort" data-sort="date">
Logged at
</th>
<th class="sort" data-sort="channel">
Channel
</th>
<th class="sort" data-sort="level">
Level
</th>
<th class="sort" data-sort="message">
Message
</th>
</tr>
</thead>
<tbody class="list">
{$rows}
</tbody>
</table>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js"></script>
<script defer>
const options = {
valueNames: ['date', 'channel', 'level', 'message']
};
new List('logs', options);
</script>
HTML;
parent::__construct($content, Response::HTTP_OK, $headers);
}
private static function removeEmptyContextFromMessage(string $message): string
{
return \trim(\preg_replace('/(\s*\[]\s*)+/', '', $message) ?? '');
}
}
<?php
declare(strict_types=1);
namespace Qbil\ApplicationManagement\Controller;
use Qbil\CommonBundle\Extension\MonospaceResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/log/{filename}/{lines}/{skip}", defaults={"lines"=100, "skip"=0}, methods={"GET"})
*/
class ReadLogs
{
private string $logsDir;
public function __construct(string $logsDir)
{
$this->logsDir = $logsDir;
}
public function __invoke(string $filename, int $lines, int $skip): Response
{
if (!\is_file($logfile = "{$this->logsDir}/{$filename}.log")) {
return new MonospaceResponse([
"Log file for '{$filename}' not found.",
]);
}
$file = new \SplFileObject($logfile, 'rb');
$file->seek(\PHP_INT_MAX);
$totalLines = $file->key();
$limit = $skip > 0 && $lines > $skip ? $lines - $skip : -1;
$reader = new \LimitIterator($file, ($offset = $totalLines - $lines) >= 0 ? $offset : 0, $limit);
return new MonospaceResponse($reader);
}
}
/*!
Pure v1.0.0
Copyright 2013 Yahoo!
Licensed under the BSD License.
https://github.com/yahoo/pure/blob/master/LICENSE.md
*/
.pure-table {
/* Remove spacing between table cells (from Normalize.css) */
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 1px solid #cbcbcb;
}
.pure-table caption {
color: #000;
font: italic 85%/1 arial, sans-serif;
padding: 1em 0;
text-align: center;
}
.pure-table td,
.pure-table th {
border-left: 1px solid #cbcbcb;/* inner column border */
border-width: 0 0 0 1px;
font-size: inherit;
margin: 0;
overflow: visible; /*to make ths where the title is really long work*/
padding: 0.5em 1em; /* cell padding */
}
/* Consider removing this next declaration block, as it causes problems when
there's a rowspan on the first cell. Case added to the tests. issue#432 */
.pure-table td:first-child,
.pure-table th:first-child {
border-left-width: 0;
}
.pure-table thead {
background-color: #e0e0e0;
color: #000;
text-align: left;
vertical-align: bottom;
}
/*
striping:
even - #fff (white)
odd - #f2f2f2 (light gray)
*/
.pure-table td {
background-color: transparent;
}
.pure-table-odd td {
background-color: #f2f2f2;
}
/* nth-child selector for modern browsers */
.pure-table-striped tr:nth-child(2n-1) td {
background-color: #f2f2f2;
}
/* BORDERED TABLES */
.pure-table-bordered td {
border-bottom: 1px solid #cbcbcb;
}
.pure-table-bordered tbody > tr:last-child > td {
border-bottom-width: 0;
}
/* HORIZONTAL BORDERED TABLES */
.pure-table-horizontal td,
.pure-table-horizontal th {
border-width: 0 0 1px 0;
border-bottom: 1px solid #cbcbcb;
}
.pure-table-horizontal tbody > tr:last-child > td {
border-bottom-width: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment