Merge branch 'develop'

This commit is contained in:
2023-02-14 17:27:30 -03:00
30 changed files with 578 additions and 0 deletions

5
Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM php:8-fpm
WORKDIR /app
COPY ./php-errors.ini /usr/local/etc/php/conf.d/docker-php-errors.ini

View File

@ -0,0 +1,16 @@
<?php
namespace ProVM\Common\Controller;
use ProVM\Common\Service\Logs;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Blade as View;
class Base
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, View $view, Logs $service): ResponseInterface
{
$files = $service->getFiles();
return $view->render($response, 'home', compact('files'));
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace ProVM\Common\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Blade as View;
use ProVM\Common\Service\Logs as Service;
use ProVM\Logview\Log;
class Logs
{
public function get(ServerRequestInterface $request, ResponseInterface $response, View $view, Service $service, string $log_file): ResponseInterface
{
$log = $service->get($log_file);
$levels = [];
foreach (Log::LEVELS as $level) {
$levels[strtolower($level)] = (object) [
'text' => Log::COLORS[$level],
'background' => Log::BACKGROUNDS[$level],
];
}
return $view->render($response, 'logs.show', compact('log', 'levels'));
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace ProVM\Common\Service;
use ProVM\Logview\Log\File;
class Logs
{
public function __construct(string $folder)
{
$this
->setFolder($folder);
}
protected string $folder;
public function getFolder(): string
{
return $this->folder;
}
public function setFolder(string $folder): Logs
{
$this->folder = $folder;
return $this;
}
public function getFiles(): array
{
$files = new \FilesystemIterator($this->getFolder());
$output = [];
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$output []= $file;
}
return $output;
}
public function get(string $log_file): File
{
$content = \Safe\file_get_contents(implode(DIRECTORY_SEPARATOR, [$this->getFolder(), $log_file]));
return (new File())->setFilename($log_file)->setContent($content);
}
}

34
app/composer.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "provm/logview",
"description": "Monolog log file viewer",
"type": "project",
"require": {
"berrnd/slim-blade-view": "^1.0",
"monolog/monolog": "^3.3",
"nyholm/psr7": "^1.5",
"nyholm/psr7-server": "^1.0",
"php-di/php-di": "^7.0",
"php-di/slim-bridge": "^3.3",
"slim/slim": "^4.11",
"thecodingmachine/safe": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"kint-php/kint": "^5.0"
},
"autoload": {
"psr-4": {
"ProVM\\Logview\\": "src/",
"ProVM\\Common\\": "common/"
}
},
"authors": [
{
"name": "Aldarien",
"email": "aldarien85@gmail.com"
}
],
"config": {
"sort-packages": true
}
}

16
app/public/index.php Normal file
View File

@ -0,0 +1,16 @@
<?php
use Psr\Log\LoggerInterface;
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'setup',
'app.php'
]);
Monolog\ErrorHandler::register($app->getContainer()->get(LoggerInterface::class));
try {
$app->run();
} catch (Exception $e) {
$app->getContainer()->get(LoggerInterface::class)->alert($e);
} catch (Error $e) {
$app->getContainer()->get(LoggerInterface::class)->error($e);
}

View File

@ -0,0 +1,9 @@
<?php
use ProVM\Common\Controller\Logs;
$app->group('/logs', function($app) {
$app->get('[/]', Logs::class);
});
$app->group('/log/{log_file}', function($app) {
$app->get('[/]', [Logs::class, 'get']);
});

View File

@ -0,0 +1,4 @@
<?php
use ProVM\Common\Controller\Base;
$app->get('[/]', Base::class);

View File

@ -0,0 +1,11 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<div class="ui list">
@foreach ($files as $file)
<a class="item" href="{{$urls->base}}/log/{{urlencode($file->getBasename())}}">{{$file->getBasename()}}</a>
@endforeach
</div>
</div>
@endsection

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html lang="es">
@include('layout.head')
@include('layout.body')
</html>

View File

@ -0,0 +1,6 @@
<body>
@include('layout.body.header')
@yield('page_content')
@include('layout.body.footer')
@include('layout.body.scripts')
</body>

View File

@ -0,0 +1,5 @@
<nav class="ui menu">
<div class="ui container">
<a class="item" href="/">Home</a>
</div>
</nav>

View File

