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 | 1183
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 | 1183
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.