-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Uploader: initial support of storing errors in S3-like storage
- Loading branch information
Showing
7 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Contributte\Sentry\Exception\Runtime; | ||
|
||
use Contributte\Sentry\Exception\RuntimeException; | ||
|
||
class UploadException extends RuntimeException | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Contributte\Sentry\Integration; | ||
|
||
use Contributte\Sentry\Exception\Runtime\UploadException; | ||
use Contributte\Sentry\Upload\S3Uploader; | ||
use Nette\DI\Container; | ||
use Sentry\Event; | ||
use Sentry\EventHint; | ||
use Sentry\State\HubInterface; | ||
use Tracy\Debugger; | ||
use Tracy\Logger; | ||
|
||
class S3UploadIntegration extends BaseIntegration | ||
{ | ||
|
||
/** @var Container */ | ||
protected $context; | ||
|
||
public function __construct(Container $context) | ||
{ | ||
$this->context = $context; | ||
} | ||
|
||
public function setup(HubInterface $hub, Event $event, EventHint $hint): ?Event | ||
{ | ||
/** @var S3Uploader|null $uploader */ | ||
$uploader = $this->context->getByType(S3Uploader::class, false); | ||
|
||
// Required services are missing | ||
if ($uploader === null) { | ||
return $event; | ||
} | ||
|
||
$exception = $hint->exception; | ||
|
||
// No exception | ||
if ($exception === null) { | ||
return $event; | ||
} | ||
|
||
// Use logger from Tracy to calculate filename | ||
$logger = new Logger(Debugger::$logDirectory, Debugger::$email, Debugger::getBlueScreen()); | ||
$file = $logger->getExceptionFile($exception); | ||
|
||
// Render bluescreen to file | ||
$bs = Debugger::getBlueScreen(); | ||
$bs->renderToFile($exception, $file); | ||
|
||
// Upload file | ||
try { | ||
$uploaded = $uploader->upload($file); | ||
$event->setTags([ | ||
'tracy_file' => $uploaded['url'], | ||
]); | ||
} catch (UploadException $e) { | ||
// Do nothing | ||
} | ||
|
||
return $event; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Contributte\Sentry\Upload; | ||
|
||
use DateTime; | ||
|
||
class S3Signer | ||
{ | ||
|
||
/** @var DateTime */ | ||
private $date; | ||
|
||
/** @var string */ | ||
private $accessKey; | ||
|
||
/** @var string */ | ||
private $secretKey; | ||
|
||
/** @var string */ | ||
private $url; | ||
|
||
/** @var string */ | ||
private $bucket; | ||
|
||
/** @var string */ | ||
private $region; | ||
|
||
/** @var string|null */ | ||
private $prefix; | ||
|
||
public function __construct( | ||
string $accessKeyId, | ||
string $secretKey, | ||
string $url, | ||
string $bucket, | ||
string $region = 'auto', | ||
?string $prefix = null | ||
) | ||
{ | ||
$this->accessKey = $accessKeyId; | ||
$this->secretKey = $secretKey; | ||
$this->url = $url; | ||
$this->bucket = $bucket; | ||
$this->region = $region; | ||
$this->prefix = $prefix; | ||
|
||
$this->date = new DateTime('UTC'); | ||
} | ||
|
||
/** | ||
* @return array{url: string, headers: array<string,string>} | ||
*/ | ||
public function sign(string $path): array | ||
{ | ||
$fullpath = sprintf('/%s/%s', $this->bucket, ($this->prefix !== null ? trim($this->prefix, '/') . '/' : '') . $path); | ||
$url = sprintf('https://%s%s', $this->url, $fullpath); | ||
|
||
$headers = [ | ||
'Host' => $this->url, | ||
'X-Amz-Date' => $this->date->format('Ymd\THis\Z'), | ||
'X-Amz-Content-Sha256' => 'UNSIGNED-PAYLOAD', | ||
]; | ||
|
||
$headers['Authorization'] = $this->doAuthorization($fullpath, $headers); | ||
$headers['Content-Type'] = 'text/html; charset=utf-8'; | ||
|
||
return ['url' => $url, 'headers' => $headers]; | ||
} | ||
|
||
|
||
/** | ||
* @param array<string, string> $headers | ||
*/ | ||
protected function doAuthorization(string $path, array $headers): string | ||
{ | ||
$method = 'PUT'; | ||
$query = ''; | ||
$payloadHash = 'UNSIGNED-PAYLOAD'; | ||
$service = 's3'; | ||
|
||
$longDate = $this->date->format('Ymd\THis\Z'); | ||
|
||
// Sort headers by key | ||
$sortedHeaders = $headers; | ||
ksort($sortedHeaders); | ||
|
||
// Build headers keys and headers lines | ||
$signedHeaderNames = []; | ||
$signedHeaderLines = []; | ||
|
||
foreach ($sortedHeaders as $key => $value) { | ||
$signedHeaderNames[] = strtolower($key); | ||
$signedHeaderLines[] = sprintf('%s:%s', strtolower($key), $value); | ||
} | ||
|
||
$signedHeaderLines = implode("\n", $signedHeaderLines); | ||
$signedHeaderNames = implode(';', $signedHeaderNames); | ||
|
||
// Scope | ||
$credentialScope = sprintf('%s/%s/%s/aws4_request', $this->date->format('Ymd'), $this->region, $service); | ||
|
||
// Canonical | ||
$canonicalRequest = sprintf( | ||
"%s\n%s\n%s\n%s\n\n%s\n%s", | ||
$method, | ||
$path, | ||
$query, | ||
$signedHeaderLines, | ||
$signedHeaderNames, | ||
$payloadHash | ||
); | ||
|
||
// Sign string | ||
$hash = hash('sha256', $canonicalRequest); | ||
$stringToSign = sprintf("AWS4-HMAC-SHA256\n%s\n%s\n%s", $longDate, $credentialScope, $hash); | ||
|
||
// Sign key | ||
$dateKey = hash_hmac('sha256', $this->date->format('Ymd'), sprintf('AWS4%s', $this->secretKey), true); | ||
$regionKey = hash_hmac('sha256', $this->region, $dateKey, true); | ||
$serviceKey = hash_hmac('sha256', 's3', $regionKey, true); | ||
$signingKey = hash_hmac('sha256', 'aws4_request', $serviceKey, true); | ||
$signature = hash_hmac('sha256', $stringToSign, $signingKey); | ||
|
||
// Compute together | ||
return sprintf( | ||
'AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s', | ||
$this->accessKey, | ||
$credentialScope, | ||
$signedHeaderNames, | ||
$signature | ||
); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Contributte\Sentry\Upload; | ||
|
||
use Contributte\Sentry\Exception\Runtime\UploadException; | ||
use Throwable; | ||
|
||
class S3Uploader | ||
{ | ||
|
||
/** @var S3Signer */ | ||
private $signer; | ||
|
||
public function __construct(S3Signer $signer) | ||
{ | ||
$this->signer = $signer; | ||
} | ||
|
||
/** | ||
* @return array{url: string} | ||
*/ | ||
public function upload(string $file): array | ||
{ | ||
$filename = basename($file); | ||
$signed = $this->signer->sign($filename); | ||
|
||
// Prepare vars | ||
$headers = []; | ||
foreach ($signed['headers'] as $key => $value) { | ||
$headers[] = sprintf('%s:%s', $key, $value); | ||
} | ||
|
||
$url = $signed['url']; | ||
|
||
// Read file | ||
$content = file_get_contents($file); | ||
|
||
try { | ||
$curl = curl_init(); | ||
curl_setopt_array($curl, [ | ||
CURLOPT_URL => $url, | ||
CURLOPT_RETURNTRANSFER => false, | ||
CURLOPT_MAXREDIRS => 10, | ||
CURLOPT_TIMEOUT => 0, | ||
CURLOPT_FOLLOWLOCATION => true, | ||
CURLOPT_CUSTOMREQUEST => 'PUT', | ||
CURLOPT_POSTFIELDS => $content, | ||
CURLOPT_HTTPHEADER => $headers, | ||
]); | ||
|
||
$response = curl_exec($curl); | ||
|
||
curl_close($curl); | ||
} catch (Throwable $e) { | ||
throw new UploadException('Cannot upload', 0, $e); | ||
} | ||
|
||
if ($response !== true) { | ||
throw new UploadException('Upload failed'); | ||
} | ||
|
||
return ['url' => $url]; | ||
} | ||
|
||
} |