diff --git a/config/larvabug.php b/config/larvabug.php index 276ec99..3c9baf0 100644 --- a/config/larvabug.php +++ b/config/larvabug.php @@ -2,16 +2,41 @@ return [ + /* + * Id of project from larvabug.com + * + */ 'project_id' => env('LB_PROJECT_ID',''), + /* + * Project secret from larvabug.com + * + */ 'project_secret' => env('LB_SECRET',''), + /* + * Array of environments to enable error reporting + * If environment configured in the array will match with the laravel app environment + * then error will be reported to larvabug.com + * + */ 'environment' => ['production','local'], + /* + * Mention any error that should skip from reporting to laravbug + * Must be mentioned as a string + * + */ 'skip_errors' => [ '\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class' ], + /* + * Add request parameters to be black listed + * any parameter defined here will not be reported to larvabug.com and + * all request, session and cookies will be filtered + * + */ 'blacklist' => [ 'password' ] diff --git a/resources/views/analytics.blade.php b/resources/views/analytics.blade.php new file mode 100644 index 0000000..b1c3474 --- /dev/null +++ b/resources/views/analytics.blade.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/resources/views/feedback.blade.php b/resources/views/feedback.blade.php new file mode 100644 index 0000000..b98624a --- /dev/null +++ b/resources/views/feedback.blade.php @@ -0,0 +1,517 @@ + + + 500 Server Error + + + +
+

500 :(

+

Seems Like we have an internal issue, Please tell us more to batter understand the issue

+
+
+
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    +
+
+
+
+
+
+
+
+ + + Powered By: LarvaBug + + + + diff --git a/routes/api.php b/routes/api.php index b3d9bbc..e4df666 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1 +1,4 @@ projectId = $projectId; $this->projectSecret = $projectSecret; @@ -39,30 +49,89 @@ public function report($exceptionData) try { $data_string = json_encode($exceptionData); - $header = [ - 'Content-Type:application/json', - 'Authorization-APP:' . $this->projectId, - 'Authorization-KEY:' . $this->projectSecret - ]; + $result = $this->postRequest($data_string,self::POST_EXCEPTION); - $ch = curl_init(self::URL); + if ($result && + isset($result['status']) && + isset($result['exceptionId']) && + $result['status'] == 200 + ){ + app('larvabug')->setLastExceptionId($result['exceptionId']); + } + + return true; + }catch (\Exception $exception) { + return false; + } + } - curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); - curl_setopt($ch, CURLOPT_HTTPHEADER, $header); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + /** + * Validate env project id and secret + * + * @param array $credentials + * @return bool + * @author Syed Faisal + */ + public function validateCredentials(array $credentials) + { + $result = $this->postRequest(json_encode($credentials),self::VALIDATE_CREDENTIALS); - $result = curl_exec($ch); + if ($result && isset($result['status']) && $result['status'] == 200){ + return true; + } - curl_close($ch); + return false; - if ($result && $result != 404){ - Session::put('lb.lastExceptionId', $result); - } + } + /** + * Curl Request + * + * @param $requestData + * @param $url + * @return bool|mixed + */ + private function postRequest($requestData, $url) + { + $header = [ + 'Content-Type:application/json', + 'Authorization-APP:' . $this->projectId, + 'Authorization-KEY:' . $this->projectSecret + ]; + + $ch = curl_init($url); + + curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($ch); + + curl_close($ch); + + if ($result) { + return json_decode($result, true); + } + + return false; + } + + /** + * Submit last exception feedback + * + * @param $data + * @return bool + */ + public function submitFeedback($data) + { + $result = $this->postRequest(json_encode($data),self::POST_FEEDBACK); + + if ($result && isset($result['status']) && $result['status'] == 200){ return true; - }catch (\Exception $exception) { - return false; } + + return false; + } -} \ No newline at end of file +} diff --git a/src/Commands/LarvaBugTestCommand.php b/src/Commands/LarvaBugTestCommand.php index a43f5ce..b764737 100644 --- a/src/Commands/LarvaBugTestCommand.php +++ b/src/Commands/LarvaBugTestCommand.php @@ -3,6 +3,7 @@ namespace LarvaBug\Commands; use Illuminate\Console\Command; +use LarvaBug\Facade\LarvaBug; class LarvaBugTestCommand extends Command { @@ -39,23 +40,35 @@ public function handle() { $this->info('Testing LarvaBug Configurations'); - if (config('larvabug.project_id')){ + if (env('LB_PROJECT_ID') && !is_null(env('LB_PROJECT_ID'))){ $this->info('1. ✓ [Larvabug] Found project id'); }else{ $this->info('1. ✗ [Larvabug] Could not find your project id, please set this in your .env'); } - if (config('larvabug.project_secret')){ + if (env('LB_SECRET') && !is_null(env('LB_SECRET'))){ $this->info('2. ✓ [Larvabug] Found secret key'); }else{ $this->info('2. ✗ [Larvabug] Could not find LarvaBug secret, please set this in your .env'); } + $requestData = [ + 'projectId' => env('LB_PROJECT_ID'), + 'projectSecret' => env('LB_SECRET') + ]; - } + if (app('larvabug') && app('larvabug')->validateCredentials($requestData)){ + $this->info('3. ✓ [Larvabug] Validation Success'); + }else{ + $this->info('3. ✗ [Larvabug] Project id and secret do not match our records'); + } - public function testCredentials() - { + try{ + throw new \Exception('Larvabug Test Exception'); + }catch (\Exception $exception){ + LarvaBug::report($exception); + } } + } \ No newline at end of file diff --git a/src/Facade/LarvaBug.php b/src/Facade/LarvaBug.php index c48de7d..b64a8af 100644 --- a/src/Facade/LarvaBug.php +++ b/src/Facade/LarvaBug.php @@ -8,6 +8,10 @@ class LarvaBug extends FacadeAlias { + /** + * @return string + * + */ protected static function getFacadeAccessor() { return 'larvabug'; diff --git a/src/Handler/LarvaBugExceptionHandler.php b/src/Handler/LarvaBugExceptionHandler.php index d4f03bf..cf6a418 100644 --- a/src/Handler/LarvaBugExceptionHandler.php +++ b/src/Handler/LarvaBugExceptionHandler.php @@ -4,6 +4,10 @@ namespace LarvaBug\Handler; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Session; +use Illuminate\Support\Facades\URL; use LarvaBug\Client\HttpClient; class LarvaBugExceptionHandler @@ -16,6 +20,10 @@ class LarvaBugExceptionHandler * @var PrepareExceptionData */ private $exceptionData; + /** + * @var string + */ + private $lastExceptionId; /** * LarvaBugExceptionHandler constructor. @@ -31,21 +39,53 @@ public function __construct( $this->exceptionData = $exceptionData; } - public function handle(\Throwable $exception) + /** + * Report exception to larvabug + * + * @param \Throwable $exception + * @return bool + */ + public function report(\Throwable $exception) { - if (!$this->checkAppEnvironment()){ - return false; - } + try { + if (!$this->checkAppEnvironment()) { + return false; + } + + if ($this->skipError(get_class($exception))) { + return false; + } - if ($this->skipError(get_class($exception))){ + $data = $this->exceptionData->prepareException($exception); + + $this->client->report($data); + + return true; + }catch (\Exception $exception){ + Log::info('Lavabug Exception :'.$exception->getMessage()); return false; } + } - $data = $this->exceptionData->prepare($exception); + /** + * Log details to larvabug + * + * @param $message + * @param array $meta + * @return bool + */ + public function log($message, array $meta = []) + { + try { + $data = $this->exceptionData->prepareLogData($message, $meta); - $this->client->report($data); + $this->client->report($data); - return true; + return true; + }catch (\Exception $exception){ + Log::info('Lavabug Exception :'.$exception->getMessage()); + return false; + } } /** @@ -53,7 +93,7 @@ public function handle(\Throwable $exception) * * @return bool */ - public function checkAppEnvironment() + private function checkAppEnvironment() { if (!config('larvabug.environment')){ return false; @@ -72,7 +112,13 @@ public function checkAppEnvironment() return false; } - protected function skipError($class) + /** + * Error dont report, configured in config file + * + * @param $class + * @return bool + */ + private function skipError($class) { if (in_array($class,config('larvabug.skip_errors'))){ return true; @@ -80,4 +126,64 @@ protected function skipError($class) return false; } + + /** + * Validate env credentials from larvabug + * + * @param array $credentials + * @return bool + */ + public function validateCredentials(array $credentials) + { + return $this->client->validateCredentials($credentials); + } + + /** + * Collect error feedback from user + * + * @return \Illuminate\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function collectFeedback() + { + if ($this->lastExceptionId) { + return redirect($this->feedbackUrl().'?exceptionId='.$this->lastExceptionId); + } + + return redirect('/'); + } + + /** + * Get feedback url + * + * @return mixed + * @author Syed Faisal + */ + public function feedbackUrl() + { + return URL::to('larvabug-api/collect/feedback'); + } + + /** + * Submit user collected feedback + * + * @param $data + * @return bool + */ + public function submitFeedback($data) + { + $this->client->submitFeedback($data); + + return true; + } + + public function setLastExceptionId(string $exceptionId) + { + return $this->lastExceptionId = $exceptionId; + } + + public function getLastExceptionId() + { + return $this->lastExceptionId; + } + } \ No newline at end of file diff --git a/src/Handler/PrepareExceptionData.php b/src/Handler/PrepareExceptionData.php index da1c1e3..936f79d 100644 --- a/src/Handler/PrepareExceptionData.php +++ b/src/Handler/PrepareExceptionData.php @@ -27,9 +27,53 @@ public function __construct() * @author Syed Faisal * @param \Throwable $exception */ - public function prepare(\Throwable $exception) + public function prepareException(\Throwable $exception) { - return $this->getExceptionData($exception); + $data = $this->getRequestInfo($exception); + + $data['exception'] = $exception->getMessage(); + $data['class'] = get_class($exception); + $data['file'] = $exception->getFile(); + $data['line'] = $exception->getLine(); + $data['error'] = $exception->getTraceAsString(); + $data['trace_with_details'] = $this->prepareTraceData($exception->getTrace()); + + $count = config('larvabug.lines_count'); + + if (!$count || $count > 12) { + $count = 10; + } + + $lines = file($data['file']); + $data['executor'] = []; + + for ($i = -1 * abs($count); $i <= abs($count); $i++) { + $data['executor'][] = $this->getLineInfo($lines, $data['line'], $i); + } + + $data['executor'] = array_filter($data['executor']); + + // to make symfony exception more readable + if ($data['class'] == 'Symfony\Component\Debug\Exception\FatalErrorException') { + preg_match("~^(.+)' in ~", $data['exception'], $matches); + if (isset($matches[1])) { + $data['exception'] = $matches[1]; + } + } + + return $data; + } + + public function prepareLogData(string $message,array $meta = []) + { + $data = $this->getRequestInfo(); + $data['exception'] = $message; + $data['class'] = 'Log Information'; + $data['type'] = 'log'; + $data['meta_data'] = $meta; + + return $data; + } /** @@ -38,22 +82,23 @@ public function prepare(\Throwable $exception) * @param \Throwable $exception * @return array */ - private function getExceptionData(\Throwable $exception) + private function getRequestInfo() { $data = []; - $data['exception'] = $exception->getMessage(); - $data['class'] = get_class($exception); - $data['file'] = $exception->getFile(); - $data['enviroment'] = App::environment(); + $data['php_version'] = PHP_VERSION; + $data['server_ip'] = $_SERVER['SERVER_ADDR'] ?? null; + $data['environment'] = App::environment(); $data['server_name'] = @gethostname(); $data['browser'] = $this->getUserBrowser(); $data['userOs'] = $this->getUserOS(); $data['host'] = Request::server('SERVER_NAME'); $data['method'] = Request::method(); $data['fullUrl'] = Request::fullUrl(); - $data['line'] = $exception->getLine(); - $data['date_time'] = date("Y-m-d H:i:s");; + $data['url'] = Request::path(); + $data['userIp'] = Request::ip(); + $data['date_time'] = date("Y-m-d H:i:s"); + $data['session_id'] = Session::getId(); $data['storage'] = [ 'SERVER' => [ 'USER' => Request::server('USER'), @@ -71,34 +116,8 @@ private function getExceptionData(\Throwable $exception) 'HEADERS' => $this->filterBlackList(Request::header()), ]; $data['auth_user'] = $this->getAuthUser(); - $data['error'] = $exception->getTraceAsString(); - $data['trace_with_details'] = $this->prepareTraceData($exception->getTrace()); - $data['storage'] = array_filter($data['storage']); - $count = config('larvabug.lines_count'); - - if (!$count || $count > 12) { - $count = 5; - } - - $lines = file($data['file']); - $data['executor'] = []; - - for ($i = -1 * abs($count); $i <= abs($count); $i++) { - $data['executor'][] = $this->getLineInfo($lines, $data['line'], $i); - } - - $data['executor'] = array_filter($data['executor']); - - // to make symfony exception more readable - if ($data['class'] == 'Symfony\Component\Debug\Exception\FatalErrorException') { - preg_match("~^(.+)' in ~", $data['exception'], $matches); - if (isset($matches[1])) { - $data['exception'] = $matches[1]; - } - } - return $data; } @@ -156,6 +175,11 @@ private function prepareTraceData($trace): array return $response; } + /** + * get auth user if exists + * + * @return |null + */ private function getAuthUser() { if (function_exists('auth') && auth()->check()) { diff --git a/src/Http/Controllers/LarvaBugMainController.php b/src/Http/Controllers/LarvaBugMainController.php new file mode 100644 index 0000000..d7c20a0 --- /dev/null +++ b/src/Http/Controllers/LarvaBugMainController.php @@ -0,0 +1,37 @@ +submitFeedback($data); + + return Redirect::to('/'); + } + + /** + * Collect feedback view + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\View\View + */ + public function postFeedback() + { + return view('larvabug::feedback'); + } + +} \ No newline at end of file diff --git a/src/Provider/BootServices.php b/src/Provider/BootServices.php index ee20ad6..381356e 100644 --- a/src/Provider/BootServices.php +++ b/src/Provider/BootServices.php @@ -4,27 +4,53 @@ namespace LarvaBug\Provider; +use Illuminate\Support\Facades\Route; use LarvaBug\Commands\LarvaBugTestCommand; trait BootServices { + /** + * List of commands to be registered along with provider + * + * @var string[] + */ protected $commands = [ LarvaBugTestCommand::class ]; - protected function bootServices() + /** + * BootServices method contains all service that needs to be registered + */ + private function bootServices() { $this->publishConfig(); $this->registerView(); $this->registerCommands(); + $this->mapLaraBugApiRoutes(); } - protected function registerView() + /** + * Register view directory with larvabug namespace + */ + private function registerView() { - $this->app['view']->addNamespace('larvabug',__DIR__.'../../resources/views'); + $this->app['view']->addNamespace('larvabug',__DIR__.'/../../resources/views'); } - protected function publishConfig() + /** + * Map api routes directory to enable all api routes for larvabug + */ + private function mapLaraBugApiRoutes() + { + Route::namespace('\LarvaBug\Http\Controllers') + ->prefix('larvabug-api') + ->group(__DIR__ . '/../../routes/api.php'); + } + + /** + * Publish package config files that contains package configurations + */ + private function publishConfig() { if (function_exists('config_path')) { $this->publishes([ @@ -33,7 +59,10 @@ protected function publishConfig() } } - public function registerCommands() + /** + * Register array of commands + */ + private function registerCommands() { $this->commands($this->commands); } diff --git a/src/Provider/ServiceProvider.php b/src/Provider/ServiceProvider.php index 0e846cb..3eb05dd 100644 --- a/src/Provider/ServiceProvider.php +++ b/src/Provider/ServiceProvider.php @@ -13,11 +13,17 @@ class ServiceProvider extends BaseServiceProvider { use BootServices; + /** + * boot method of service provider + */ public function boot() { $this->bootServices(); } + /** + * Register method of service provider + */ public function register() { $this->app->singleton('larvabug',function ($app){