I need to add php sentry error handler to my slim 3 project.
how can I do so ?
where should put sentry integration code?
what I'm doing now is :
// monolog
$container['logger'] = function ($c) {
$settings = $c->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
$client = new Raven_Client(
'http://key#ip:9000/2'
);
$handler = new Monolog\Handler\RavenHandler($client);
$handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n"));
$logger->pushHandler($handler);
return $logger;
};
but I'm not getting all errors in my sentry dashboard.
for example accessing undefined array indexes.
thanks.
I think the best way is to just do the following (I did not test this or have ever used Slim but looking at the Slim docs this is a way to do it):
In your index.php (which should be the app entrypoint) just after require '../../vendor/autoload.php'; (the composer autoload).
Add the Raven initialization code:
$sentry = new Raven_Client('http://key#ip:9000/2');
$sentry->install();
This will configure the SDK to handle (and send) all errors, no need for the Monolog handler anymore.
If you want to integration it in a ErrorHandler class you created looking at this skeleton project might give you some ideas.
I am using a custom error handler to catch exceptions. This way i can use the default slim error handler and Sentry error reporting at the same time.
This is my code:
// initalize sentry
Sentry\init(['dsn' => 'your_dsn' ]);
// Run app
$app = (new App())->get();
// register custom error handler
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
// send error to sentry
Sentry\captureException($exception);
// invoke default error handler
$handler = new Slim\Handlers\Error();
return $handler->__invoke($request, $response, $exception);
};
};
$app->run();
Not sure if this is the "recommended" way, but it works.
Related
I'm migrating from Slim/3 to Slim/4. I've found or figured out replacements for all the features I was using that have been removed, except 404 Not Found Handler (part of the now gone App::$settings):
Slim App::$settings have been removed, multiple middleware have been implemented to replace the functionality from each individual settings.
Is there a middleware for notFoundHandler? If there isn't, how can I implement it?
Mine used to look like this:
use Slim\Container;
$config = new Container();
$config['notFoundHandler'] = function (Container $c) {
return function (Request $request, Response $response) use ($c): Response {
$page = new Alvaro\Pages\Error($c);
return $page->notFound404($request, $response);
};
};
According to Slim 4 documentation on error handling
Each Slim Framework application has an error handler that receives all
uncaught PHP exceptions
You can set a custom error handler to handle each type of exceptions thrown. A list of predefined exception classes is available on same page.
Here is a very basic example of how to register a closure as an error handler, to handle only HttpNotFoundException exceptions. You can also put the handler in a class that extends Slim\Handlers\ErrorHandler. Also, I did not actually use your Alvaro\Pages\Error to generate the response, but changing it should be straightforward:
<?php
require '../vendor/autoload.php';
$app = Slim\Factory\AppFactory::create();
// Define Custom Error Handler
$customErrorHandler = function (
Psr\Http\Message\ServerRequestInterface $request,
\Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app) {
$response = $app->getResponseFactory()->createResponse();
// seems the followin can be replaced by your custom response
// $page = new Alvaro\Pages\Error($c);
// return $page->notFound404($request, $response);
$response->getBody()->write('not found');
return $response->withStatus(404);
};
// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Register the handler to handle only HttpNotFoundException
// Changing the first parameter registers the error handler for other types of exceptions
$errorMiddleware->setErrorHandler(Slim\Exception\HttpNotFoundException::class, $customErrorHandler);
$app->get('/', function ($request, $response) {
$response->getBody()->write('Hello Slim 4');
return $response;
});
$app->run();
Another approach is to create a generic error handler and register it as the default handler, and inside that handler, decide what response should be sent based on type of exception that is thrown. Something like:
$customErrorHandler = function (
Psr\Http\Message\ServerRequestInterface $request,
\Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app) {
$response = $app->getResponseFactory()->createResponse();
if ($exception instanceof HttpNotFoundException) {
$message = 'not found';
$code = 404;
} elseif ($exception instanceof HttpMethodNotAllowedException) {
$message = 'not allowed';
$code = 403;
}
// ...other status codes, messages, or generally other responses for other types of exceptions
$response->getBody()->write($message);
return $response->withStatus($code);
};
Then you can set this as the default error handler:
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorMiddleware->setDefaultErrorHandler($customErrorHandler);
I am using Serverless framework to deploy my PHP code as IBM Cloud Function.
Here is the code from the action PHP file:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
}
And this is the serverless.yml file:
service: cloudfunc
provider:
name: openwhisk
runtime: php
package:
individually: true
exclude:
- "**"
include:
- "vendor/**"
functions:
test-sentry:
handler: actions/test-sentry.main
annotations:
raw-http: true
events:
- http:
path: /test-sentry
method: post
resp: http
package:
include:
- actions/test-sentry.php
plugins:
- serverless-openwhisk
When I test the action handler from my local environment(NGINX/PHP Docker containers) the errors are being sent to Sentry.
But when I try to invoke the action from IBM Cloud nothing appears in the Sentry console.
Edit:
After some time trying to investigate the source of the problem I saw that its related with the async nature of sending the http request to Sentry(I have other libraries that make HTTP/TCP connections to Loggly, RabbitMQ, MySQL and they all work as expected):
vendor/sentry/sentry/src/Transport/HttpTransport.php
in the send method where the actual http request is being sent:
public function send(Event $event): ?string
{
$request = $this->requestFactory->createRequest(
'POST',
sprintf('/api/%d/store/', $this->config->getProjectId()),
['Content-Type' => 'application/json'],
JSON::encode($event)
);
$promise = $this->httpClient->sendAsyncRequest($request);
//The promise state here is "pending"
//This line here is being logged in the stdout of the invoked action
var_dump($promise->getState());
// This function is defined in-line so it doesn't show up for type-hinting
$cleanupPromiseCallback = function ($responseOrException) use ($promise) {
//The promise state here is "fulfilled"
//This line here is never logged in the stdout of the invoked action
//Like the execution never happens here
var_dump($promise->getState());
$index = array_search($promise, $this->pendingRequests, true);
if (false !== $index) {
unset($this->pendingRequests[$index]);
}
return $responseOrException;
};
$promise->then($cleanupPromiseCallback, $cleanupPromiseCallback);
$this->pendingRequests[] = $promise;
return $event->getId();
}
The requests that are registered asynchronously are sent in the destructor of the HttpTransport instance or when PHP shuts down as a shutdown function is registered. In OpenWhisk we never shut down as we run in a never-ending loop until the Docker container is killed.
Update: You can now call $client-flush() and don't need to worry about reflection.
main() now looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$client->flush();
return [
'body' => ['result' => 'ok']
];
}
Original explanation:
As a result, to make this work, we need to call the destructor of the $transport property of the Hub's $client. Unfortunately, this private, so the easiest way to do this is to use reflection to make it visible and then call it:
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
This will make the $transport property visible so that we can retrieve it and call its destructor which will in turn call cleanupPendingRequests() that will then send the requests to sentry.io.
The main() therefore looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
return [
'body' => ['result' => 'ok']
];
}
Incidentally, I wonder if this Sentry SDK works with Swoole?
Function runtimes are "paused" between requests by the platform. This means any background processes will be blocked if they aren't finished when the function returns.
It looks like the asynchronous HTTP request doesn't get a chance to complete before the runtime pauses.
You will need to find some way to block returning from the function until that request is completed. If the Sentry SDK has some callback handler or other mechanism to be notified when messages have been sent, you could use that?
I am using slim3 with monolog from composer
my dependencies is looks like that:
// monolog
$container['logger'] = function ($c) {
$settings = $c->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\RotatingFileHandler($settings['path'], $settings['level']));
return $logger;
};
I did not manage to set limit(filesize) for those logs
basically, my log file name looks like: server-[date].log
I want to create a new log when it exceeded the 5MB for example:
server-[todaydate].log
server-[todaydate]-1.log
server-[todaydate]-2.log
I am trying to add new route in test case extending Symfony\Bundle\FrameworkBundle\Test\KernelTestCase that will throw exception, to test EventListener for it. Simplified tests will looks like this:
public function testHandlingException()
{
$kernel = $this->createKernel(['environment' => 'test', 'debug' => true]);
$kernel->boot();
$client = $kernel->getContainer()->get('test.client');
$controller = new class extends Controller {
public function testAction()
{
throw new \Exception();
}
};
$route = new Route(
'route_500',
['_controller' => get_class($controller).'::testAction']
);
$client
->getContainer()
->get('router')
->getRouteCollection()
->add('route_500', $route);
$this->expectException(\Exception::class);
$client->request('GET', '/route_500');
}
It fails with:
Failed asserting that exception of type "Exception" is thrown.
How can I add route in-fly so calling it will work?
You can catch exceptions in unit tests. Not in end2end ones. If a GET request throw an exception you can get it: but you must catch that status code is 500 (for example). Another check could be that the response is containing the exception message.
I use the laravel framework and I want to check if a connection to Soap server was successful or not, without the app dying with fatal error.
Both this:
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
if (is_soap_fault($this->session)) {
return "Error";
}
And this:
try {
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
} catch (SoapFault $e) {
return "Error";
}
Result in a fatal error:
Symfony \ Component \ Debug \ Exception \ FatalErrorException
SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://some.url/test.wsdl' : failed to load external entity "http://some.url/test.wsdl"
Thanks
I struggled with this issue today as well. The problem is the Laravel error handler is interpreting this catchable error as a fatal error, and aborting the program as a result.
To counter this, you need to intercept the error prior to Laravel's internal error handler. This method varies depending on your Laravel version:
Laravel 4.*
Go to your globals.php file. This should be in your app\start\ folder.
Add the following code (Thanks dmgfjaved):
App::fatal(function($exception)
{ //If SOAP Error is found, we don't want to FATALLY crash but catch it instead
if(strpos($exception->getMessage(), 'SOAP-ERROR') !== FALSE)
{
return '';
}
});
Laravel 5.*
There is no globals.php file. All IoC calls are handled via ServiceProviders. Go to app\Providers\AppServiceProvider.php.
Find the render() function.
Add the following code before the return parent::render($request, $e);
if(strpos($e->getMessage(), 'SOAP-ERROR') !== false)
{
return false;
}
This will remove the SoapFault error type from your error handler. Remember to catch the SoapFault as Laravel won't!
Try this:
try {
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
} catch (\Throwable $e) {
return "Error";
}
The solution is to actually ask the Soap client to throw a SoapFault instead of reporting an E_ERROR.
When the Soap client reports an E_ERROR, there is nothing for you to catch.
To fix this initialise you SoapClient like this:
$clientOptions = array(
'exceptions' => true,
);
try {
$client = new \SoapClient("foo.wsdl", $clientOptions);
} catch (\SoapFault $e) {
// Do what you need to do!;
}
try {
$result = $client->__soapCall($method, $data);
} catch (\SoapFault $e) {
// Do what you need to do!;
}
This is how I got soap to work in Laravel 5.1
clean install laravel 5.1
install artisaninweb/laravel-soap
create a controller SoapController.php
<?php
namespace App\Http\Controllers;
use Artisaninweb\SoapWrapper\Facades\SoapWrapper;
class SoapController extends Controller {
public function demo()
{
// Add a new service to the wrapper
SoapWrapper::add(function ($service) {
$service
->name('currency')
->wsdl('http://currencyconverter.kowabunga.net/converter.asmx?WSDL')
->trace(true);
});
$data = [
'CurrencyFrom' => 'USD',
'CurrencyTo' => 'EUR',
'RateDate' => '2014-06-05',
'Amount' => '1000'
];
// Using the added service
SoapWrapper::service('currency', function ($service) use ($data) {
var_dump($service->getFunctions());
var_dump($service->call('GetConversionAmount', [$data])->GetConversionAmountResult);
});
}
}
Create a route in your routes.php
Route::get('/demo', ['as' => 'demo', 'uses' => 'SoapController#demo']);
#Adam Link provided a good hint, but in Laravel 5.1, it appear stha tthere is not longer a render method in AppServiceProvider.
Instead, it has been moved to app\Exceptions\Handler.php