@ -0,0 +1,4 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js" integrity="sha512-5cguXwRllb+6bcc2pogwIeQmQPXEzn2ddsqAexIBhh7FO1z5Hkek1J9mrK2+rmZCTU6b6pERxI7acnp1MpAg4Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@stack('page_scripts')

View File

@ -0,0 +1,10 @@
<head>
<meta charset="utf8" />
@hasSection('page_title')
<title>Logs - @yield('page_title')</title>
@else
<title>Logs</title>
@endif
@include('layout.head.styles')
</head>

View File

@ -0,0 +1,3 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css" integrity="sha512-n//BDM4vMPvyca4bJjZPDh7hlqsQ7hqbP9RH18GF2hTXBY5amBwM2501M0GPiwCU/v9Tor2m13GOTFjk00tkQA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
@stack('page_styles')

40
app/setup/app.php Normal file
View File

@ -0,0 +1,40 @@
<?php
use DI\ContainerBuilder;
use DI\Bridge\Slim\Bridge;
require_once 'composer.php';
$builder = new ContainerBuilder();
$folders = [
'settings',
'setups'
];
foreach ($folders as $f) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, $f]);
if (!file_exists($folder)) {
continue;
}
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$builder->addDefinitions($file->getRealPath());
}
}
$app = Bridge::create($builder->build());
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'middlewares']);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
}
return $app;

6
app/setup/composer.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'vendor',
'autoload.php'
]);

View File

@ -0,0 +1,9 @@
<?php
$folder = $app->getContainer()->get('folders')->get('routes');
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}

View File

@ -0,0 +1,4 @@
<?php
return [
'logs_folder' => $_ENV['LOGS_PATH'] ?? '/logs'
];

View File

@ -0,0 +1,22 @@
<?php
return [
'folders' => function() {
return new DI\Container([
'base' => dirname(__FILE__, 3),
'resources' => DI\String('{base}/resources'),
'routes' => DI\String('{resources}/routes'),
'cache' => DI\String('{base}/cache'),
'templates' => DI\String('{resources}/views')
]);
/*$arr = ['base' => dirname(__FILE__, 3)];
$arr['resources'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'resources'
]);
$arr['routes'] = implode(DIRECTORY_SEPARATOR, [
$arr['resources'],
'routes'
]);
return (object) $arr;*/
}
];

View File

@ -0,0 +1,11 @@
<?php
return [
'urls' => function() {
$arr = ['base' => $_ENV['WEB_URL'] ?? 'http://localhost:' . ($_ENV['WEB_PORT'] ?? 8030)];
$arr['assets'] = implode('/', [
$arr['base'],
'assets'
]);
return (object) $arr;
}
];

View File

@ -0,0 +1,16 @@
<?php
use Psr\Container\ContainerInterface;
return [
Psr\Log\LoggerInterface::class => function(ContainerInterface $container) {
$logger = new Monolog\Logger('logger');
$logger->pushHandler(
new Monolog\Handler\RotatingFileHandler(
implode(DIRECTORY_SEPARATOR, [
$container->get('logs_folder'), 'php.log'
])
)
);
return $logger;
}
];

View File

@ -0,0 +1,15 @@
<?php
use Psr\Container\ContainerInterface;
return [
Slim\Views\Blade::class => function(ContainerInterface $container) {
return new Slim\Views\Blade(
$container->get('folders')->get('templates'),
$container->get('folders')->get('cache'),
null,
[
'urls' => $container->get('urls')
]
);
}
];

View File

@ -0,0 +1,10 @@
<?php
use Psr\Container\ContainerInterface;
return [
ProVM\Common\Service\Logs::class => function(ContainerInterface $container) {
return new ProVM\Common\Service\Logs(
$container->get('logs_folder')
);
}
];

156
app/src/Log.php Normal file
View File

