Apitte skeleton – nelogguje errory a jen zobrazi error 500
- Majksa
- Člen | 17
Ahoj,
začal jsem používat apitte-skeleton jako backend pro mojí aplikaci a neloggují se mi errory. Nejspíš to bude nějaká chyba v Middlewares, ale nenapadá mě co. Posílám moje soubory, ale většinou to je podobné apitte-skeletonu. Jestli je potřeba ještě něco, rád pošlu.
apitte.neon
extensions:
middlewares: Contributte\Middlewares\DI\MiddlewaresExtension
resource: Contributte\DI\Extension\ResourceExtension
api: Apitte\Core\DI\ApiExtension
resource:
resources:
App\Module\Api\:
paths:
- %appDir%/Module/Api
decorator:
inject: true
App\Module\Backend\:
paths:
- %appDir%/Module/Backend
decorator:
inject: true
api:
debug: false
plugins:
Apitte\Core\DI\Plugin\CoreSchemaPlugin:
Apitte\Core\DI\Plugin\CoreServicesPlugin:
Apitte\OpenApi\DI\OpenApiPlugin:
Apitte\Debug\DI\DebugPlugin:
Apitte\Middlewares\DI\MiddlewaresPlugin:
tracy: false
autobasepath: false
services:
middleware.tryCatch:
create: Contributte\Middlewares\TryCatchMiddleware
tags: [middleware: [priority: 1]]
setup:
- setDebugMode(%debugMode%)
- setCatchExceptions(%productionMode%) # used in debug only
middlewares.logging:
create: Contributte\Middlewares\LoggingMiddleware
arguments: [@monolog.logger.default]
tags: [middleware: [priority: 100]]
middleware.methodOverride:
create: Contributte\Middlewares\MethodOverrideMiddleware
tags: [middleware: [priority: 150]]
middleware.authenticator:
create: App\Model\Api\Middleware\AuthenticationMiddleware(
App\Model\Api\Security\TokenAuthenticator(%api.bot.token%)
)
tags: [middleware: [priority: 200]]
api.core.dispatcher: App\Model\Api\Dispatcher\JsonDispatcher
contributte.neon
extensions:
console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)
monolog: Contributte\Monolog\DI\MonologExtension
console:
url: http://localhost/
lazy: true
monolog:
holder:
enabled: true
channel:
default:
handlers:
- Monolog\Handler\RotatingFileHandler(%logDir%/syslog.log, 30, Monolog\Logger::WARNING)
processors:
- Monolog\Processor\WebProcessor()
- Monolog\Processor\IntrospectionProcessor()
- Monolog\Processor\MemoryPeakUsageProcessor()
- Monolog\Processor\ProcessIdProcessor()
JsonDispatcher
<?php
declare(strict_types=1);
namespace App\Model\Api\Dispatcher;
use Apitte\Core\Dispatcher\JsonDispatcher as ApitteJsonDispatcher;
use Apitte\Core\Handler\IHandler;
use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use Apitte\Core\Http\RequestAttributes;
use Apitte\Core\Router\IRouter;
use Apitte\Core\Schema\Endpoint;
use Nette\Utils\Json;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class JsonDispatcher extends ApitteJsonDispatcher
{
/** @var SerializerInterface */
protected SerializerInterface $serializer;
/** @var ValidatorInterface */
protected ValidatorInterface $validator;
public function __construct(
IRouter $router,
IHandler $handler,
SerializerInterface $serializer,
ValidatorInterface $validator
) {
parent::__construct($router, $handler);
$this->serializer = $serializer;
$this->validator = $validator;
}
protected function handle(ApiRequest $request, ApiResponse $response): ApiResponse
{
try {
$request = $this->transformRequest($request);
$result = $this->handler->handle($request, $response);
// Except ResponseInterface convert all to json
if (!($result instanceof ApiResponse)) {
$response = $this->transformResponse($result, $response);
} else {
$response = $result;
}
} catch (\Apitte\Core\Exception\Api\ClientErrorException | \Apitte\Core\Exception\Api\ServerErrorException $e) {
$data = [];
if ($e->getMessage()) {
$data['message'] = $e->getMessage();
}
if ($e->getContext()) {
$data['context'] = $e->getContext();
}
if ($e->getCode()) {
$data['code'] = $e->getCode();
}
$response = $response->withStatus($e->getCode() ?: 500)
->withHeader('Content-Type', 'application/json');
$response->getBody()->write(Json::encode($data));
} catch (\RuntimeException $e) {
$response = $response->withStatus($e->getCode() ?: 500)
->withHeader('Content-Type', 'application/json');
$response->getBody()->write(Json::encode([
'message' => $e->getMessage() ?: 'Application encountered an internal error. Please try again later.',
]));
}
return $response;
}
/**
* Transform incoming request to request DTO, if needed.
*/
protected function transformRequest(ApiRequest $request): ApiRequest
{
// If Apitte endpoint is not provided, skip transforming.
$endpoint = $request->getAttribute(RequestAttributes::ATTR_ENDPOINT);
if (!$endpoint) {
return $request;
}
// @safety
\assert($endpoint instanceof Endpoint);
// Get incoming request entity class, if defined. Otherwise, skip transforming.
$entity = $endpoint->getTag('request.dto');
if (!$entity) {
return $request;
}
try {
// Create request DTO from incoming request, using serializer.
$dto = $this->serializer->deserialize(
$request->getBody()->getContents(),
$entity,
'json',
['allow_extra_attributes' => false],
);
$request = $request->withParsedBody($dto);
} catch (\Symfony\Component\Serializer\Exception\ExtraAttributesException $e) {
throw \Apitte\Core\Exception\Api\ValidationException::create()
->withMessage($e->getMessage());
}
// Try to validate entity only if its enabled
$violations = $this->validator->validate($dto);
if (\count($violations) > 0) {
$fields = [];
foreach ($violations as $violation) {
$fields[$violation->getPropertyPath()][] = $violation->getMessage();
}
throw \Apitte\Core\Exception\Api\ValidationException::create()
->withMessage('Invalid request data')
->withFields($fields);
}
return $request;
}
/**
* Transform outgoing response data to JSON, if needed.
*
* @param mixed $data
*/
protected function transformResponse($data, ApiResponse $response): ApiResponse
{
$response = $response->withStatus(200)
->withHeader('Content-Type', 'application/json');
// Serialize entity with symfony/serializer to JSON
$serialized = $this->serializer->serialize($data, 'json');
$response->getBody()->write($serialized);
return $response;
}
}
AuthenticationMiddleware
<?php
declare(strict_types=1);
namespace App\Model\Api\Middleware;
use App\Model\Utils\Strings;
use Contributte\Middlewares\IMiddleware;
use Contributte\Middlewares\Security\IAuthenticator;
use Nette\Utils\Json;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class AuthenticationMiddleware implements IMiddleware
{
private const WHITELIST_PATHS = ['/backend/v1/user/register', '/backend/v1/user/login', '/backend/v1/openapi'];
/** @var IAuthenticator */
private IAuthenticator $authenticator;
public function __construct(IAuthenticator $authenticator)
{
$this->authenticator = $authenticator;
}
protected function denied(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$response->getBody()->write(Json::encode([
'status' => 'error',
'message' => 'Client authentication failed',
'code' => 401,
]));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(401);
}
protected function isWhitelisted(ServerRequestInterface $request): bool
{
foreach (self::WHITELIST_PATHS as $whitelist) {
if (Strings::startsWith($request->getUri()->getPath(), $whitelist)) {
return true;
}
}
return false;
}
/**
* Authenticate user from given request
*/
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface {
if ($this->isWhitelisted($request)) {
return $next($request, $response);
}
// If we have a identity, then go to next middlewares,
// otherwise stop and return current response
if (!$this->authenticator->authenticate($request)) {
return $this->denied($request, $response);
}
// Pass to next middleware
return $next($request, $response);
}
}
- Majksa
- Člen | 17
Jenom dodám, že ostatní logování mi normálně funguje:
log/info.log
[2020-11-15 19-30-27] Requested url: http://localhost:8000/api/ @ http://localhost:8000/api/
[2020-11-15 19-30-28] Requested url: http://localhost:8000/backend/ @ http://localhost:8000/backend/
[2020-11-15 19-30-28] Requested url: http://localhost:8000/backend/v1/user/register @ http://localhost:8000/backend/v1/user/register
log/debug.log
[2020-11-15 20-29-34] array (2) message => "Query: "START TRANSACTION"" (26) context => array (1) | time => "1 ms" (4) @ http://localhost:8000/backend/v1/user/register
[2020-11-15 20-29-34] array (2) message => "Query: INSERT INTO user (username, email, password, last_logged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)" (117) context => array (1) | time => "3 ms" (4) @ http://localhost:8000/backend/v1/user/register
[2020-11-15 20-29-34] array (2) message => "Query: "COMMIT"" (15) context => array (1) | time => "1 ms" (4) @ http://localhost:8000/backend/v1/user/register
Editoval Majksa (16. 11. 2020 12:48)
- Majksa
- Člen | 17
A ještě directory tree, jestli to pomůže
├───app
│ │ Bootstrap.php
│ │
│ ├───config
│ │ │ config.local.neon
│ │ │ config.local.neon.dist
│ │ │
│ │ ├───app
│ │ │ parameters.neon
│ │ │ services.neon
│ │ │
│ │ ├───env
│ │ │ base.neon
│ │ │ dev.neon
│ │ │ prod.neon
│ │ │ test.neon
│ │ │
│ │ └───ext
│ │ apitte.neon
│ │ contributte.neon
│ │ nettrine.neon
│ │
│ ├───Domain
│ │ └───Backend
│ │ ├───Facade
│ │ │ Users.php
│ │ │
│ │ └───Request
│ │ LoginUserReqDto.php
│ │ RegisterUserReqDto.php
│ │
│ ├───Model
│ │ │ App.php
│ │ │
│ │ ├───Api
│ │ │ │ RequestAttributes.php
│ │ │ │
│ │ │ ├───Dispatcher
│ │ │ │ JsonDispatcher.php
│ │ │ │
│ │ │ ├───Middleware
│ │ │ │ AuthenticationMiddleware.php
│ │ │ │
│ │ │ └───Security
│ │ │ AbstractAuthenticator.php
│ │ │ TokenAuthenticator.php
│ │ │
│ │ ├───Database
│ │ │ │ EntityManager.php
│ │ │ │ TRepositories.php
│ │ │ │
│ │ │ ├───Entity
│ │ │ │ │ AbstractEntity.php
│ │ │ │ │ User.php
│ │ │ │ │
│ │ │ │ └───Attributes
│ │ │ │ TCreatedAt.php
│ │ │ │ TId.php
│ │ │ │ TUpdatedAt.php
│ │ │ │
│ │ │ └───Repository
│ │ │ AbstractRepository.php
│ │ │ User.php
│ │ │
│ │ ├───Exception
│ │ │ │ LogicException.php
│ │ │ │ RuntimeException.php
│ │ │ │
│ │ │ ├───Logic
│ │ │ │ InvalidArgumentException.php
│ │ │ │
│ │ │ └───Runtime
│ │ │ │ AuthenticationException.php
│ │ │ │ InvalidStateException.php
│ │ │ │ IOException.php
│ │ │ │
│ │ │ ├───Database
│ │ │ │ EntityNotFoundException.php
│ │ │ │ PersistenceException.php
│ │ │ │
│ │ │ └───User
│ │ │ EmailTakenException.php
│ │ │ UsernameTakenException.php
│ │ │
│ │ ├───Security
│ │ │ Passwords.php
│ │ │
│ │ └───Utils
│ │ Arrays.php
│ │ DateTime.php
│ │ FileSystem.php
│ │ Html.php
│ │ Image.php
│ │ Strings.php
│ │ Validators.php
│ │
│ ├───Module
│ │ ├───Api
│ │ │ │ Base.php
│ │ │ │
│ │ │ └───V1
│ │ │ Base.php
│ │ │ OpenApi.php
│ │ │
│ │ └───Backend
│ │ │ Base.php
│ │ │
│ │ └───V1
│ │ Base.php
│ │ OpenApi.php
│ │ User.php
│ │
│ └───resources
│ └───tracy
│ 500.json
│ 500.phtml
│ 500.txt
├───bin
│ console
├───db
│ ├───Fixtures
│ │ AbstractFixture.php
│ │
│ └───Migrations
│ Version20201115144710.php
├───log
│ .gitignore
│ debug.log
│ info.log
├───temp
├───tests
│ ├───api
│ ├───backend
│ ├───integration
│ │ └───config
│ ├───unit
│ ├───_data
│ ├───_output
│ ├───_support
│ └───_temp
├───vendor
└───www
.htaccess
index.php
robots.txt
web.config
- Felix
- Nette Core | 1245
Ahoj!
- Jsem rad, ze to stavis na/podle contributte/apitte-skeleton.
- Jenom se chci ujistit, kde by jsi ty errory chtel logovat?
- A co se deje, kdyz se neloguji?
- Ukazuje se error, bila stranka, nebo neco jineho?
- Zkousel jsi xdebug a krokovat si to?
- Muzes ten projekt pushnout nekam?
- Majksa
- Člen | 17
Ahoj :)
- Já taky. Díky, že to existuje!
- Errory bych chtěl logovat pomocí contributte/monolog, do nastavené cesty: takže nejspíš log/error.log nebo něco takového.
- +
- Děje se to například, když selže INSERT request do databáze při porušení unique. Vypíše to:
{
"status": "error",
"code": 500,
"message": "Application encountered an internal error. Please try again later."
}
Ale nemám jak se dostat k problému, protože error není zalogován ani
vypsán.
Mode je debug.
- Ještě ne.
- Zatím to mám na soukromém GitLab repozitáři ale můžu vytvořit nový public.
- vml
- Člen | 2
Řešil jsem stejný problém a vyřešil jsem ho pomocí error dekorátoru. V dokumentaci zde
Chyby najdeš pak ve složce log, jak jsi zvyklý.
appite.neon
apitte:
debug: %debugMode%
catchException: true # Sets if exception should be catched and transformed into response or rethrown to output (debug only)
plugins:
Apitte\Console\DI\ConsolePlugin:
Apitte\Debug\DI\DebugPlugin:
debug:
panel: %debugMode%
negotiation: %debugMode%
Apitte\Core\DI\Plugin\CoreDecoratorPlugin:
services:
decorator.request.authentication:
class: App\Model\Security\RequestAuthenticationDecorator
tags:
apitte.core.decorator:
priority: 1
type: handler.before
decorator.error.exception:
class: App\Model\Debug\ExceptionDecorator
ExceptionDecorator.php
<?php
namespace App\Model\Debug;
use Apitte\Core\Decorator\IErrorDecorator;
use Apitte\Core\Exception\Api\ClientErrorException;
use Apitte\Core\Exception\Api\ServerErrorException;
use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use Throwable;
use Tracy\Debugger;
use Tracy\ILogger;
class ExceptionDecorator implements IErrorDecorator
{
public function decorateError(ApiRequest $request, ApiResponse $response, Throwable $error): ApiResponse
{
Debugger::log($error, ILogger::ERROR);
if ($error instanceof ServerErrorException) {
$response = $response->writeJsonBody([
'error' => 'Server error.'
])->withStatus(500);
}
if ($error instanceof ClientErrorException) {
$response = $response->writeJsonBody([
'error' => $error->getMessage(),
'previous' => $error->getPrevious()?->getMessage(),
])->withStatus($error->getCode());
}
return $response;
}
}
Editoval vml (6. 3. 17:12)
- Felix
- Nette Core | 1245
Pred casem nekdo zakladal issue (https://github.com/…n/issues/664) na Githubu u apitte-skeletonu.
Pridal jsem primou ukazku https://github.com/…390a76a30db1
Dej kdyztak vedet, kdyby neco.
- elnathan
- Člen | 17
Felix napsal(a):
Pred casem nekdo zakladal issue (https://github.com/…n/issues/664) na Githubu u apitte-skeletonu.
Pridal jsem primou ukazku https://github.com/…390a76a30db1
Dej kdyztak vedet, kdyby neco.
Díky, funguje, jenom k zamyšlení: Používám v různých
systémech/jazycích API Frameworky. Zde existuje základ Nette framework,
který umí slušně logovat. K tomu si stáhnu knihovnu Apitte, která při
chybě (viz dřívější komunikace) vrací správně JSON chybovou hlášku =
to je super. Jenže vůbec nic nezaloguje (nevyužije existující Nette
logování). V dokumentaci nic není, já si musím ze skeletonu úplnou
náhodou díky tomuto vláknu dohledat nějaké třídy, které né že něco
stačí nakonfigurovat, já je musím vytvořit, aby to začalo logovat chyby,
uff … pokud dělám knihovnu pro Nette, tak by měla v základu logovat
stejně jako Nette. Pokud chce někdo něco jiného, tak si to přepíše, ale
ten základ musí existovat už v samotné v knihovně a né nějakém
ukázkovém kódu.
Neumím si představit, že třeba ve Springu při implemetaci REST API by mi
začalo vracet chybu 500 a já v logu nic nenašel. Samozřejmě výchozí je
výstup do konzole (v našem případě nějaký Nette log) a pokud chci něco
jiného, tak si to tam dodám, ale ten základ tam musí být a né že
knihovna mlčí.
Editoval elnathan (23. 4. 11:32)
- Felix
- Nette Core | 1245
@elnathan Jop, uplne s tebou souhlasim. Dokazal bys takovou upravu pripravit a poslat jako PR do https://github.com/…utte/apitte/
- Felix
- Nette Core | 1245
Vec logovani a error handlingu jsme jiz nekolikrat resili, aktualizoval jsem i https://github.com/…tte-skeleton