PHP's single-threaded nature often clashes with the need for real-time error reporting. Sending Sentry alerts synchronously—the default behavior of the sentry-php SDK—can lead to noticeable performance lags, especially in applications with frequent error logging. This article explores a clever workaround: achieving asynchronous alert delivery using PHP's capabilities in a way that minimizes latency and maximizes efficiency. We’ll transform the synchronous, blocking nature of traditional Sentry reporting into a streamlined, non-blocking process, keeping your application responsive even during peak error activity.
// Initialize Sentry with your DSN
\Sentry\init([
'dsn' => $_ENV['DSN'], // Ensure your DSN is properly configured, perhaps from a .env file
]);
// Capture a message; this blocks until the message is sent.
\Sentry\captureMessage('test', \Sentry\Severity::warning());
// ... other code execution ...
namespace Sentry\HttpClient;
class HttpClient implements HttpClientInterface
{
public function sendRequest(Request $request, Options $options): Response
{
// ... (other code omitted for brevity) ...
$curlHandle = curl_init(); // Initialize cURL resource
// ... (setting request headers and cURL options) ...
$body = curl_exec($curlHandle); // Blocking call – waits for server response
curl_close($curlHandle); // Close cURL resource
return new Response($statusCode, $responseHeaders, ''); // Return the response
}
}
namespace Jun\PhpSentryExample;
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;
use GuzzleHttp\Psr7\Request as Psr7Request;
use Sentry\Client as SentryClient;
use Sentry\HttpClient\HttpClientInterface;
use Sentry\HttpClient\Request;
use Sentry\Options;
use Sentry\HttpClient\Response;
class SentryAsyncClientWrapper implements HttpClientInterface
{
// ... (Singleton pattern implementation omitted for brevity) ...
private Client $client; // Guzzle HTTP client instance
private array $promises = []; // Array to store promises for asynchronous requests
// ... (other properties omitted for brevity) ...
public function sendRequest(Request $request, Options $options): Response
{
$dsn = $options->getDsn();
if ($dsn === null) {
throw new \RuntimeException('The DSN option must be set to use the HttpClient.');
}
$requestData = $request->getStringBody();
if ($requestData === '') {
throw new \RuntimeException('The request data is empty.');
}
// Prepare request headers, including authentication information
$sentry_version = SentryClient::PROTOCOL_VERSION;
$sentry_client = "{$this->sdkIdentifier}/{$this->sdkVersion}";
$sentry_key = $dsn->getPublicKey();
$requestHeaders['sentry_version'] = $sentry_version;
$requestHeaders['sentry_client'] = $sentry_client;
$requestHeaders['sentry_key'] = $sentry_key;
$requestHeaders['Content-Type'] = 'application/x-sentry-envelope';
// Build the authentication header
$authHeader = [
'sentry_version=' . $sentry_version,
'sentry_client=' . $sentry_client,
'sentry_key=' . $sentry_key,
];
$requestHeaders['X-Sentry-Auth'] = 'Sentry ' . implode(', ', $authHeader);
// Enable gzip compression if available and enabled in Sentry options
if (\extension_loaded('zlib') && $options->isHttpCompressionEnabled()) {
$requestData = gzcompress($requestData, -1, \ZLIB_ENCODING_GZIP);
$requestHeaders['Content-Encoding'] = 'gzip';
}
// Create and queue the asynchronous request using Guzzle
$this->promises[] = $this->client->sendAsync(new Psr7Request(
'POST',
$dsn->getEnvelopeApiEndpointUrl(),
$requestHeaders,
$requestData
))->then(
function ($response) {
// Success callback: Handle successful response
echo 'Request succeeded: ' . $response->getBody() . PHP_EOL;
},
function ($reason) {
// Failure callback: Handle request failures
echo 'Request failed: ' . $reason . PHP_EOL;
}
);
// Return an empty response; we don't wait for the actual response here
return new Response(200, [], '');
}
// Method to wait for all queued requests to complete
public function wait(): void
{
if (!empty($this->promises)) {
Utils::settle($this->promises)->wait(); // Wait for all promises to resolve
$this->promises = []; // Clear the promises array
}
}
}
// ... (includes and setup) ...
$asyncClient = SentryAsyncClientWrapper::getInstance(new \GuzzleHttp\Client([
'timeout' => 1, // Adjust timeout as needed
]));
// Initialize Sentry, specifying the asynchronous client
\Sentry\init([
'dsn' => $_ENV['DSN'],
'http_client' => $asyncClient,
]);
// Now, capture messages as usual
\Sentry\captureMessage('test', \Sentry\Severity::warning());
// ... additional Sentry calls ...
// ... (rest of your application logic) ...
// At the end of the request cycle, wait for all asynchronous requests to complete
$asyncClient->wait();
use CodeIgniter\Events;
use Jun\PhpSentryExample\SentryAsyncClientWrapper;
Events::on('pre_system', static function () {
// Initialize Sentry with the async client here; you may need adjustments based on your service provider setup
service('sentry')->initialize();
});
// ... (Your application code) ...
Events::on('post_system', function () {
// ... (Database closing, etc.) ...
SentryAsyncClientWrapper::getInstance(new \GuzzleHttp\Client(['timeout' => 1]))->wait(); // Send all pending Sentry alerts
});
0 comments:
Post a Comment