@ -0,0 +1,156 @@
<?php
namespace ProVM\Logview;
use DateTimeInterface;
use DateTimeImmutable;
class Log
{
protected DateTimeInterface $dateTime;
protected string $channel;
protected string $severity;
protected string $message;
protected array $stack;
protected string $context;
protected string $extra;
public function getDate(): DateTimeInterface
{
return $this->dateTime;
}
public function getChannel(): string
{
return $this->channel;
}
public function getSeverity(): string
{
return $this->severity;
}
public function getMessage(): string
{
return $this->message;
}
public function getStack(): array
{
return $this->stack ?? [];
}
public function getContext(): string
{
return $this->context;
}
public function getExtra(): string
{
return $this->extra ?? '';
}
public function setDate(DateTimeInterface $dateTime): Log
{
$this->dateTime = $dateTime;
return $this;
}
public function setChannel(string $channel): Log
{
$this->channel = $channel;
return $this;
}
public function setSeverity(string $severity): Log
{
$this->severity = $severity;
return $this;
}
public function setMessage(string $message): Log
{
$this->message = $message;
return $this;
}
public function setStack(array $stack): Log
{
$this->stack = $stack;
return $this;
}
public function setContext(string $context): Log
{
$this->context = $context;
return $this;
}
public function setExtra(string $extra): Log
{
$this->extra = $extra;
return $this;
}
public function hasStack(): bool
{
return isset($this->stack);
}
public function hasContext(): bool
{
return $this->context !== '';
}
public function getColor(): string
{
return self::COLORS[strtoupper($this->getSeverity())];
}
public function getBackgroundColor(): string
{
return self::BACKGROUNDS[strtoupper($this->getSeverity())];
}
public static function parse(string $content): Log
{
$log = new Log();
$regex = "/\[(?P<date>.*)\]\s(?<channel>\w*)\.(?<severity>\w*):\s(?<message>.*)\s[\[|\{](?<context>.*)[\]|\}]\s\[(?<extra>.*)\]/";
preg_match($regex, $content, $matches);
$log->setDate(DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $matches['date']));
$log->setChannel($matches['channel']);
$log->setSeverity($matches['severity']);
$message = $matches['message'];
if (str_contains($message, 'Stack trace')) {
list($msg, $data) = explode('Stack trace:', $message);
$message = trim($msg);
$regex = '/\s#\d+\s/';
$lines = preg_split($regex, $data);
array_shift($lines);
$log->setStack($lines);
}
$log->setMessage($message);
$log->setContext($matches['context']);
if (isset($matches['extra'])) {
$log->setExtra($matches['extra']);
}
return $log;
}
const LEVELS = [
'DEBUG',
'INFO',
'NOTICE',
'WARNING',
'ERROR',
'CRITICAL',
'ALERT',
'EMERGENCY',
];
const COLORS = [
'DEBUG' => '#000',
'INFO' => '#000',
'NOTICE' => '#fff',
'WARNING' => '#000',
'ERROR' => '#fff',
'CRITICAL' => '#fff',
'ALERT' => '#fff',
'EMERGENCY' => '#fff',
];
const BACKGROUNDS = [
'DEBUG' => '#fff',
'INFO' => '#00f',
'NOTICE' => '#55f',
'WARNING' => '#dd5',
'ERROR' => '#555',
'CRITICAL' => '#f00',
'ALERT' => '#f55',
'EMERGENCY' => '#f55',
];
}

44
app/src/Log/File.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace ProVM\Logview\Log;
use Generator;
use ProVM\Logview\Log;
class File
{
protected string $filename;
protected string $content;
public function getFilename(): string
{
return $this->filename;
}
public function getContent(): string
{
return $this->content;
}
public function setFilename(string $filename): File
{
$this->filename = $filename;
return $this;
}
public function setContent(string $content): File
{
$this->content = $content;
return $this;
}
public function getLogs(): array
{
$lines = explode(PHP_EOL, $this->getContent());
$logs = [];
foreach ($lines as $line) {
if (trim($line) === '') {
continue;
}
$logs []= Log::parse($line);
}
return array_reverse($logs);
}
}

23
docker-compose.yml Normal file
View File

@ -0,0 +1,23 @@
version: '3'
services:
proxy:
container_name: lv_proxy
profiles:
- app
image: nginx
ports:
- "${WEB_PORT:-8030}:80"
volumes:
- "./nginx.conf:/etc/nginx/conf.d/default.conf"
- "./src:/app"
- "./logs:/logs"
php:
container_name: lv_app
profiles:
- app
build: .
volumes:
- "./src:/app"
- "./logs:/logs"

22
nginx.conf Normal file
View File

@ -0,0 +1,22 @@
server {
listen 80;
error_log /logs/error.log;
access_log /logs/access.log;
root /app/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
fastcgi_read_timeout 3600;
fastcgi_pass php:9000;
}
}

3
php-errors.ini Normal file
View File

@ -0,0 +1,3 @@
display_errors=no
log_errors=yes
error_log=/logs/php_errors.log