phpmyadmin / scripts

Various scripts related to project
16 stars 19 forks source link

Migrate our error reporting to a Sentry backend #31

Closed williamdes closed 3 years ago

williamdes commented 4 years ago

You can find a PHP code sample to send our error reports to Sentry. It works quite well.

here

```php ____userMessage !== null; } public function getUserFeedback(): string { return $this->____userMessage ?? ''; } public static function fromString(string $input): Report { $obj = (object) json_decode($input); $report = new Report(); foreach ($obj as $property => $argument) { $report->{$property} = $argument; } return $report; } public static function fromObject(stdClass $input): Report { $obj = (object) $input; $report = new Report(); foreach ($obj as $property => $argument) { if ($property === 'steps') { $report->____userMessage = $argument; } else { $report->{$property} = $argument; } } return $report; } public function setTimestamp(string $timestamp): void { $this->____date = $timestamp; } public function getEventId(): string { return bin2hex(openssl_random_pseudo_bytes(16)); } public function getTimestampUTC(): string { return $this->____date === null ? date('Y-m-d\TH:i:s') : $this->____date; } public function getTags(): stdClass { /* "pma_version": "4.8.6-dev", "browser_name": "CHROME", "browser_version": "72.0.3626.122", "user_os": "Linux", "server_software": "nginx/1.14.0", "user_agent_string": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36 Vivaldi/2.3.1440.61", "locale": "fr", "configuration_storage": "enabled", "php_version": "7.2.16-1+ubuntu18.04.1+deb.sury.org+1", "exception_type": "php", */ $tags = new stdClass(); //$tags->pma_version = $this->{'pma_version'} ?? null; //$tags->browser_name = $this->{'browser_name'} ?? null; //$tags->browser_version = $this->{'browser_version'} ?? null; //$tags->user_os = $this->{'user_os'} ?? null; $tags->server_software = $this->{'server_software'} ?? null; $tags->user_agent_string = $this->{'user_agent_string'} ?? null; $tags->locale = $this->{'locale'} ?? null; $tags->configuration_storage = $this->{'configuration_storage'} === "enabled"; // "enabled" or "disabled" $tags->php_version = $this->{'php_version'} ?? null; $tags->exception_type = $this->{'exception_type'} ?? null;// js or php return $tags; } public function getContexts() : stdClass { $contexts = new stdClass(); $contexts->os = new stdClass(); $contexts->os->name = $this->{'user_os'}; $contexts->browser = new stdClass(); $contexts->browser->name = $this->{'browser_name'}; $contexts->browser->version = $this->{'browser_version'}; return $contexts; } public function decode(string $text): string { return htmlspecialchars_decode(html_entity_decode($text, ENT_QUOTES|ENT_HTML5)); } public function getExceptionJS(): array { $exception = new stdClass(); $exception->type = $this->decode($this->{'exception'}->name ?? ''); $exception->value = $this->decode($this->{'exception'}->message ?? ''); $exception->stacktrace = new stdClass(); $exception->stacktrace->frames = []; foreach ($this->{'exception'}->{'stack'} as $stack) { $exception->stacktrace->frames[] = [ 'platform' => 'javascript', 'function' => $this->decode($stack->{'func'} ?? ''), 'lineno' => (int) ($stack->{'line'} ?? 0), 'colno' => (int) ($stack->{'column'} ?? 0), 'abs_path' => $stack->{'uri'} ?? '', 'filename' => $stack->{'scriptname'} ?? '' ]; } return [ 'platform' => 'javascript', 'exception' => [ 'values' => [$exception] ], 'message' => $this->decode($this->{'exception'}->message ?? ''), 'culprit' => $this->{'script_name'}, ]; } public function getExtras(): stdClass { $extras = new stdClass(); return $extras; } public function getUserMessage(): stdClass { $userMessage = new stdClass(); $userMessage->{'message'} = $this->decode($this->{'description'} ?? ''); return $userMessage; } public function getUser(): stdClass { $user = new stdClass(); $user->ip_address = '0.0.0.0'; return $user; } public function isMultiReports(): bool { return isset($this->{'exception_type'}) && $this->{'exception_type'} === 'php'; } public function typeToLevel(string $type): string { switch ($type) { case 'Internal error': case 'Parsing Error': case 'Error': case 'Core Error': return 'error'; case 'User Error': case 'User Warning': case 'User Notice': return 'info'; case 'Warning': case 'Runtime Notice': case 'Deprecation Notice': case 'Notice': case 'Compile Warning': return 'warning'; case 'Catchable Fatal Error': return 'tatal'; default: return 'error'; } } public function getMultiDataToSend(): array { $reports = []; /* { "lineNum": 272, "file": "./libraries/classes/Plugins/Export/ExportXml.php", "type": "Warning", "msg": "count(): Parameter must be an array or an object that implements Countable", "stackTrace": [ { "file": "./libraries/classes/Plugins/Export/ExportXml.php", "line": 272, "function": "count", "args": [ "NULL" ] }, { "file": "./export.php", "line": 415, "function": "exportHeader", "class": "PhpMyAdmin\\Plugins\\Export\\ExportXml", "type": "->" } ], "stackhash": "e6e0b1e1b9d90fee08a5ab8226e485fb" } */ foreach ($this->{'errors'} as $error) { $exception = new stdClass(); $exception->type = $this->decode($error->{'type'} ?? ''); $exception->value = $this->decode($error->{'msg'} ?? ''); $exception->stacktrace = new stdClass(); $exception->stacktrace->frames = []; foreach ($error->{'stackTrace'} as $stack) { $trace = [ 'platform' => 'php', 'function' => $stack->{'function'}, 'lineno' => (int) $stack->{'line'}, 'filename' => $error->{'file'}, ]; if (isset($stack->{'class'})) { $trace['package'] = $stack->{'class'}; } if (isset($stack->{'type'})) { $trace['symbol_addr'] = $stack->{'type'}; } if (isset($stack->{'args'})) {// function arguments $trace['vars'] = (object) $stack->{'args'}; } $exception->stacktrace->frames[] = $trace; } $reports[] = [ 'platform' => 'php', 'level' => $this->typeToLevel($error->{'type'}), 'exception' => [ 'values' => [$exception] ], 'message' => $this->decode($error->{'msg'} ?? ''), 'culprit' => $error->{'file'}, ]; } return $reports; } public function toJson(): array { $exType = $this->{'exception_type'} ?? 'js'; return [ 'sentry.interfaces.Message' => $this->getUserMessage(), 'release' => $this->{'pma_version'}, 'platform' => $exType === 'js' ? 'javascript' : 'php', 'timestamp' => $this->getTimestampUTC(), 'tags' => $this->getTags(), 'extra' => $this->getExtras(), 'contexts' => $this->getContexts(), 'user' => $this->getUser(), //TODO: 'environment' //TODO: 'level' ]; } public function getReports(): array { if ($this->isMultiReports()) { $reports = []; foreach($this->getMultiDataToSend() as $data) { $reports[] = array_merge($this->toJson(), $data, [ 'event_id' => $this->getEventId() ]); } return $reports; } else { return [ array_merge( $this->toJson(), $this->getExceptionJs(), [ 'event_id' => $this->getEventId() ] ), ]; } } } class SendReports { public static function getSentryTimestamp(): int { date_default_timezone_set('UTC'); return time(); } //const SENTRY_HOST = 'dc2.domain.tld:9000'; //const SENTRY_PROJECTID = 1; //const SENTRY_KEY = '8c96170af02f4830a5a68844e04ebf72'; //const SENTRY_SECRET = 'xxxxxxxxxxxxxxx'; const SENTRY_HOST = 'williamdes.local:8000'; const SENTRY_PROJECTID = 2; const SENTRY_KEY = 'b50e41750c7b40528c689c0bbb63fdd3'; const SENTRY_SECRET = 'xxxxxxxxxxxxxxxxxx'; const SENTRY_ORG = 'pma'; const SENTRY_PROJECT = 'phpmyadmin'; /** * Send report and get ID * * @param array $report The report as an array * @return string The event ID */ public static function sendReport(array $report): string { $data = json_encode($report); //$cookiesFile = __DIR__ . DIRECTORY_SEPARATOR . 'cookies'; $ch = curl_init ("http://" . self::SENTRY_KEY . "@" . self::SENTRY_HOST . "/api/" . self::SENTRY_PROJECTID . "/store/"); //curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiesFile); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-type: application/json", "X-Sentry-Auth: Sentry sentry_version=7, sentry_timestamp=" . SendReports::getSentryTimestamp() . ", sentry_key=" . self::SENTRY_KEY . ", sentry_client=phpmyadmin-proxy/0.1" . ", sentry_secret=" . self::SENTRY_SECRET ]); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); return json_decode(curl_exec($ch))->id; } /** * Set user feedback to Sentry API * * @param string $eventId The event ID * @param string $comments The comment sent by the user * @return void */ public static function sendFeedback(string $eventId, string $comments): void { $data = [ 'comments' => $comments, 'email' => 'users@pma.local', 'name' => 'phpMyAdmin User', ]; //$cookiesFile = __DIR__ . DIRECTORY_SEPARATOR . 'cookies'; $DSN = "http://" . self::SENTRY_KEY . "@" . self::SENTRY_HOST . "/" . self::SENTRY_PROJECTID; $ch = curl_init ( "http://" . self::SENTRY_HOST . "/api/embed/error-page/?dsn=$DSN&eventId=$eventId" ); //curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiesFile); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_exec($ch); } public static function process(Report $report): void { foreach($report->getReports() as $reportData) { $eventId = self::sendReport($reportData); if ($report->hasUserFeedback()) { self::sendFeedback($eventId, $report->getUserFeedback()); } } } } foreach(glob(__DIR__ . DIRECTORY_SEPARATOR . 'reports' . DIRECTORY_SEPARATOR . '*') as $file) { echo $file . PHP_EOL; $input = file_get_contents($file);//'php://input' $input = json_decode($input); $input->full_report->pma_version = $input->pma_version;// Use the PMA processed versions $input->full_report->php_version = $input->php_version; $report = Report::fromObject($input->full_report); $report->setTimestamp(date('Y-m-d\TH:i:s', strtotime($input->created))); SendReports::process($report); } echo json_encode([ 'success' => true ]); ```

williamdes commented 4 years ago

The question is, now: can we have a Sentry instance and throw all the trafic to it and see what happens ?

williamdes commented 4 years ago

For the users having access to this repository: https://github.com/wdesportes/phpmyadmin-error-reports/blob/main/sendToSentry.php

MauricioFauth commented 4 years ago
williamdes commented 4 years ago
  • Will we migrate our current data from old versions to Sentry?

No, maybe only the last bunch of issues received recently.

  • Will we keep the Error Reporting Server for current versions and use Sentry only for new versions?

We will switch all reports to Sentry, because we are able to convert our report format to the Sentry one.