From b9d0318bda74251f7672ff72f87762fabcfc1293 Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Wed, 28 Aug 2019 16:22:14 +0300 Subject: [PATCH 01/27] update project configuration --- .gitignore | 1 + LICENSE | 2 +- composer.json | 19 +++++++++++++++++-- phpunit.xml.dist | 23 +++++++++++++++++++++++ tests/HawkTest.php | 10 ++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 phpunit.xml.dist create mode 100644 tests/HawkTest.php diff --git a/.gitignore b/.gitignore index c4aff8a..7b4ebf4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ composer.lock vendor/ .idea/ .DS_Store +.phpunit.result.cache diff --git a/LICENSE b/LICENSE index c4eb4ae..9612b11 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 CodeX +Copyright (c) 2019 CodeX Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/composer.json b/composer.json index ef46fbb..0631ae2 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,31 @@ { "name": "codex-team/hawk.php", "description": "PHP errors Catcher module for Hawk.so", + "keywords": ["hawk", "catcher", "monolog"], "type": "library", + "version": "2.0.0-alpha", "license": "MIT", "require": { "ext-curl": "*", - "php": ">=5.3" + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" }, "autoload": { "classmap": [ "src/Hawk.php" ], - "psr-4": {"Hawk\\": "src/"} + "psr-4": { + "Hawk\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Hawk\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit --testdox" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..30adf85 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + ./tests + + + \ No newline at end of file diff --git a/tests/HawkTest.php b/tests/HawkTest.php new file mode 100644 index 0000000..8c346ec --- /dev/null +++ b/tests/HawkTest.php @@ -0,0 +1,10 @@ + Date: Thu, 29 Aug 2019 00:02:53 +0300 Subject: [PATCH 02/27] add code style fixer with rules --- .php_cs | 44 ++++++++++++++++++++++++++++++++++++++++++++ composer.json | 11 +++++++---- tests/HawkTest.php | 2 +- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..8b74de8 --- /dev/null +++ b/.php_cs @@ -0,0 +1,44 @@ +setUsingCache(false) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['align_double_arrow' => true], + 'blank_line_before_return' => true, + 'cast_spaces' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => true, + 'line_ending' => false, + 'method_argument_space' => true, + 'method_separation' => true, + 'no_blank_lines_before_namespace' => false, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiline_whitespace_before_semicolons' => true, + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unused_imports' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_empty_return' => false, + 'phpdoc_order' => false, + 'phpdoc_separation' => true, + 'phpdoc_scalar' => true, + 'return_type_declaration' => true, + 'short_scalar_cast' => true, + 'single_import_per_statement' => false, + 'single_quote' => true, + 'ternary_operator_spaces' => true, + 'trim_array_spaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__) + ); diff --git a/composer.json b/composer.json index 0631ae2..cf15296 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,18 @@ { "name": "codex-team/hawk.php", "description": "PHP errors Catcher module for Hawk.so", - "keywords": ["hawk", "catcher", "monolog"], + "keywords": ["hawk", "php", "error", "catcher", "monolog"], "type": "library", "version": "2.0.0-alpha", "license": "MIT", "require": { "ext-curl": "*", - "php": "^7.2" + "ext-json": "*", + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^8.2", + "friendsofphp/php-cs-fixer": "^2.15" }, "autoload": { "classmap": [ @@ -26,6 +28,7 @@ } }, "scripts": { - "test": "vendor/bin/phpunit --testdox" + "test": "vendor/bin/phpunit --testdox", + "csfix": "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose" } } diff --git a/tests/HawkTest.php b/tests/HawkTest.php index 8c346ec..07a38b3 100644 --- a/tests/HawkTest.php +++ b/tests/HawkTest.php @@ -7,4 +7,4 @@ class HawkTest extends TestCase { // -} \ No newline at end of file +} From db777dd99d7f7faecf6a43ebea2e40879facb64d Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Thu, 29 Aug 2019 00:03:32 +0300 Subject: [PATCH 03/27] improve code style and add modern syntax --- src/Hawk.php | 106 ++++++++++++++++++++++------------------ src/Helper/Stack.php | 12 ++--- src/Monolog/Handler.php | 22 ++++----- 3 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/Hawk.php b/src/Hawk.php index 61e157d..5a00c40 100644 --- a/src/Hawk.php +++ b/src/Hawk.php @@ -8,6 +8,7 @@ * Hawk PHP Catcher * * @copyright CodeX Team + * * @see https://hawk.so/docs#add-server-handler */ class HawkCatcher @@ -79,6 +80,7 @@ public static function instance($accessToken, $url = '') * @example catch only target types of error * enter a bitmask of error types as second param * by default TRUE converts to E_ALL + * * @see http://php.net/manual/en/errorfunc.constants.php * \Hawk\HawkCatcher::enableHandlers( * FALSE, // exceptions @@ -86,40 +88,40 @@ public static function instance($accessToken, $url = '') * TRUE // shutdown * ); * - * @param bool $exceptions (TRUE) enable catching exceptions - * @param bool|integer $errors (TRUE) enable catching errors - * You can pass a bitmask of error types - * See an example above - * @param bool $shutdown (TRUE) enable catching shutdowns + * @param bool $exceptions (TRUE) enable catching exceptions + * @param bool|int $errors (TRUE) enable catching errors + * You can pass a bitmask of error types + * See an example above + * @param bool $shutdown (TRUE) enable catching shutdowns * * @return void */ public static function enableHandlers( - $exceptions = TRUE, - $errors = TRUE, - $shutdown = TRUE + $exceptions = true, + $errors = true, + $shutdown = true ) { /** * Catch uncaught exceptions */ if ($exceptions) { - set_exception_handler(array('\Hawk\HawkCatcher', 'catchException')); + set_exception_handler(['\Hawk\HawkCatcher', 'catchException']); } /** * Catch errors * By default if $errors equals True then catch all errors */ - $errors = $errors === TRUE ? null : $errors; + $errors = $errors === true ? null : $errors; if ($errors) { - set_error_handler(array('\Hawk\HawkCatcher', 'catchError'), $errors); + set_error_handler(['\Hawk\HawkCatcher', 'catchError'], $errors); } /** * Catch fatal errors */ if ($shutdown) { - register_shutdown_function(array('\Hawk\HawkCatcher', 'catchFatal')); + register_shutdown_function(['\Hawk\HawkCatcher', 'catchFatal']); } } @@ -127,17 +129,17 @@ public static function enableHandlers( * Process given exception * * @param \Exception $exception - * @param array $context array of data to be passed with event + * @param array $context array of data to be passed with event * * @return string */ - public static function catchException($exception, $context = array()) + public static function catchException($exception, $context = []) { /** * If $context is not array then clean it up */ if (!is_array($context)) { - $context = array(); + $context = []; } /** @@ -149,20 +151,20 @@ public static function catchException($exception, $context = array()) /** * Errors catcher. PHP would call this function on error by himself * - * @param integer $errCode - * @param string $errMessage - * @param string $errFile - * @param integer $errLine + * @param int $errCode + * @param string $errMessage + * @param string $errFile + * @param int $errLine * @param mixed|array $context * - * @return string|boolean + * @return string|bool */ public static function catchError($errCode, $errMessage, $errFile, $errLine, $context) { /** * Create an exception with error's data */ - $exception = new \ErrorException($errMessage, $errCode, null, $errFile, $errLine); + $exception = new \ErrorException($errMessage, $errCode, null, $errFile, $errLine); /** * Process exception @@ -177,7 +179,7 @@ public static function catchError($errCode, $errMessage, $errFile, $errLine, $co * Fatal errors catch method * Being called on script exit * - * @return string|boolean|void + * @return string|bool|void */ public static function catchFatal() { @@ -213,14 +215,14 @@ public static function catchFatal() * Construct logs package and send them to service with access token * * @param \Exception $exception - * @param array $context array of data to be passed with event + * @param array $context array of data to be passed with event * * @return string */ - public static function processException($exception, $context = array()) + public static function processException($exception, $context = []) { if (empty($exception)) { - return "No exception was passed"; + return 'No exception was passed'; } /** @@ -241,25 +243,25 @@ public static function processException($exception, $context = array()) /** * Compose event's data */ - $data = array( + $data = [ /** Exception data */ - "error_type" => $errCode, - "error_description" => $errMessage, - "error_file" => $errFile, - "error_line" => $errLine, - "error_context" => $context, - "debug_backtrace" => $stack, + 'error_type' => $errCode, + 'error_description' => $errMessage, + 'error_file' => $errFile, + 'error_line' => $errLine, + 'error_context' => $context, + 'debug_backtrace' => $stack, /** Project's token */ - "access_token" => self::$_accessToken, + 'access_token' => self::$_accessToken, /** Environment variables */ - "http_params" => $_SERVER, - "GET" => $_GET, - "POST" => $_POST, - "COOKIES" => $_COOKIE, - "HEADERS" => Helper\Headers::get() - ); + 'http_params' => $_SERVER, + 'GET' => $_GET, + 'POST' => $_POST, + 'COOKIES' => $_COOKIE, + 'HEADERS' => Helper\Headers::get() + ]; /** * Send event to Hawk @@ -273,21 +275,21 @@ public static function processException($exception, $context = array()) * @param array $package * * @return string|bool - return string or bool - * 'OK' on success server response - * 'No access token' if no token was passed - * false if curl request was failed + * 'OK' on success server response + * 'No access token' if no token was passed + * false if curl request was failed */ private static function send($package) { - if ( !self::$_accessToken ) { - return "No access token"; + if (!self::$_accessToken) { + return 'No access token'; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, self::$_url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($package)); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $server_output = curl_exec($ch); @@ -309,7 +311,15 @@ private function __construct($accessToken) /** * Set private functions cause Singleton */ - private function __clone() {} - private function __sleep() {} - private function __wakeup() {} + private function __clone() + { + } + + private function __sleep() + { + } + + private function __wakeup() + { + } } diff --git a/src/Helper/Stack.php b/src/Helper/Stack.php index 272561e..526d02e 100644 --- a/src/Helper/Stack.php +++ b/src/Helper/Stack.php @@ -8,9 +8,9 @@ class Stack * Get path of file near target line to return as array * * @param string $filepath - * @param integer $line - * @param integer $margin max number of lines before and after target line - * to be returned + * @param int $line + * @param int $margin max number of lines before and after target line + * to be returned * * @return array */ @@ -60,7 +60,7 @@ public static function getAdjacentLines($filepath, $line, $margin = 5) /** * Save real line */ - 'line' => $line + 1, + 'line' => $line + 1, 'content' => $lineContent ]; } @@ -168,8 +168,8 @@ public static function buildStack($exception) * Add real error's path to trace chain */ $stack[] = [ - 'file' => $errorPosition['file'], - 'line' => $errorPosition['line'], + 'file' => $errorPosition['file'], + 'line' => $errorPosition['line'], 'trace' => self::getAdjacentLines($errorPosition['file'], $errorPosition['line']) ]; diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index ba8f265..a18256b 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -3,8 +3,8 @@ namespace Hawk\Monolog; use Hawk\HawkCatcher; -use Monolog\Logger; use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Logger; /** * Class MonologHandler @@ -18,10 +18,10 @@ class Handler extends AbstractProcessingHandler /** * Contructor sets up a Hawk catcher * - * @param string $token Project's token from hawk.so - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param string $url path to catcher on custom server + * @param string $token Project's token from hawk.so + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $url path to catcher on custom server * * @throws \Hawk\MissingExtensionException */ @@ -42,12 +42,12 @@ protected function write(array $record) /** * Get log context */ - $context = isset($record['context']) ? $record['context'] : null; + $context = $record['context'] ?? null; /** * Try to get 'exception' property from 'context' */ - $exception = isset($context['exception']) ? $context['exception'] : null; + $exception = $context['exception'] ?? null; unset($context['exception']); /** @@ -58,25 +58,25 @@ protected function write(array $record) /** * Get exception message */ - $message = isset($context['message']) ? $context['message'] : null; + $message = $context['message'] ?? null; unset($context['message']); /** * Get exception code */ - $code = isset($context['code']) ? $context['code'] : null; + $code = $context['code'] ?? null; unset($context['code']); /** * Get path to file with exception */ - $file = isset($context['file']) ? $context['file'] : null; + $file = $context['file'] ?? null; unset($context['file']); /** * Get line in the file exception */ - $line = isset($context['line']) ? $context['line'] : null; + $line = $context['line'] ?? null; unset($context['line']); /** From 024e8412376d6da076ee60ff6af533131f990707 Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Thu, 29 Aug 2019 13:17:03 +0300 Subject: [PATCH 04/27] remove classmap configuration --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index cf15296..856b21c 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,6 @@ "friendsofphp/php-cs-fixer": "^2.15" }, "autoload": { - "classmap": [ - "src/Hawk.php" - ], "psr-4": { "Hawk\\": "src/" } From 0ebb300ad7d47dcd37ebddc32458ebe30aeb739d Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Thu, 29 Aug 2019 13:21:13 +0300 Subject: [PATCH 05/27] add declare strict types support and set argument types --- src/{Hawk.php => Catcher.php} | 134 +++++++++--------- .../MissingExtensionException.php | 2 +- src/Helper/Headers.php | 91 +++++++----- src/Helper/Stack.php | 10 +- src/Monolog/Handler.php | 20 +-- 5 files changed, 141 insertions(+), 116 deletions(-) rename src/{Hawk.php => Catcher.php} (66%) rename src/{ => Exception}/MissingExtensionException.php (84%) diff --git a/src/Hawk.php b/src/Catcher.php similarity index 66% rename from src/Hawk.php rename to src/Catcher.php index 5a00c40..ba5e257 100644 --- a/src/Hawk.php +++ b/src/Catcher.php @@ -1,8 +1,13 @@ $stack, /** Project's token */ - 'access_token' => self::$_accessToken, + 'access_token' => self::$accessToken, /** Environment variables */ 'http_params' => $_SERVER, @@ -274,28 +280,22 @@ public static function processException($exception, $context = []) * * @param array $package * - * @return string|bool - return string or bool - * 'OK' on success server response - * 'No access token' if no token was passed - * false if curl request was failed + * @return bool - return true on success and false otherwise */ - private static function send($package) + private static function send(array $package): bool { - if (!self::$_accessToken) { - return 'No access token'; - } + highlight_string(""); $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, self::$_url); + curl_setopt($ch, CURLOPT_URL, self::$url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($package)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $server_output = curl_exec($ch); + $serverOutput = curl_exec($ch); curl_close($ch); - return $server_output; + return (bool) $serverOutput; } /** @@ -303,9 +303,9 @@ private static function send($package) * * @param string $accessToken */ - private function __construct($accessToken) + private function __construct(string $accessToken) { - self::$_accessToken = $accessToken; + self::$accessToken = $accessToken; } /** diff --git a/src/MissingExtensionException.php b/src/Exception/MissingExtensionException.php similarity index 84% rename from src/MissingExtensionException.php rename to src/Exception/MissingExtensionException.php index 8cb33ba..4f91536 100644 --- a/src/MissingExtensionException.php +++ b/src/Exception/MissingExtensionException.php @@ -1,6 +1,6 @@ "USER_AGENT" - */ - $headerName = substr($name, 5); - - /** - * Replace all underscores to spaces - * - * "USER_AGENT" -> "USER AGENT" - */ - $headerName = str_replace('_', ' ', $headerName); - - /** - * Lowercase string - * - * "USER AGENT" -> "user agent" - */ - $headerName = strtolower($headerName); - - /** - * Uppercase words - * - * "user agent" -> "User Agent" - */ - $headerName = ucwords($headerName); - - /** - * Replace all spaces to hyphens - * - * "User Agent" -> "User-Agent" - */ - $headerName = str_replace(' ', '-', $headerName); + if (substr($name, 0, 5) === 'HTTP_') { + $headerName = self::prettifyHeaderName($name); /** * Save header with right key to separate array */ $headers[$headerName] = $value; + /** * If this is header in $_SERVER without HTTP_ in the name */ - } elseif (in_array($name, $otherHeadersVars) && $value) { + } elseif ($value && in_array($name, $otherHeadersVars, true)) { $headers[$otherHeadersVars[$name]] = $value; } } @@ -121,4 +91,51 @@ public static function get() return $headers; } + + /** + * Convert header name to + * + * @param string $headerName + * + * @return string + */ + private static function prettifyHeaderName(string $headerName): string + { + /** + * Remove HTTP_ from the start of key + * + * "HTTP_USER_AGENT" -> "USER_AGENT" + */ + $headerName = substr($headerName, 5); + + /** + * Replace all underscores to spaces + * + * "USER_AGENT" -> "USER AGENT" + */ + $headerName = str_replace('_', ' ', $headerName); + + /** + * Lowercase string + * + * "USER AGENT" -> "user agent" + */ + $headerName = strtolower($headerName); + + /** + * Uppercase words + * + * "user agent" -> "User Agent" + */ + $headerName = ucwords($headerName); + + /** + * Replace all spaces to hyphens + * + * "User Agent" -> "User-Agent" + */ + $headerName = str_replace(' ', '-', $headerName); + + return $headerName; + } } diff --git a/src/Helper/Stack.php b/src/Helper/Stack.php index 526d02e..a167efa 100644 --- a/src/Helper/Stack.php +++ b/src/Helper/Stack.php @@ -1,7 +1,11 @@ Date: Fri, 30 Aug 2019 15:14:04 +0300 Subject: [PATCH 06/27] modify catcher data structure --- src/Catcher.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Catcher.php b/src/Catcher.php index ba5e257..a9e75af 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -5,7 +5,6 @@ namespace Hawk; use ErrorException; -use Hawk\Exception\CurlRequestException; use Hawk\Exception\MissingExtensionException; use Throwable; @@ -250,23 +249,24 @@ public static function processException(Throwable $exception, array $context = [ * Compose event's data */ $data = [ - /** Exception data */ - 'error_type' => $errCode, - 'error_description' => $errMessage, - 'error_file' => $errFile, - 'error_line' => $errLine, - 'error_context' => $context, - 'debug_backtrace' => $stack, - - /** Project's token */ - 'access_token' => self::$accessToken, - - /** Environment variables */ - 'http_params' => $_SERVER, - 'GET' => $_GET, - 'POST' => $_POST, - 'COOKIES' => $_COOKIE, - 'HEADERS' => Helper\Headers::get() + 'token' => self::$accessToken, + 'catcher_type' => 'errors/php', + 'payload' => [ + /** Exception data */ + 'error_type' => $errCode, + 'error_description' => $errMessage, + 'error_file' => $errFile, + 'error_line' => $errLine, + 'error_context' => $context, + 'debug_backtrace' => $stack, + + /** Environment variables */ + 'http_params' => $_SERVER, + 'GET' => $_GET, + 'POST' => $_POST, + 'COOKIES' => $_COOKIE, + 'HEADERS' => Helper\Headers::get() + ] ]; /** From 627ff1ee6f854a2dd5ed707577ed54b0736d21f4 Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Fri, 30 Aug 2019 15:41:56 +0300 Subject: [PATCH 07/27] Update instance return type Co-Authored-By: Murod Khaydarov --- src/Catcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Catcher.php b/src/Catcher.php index a9e75af..c77ebd6 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -42,7 +42,7 @@ class Catcher * * @throws MissingExtensionException */ - public static function instance(string $accessToken, string $url = ''): self + public static function instance(string $accessToken, string $url = ''): Catcher { /** * If php-curl is not available then throw an exception From ed56a8cf83285ff55985e87ea5eb73975a431f44 Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Fri, 30 Aug 2019 21:02:42 +0300 Subject: [PATCH 08/27] update instruction --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 20d0977..07d4e05 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ $ composer require codex-team/hawk.php Create an instance with Token at the entry point of your project. ```php -\Hawk\HawkCatcher::instance('abcd1234-1234-abcd-1234-123456abcdef'); +\Hawk\Catcher::instance('abcd1234-1234-abcd-1234-123456abcdef'); ``` ### Enable handlers @@ -31,16 +31,16 @@ Create an instance with Token at the entry point of your project. By default Hawk will catch everything. You can run function with no params. ```php -\Hawk\HawkCatcher::enableHandlers(); +\Hawk\Catcher::enableHandlers(); ``` It's similar to ```php -\Hawk\HawkCatcher::enableHandlers( - TRUE, // exceptions - TRUE, // errors - TRUE // shutdown +\Hawk\Catcher::enableHandlers( + true, // exceptions + true, // errors + true // shutdown ); ``` @@ -48,19 +48,19 @@ You can pass types of errors you want to track: ```php // Catch run-time warnings or compile-time parse errors -\Hawk\HawkCatcher::enableHandlers( - TRUE, // exceptions +\Hawk\Catcher::enableHandlers( + true, // exceptions E_WARNING | E_PARSE, // errors - TRUE // shutdown + true // shutdown ); ``` ```php // Catch everything except notices -\Hawk\HawkCatcher::enableHandlers( - TRUE, // exceptions +\Hawk\Catcher::enableHandlers( + true, // exceptions E_ALL & ~E_NOTICE, // errors - TRUE // shutdown + true // shutdown ); ``` @@ -72,7 +72,7 @@ You can catch exceptions manually with `catchException` method. try { throw new Exception("Error Processing Request", 1); } catch (Exception $e) { - \Hawk\HawkCatcher::catchException($e); + \Hawk\Catcher::catchException($e); } ``` From 70d5dfa0680bc04a4a4a35696b80726b5dd1d3d8 Mon Sep 17 00:00:00 2001 From: Ilya Goryachev Date: Fri, 30 Aug 2019 21:06:11 +0300 Subject: [PATCH 09/27] remove debug statement --- src/Catcher.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Catcher.php b/src/Catcher.php index c77ebd6..35a5a45 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -284,14 +284,14 @@ public static function processException(Throwable $exception, array $context = [ */ private static function send(array $package): bool { - highlight_string(""); - $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, self::$url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($package)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $serverOutput = curl_exec($ch); curl_close($ch); From b230e7f8ba5b6ca2383bad189ae4a7cb19439170 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Sun, 21 Mar 2021 20:34:11 +0300 Subject: [PATCH 10/27] refactor --- composer.json | 5 +- index.php | 9 + src/Catcher.php | 280 +++++++------------- src/Event.php | 57 ++++ src/EventPayload.php | 128 +++++++++ src/Exception/MissingExtensionException.php | 10 - src/Helper/{Stack.php => Stacktrace.php} | 132 ++++----- src/Monolog/Handler.php | 107 -------- src/Testing.php | 23 ++ 9 files changed, 387 insertions(+), 364 deletions(-) create mode 100644 index.php create mode 100644 src/Event.php create mode 100644 src/EventPayload.php delete mode 100644 src/Exception/MissingExtensionException.php rename src/Helper/{Stack.php => Stacktrace.php} (97%) delete mode 100644 src/Monolog/Handler.php create mode 100644 src/Testing.php diff --git a/composer.json b/composer.json index 856b21c..dc039ac 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "PHP errors Catcher module for Hawk.so", "keywords": ["hawk", "php", "error", "catcher", "monolog"], "type": "library", - "version": "2.0.0-alpha", + "version": "2.0.0", "license": "MIT", "require": { "ext-curl": "*", @@ -12,7 +12,8 @@ }, "require-dev": { "phpunit/phpunit": "^8.2", - "friendsofphp/php-cs-fixer": "^2.15" + "friendsofphp/php-cs-fixer": "^2.15", + "symfony/var-dumper": "^5.2" }, "autoload": { "psr-4": { diff --git a/index.php b/index.php new file mode 100644 index 0000000..5aa8fb0 --- /dev/null +++ b/index.php @@ -0,0 +1,9 @@ +enableHandlers(); + +$t = new \Hawk\Testing(); +$t->test(); \ No newline at end of file diff --git a/src/Catcher.php b/src/Catcher.php index 35a5a45..794420a 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -4,8 +4,7 @@ namespace Hawk; -use ErrorException; -use Hawk\Exception\MissingExtensionException; +use Hawk\Helper\Stacktrace; use Throwable; /** @@ -15,7 +14,7 @@ * * @see https://hawk.so/docs#add-server-handler */ -class Catcher +final class Catcher { /** * Hawk instance @@ -25,12 +24,12 @@ class Catcher /** * Default Hawk server catcher URL */ - private static $url = 'https://hawk.so/catcher/php'; + private $url = 'https://hawk.so/catcher/php'; /** * Project access token. Generated on https://hawk.so */ - private static $accessToken; + private $accessToken; /** * Main instance method @@ -39,30 +38,25 @@ class Catcher * @param string $url * * @return Catcher - * - * @throws MissingExtensionException */ - public static function instance(string $accessToken, string $url = ''): Catcher + public static function init(string $accessToken, string $url = ''): Catcher { - /** - * If php-curl is not available then throw an exception - */ - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The cURL PHP extension is required to use the Hawk PHP Catcher'); + if (!self::$instance) { + self::$instance = new self($accessToken, $url); } - /** - * Update Catcher's URL - */ - if ($url) { - self::$url = $url; - } + return self::$instance; + } - /** - * Singleton - */ + /** + * @return Catcher + * + * @throws \Exception + */ + public static function get(): Catcher + { if (!self::$instance) { - self::$instance = new self($accessToken); + throw new \Exception('Init before'); } return self::$instance; @@ -72,9 +66,9 @@ public static function instance(string $accessToken, string $url = ''): Catcher * Enable Hawk handlers functions for Exceptions, Errors and Shutdowns * * @example catch everything - * \Hawk\HawkCatcher::enableHandlers(); + * \Hawk\Catcher::enableHandlers(); * @example catch only fatals - * \Hawk\HawkCatcher::enableHandlers( + * \Hawk\Catcher::enableHandlers( * false, // exceptions * false, // errors * true // shutdown @@ -84,7 +78,7 @@ public static function instance(string $accessToken, string $url = ''): Catcher * by default TRUE converts to E_ALL * * @see http://php.net/manual/en/errorfunc.constants.php - * \Hawk\HawkCatcher::enableHandlers( + * \Hawk\Catcher::enableHandlers( * false, // exceptions * E_WARNING | E_PARSE, // Run-time warnings or compile-time parse errors * true // shutdown @@ -98,16 +92,13 @@ public static function instance(string $accessToken, string $url = ''): Catcher * * @return void */ - public static function enableHandlers( - bool $exceptions = true, - $errors = true, - bool $shutdown = true - ): void { + public function enableHandlers(bool $exceptions = true, bool $errors = true, bool $shutdown = true): void + { /** * Catch uncaught exceptions */ if ($exceptions) { - set_exception_handler([Catcher::class, 'catchException']); + set_exception_handler([$this, 'catchException']); } /** @@ -116,210 +107,141 @@ public static function enableHandlers( */ $errors = $errors === true ? null : $errors; if ($errors) { - set_error_handler([Catcher::class, 'catchError'], $errors); + set_error_handler([$this, 'catchError'], $errors); } /** * Catch fatal errors */ if ($shutdown) { - register_shutdown_function([Catcher::class, 'catchFatal']); + register_shutdown_function([$this, 'catchFatal']); } } /** - * Process given exception - * - * @param Throwable $exception - * @param array $context array of data to be passed with event - * - * @return bool + * @param array $payload */ - public static function catchException(Throwable $exception, array $context = []): bool + public function catchEvent(array $payload) { - /** - * If $context is not array then clean it up - */ - if (!is_array($context)) { - $context = []; - } + $event = new Event(); + $event->setEventPayload(new EventPayload($payload)); - /** - * Process exception - */ - return self::processException($exception, $context); + $this->send($event); } /** - * Errors catcher. PHP would call this function on error by himself - * - * @param int $errCode - * @param string $errMessage - * @param string $errFile - * @param int $errLine - * @param array $context + * Process given exception * - * @return bool + * @param Throwable $exception + * @param array $context array of data to be passed with event */ - public static function catchError( - int $errCode, - string $errMessage, - string $errFile, - int $errLine, - array $context - ): bool { - /** - * Create an exception with error's data - */ - $exception = new ErrorException($errMessage, $errCode, null, $errFile, $errLine); + public function catchException(Throwable $exception, array $context = []): void + { + $payload = [ + 'title' => $exception->getMessage(), + 'type' => '', + 'timestamp' => time(), + 'level' => 1 + ]; - /** - * Process exception - * - * Ignore $context because there are global variables - * as POST, ENV, SERVER etc. We will get them later. - */ - return self::processException($exception); - } + // Prepare GET params + if (!empty($_GET)) { + $payload['getParams'] = $_GET; + } - /** - * Fatal errors catch method - * Being called on script exit - * - * @return bool|null - */ - public static function catchFatal(): ?bool - { - /** - * Get the last occurred error - */ - $error = error_get_last(); + // Prepare POST params + if (!empty($_POST)) { + $payload['postParams'] = $_POST; + } - /** - * Check if last error has a message - * Otherwise the script has been executed successfully - */ - if ($error['message']) { - /** - * Create an exception with error's data - */ - $exception = new ErrorException( - $error['message'], - $error['type'], - null, - $error['file'], - $error['line'] - ); - - /** - * Process exception - */ - return self::processException($exception); + if (!empty($context)) { + $payload['context'] = $context; } - return null; + $payload['backtrace'] = Stacktrace::buildStack($exception); + + $event = new Event(); + $event->setEventPayload(new EventPayload($payload)); + + $this->send($event); } /** - * Construct logs package and send them to service with access token + * Errors catcher. PHP would call this function on error by himself * - * @param Throwable $exception - * @param array $context array of data to be passed with event + * @param string $message + * @param string $file + * @param int $code + * @param int $line + * @param array $context * * @return bool */ - public static function processException(Throwable $exception, array $context = []): bool + public function catchError(string $message, string $file, int $code, int $line, array $context = []): void { - /** - * Get exception data - * - * If no code was passed then mark event as notice - */ - $errCode = $exception->getCode() ?: E_NOTICE; - $errMessage = $exception->getMessage(); - $errFile = $exception->getFile(); - $errLine = $exception->getLine(); - - /** - * Get stack - */ - $stack = Helper\Stack::buildStack($exception); + $payload = [ - /** - * Compose event's data - */ - $data = [ - 'token' => self::$accessToken, - 'catcher_type' => 'errors/php', - 'payload' => [ - /** Exception data */ - 'error_type' => $errCode, - 'error_description' => $errMessage, - 'error_file' => $errFile, - 'error_line' => $errLine, - 'error_context' => $context, - 'debug_backtrace' => $stack, - - /** Environment variables */ - 'http_params' => $_SERVER, - 'GET' => $_GET, - 'POST' => $_POST, - 'COOKIES' => $_COOKIE, - 'HEADERS' => Helper\Headers::get() - ] ]; - /** - * Send event to Hawk - */ - return self::send($data); + $event = new Event(); + $event->setEventPayload(new EventPayload($payload)); + + $this->send($event); } /** - * Send package to service defined by api_url from settings - * - * @param array $package + * Fatal errors catch method + * Being called on script exit * - * @return bool - return true on success and false otherwise + * @return bool|null */ - private static function send(array $package): bool + public function catchFatal(): void { - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, self::$url); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($package)); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $error = error_get_last(); + $payload = [ + 'title' => $error['message'], + 'type' => $error['type'], + 'timestamp' => time(), + ]; - $serverOutput = curl_exec($ch); - curl_close($ch); + $event = new Event(); + $event->setEventPayload(new EventPayload($payload)); - return (bool) $serverOutput; + $this->send($event); } /** * Set Project's access token * * @param string $accessToken + * @param string $url */ - private function __construct(string $accessToken) + private function __construct(string $accessToken, string $url = '') { - self::$accessToken = $accessToken; + $this->accessToken = $accessToken; + + if (!empty($url)) { + $this->url = $url; + } } /** - * Set private functions cause Singleton + * @param Event $event */ - private function __clone() - { - } - - private function __sleep() + private function send(Event $event): void { + dd(json_encode($event)); } - private function __wakeup() + /** + * Send package to service defined by api_url from settings + * + * @param array $package + * + * @return bool - return true on success and false otherwise + */ + private function _send(array $package): bool { + return false; } } diff --git a/src/Event.php b/src/Event.php new file mode 100644 index 0000000..e476ca7 --- /dev/null +++ b/src/Event.php @@ -0,0 +1,57 @@ +catcherType; + } + + /** + * @return EventPayload + */ + public function getEventPayload(): EventPayload + { + return $this->eventPayload; + } + + /** + * @param EventPayload $eventPayload + * + * @return $this + */ + public function setEventPayload(EventPayload $eventPayload): self + { + $this->eventPayload = $eventPayload; + + return $this; + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'catcherType' => $this->getCatcherType(), + 'eventPayload' => $this->getEventPayload() + ]; + } +} diff --git a/src/EventPayload.php b/src/EventPayload.php new file mode 100644 index 0000000..4b10937 --- /dev/null +++ b/src/EventPayload.php @@ -0,0 +1,128 @@ + $value) { + if (property_exists($this, $prop)) { + $this->{$prop} = $value; + } + } + } + + /** + * @return array|mixed + */ + public function jsonSerialize() + { + return [ + 'title' => $this->title, + 'type' => $this->type, + 'description' => $this->description, + 'timestamp' => $this->timestamp, + 'level' => $this->level, + 'backtrace' => $this->backtrace, + 'get' => $this->getParams, + 'post' => $this->postParams, + 'headers' => $this->headers, + 'addons' => $this->addons, + 'release' => $this->release, + 'user' => $this->user, + 'context' => $this->context + ]; + } +} diff --git a/src/Exception/MissingExtensionException.php b/src/Exception/MissingExtensionException.php deleted file mode 100644 index 4f91536..0000000 --- a/src/Exception/MissingExtensionException.php +++ /dev/null @@ -1,10 +0,0 @@ - $line + 1, - 'content' => $lineContent - ]; - } - } - - return $nearErrorFileLines; - } - /** * Build exception backtrace. * @@ -189,4 +124,69 @@ public static function buildStack(Throwable $exception): array return $stack; } + + /** + * Get path of file near target line to return as array + * + * @param string $filepath + * @param int $line + * @param int $margin max number of lines before and after target line + * to be returned + * + * @return array + */ + private static function getAdjacentLines(string $filepath, int $line, int $margin = 5): array + { + /** + * Get file as array of lines + */ + $fileLines = file($filepath); + + /** + * In the file lines are counted from 1 but in array first element + * is on 0 position. So to get line position in array + * we need to decrease real line by 1 + */ + $errorLineInArray = $line - 1; + + /** + * Get upper and lower lines positions to return part of file + */ + $firstLine = $errorLineInArray - $margin; + $lastLine = $errorLineInArray + $margin; + + /** + * Create an empty array to be returned + */ + $nearErrorFileLines = []; + + /** + * Read file from $firstLine to $lastLine by lines + */ + for ($line = $firstLine; $line <= $lastLine; $line++) { + /** + * Check if line doesn't exist. For elements positions in array before 0 + * and after end of file will be returned NULL + */ + if (!empty($fileLines[$line])) { + /** + * Escape HTML chars + */ + $lineContent = htmlspecialchars($fileLines[$line]); + + /** + * Add new line + */ + $nearErrorFileLines[] = [ + /** + * Save real line + */ + 'line' => $line + 1, + 'content' => $lineContent + ]; + } + } + + return $nearErrorFileLines; + } } diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php deleted file mode 100644 index 115a59e..0000000 --- a/src/Monolog/Handler.php +++ /dev/null @@ -1,107 +0,0 @@ -foo(); + } + + private function foo() + { + $this->bar(); + } + + private function bar() + { + throw new \Exception("sdfdsf"); + } +} \ No newline at end of file From 86ee9afaeb6d2f108c6e8f2edc8a9f59e23111a3 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Sun, 21 Mar 2021 20:45:41 +0300 Subject: [PATCH 11/27] transport interface and error generation --- src/Catcher.php | 36 +++++++++++++++------------- src/Transport/CurlTransport.php | 15 ++++++++++++ src/Transport/GuzzleTransport.php | 22 +++++++++++++++++ src/Transport/TransportInterface.php | 17 +++++++++++++ src/{Helper => Util}/Headers.php | 2 +- src/{Helper => Util}/Stacktrace.php | 2 +- 6 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 src/Transport/CurlTransport.php create mode 100644 src/Transport/GuzzleTransport.php create mode 100644 src/Transport/TransportInterface.php rename src/{Helper => Util}/Headers.php (99%) rename src/{Helper => Util}/Stacktrace.php (99%) diff --git a/src/Catcher.php b/src/Catcher.php index 794420a..b134356 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -4,7 +4,8 @@ namespace Hawk; -use Hawk\Helper\Stacktrace; +use ErrorException; +use Hawk\Util\Stacktrace; use Throwable; /** @@ -55,8 +56,8 @@ public static function init(string $accessToken, string $url = ''): Catcher */ public static function get(): Catcher { - if (!self::$instance) { - throw new \Exception('Init before'); + if (self::$instance === null) { + throw new \Exception('Catcher is not initialized'); } return self::$instance; @@ -180,9 +181,19 @@ public function catchException(Throwable $exception, array $context = []): void public function catchError(string $message, string $file, int $code, int $line, array $context = []): void { $payload = [ - + 'title' => $message, + 'type' => '', + 'timestamp' => time(), + 'level' => 1 ]; + if (!empty($context)) { + $payload['context'] = $context; + } + + $exception = new ErrorException($message, $code, 1, $file, $line); + $payload['backtrace'] = \Hawk\Util\Stacktrace::buildStack($exception); + $event = new Event(); $event->setEventPayload(new EventPayload($payload)); @@ -204,6 +215,9 @@ public function catchFatal(): void 'timestamp' => time(), ]; + $exception = new ErrorException($error['message'], $error['code'], 1, $error['file'], $error['line']); + $payload['backtrace'] = \Hawk\Util\Stacktrace::buildStack($exception); + $event = new Event(); $event->setEventPayload(new EventPayload($payload)); @@ -226,22 +240,12 @@ private function __construct(string $accessToken, string $url = '') } /** + * Send package to service defined by api_url from settings + * * @param Event $event */ private function send(Event $event): void { dd(json_encode($event)); } - - /** - * Send package to service defined by api_url from settings - * - * @param array $package - * - * @return bool - return true on success and false otherwise - */ - private function _send(array $package): bool - { - return false; - } } diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php new file mode 100644 index 0000000..abce7cb --- /dev/null +++ b/src/Transport/CurlTransport.php @@ -0,0 +1,15 @@ +guzzle = ... + } + + public function send(Event $event): void + { + // TODO: Implement send() method. + } +} \ No newline at end of file diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php new file mode 100644 index 0000000..bc57e51 --- /dev/null +++ b/src/Transport/TransportInterface.php @@ -0,0 +1,17 @@ + Date: Tue, 23 Mar 2021 12:45:34 +0300 Subject: [PATCH 12/27] common enhancements --- index.php | 9 +- src/Catcher.php | 168 +++++++-------------------- src/Event.php | 39 ++++--- src/EventPayload.php | 3 +- src/Handler.php | 141 ++++++++++++++++++++++ src/Transport/CurlTransport.php | 52 ++++++++- src/Transport/GuzzleTransport.php | 22 +++- src/Transport/TransportInterface.php | 16 ++- tests/HawkTest.php | 4 +- src/Testing.php => tests/Stub.php | 8 +- 10 files changed, 306 insertions(+), 156 deletions(-) create mode 100644 src/Handler.php rename src/Testing.php => tests/Stub.php (72%) diff --git a/index.php b/index.php index 5aa8fb0..f20553c 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,10 @@ enableHandlers(); -$t = new \Hawk\Testing(); -$t->test(); \ No newline at end of file +$stub->test(); diff --git a/src/Catcher.php b/src/Catcher.php index b134356..909b799 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -4,12 +4,11 @@ namespace Hawk; -use ErrorException; -use Hawk\Util\Stacktrace; +use Hawk\Transport\CurlTransport; use Throwable; /** - * Hawk PHP Catcher + * Hawk PHP Catcher SDK * * @copyright CodeX Team * @@ -18,22 +17,28 @@ final class Catcher { /** - * Hawk instance + * Catcher SDK private instance. Created once + * + * @var Catcher */ private static $instance; /** * Default Hawk server catcher URL + * + * @var string */ private $url = 'https://hawk.so/catcher/php'; /** - * Project access token. Generated on https://hawk.so + * SDK handler: contains methods that catchs errors and exceptions + * + * @var Handler */ - private $accessToken; + private $handler; /** - * Main instance method + * Static method to initialize Catcher * * @param string $accessToken * @param string $url @@ -50,6 +55,8 @@ public static function init(string $accessToken, string $url = ''): Catcher } /** + * Returns initialized instance or throws an exception if it is not created yet + * * @return Catcher * * @throws \Exception @@ -64,12 +71,12 @@ public static function get(): Catcher } /** - * Enable Hawk handlers functions for Exceptions, Errors and Shutdowns + * Enable Catcher handlers functions for Exceptions, Errors and Shutdowns * * @example catch everything - * \Hawk\Catcher::enableHandlers(); + * \Hawk\Catcher::init()->enableHandlers(); * @example catch only fatals - * \Hawk\Catcher::enableHandlers( + * \Hawk\Catcher::init()->enableHandlers( * false, // exceptions * false, // errors * true // shutdown @@ -79,7 +86,7 @@ public static function get(): Catcher * by default TRUE converts to E_ALL * * @see http://php.net/manual/en/errorfunc.constants.php - * \Hawk\Catcher::enableHandlers( + * \Hawk\Catcher::init()->enableHandlers( * false, // exceptions * E_WARNING | E_PARSE, // Run-time warnings or compile-time parse errors * true // shutdown @@ -89,17 +96,17 @@ public static function get(): Catcher * @param bool|int $errors (true) enable catching errors * You can pass a bitmask of error types * See an example above - * @param bool $shutdown (true) enable catching shutdowns + * @param bool $shutdown (false) enable catching shutdowns * * @return void */ - public function enableHandlers(bool $exceptions = true, bool $errors = true, bool $shutdown = true): void + public function enableHandlers(bool $exceptions = true, $errors = true, bool $shutdown = false): void { /** * Catch uncaught exceptions */ if ($exceptions) { - set_exception_handler([$this, 'catchException']); + set_exception_handler([$this->handler, 'catchException']); } /** @@ -108,144 +115,59 @@ public function enableHandlers(bool $exceptions = true, bool $errors = true, boo */ $errors = $errors === true ? null : $errors; if ($errors) { - set_error_handler([$this, 'catchError'], $errors); + set_error_handler([$this->handler, 'catchError'], $errors); } /** * Catch fatal errors */ if ($shutdown) { - register_shutdown_function([$this, 'catchFatal']); + register_shutdown_function([$this->handler, 'catchFatal']); } } /** * @param array $payload - */ - public function catchEvent(array $payload) - { - $event = new Event(); - $event->setEventPayload(new EventPayload($payload)); - - $this->send($event); - } - - /** - * Process given exception * - * @param Throwable $exception - * @param array $context array of data to be passed with event + * @example + * \Hawk\Catcher::get() + * ->catchEvent([ + * 'message' => 'my special message' + * ]) */ - public function catchException(Throwable $exception, array $context = []): void - { - $payload = [ - 'title' => $exception->getMessage(), - 'type' => '', - 'timestamp' => time(), - 'level' => 1 - ]; - - // Prepare GET params - if (!empty($_GET)) { - $payload['getParams'] = $_GET; - } - - // Prepare POST params - if (!empty($_POST)) { - $payload['postParams'] = $_POST; - } - - if (!empty($context)) { - $payload['context'] = $context; - } - - $payload['backtrace'] = Stacktrace::buildStack($exception); - - $event = new Event(); - $event->setEventPayload(new EventPayload($payload)); - - $this->send($event); - } - - /** - * Errors catcher. PHP would call this function on error by himself - * - * @param string $message - * @param string $file - * @param int $code - * @param int $line - * @param array $context - * - * @return bool - */ - public function catchError(string $message, string $file, int $code, int $line, array $context = []): void + public function catchEvent(array $payload) { - $payload = [ - 'title' => $message, - 'type' => '', - 'timestamp' => time(), - 'level' => 1 - ]; - - if (!empty($context)) { - $payload['context'] = $context; - } - - $exception = new ErrorException($message, $code, 1, $file, $line); - $payload['backtrace'] = \Hawk\Util\Stacktrace::buildStack($exception); - - $event = new Event(); - $event->setEventPayload(new EventPayload($payload)); - - $this->send($event); + $this->handler->catchEvent($payload); } /** - * Fatal errors catch method - * Being called on script exit + * @param Throwable $throwable + * @param array $context * - * @return bool|null + * @example + * \Hawk\Catcher::get() + * ->catchException($exception, [ + * 'message' => 'my special message' + * ]) */ - public function catchFatal(): void + public function catchException(Throwable $throwable, array $context = []) { - $error = error_get_last(); - $payload = [ - 'title' => $error['message'], - 'type' => $error['type'], - 'timestamp' => time(), - ]; - - $exception = new ErrorException($error['message'], $error['code'], 1, $error['file'], $error['line']); - $payload['backtrace'] = \Hawk\Util\Stacktrace::buildStack($exception); - - $event = new Event(); - $event->setEventPayload(new EventPayload($payload)); - - $this->send($event); + $this->handler->catchException($throwable); } /** - * Set Project's access token - * * @param string $accessToken * @param string $url */ private function __construct(string $accessToken, string $url = '') { - $this->accessToken = $accessToken; - - if (!empty($url)) { - $this->url = $url; + if (empty($url)) { + $url = $this->url; } - } - /** - * Send package to service defined by api_url from settings - * - * @param Event $event - */ - private function send(Event $event): void - { - dd(json_encode($event)); + $this->handler = new Handler( + new CurlTransport($url), + $accessToken + ); } } diff --git a/src/Event.php b/src/Event.php index e476ca7..0fd7a93 100644 --- a/src/Event.php +++ b/src/Event.php @@ -4,6 +4,9 @@ namespace Hawk; +/** + * @package Hawk + */ final class Event implements \JsonSerializable { /** @@ -12,19 +15,32 @@ final class Event implements \JsonSerializable private $catcherType = 'error/php'; /** + * @var string + */ + private $accessToken = ''; + + /** + * Events payload corresponding to Hawk format + * * @var EventPayload */ private $eventPayload; /** - * @return string + * Event constructor. + * + * @param string $accessToken + * @param EventPayload $eventPayload */ - public function getCatcherType(): string + public function __construct(string $accessToken, EventPayload $eventPayload) { - return $this->catcherType; + $this->accessToken = $accessToken; + $this->eventPayload = $eventPayload; } /** + * Returns event payload + * * @return EventPayload */ public function getEventPayload(): EventPayload @@ -32,26 +48,15 @@ public function getEventPayload(): EventPayload return $this->eventPayload; } - /** - * @param EventPayload $eventPayload - * - * @return $this - */ - public function setEventPayload(EventPayload $eventPayload): self - { - $this->eventPayload = $eventPayload; - - return $this; - } - /** * @return array */ public function jsonSerialize() { return [ - 'catcherType' => $this->getCatcherType(), - 'eventPayload' => $this->getEventPayload() + 'token' => $this->accessToken, + 'catcherType' => $this->catcherType, + 'payload' => $this->getEventPayload() ]; } } diff --git a/src/EventPayload.php b/src/EventPayload.php index 4b10937..f89f3bb 100644 --- a/src/EventPayload.php +++ b/src/EventPayload.php @@ -14,7 +14,6 @@ final class EventPayload implements \JsonSerializable private $title = ''; /** - * @todo descripbe PHP types * Events error type * * @var int @@ -45,7 +44,7 @@ final class EventPayload implements \JsonSerializable /** * Events stacktrace * - * @var Backtrace[] + * @var array */ private $backtrace; diff --git a/src/Handler.php b/src/Handler.php new file mode 100644 index 0000000..2079fc9 --- /dev/null +++ b/src/Handler.php @@ -0,0 +1,141 @@ +transport = $transport; + $this->accessToken = $accessToken; + } + + /** + * Method to send any event to Hawk + * + * @param array $payload + */ + public function catchEvent(array $payload): void + { + $event = new Event( + $this->accessToken, + new EventPayload($payload) + ); + + $this->send($event); + } + + /** + * Process exception and sent to Hawk + * + * @param Throwable $exception + * @param array $context array of data to be passed with event + */ + public function catchException(Throwable $exception, array $context = []): void + { + $payload = [ + 'title' => $exception->getMessage(), + 'context' => $context, + 'backtrace' => Stacktrace::buildStack($exception) + ]; + + $event = new Event( + $this->accessToken, + new EventPayload($payload) + ); + + $this->send($event); + } + + /** + * Catches error and sends to the Hawk + * + * @param string $message + * @param string $file + * @param int $code + * @param int $line + * @param array $context + * + * @return bool + */ + public function catchError(string $message, string $file, int $code, int $line, array $context = []): void + { + $payload = [ + 'title' => $message, + 'context' => $context + ]; + + $exception = new ErrorException($message, $code, 0, $file, $line); + $payload['backtrace'] = Stacktrace::buildStack($exception); + + $event = new Event( + $this->accessToken, + new EventPayload($payload) + ); + + $this->send($event); + } + + /** + * Fatal errors catch method + * Being called on script exit + * + * @return bool|null + */ + public function catchFatal(): void + { + $error = error_get_last(); + $payload = [ + 'title' => $error['message'] + ]; + + $exception = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']); + $payload['backtrace'] = Stacktrace::buildStack($exception); + + $event = new Event( + $this->accessToken, + new EventPayload($payload) + ); + $this->send($event); + } + + /** + * Send event to Hawk + * + * @param Event $event + */ + private function send(Event $event): void + { + $this->transport->send($event); + } +} diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php index abce7cb..5dcecd4 100644 --- a/src/Transport/CurlTransport.php +++ b/src/Transport/CurlTransport.php @@ -6,10 +6,58 @@ use Hawk\Event; +/** + * Class CurlTransport + * + * @package Hawk\Transport + */ class CurlTransport implements TransportInterface { - public function send(Event $event): void + /** + * @var string + */ + private $url; + + /** + * CurlTransport constructor. + * + * @param string $url + */ + public function __construct(string $url) + { + $this->url = $url; + } + + /** + * @inheritDoc + */ + public function getUrl(): string { + return $this->url; + } + + /** + * @inheritDoc + */ + public function send(Event $event) + { + /** + * If php-curl is not available then throw an exception + */ + if (!extension_loaded('curl')) { + throw new \Exception('The cURL PHP extension is required to use the Hawk PHP Catcher'); + } + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->url); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($event)); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + $response = curl_exec($curl); + curl_close($curl); + return $response; } -} \ No newline at end of file +} diff --git a/src/Transport/GuzzleTransport.php b/src/Transport/GuzzleTransport.php index eb9e951..13f2941 100644 --- a/src/Transport/GuzzleTransport.php +++ b/src/Transport/GuzzleTransport.php @@ -6,17 +6,35 @@ use Hawk\Event; +/** + * Class GuzzleTransport + * + * @package Hawk\Transport + */ class GuzzleTransport implements TransportInterface { private $guzzle; + private $url; - public function __construct() + public function __construct(string $url) { // $this->guzzle = ... + $this->url = $url; } + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ public function send(Event $event): void { // TODO: Implement send() method. } -} \ No newline at end of file +} diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index bc57e51..dcc2428 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -13,5 +13,19 @@ */ interface TransportInterface { - public function send(Event $event): void; + /** + * Returns URL that object must send an Event + * + * @return string + */ + public function getUrl(): string; + + /** + * Sends an Event + * + * @param Event $event + * + * @return mixed + */ + public function send(Event $event); } diff --git a/tests/HawkTest.php b/tests/HawkTest.php index 07a38b3..03086fc 100644 --- a/tests/HawkTest.php +++ b/tests/HawkTest.php @@ -6,5 +6,7 @@ class HawkTest extends TestCase { - // + public function testCatchEvent() + { + } } diff --git a/src/Testing.php b/tests/Stub.php similarity index 72% rename from src/Testing.php rename to tests/Stub.php index 0d436b6..6eb9687 100644 --- a/src/Testing.php +++ b/tests/Stub.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hawk; +namespace Hawk\Tests; -class Testing +class Stub { public function test() { @@ -18,6 +18,6 @@ private function foo() private function bar() { - throw new \Exception("sdfdsf"); + throw new \Exception('sdfdsf'); } -} \ No newline at end of file +} From f8496483887aada7dc8eafcfb462dd2a9f8d09fe Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Sat, 27 Mar 2021 22:53:49 +0300 Subject: [PATCH 13/27] php docs, enhancements --- index.php => public/index.php | 6 +- src/Addons/AddonInterface.php | 20 +++ src/Addons/Headers.php | 43 +++++ src/Addons/Os.php | 21 +++ src/Addons/Runtime.php | 24 +++ src/Catcher.php | 87 ++-------- src/EventPayload.php | 267 +++++++++++++++++++++++++----- src/EventPayloadFactory.php | 92 ++++++++++ src/Handler.php | 139 ++++++++++------ src/Options.php | 75 +++++++++ src/Transport/CurlTransport.php | 4 +- src/Transport/GuzzleTransport.php | 2 - src/Util/Headers.php | 7 + src/Util/Stacktrace.php | 12 +- tests/HawkTest.php | 12 -- tests/Stub.php | 12 +- 16 files changed, 634 insertions(+), 189 deletions(-) rename index.php => public/index.php (52%) create mode 100644 src/Addons/AddonInterface.php create mode 100644 src/Addons/Headers.php create mode 100644 src/Addons/Os.php create mode 100644 src/Addons/Runtime.php create mode 100644 src/EventPayloadFactory.php create mode 100644 src/Options.php delete mode 100644 tests/HawkTest.php diff --git a/index.php b/public/index.php similarity index 52% rename from index.php rename to public/index.php index f20553c..fa42a64 100644 --- a/index.php +++ b/public/index.php @@ -4,7 +4,9 @@ $stub = new \Hawk\Tests\Stub(); -\Hawk\Catcher::init('token') - ->enableHandlers(); +\Hawk\Catcher::init([ + 'accessToken' => 'token', + 'release' => '123321' +]); $stub->test(); diff --git a/src/Addons/AddonInterface.php b/src/Addons/AddonInterface.php new file mode 100644 index 0000000..e764421 --- /dev/null +++ b/src/Addons/AddonInterface.php @@ -0,0 +1,20 @@ +fields as $field) { + $result[$field] = $_SERVER[$field] ?? ''; + } + + return $result; + } +} diff --git a/src/Addons/Os.php b/src/Addons/Os.php new file mode 100644 index 0000000..e288e68 --- /dev/null +++ b/src/Addons/Os.php @@ -0,0 +1,21 @@ + php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ]; + } +} diff --git a/src/Addons/Runtime.php b/src/Addons/Runtime.php new file mode 100644 index 0000000..e7370a6 --- /dev/null +++ b/src/Addons/Runtime.php @@ -0,0 +1,24 @@ + 'php', + 'version' => \PHP_VERSION + ]; + } +} diff --git a/src/Catcher.php b/src/Catcher.php index 909b799..70f5a62 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -23,13 +23,6 @@ final class Catcher */ private static $instance; - /** - * Default Hawk server catcher URL - * - * @var string - */ - private $url = 'https://hawk.so/catcher/php'; - /** * SDK handler: contains methods that catchs errors and exceptions * @@ -40,15 +33,14 @@ final class Catcher /** * Static method to initialize Catcher * - * @param string $accessToken - * @param string $url + * @param array $options * * @return Catcher */ - public static function init(string $accessToken, string $url = ''): Catcher + public static function init(array $options): Catcher { if (!self::$instance) { - self::$instance = new self($accessToken, $url); + self::$instance = new self($options); } return self::$instance; @@ -70,62 +62,6 @@ public static function get(): Catcher return self::$instance; } - /** - * Enable Catcher handlers functions for Exceptions, Errors and Shutdowns - * - * @example catch everything - * \Hawk\Catcher::init()->enableHandlers(); - * @example catch only fatals - * \Hawk\Catcher::init()->enableHandlers( - * false, // exceptions - * false, // errors - * true // shutdown - * ); - * @example catch only target types of error - * enter a bitmask of error types as second param - * by default TRUE converts to E_ALL - * - * @see http://php.net/manual/en/errorfunc.constants.php - * \Hawk\Catcher::init()->enableHandlers( - * false, // exceptions - * E_WARNING | E_PARSE, // Run-time warnings or compile-time parse errors - * true // shutdown - * ); - * - * @param bool $exceptions (true) enable catching exceptions - * @param bool|int $errors (true) enable catching errors - * You can pass a bitmask of error types - * See an example above - * @param bool $shutdown (false) enable catching shutdowns - * - * @return void - */ - public function enableHandlers(bool $exceptions = true, $errors = true, bool $shutdown = false): void - { - /** - * Catch uncaught exceptions - */ - if ($exceptions) { - set_exception_handler([$this->handler, 'catchException']); - } - - /** - * Catch errors - * By default if $errors equals True then catch all errors - */ - $errors = $errors === true ? null : $errors; - if ($errors) { - set_error_handler([$this->handler, 'catchError'], $errors); - } - - /** - * Catch fatal errors - */ - if ($shutdown) { - register_shutdown_function([$this->handler, 'catchFatal']); - } - } - /** * @param array $payload * @@ -156,18 +92,15 @@ public function catchException(Throwable $throwable, array $context = []) } /** - * @param string $accessToken - * @param string $url + * @param array $options */ - private function __construct(string $accessToken, string $url = '') + private function __construct(array $options) { - if (empty($url)) { - $url = $this->url; - } + $options = new Options($options); + $factory = new EventPayloadFactory(); + $transport = new CurlTransport($options->getUrl()); - $this->handler = new Handler( - new CurlTransport($url), - $accessToken - ); + $this->handler = new Handler($options, $transport, $factory); + $this->handler->enableHandlers(); } } diff --git a/src/EventPayload.php b/src/EventPayload.php index f89f3bb..ac2ebda 100644 --- a/src/EventPayload.php +++ b/src/EventPayload.php @@ -4,6 +4,11 @@ namespace Hawk; +/** + * Class EventPayload keeps events information about occurred event + * + * @package Hawk + */ final class EventPayload implements \JsonSerializable { /** @@ -28,63 +33,50 @@ final class EventPayload implements \JsonSerializable private $description = ''; /** - * Event occurence timestamp + * Event occurrence timestamp * * @var int */ - private $timestamp; + private $timestamp = 0; /** * Events level * * @var int */ - private $level; + private $level = 0; /** * Events stacktrace * * @var array */ - private $backtrace; + private $backtrace = []; /** - * HTTP Request GET params + * Environment specific data (OS, Runtime, Platform (Web, CLI)) * * @var array */ - private $getParams = []; + private $addons = []; /** - * HTTP Request POST params + * Application release * - * @var array + * @var string */ - private $postParams = []; + private $release = ''; /** - * HTTP Request headers + * Occurred event on user * * @var array */ - private $headers = []; - - /** - * @var array - */ - private $addons = []; - - /** - * @var array - */ - private $release = []; - - /** - * @var array - */ private $user = []; /** + * Application custom context + * * @var array */ private $context = []; @@ -94,7 +86,7 @@ final class EventPayload implements \JsonSerializable * * @param array $payload */ - public function __construct(array $payload) + public function __construct(array $payload = []) { foreach ($payload as $prop => $value) { if (property_exists($this, $prop)) { @@ -103,25 +95,222 @@ public function __construct(array $payload) } } + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + * + * @return $this + */ + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + /** + * @return int + */ + public function getType(): int + { + return $this->type; + } + + /** + * @param int $type + * + * @return $this + */ + public function setType(int $type): self + { + $this->type = $type; + + return $this; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + * + * @return $this + */ + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + /** + * @return int + */ + public function getTimestamp(): int + { + return $this->timestamp; + } + + /** + * @param int $timestamp + * + * @return $this + */ + public function setTimestamp(int $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + + /** + * @return int + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * @param int $level + * + * @return $this + */ + public function setLevel(int $level): self + { + $this->level = $level; + + return $this; + } + + public function getBacktrace(): array + { + return $this->backtrace; + } + + public function setBacktrace(array $backtrace): self + { + $this->backtrace = $backtrace; + + return $this; + } + + /** + * @return array + */ + public function getAddons(): array + { + return $this->addons; + } + + /** + * @param array $addons + * + * @return $this + */ + public function setAddons(array $addons): self + { + $this->addons = $addons; + + return $this; + } + + /** + * @return string + */ + public function getRelease(): string + { + return $this->release; + } + + /** + * @param string $release + * + * @return $this + */ + public function setRelease(string $release): self + { + $this->release = $release; + + return $this; + } + + /** + * @return array + */ + public function getUser(): array + { + return $this->user; + } + + /** + * @param array $user + * + * @return $this + */ + public function setUser(array $user): self + { + $this->user = $user; + + return $this; + } + + /** + * @return array + */ + public function getContext(): array + { + return $this->context; + } + + /** + * @param array $context + * + * @return $this + */ + public function setContext(array $context): self + { + $this->context = $context; + + return $this; + } + /** * @return array|mixed */ public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * @return array + */ + private function toArray(): array { return [ - 'title' => $this->title, - 'type' => $this->type, - 'description' => $this->description, - 'timestamp' => $this->timestamp, - 'level' => $this->level, - 'backtrace' => $this->backtrace, - 'get' => $this->getParams, - 'post' => $this->postParams, - 'headers' => $this->headers, - 'addons' => $this->addons, - 'release' => $this->release, - 'user' => $this->user, - 'context' => $this->context + 'title' => $this->getTitle(), + 'type' => $this->getType(), + 'description' => $this->getDescription(), + 'timestamp' => $this->getTimestamp(), + 'level' => $this->getLevel(), + 'backtrace' => $this->getBacktrace(), + 'addons' => $this->getAddons(), + 'release' => $this->getRelease(), + 'user' => $this->getUser(), + 'context' => $this->getContext() ]; } } diff --git a/src/EventPayloadFactory.php b/src/EventPayloadFactory.php new file mode 100644 index 0000000..62fbaea --- /dev/null +++ b/src/EventPayloadFactory.php @@ -0,0 +1,92 @@ +addonsResolvers['runtime'] = new Runtime(); + $this->addonsResolvers['server'] = new Os(); + $this->addonsResolvers['header'] = new Headers(); + } + + /** + * Returns EventPayload object + * + * @param array $data - event payload + * + * @return EventPayload + */ + public function create(array $data): EventPayload + { + $eventPayload = new EventPayload(); + + if (isset($data['context'])) { + $eventPayload->setContext($data['context']); + } + + if (isset($data['user'])) { + $eventPayload->setUser($data['user']); + } + + if (isset($data['exception']) && $data['exception'] instanceof \Throwable) { + $exception = $data['exception']; + $backtrace = Stacktrace::buildStack($exception); + + $eventPayload->setTitle($exception->getMessage()); + } else { + $backtrace = debug_backtrace(); + } + + $eventPayload->setBacktrace($backtrace); + + // Resolve addons + $eventPayload->setAddons($this->resolveAddons()); + + return $eventPayload; + } + + /** + * Resolves addons list and returns array + * + * @return array + */ + private function resolveAddons(): array + { + $result = []; + + /** + * @var string $key + * @var AddonInterface $resolver + */ + foreach ($this->addonsResolvers as $key => $resolver) { + $result[$key] = $resolver->resolve(); + } + + return $result; + } +} diff --git a/src/Handler.php b/src/Handler.php index 2079fc9..8d12c4a 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -6,57 +6,68 @@ use ErrorException; use Hawk\Transport\TransportInterface; -use Hawk\Util\Stacktrace; use Throwable; /** - * Class Handler + * Class Handler is responsible for enabling and handling any occurred errors on application * * @package Hawk */ -class Handler +final class Handler { /** + * Options object + * + * @var Options + */ + private $options; + + /** + * Transport object + * * @var TransportInterface */ private $transport; /** - * Project access token. Generated on https://hawk.so + * Events payload factory object * - * @var string + * @var EventPayloadFactory */ - private $accessToken; + private $eventPayloadFactory; /** * Handler constructor. * - * @param TransportInterface $transport - * @param string $accessToken + * @param Options $options + * @param TransportInterface $transport + * @param EventPayloadFactory $eventPayloadFactory */ - public function __construct(TransportInterface $transport, string $accessToken) - { + public function __construct( + Options $options, + TransportInterface $transport, + EventPayloadFactory $eventPayloadFactory + ) { + $this->options = $options; $this->transport = $transport; - $this->accessToken = $accessToken; + $this->eventPayloadFactory = $eventPayloadFactory; } /** - * Method to send any event to Hawk + * Method to send manually any event to Hawk * * @param array $payload */ public function catchEvent(array $payload): void { - $event = new Event( - $this->accessToken, - new EventPayload($payload) - ); + $eventPayload = $this->eventPayloadFactory->create($payload); + $event = $this->prepareEvent($eventPayload); $this->send($event); } /** - * Process exception and sent to Hawk + * Process exception and send to Hawk * * @param Throwable $exception * @param array $context array of data to be passed with event @@ -64,69 +75,101 @@ public function catchEvent(array $payload): void public function catchException(Throwable $exception, array $context = []): void { $payload = [ - 'title' => $exception->getMessage(), + 'exception' => $exception, 'context' => $context, - 'backtrace' => Stacktrace::buildStack($exception) ]; - $event = new Event( - $this->accessToken, - new EventPayload($payload) - ); + $eventPayload = $this->eventPayloadFactory->create($payload); + $event = $this->prepareEvent($eventPayload); $this->send($event); } /** - * Catches error and sends to the Hawk + * Catches error and sends to Hawk * + * @param int $level * @param string $message * @param string $file - * @param int $code * @param int $line - * @param array $context - * - * @return bool */ - public function catchError(string $message, string $file, int $code, int $line, array $context = []): void + public function catchError(int $level, string $message, string $file, int $line): void { + $exception = new ErrorException($message, $level, 0, $file, $line); $payload = [ - 'title' => $message, - 'context' => $context + 'exception' => $exception ]; - $exception = new ErrorException($message, $code, 0, $file, $line); - $payload['backtrace'] = Stacktrace::buildStack($exception); - - $event = new Event( - $this->accessToken, - new EventPayload($payload) - ); + $eventPayload = $this->eventPayloadFactory->create($payload); + $event = $this->prepareEvent($eventPayload); $this->send($event); } /** - * Fatal errors catch method - * Being called on script exit - * - * @return bool|null + * Catches fatal errors being called on script exit */ public function catchFatal(): void { $error = error_get_last(); + if ($error === null) { + return; + } + $payload = [ - 'title' => $error['message'] + 'exception' => new ErrorException( + $error['message'], + 0, + $error['type'], + $error['file'], + $error['line'] + ) ]; - $exception = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']); - $payload['backtrace'] = Stacktrace::buildStack($exception); + $eventPayload = $this->eventPayloadFactory->create($payload); + $event = $this->prepareEvent($eventPayload); + $this->send($event); + } + + /** + * Enable Catcher handlers functions for Exceptions, Errors and Shutdowns + */ + public function enableHandlers(): void + { + /** + * Catch uncaught exceptions + */ + set_exception_handler([$this, 'catchException']); + + /** + * Catch errors + * By default if $errors equals True then catch all errors + */ + set_error_handler([$this, 'catchError'], $this->options->getErrorTypes()); + + /** + * Catch fatal errors + */ + register_shutdown_function([$this, 'catchFatal']); + } + + /** + * Prepares event and returns it + * + * @param EventPayload $eventPayload + * + * @return Event + */ + private function prepareEvent(EventPayload $eventPayload): Event + { + $eventPayload->setRelease($this->options->getRelease()); $event = new Event( - $this->accessToken, - new EventPayload($payload) + $this->options->getAccessToken(), + $eventPayload ); - $this->send($event); + + return $event; } /** diff --git a/src/Options.php b/src/Options.php new file mode 100644 index 0000000..bbdbfc1 --- /dev/null +++ b/src/Options.php @@ -0,0 +1,75 @@ + '', + 'url' => 'https://hawk.so/catcher/php', + 'release' => '', + 'error_types' => \E_ALL + ]; + + /** + * Options constructor. + * + * @param array $options + */ + public function __construct(array $options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * Returns access token. It is available on projects settings + * + * @return string + */ + public function getAccessToken(): string + { + return $this->options['accessToken']; + } + + /** + * Returns URL that should be send + * + * @return string + */ + public function getUrl(): string + { + return $this->options['url']; + } + + /** + * Returns application release + * + * @return string + */ + public function getRelease(): string + { + return $this->options['release']; + } + + /** + * Returns error types + * + * @return int + */ + public function getErrorTypes(): int + { + return $this->options['error_types']; + } +} diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php index 5dcecd4..70745ea 100644 --- a/src/Transport/CurlTransport.php +++ b/src/Transport/CurlTransport.php @@ -7,13 +7,15 @@ use Hawk\Event; /** - * Class CurlTransport + * Class CurlTransport is a transport object * * @package Hawk\Transport */ class CurlTransport implements TransportInterface { /** + * URL to send occurred event + * * @var string */ private $url; diff --git a/src/Transport/GuzzleTransport.php b/src/Transport/GuzzleTransport.php index 13f2941..6ff5153 100644 --- a/src/Transport/GuzzleTransport.php +++ b/src/Transport/GuzzleTransport.php @@ -13,12 +13,10 @@ */ class GuzzleTransport implements TransportInterface { - private $guzzle; private $url; public function __construct(string $url) { -// $this->guzzle = ... $this->url = $url; } diff --git a/src/Util/Headers.php b/src/Util/Headers.php index 8611452..6ad12ec 100644 --- a/src/Util/Headers.php +++ b/src/Util/Headers.php @@ -4,6 +4,13 @@ namespace Hawk\Util; +/** + * @deprecated + * + * Class Headers + * + * @package Hawk\Util + */ class Headers { /** diff --git a/src/Util/Stacktrace.php b/src/Util/Stacktrace.php index 0c8f765..ee17f72 100644 --- a/src/Util/Stacktrace.php +++ b/src/Util/Stacktrace.php @@ -6,6 +6,11 @@ use Throwable; +/** + * Class Stacktrace + * + * @package Hawk\Util + */ final class Stacktrace { /** @@ -25,13 +30,6 @@ final class Stacktrace */ public static function buildStack(Throwable $exception): array { - /** - * If exception was not passed then return full backtrace - */ - if (!isset($exception)) { - return debug_backtrace(); - } - /** * Get trace to exception */ diff --git a/tests/HawkTest.php b/tests/HawkTest.php deleted file mode 100644 index 03086fc..0000000 --- a/tests/HawkTest.php +++ /dev/null @@ -1,12 +0,0 @@ -catchEvent([ +// 'context' => [ +// 'header' => '123' +// ], +// 'user' => [ +// 'id' => 11 +// ] +// ]); + $a = 1 / 0; } } From 9595fd0cddaa47caefa3215712cbccc8116a689b Mon Sep 17 00:00:00 2001 From: Taly Date: Tue, 30 Mar 2021 19:59:25 +0300 Subject: [PATCH 14/27] Update year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9612b11..e38e80b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 CodeX +Copyright (c) 2021 CodeX Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5d9e12919de7a9678361c2d0683e40620aafc5d4 Mon Sep 17 00:00:00 2001 From: Taly Date: Tue, 30 Mar 2021 20:56:43 +0300 Subject: [PATCH 15/27] Fix queue name --- src/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Event.php b/src/Event.php index 0fd7a93..68f666f 100644 --- a/src/Event.php +++ b/src/Event.php @@ -12,7 +12,7 @@ final class Event implements \JsonSerializable /** * @var string */ - private $catcherType = 'error/php'; + private $catcherType = 'errors/php'; /** * @var string From 08c112ad06779c27983f5e2f10246d5ac0a8f33b Mon Sep 17 00:00:00 2001 From: Taly Date: Tue, 30 Mar 2021 20:57:03 +0300 Subject: [PATCH 16/27] Update default collector url --- src/Options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index bbdbfc1..eccecfc 100644 --- a/src/Options.php +++ b/src/Options.php @@ -18,7 +18,7 @@ final class Options */ private $options = [ 'accessToken' => '', - 'url' => 'https://hawk.so/catcher/php', + 'url' => 'https://k1.hawk.so/', 'release' => '', 'error_types' => \E_ALL ]; From b0e5a8f8ab32a4bb2a516e98dea62f77269aaaad Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Wed, 31 Mar 2021 19:35:22 +0300 Subject: [PATCH 17/27] requested changes --- src/Catcher.php | 2 +- src/Event.php | 10 ++++---- src/EventPayload.php | 54 +++++++++++++++++++++----------------------- src/Handler.php | 3 ++- src/Options.php | 12 +++++----- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/Catcher.php b/src/Catcher.php index 70f5a62..8303a1d 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -10,7 +10,7 @@ /** * Hawk PHP Catcher SDK * - * @copyright CodeX Team + * @copyright CodeX * * @see https://hawk.so/docs#add-server-handler */ diff --git a/src/Event.php b/src/Event.php index 68f666f..0042b37 100644 --- a/src/Event.php +++ b/src/Event.php @@ -17,7 +17,7 @@ final class Event implements \JsonSerializable /** * @var string */ - private $accessToken = ''; + private $integrationToken = ''; /** * Events payload corresponding to Hawk format @@ -29,12 +29,12 @@ final class Event implements \JsonSerializable /** * Event constructor. * - * @param string $accessToken + * @param string $integrationToken * @param EventPayload $eventPayload */ - public function __construct(string $accessToken, EventPayload $eventPayload) + public function __construct(string $integrationToken, EventPayload $eventPayload) { - $this->accessToken = $accessToken; + $this->integrationToken = $integrationToken; $this->eventPayload = $eventPayload; } @@ -54,7 +54,7 @@ public function getEventPayload(): EventPayload public function jsonSerialize() { return [ - 'token' => $this->accessToken, + 'token' => $this->integrationToken, 'catcherType' => $this->catcherType, 'payload' => $this->getEventPayload() ]; diff --git a/src/EventPayload.php b/src/EventPayload.php index ac2ebda..72d99a3 100644 --- a/src/EventPayload.php +++ b/src/EventPayload.php @@ -33,14 +33,7 @@ final class EventPayload implements \JsonSerializable private $description = ''; /** - * Event occurrence timestamp - * - * @var int - */ - private $timestamp = 0; - - /** - * Events level + * Events severity level * * @var int */ @@ -96,6 +89,8 @@ public function __construct(array $payload = []) } /** + * Returns event title + * * @return string */ public function getTitle(): string @@ -116,6 +111,8 @@ public function setTitle(string $title): self } /** + * Returns errors' type + * * @return int */ public function getType(): int @@ -136,6 +133,8 @@ public function setType(int $type): self } /** + * Returns errors' description + * * @return string */ public function getDescription(): string @@ -156,26 +155,8 @@ public function setDescription(string $description): self } /** - * @return int - */ - public function getTimestamp(): int - { - return $this->timestamp; - } - - /** - * @param int $timestamp + * Returns errors' severity level * - * @return $this - */ - public function setTimestamp(int $timestamp): self - { - $this->timestamp = $timestamp; - - return $this; - } - - /** * @return int */ public function getLevel(): int @@ -195,11 +176,21 @@ public function setLevel(int $level): self return $this; } + /** + * Returns errors' backtrace + * + * @return array + */ public function getBacktrace(): array { return $this->backtrace; } + /** + * @param array $backtrace + * + * @return $this + */ public function setBacktrace(array $backtrace): self { $this->backtrace = $backtrace; @@ -208,6 +199,8 @@ public function setBacktrace(array $backtrace): self } /** + * Returns event addons + * * @return array */ public function getAddons(): array @@ -228,6 +221,8 @@ public function setAddons(array $addons): self } /** + * Returns release version + * * @return string */ public function getRelease(): string @@ -248,6 +243,8 @@ public function setRelease(string $release): self } /** + * Returns user, if passed on event + * * @return array */ public function getUser(): array @@ -268,6 +265,8 @@ public function setUser(array $user): self } /** + * Returns event context (any additional data) + * * @return array */ public function getContext(): array @@ -304,7 +303,6 @@ private function toArray(): array 'title' => $this->getTitle(), 'type' => $this->getType(), 'description' => $this->getDescription(), - 'timestamp' => $this->getTimestamp(), 'level' => $this->getLevel(), 'backtrace' => $this->getBacktrace(), 'addons' => $this->getAddons(), diff --git a/src/Handler.php b/src/Handler.php index 8d12c4a..06545e6 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -104,6 +104,7 @@ public function catchError(int $level, string $message, string $file, int $line) $event = $this->prepareEvent($eventPayload); $this->send($event); + throw $exception; } /** @@ -165,7 +166,7 @@ private function prepareEvent(EventPayload $eventPayload): Event { $eventPayload->setRelease($this->options->getRelease()); $event = new Event( - $this->options->getAccessToken(), + $this->options->getIntegrationToken(), $eventPayload ); diff --git a/src/Options.php b/src/Options.php index eccecfc..b4a6513 100644 --- a/src/Options.php +++ b/src/Options.php @@ -17,10 +17,10 @@ final class Options * @var array */ private $options = [ - 'accessToken' => '', - 'url' => 'https://k1.hawk.so/', - 'release' => '', - 'error_types' => \E_ALL + 'integrationToken' => '', + 'url' => 'https://k1.hawk.so/', + 'release' => '', + 'error_types' => \E_ALL ]; /** @@ -38,9 +38,9 @@ public function __construct(array $options) * * @return string */ - public function getAccessToken(): string + public function getIntegrationToken(): string { - return $this->options['accessToken']; + return $this->options['integrationToken']; } /** From bd4d439f9e8dd1f3ba6beff5929a52b5c4271302 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Wed, 31 Mar 2021 19:36:48 +0300 Subject: [PATCH 18/27] remove redundant class --- src/Util/Headers.php | 148 ------------------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 src/Util/Headers.php diff --git a/src/Util/Headers.php b/src/Util/Headers.php deleted file mode 100644 index 6ad12ec..0000000 --- a/src/Util/Headers.php +++ /dev/null @@ -1,148 +0,0 @@ - $headers['User-Agent'] - * $_SERVER['HTTP_ACCEPT_ENCODING'] -> $headers['Accept-Encoding'] - * $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] -> $headers['Upgrade-Insecure-Requests'] - * - * @return array $headers - */ - public static function get(): array - { - $headers = []; - - /** - * List of $_SERVER headers vars without HTTP_ at the start - */ - $otherHeadersVars = [ - 'CONTENT_TYPE' => 'Content-Type', - 'CONTENT_LENGTH' => 'Content-Length', - 'CONTENT_MD5' => 'Content-Md5' - ]; - - foreach ($_SERVER as $name => $value) { - /** - * If $_SERVER key starts with 'HTTP_' then it is a header - */ - if (substr($name, 0, 5) === 'HTTP_') { - $headerName = self::prettifyHeaderName($name); - - /** - * Save header with right key to separate array - */ - $headers[$headerName] = $value; - - /** - * If this is header in $_SERVER without HTTP_ in the name - */ - } elseif ($value && in_array($name, $otherHeadersVars, true)) { - $headers[$otherHeadersVars[$name]] = $value; - } - } - - /** - * Add Authorization header if not exist - */ - if (!isset($headers['Authorization'])) { - /** - * Check for rewriten header by PHP-CGI - */ - if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { - $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; - - /** - * When doing HTTP authentication this variable is set - * to the username provided by the user. - */ - } elseif (isset($_SERVER['PHP_AUTH_USER'])) { - /** - * When doing HTTP authentication this variable is set - * to the password provided by the user. - */ - $basic_pass = $_SERVER['PHP_AUTH_PW'] ?: ''; - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); - - /** - * When doing Digest HTTP authentication this variable is set - * to the 'Authorization' header sent by the client (which you - * should then use to make the appropriate validation). - */ - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; - } - } - - /** - * Sort headers by key - */ - ksort($headers); - - return $headers; - } - - /** - * Convert header name to - * - * @param string $headerName - * - * @return string - */ - private static function prettifyHeaderName(string $headerName): string - { - /** - * Remove HTTP_ from the start of key - * - * "HTTP_USER_AGENT" -> "USER_AGENT" - */ - $headerName = substr($headerName, 5); - - /** - * Replace all underscores to spaces - * - * "USER_AGENT" -> "USER AGENT" - */ - $headerName = str_replace('_', ' ', $headerName); - - /** - * Lowercase string - * - * "USER AGENT" -> "user agent" - */ - $headerName = strtolower($headerName); - - /** - * Uppercase words - * - * "user agent" -> "User Agent" - */ - $headerName = ucwords($headerName); - - /** - * Replace all spaces to hyphens - * - * "User Agent" -> "User-Agent" - */ - $headerName = str_replace(' ', '-', $headerName); - - return $headerName; - } -} From 07adee9d5941948a0bb7e41d25d9881680efd84d Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Wed, 31 Mar 2021 21:16:24 +0300 Subject: [PATCH 19/27] update --- src/Addons/Headers.php | 2 +- src/Addons/Os.php | 7 ++++++- src/Addons/Runtime.php | 2 +- src/Catcher.php | 4 ++-- src/Event.php | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Addons/Headers.php b/src/Addons/Headers.php index 84ba4b0..172cd24 100644 --- a/src/Addons/Headers.php +++ b/src/Addons/Headers.php @@ -29,7 +29,7 @@ class Headers implements AddonInterface ]; /** - * @return array + * @inheritDoc */ public function resolve(): array { diff --git a/src/Addons/Os.php b/src/Addons/Os.php index e288e68..f716f9f 100644 --- a/src/Addons/Os.php +++ b/src/Addons/Os.php @@ -4,10 +4,15 @@ namespace Hawk\Addons; +/** + * Class Os + * + * @package Hawk\Addons + */ class Os implements AddonInterface { /** - * @return array + * @inheritDoc */ public function resolve(): array { diff --git a/src/Addons/Runtime.php b/src/Addons/Runtime.php index e7370a6..c27cbe4 100644 --- a/src/Addons/Runtime.php +++ b/src/Addons/Runtime.php @@ -12,7 +12,7 @@ class Runtime implements AddonInterface { /** - * @return array + * @inheritDoc */ public function resolve(): array { diff --git a/src/Catcher.php b/src/Catcher.php index 8303a1d..7057e47 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -71,7 +71,7 @@ public static function get(): Catcher * 'message' => 'my special message' * ]) */ - public function catchEvent(array $payload) + public function sendEvent(array $payload) { $this->handler->catchEvent($payload); } @@ -86,7 +86,7 @@ public function catchEvent(array $payload) * 'message' => 'my special message' * ]) */ - public function catchException(Throwable $throwable, array $context = []) + public function sendException(Throwable $throwable, array $context = []) { $this->handler->catchException($throwable); } diff --git a/src/Event.php b/src/Event.php index 0042b37..68a3906 100644 --- a/src/Event.php +++ b/src/Event.php @@ -12,7 +12,7 @@ final class Event implements \JsonSerializable /** * @var string */ - private $catcherType = 'errors/php'; + private $catcherType = 'error/php'; /** * @var string From 37429a26b2c8f9e9420c3c7a580a23f314f7c5c4 Mon Sep 17 00:00:00 2001 From: Taly Date: Mon, 5 Apr 2021 16:57:13 +0300 Subject: [PATCH 20/27] fix queue name again! --- src/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Event.php b/src/Event.php index 68a3906..0042b37 100644 --- a/src/Event.php +++ b/src/Event.php @@ -12,7 +12,7 @@ final class Event implements \JsonSerializable /** * @var string */ - private $catcherType = 'error/php'; + private $catcherType = 'errors/php'; /** * @var string From 682268d6b1e7041e2c2179ce3e5499a278a00515 Mon Sep 17 00:00:00 2001 From: Taly Date: Mon, 5 Apr 2021 17:14:16 +0300 Subject: [PATCH 21/27] rename file lines field --- src/Util/Stacktrace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/Stacktrace.php b/src/Util/Stacktrace.php index ee17f72..d8f0d56 100644 --- a/src/Util/Stacktrace.php +++ b/src/Util/Stacktrace.php @@ -107,7 +107,7 @@ public static function buildStack(Throwable $exception): array $stack[] = [ 'file' => $errorPosition['file'], 'line' => $errorPosition['line'], - 'trace' => self::getAdjacentLines($errorPosition['file'], $errorPosition['line']) + 'sourceCode' => self::getAdjacentLines($errorPosition['file'], $errorPosition['line']) ]; /** From ad126f0783de39f939636e7d1610a0ced2e7cab8 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Mon, 5 Apr 2021 18:18:45 +0300 Subject: [PATCH 22/27] fix error handlers --- README.md | 107 +++++++++-------------------------------------- public/index.php | 12 ------ src/Handler.php | 9 +++- tests/Stub.php | 33 --------------- 4 files changed, 27 insertions(+), 134 deletions(-) delete mode 100644 public/index.php delete mode 100644 tests/Stub.php diff --git a/README.md b/README.md index 07d4e05..070a995 100644 --- a/README.md +++ b/README.md @@ -4,115 +4,48 @@ PHP errors Catcher for [Hawk.so](https://hawk.so). ![](https://capella.pics/c0fe5eeb-027d-427a-9e0d-b2e1dcaaf303) -## Usage +## Setup -1. [Register](https://hawk.so/join) an account and get an Integration Token. +1. [Register](https://hawk.so/join) an account and get Integration Token. -2. Install module - -Use [composer](https://getcomposer.org) to install Catcher +2. Install SDK via [composer](https://getcomposer.org) to install Catcher ```bash $ composer require codex-team/hawk.php ``` -3. Use as a [standalone catcher](#standalone-error-catcher) or use with [Monolog](#monolog-support). - -## Standalone error catcher - -Create an instance with Token at the entry point of your project. - -```php -\Hawk\Catcher::instance('abcd1234-1234-abcd-1234-123456abcdef'); -``` - -### Enable handlers - -By default Hawk will catch everything. You can run function with no params. - -```php -\Hawk\Catcher::enableHandlers(); -``` - -It's similar to - -```php -\Hawk\Catcher::enableHandlers( - true, // exceptions - true, // errors - true // shutdown -); -``` - -You can pass types of errors you want to track: - -```php -// Catch run-time warnings or compile-time parse errors -\Hawk\Catcher::enableHandlers( - true, // exceptions - E_WARNING | E_PARSE, // errors - true // shutdown -); -``` +### Configuration ```php -// Catch everything except notices -\Hawk\Catcher::enableHandlers( - true, // exceptions - E_ALL & ~E_NOTICE, // errors - true // shutdown -); +\Hawk\Catcher::init([ + 'integrationToken' => 'your integration token' +]); ``` -### Catch handled exceptions +### Send events and exceptions manually -You can catch exceptions manually with `catchException` method. +Use `sendException` method to send any caught exception ```php try { throw new Exception("Error Processing Request", 1); } catch (Exception $e) { - \Hawk\Catcher::catchException($e); + \Hawk\Catcher::get()->sendException($e); } ``` -## Monolog support - -Add a handler to the Monolog. It will catch errors/exception and ignore general logs. - -```php -$logger = new \Monolog\Logger('hawk-test'); - -$HAWK_TOKEN = 'abcd1234-1234-abcd-1234-123456abcdef'; -$logger->pushHandler(new \Hawk\Monolog\Handler($HAWK_TOKEN, \Monolog\Logger::DEBUG)); -``` - -Now you can use logger's functions to process handled exceptions. Pass it to context array in 'exception' field. - -```php -try { - throw new Exception('Something went wrong'); -} catch (\Exception $e) { - $logger->error($e->getMessage(), ['exception' => $e]); -} -``` - -### Default error catcher - -Register Monolog's handler as catcher. - -```php -/** Set monolog as default error handler */ -$handler = \Monolog\ErrorHandler::register($logger); -``` - -It catches all errors and sends them to Hawk. - -Throwing unhandled error example (without try-catch construction): +Use `sendEvent` method to send any data (logs, notices or something else) ```php -/** Fatal Error: "Just an error in a high quality code" */ -throw new Error('Just an error in a high quality code', E_USER_ERROR); +\Hawk\Catcher::get()->sendEvent([ + 'title' => 'log title', + 'user' => [ + 'name' => 'users name', + ], + 'context' => [ + ... // some extra information + ] +]); ``` ## Issues and improvements diff --git a/public/index.php b/public/index.php deleted file mode 100644 index fa42a64..0000000 --- a/public/index.php +++ /dev/null @@ -1,12 +0,0 @@ - 'token', - 'release' => '123321' -]); - -$stub->test(); diff --git a/src/Handler.php b/src/Handler.php index 06545e6..08c24b6 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -83,6 +83,8 @@ public function catchException(Throwable $exception, array $context = []): void $event = $this->prepareEvent($eventPayload); $this->send($event); + + throw $exception; } /** @@ -92,8 +94,10 @@ public function catchException(Throwable $exception, array $context = []): void * @param string $message * @param string $file * @param int $line + * + * @return bool */ - public function catchError(int $level, string $message, string $file, int $line): void + public function catchError(int $level, string $message, string $file, int $line): bool { $exception = new ErrorException($message, $level, 0, $file, $line); $payload = [ @@ -104,7 +108,8 @@ public function catchError(int $level, string $message, string $file, int $line) $event = $this->prepareEvent($eventPayload); $this->send($event); - throw $exception; + + return false; } /** diff --git a/tests/Stub.php b/tests/Stub.php deleted file mode 100644 index 07792db..0000000 --- a/tests/Stub.php +++ /dev/null @@ -1,33 +0,0 @@ -foo(); - } - - private function foo() - { - $this->bar(); - } - - private function bar() - { -// Catcher::get()->catchEvent([ -// 'context' => [ -// 'header' => '123' -// ], -// 'user' => [ -// 'id' => 11 -// ] -// ]); - $a = 1 / 0; - } -} From 0eaf23c780027bc270d60f9d100a54844a0a3119 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Mon, 5 Apr 2021 19:39:44 +0300 Subject: [PATCH 23/27] add fatal catching constraints --- src/Handler.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Handler.php b/src/Handler.php index 08c24b6..b16c479 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -118,7 +118,10 @@ public function catchError(int $level, string $message, string $file, int $line) public function catchFatal(): void { $error = error_get_last(); - if ($error === null) { + if ( + $error === null + || is_array($error) && $error['type'] && (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_CORE_WARNING | \E_COMPILE_ERROR | \E_COMPILE_WARNING) + ) { return; } From 89792cc222cd19e3ff84cf299fffc481c1a517fb Mon Sep 17 00:00:00 2001 From: Taly Date: Mon, 5 Apr 2021 20:35:14 +0300 Subject: [PATCH 24/27] Create example.php --- example.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 example.php diff --git a/example.php b/example.php new file mode 100644 index 0000000..b6bac14 --- /dev/null +++ b/example.php @@ -0,0 +1,20 @@ + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI2MDY2ZWI5N2U3NTU2ZDAwMjM2M2UyNjYiLCJpYXQiOjE2MTczNTc3MTl9.OpelHPPvS_TB8wUqCHRzcO3-Cp1VNL0UzlFuMfR35tk', + 'release' => '12345', + 'url' => 'http://localhost:3000/' +]); + +$a = 1 / 0; From 7e396b72a8e16d7d2b6459e70a18fa9c4f60636b Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Mon, 5 Apr 2021 20:36:26 +0300 Subject: [PATCH 25/27] common enhancements --- README.md | 10 ++----- src/Catcher.php | 52 ++++++++++++++++++++++++++++++------- src/EventPayloadFactory.php | 26 +++++++++++++++---- src/Handler.php | 8 +++--- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 070a995..05a9b93 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,8 @@ try { Use `sendEvent` method to send any data (logs, notices or something else) ```php -\Hawk\Catcher::get()->sendEvent([ - 'title' => 'log title', - 'user' => [ - 'name' => 'users name', - ], - 'context' => [ - ... // some extra information - ] +\Hawk\Catcher::get()->sendMessage('your message', [ + ... // Context ]); ``` diff --git a/src/Catcher.php b/src/Catcher.php index 7057e47..4355ed3 100644 --- a/src/Catcher.php +++ b/src/Catcher.php @@ -30,6 +30,16 @@ final class Catcher */ private $handler; + /** + * @var array + */ + private $user = []; + + /** + * @var array + */ + private $context = []; + /** * Static method to initialize Catcher * @@ -63,17 +73,39 @@ public static function get(): Catcher } /** - * @param array $payload + * @param array $user * - * @example - * \Hawk\Catcher::get() - * ->catchEvent([ - * 'message' => 'my special message' - * ]) + * @return $this + */ + public function setUser(array $user): self + { + $this->user = $user; + + return $this; + } + + /** + * @param array $context + * + * @return $this + */ + public function setContext(array $context): self + { + $this->context = $context; + + return $this; + } + + /** + * @param string $message + * @param array $context */ - public function sendEvent(array $payload) + public function sendMessage(string $message, array $context = []): void { - $this->handler->catchEvent($payload); + $this->handler->catchEvent([ + 'title' => $message, + 'context' => $context + ]); } /** @@ -88,7 +120,7 @@ public function sendEvent(array $payload) */ public function sendException(Throwable $throwable, array $context = []) { - $this->handler->catchException($throwable); + $this->handler->catchException($throwable, $context); } /** @@ -97,7 +129,7 @@ public function sendException(Throwable $throwable, array $context = []) private function __construct(array $options) { $options = new Options($options); - $factory = new EventPayloadFactory(); + $factory = new EventPayloadFactory($this->user, $this->context); $transport = new CurlTransport($options->getUrl()); $this->handler = new Handler($options, $transport, $factory); diff --git a/src/EventPayloadFactory.php b/src/EventPayloadFactory.php index 62fbaea..818bce2 100644 --- a/src/EventPayloadFactory.php +++ b/src/EventPayloadFactory.php @@ -24,11 +24,27 @@ class EventPayloadFactory */ private $addonsResolvers = []; + /** + * @var array + */ + private $user; + + /** + * @var array + */ + private $context; + /** * EventPayloadFactory constructor. + * + * @param array $user + * @param array $context */ - public function __construct() + public function __construct(array $user, array $context) { + $this->user = $user; + $this->context = $context; + $this->addonsResolvers['runtime'] = new Runtime(); $this->addonsResolvers['server'] = new Os(); $this->addonsResolvers['header'] = new Headers(); @@ -46,12 +62,12 @@ public function create(array $data): EventPayload $eventPayload = new EventPayload(); if (isset($data['context'])) { - $eventPayload->setContext($data['context']); + $eventPayload->setContext(array_merge($this->context, $data['context'])); + } else { + $eventPayload->setContext($this->context); } - if (isset($data['user'])) { - $eventPayload->setUser($data['user']); - } + $eventPayload->setUser($this->user); if (isset($data['exception']) && $data['exception'] instanceof \Throwable) { $exception = $data['exception']; diff --git a/src/Handler.php b/src/Handler.php index b16c479..95dea14 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -74,12 +74,12 @@ public function catchEvent(array $payload): void */ public function catchException(Throwable $exception, array $context = []): void { - $payload = [ + $data = [ 'exception' => $exception, 'context' => $context, ]; - $eventPayload = $this->eventPayloadFactory->create($payload); + $eventPayload = $this->eventPayloadFactory->create($data); $event = $this->prepareEvent($eventPayload); $this->send($event); @@ -100,11 +100,11 @@ public function catchException(Throwable $exception, array $context = []): void public function catchError(int $level, string $message, string $file, int $line): bool { $exception = new ErrorException($message, $level, 0, $file, $line); - $payload = [ + $data = [ 'exception' => $exception ]; - $eventPayload = $this->eventPayloadFactory->create($payload); + $eventPayload = $this->eventPayloadFactory->create($data); $event = $this->prepareEvent($eventPayload); $this->send($event); From 9e7933ddf665c4659025929b64200b7445d3d9d9 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Mon, 5 Apr 2021 20:37:45 +0300 Subject: [PATCH 26/27] php cs fixes --- src/Util/Stacktrace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Util/Stacktrace.php b/src/Util/Stacktrace.php index d8f0d56..bb3be21 100644 --- a/src/Util/Stacktrace.php +++ b/src/Util/Stacktrace.php @@ -105,8 +105,8 @@ public static function buildStack(Throwable $exception): array * Add real error's path to trace chain */ $stack[] = [ - 'file' => $errorPosition['file'], - 'line' => $errorPosition['line'], + 'file' => $errorPosition['file'], + 'line' => $errorPosition['line'], 'sourceCode' => self::getAdjacentLines($errorPosition['file'], $errorPosition['line']) ]; From 8e8787a8e2b6c4877935290b7ab033a92681d162 Mon Sep 17 00:00:00 2001 From: Murod Khaydarov Date: Mon, 5 Apr 2021 20:58:26 +0300 Subject: [PATCH 27/27] update readme --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05a9b93..5f3907a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ PHP errors Catcher for [Hawk.so](https://hawk.so). -![](https://capella.pics/c0fe5eeb-027d-427a-9e0d-b2e1dcaaf303) +![](https://capella.pics/image/4c6e5fee-da7e-4bc5-a898-f19d12acb005) ## Setup @@ -22,6 +22,20 @@ $ composer require codex-team/hawk.php ]); ``` +After initialization you can set `user` or `context` for any event that will be send to Hawk + +```php +\Hawk\Catcher::get() + ->setUser([ + 'name' => 'user name', + 'photo' => 'user photo', + ]) + ->setContext([ + ... + ]); +``` + + ### Send events and exceptions manually Use `sendException` method to send any caught exception