├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── error-share.php ├── resources └── views │ └── components │ ├── error-share.blade.php │ └── navigation.blade.php └── src ├── Actions ├── MapLaravelExceptionAction.php ├── MapLaravelStackTraceAction.php └── ResolveErrorSharePropertiesAction.php └── LaravelErrorShareServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-error-share` will be documented in this file. 4 | 5 | ## 1.0.3 - 2025-02-28 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-error-share/pull/3 10 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-error-share/pull/4 11 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/spatie/laravel-error-share/pull/5 12 | 13 | ### New Contributors 14 | 15 | * @dependabot made their first contribution in https://github.com/spatie/laravel-error-share/pull/3 16 | 17 | **Full Changelog**: https://github.com/spatie/laravel-error-share/compare/1.0.2...1.0.3 18 | 19 | ## 1.0.2 - 2024-06-21 20 | 21 | - Fix an issue where a Laravel install could not be optimized anymore 22 | 23 | **Full Changelog**: https://github.com/spatie/laravel-error-share/compare/1.0.1...1.0.2 24 | 25 | ## 1.0.1 - 2024-06-07 26 | 27 | ### What's Changed 28 | 29 | * Add --dev to installation instruction by @riasvdv in https://github.com/spatie/laravel-error-share/pull/2 30 | * Add dev as a tag by @riasvdv in https://github.com/spatie/laravel-error-share/pull/1 31 | 32 | ### New Contributors 33 | 34 | * @riasvdv made their first contribution in https://github.com/spatie/laravel-error-share/pull/2 35 | 36 | **Full Changelog**: https://github.com/spatie/laravel-error-share/compare/1.0.0...1.0.1 37 | 38 | ## 1.0.0 - 2024-06-07 39 | 40 | **Full Changelog**: https://github.com/spatie/laravel-error-share/compare/0.0.1...1.0.0 41 | 42 | ## 0.0.1 - 2024-06-05 43 | 44 | **Full Changelog**: https://github.com/spatie/laravel-error-share/commits/0.0.1 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Share your Laravel errors 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-error-share.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-error-share) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-error-share/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/spatie/laravel-error-share/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-error-share/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/spatie/laravel-error-share/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-error-share.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-error-share) 7 | 8 | Imagine that you’re working on a project locally and that you bump into an exception. You try to figure out what the problem is, but you’re unable to find a solution. In that case, you might want to ask a colleague for help. 9 | 10 | Instead of immediately start screen sharing with your colleague (or letting your colleague roll their chair to your desk if you work in office), it might be handy to just send a link to your colleague where he or she can see that exception in detail. 11 | 12 | ![Screenshot of Laravel Error Share](https://github.com/spatie/laravel-error-share/assets/1561079/99cc3fdb-b1df-4b14-a89f-229bbe79dabc) 13 | 14 | ## Support us 15 | 16 | [](https://spatie.be/github-ad-click/laravel-error-share) 17 | 18 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 19 | 20 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 21 | 22 | ## Installation 23 | 24 | You can install the package via composer: 25 | 26 | ```bash 27 | composer require spatie/laravel-error-share --dev 28 | ``` 29 | 30 | That's it. Trigger an error and you'll see a share button. 31 | 32 | ## Testing 33 | 34 | ```bash 35 | composer test 36 | ``` 37 | 38 | ## Changelog 39 | 40 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 41 | 42 | ## Contributing 43 | 44 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 45 | 46 | ## Security Vulnerabilities 47 | 48 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 49 | 50 | ## Credits 51 | 52 | - [Ruben Van Assche](https://github.com/rubenvanassche) 53 | - [All Contributors](../../contributors) 54 | 55 | ## License 56 | 57 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-error-share", 3 | "description": "Share your Laravel errors to Flare", 4 | "keywords": [ 5 | "Spatie", 6 | "laravel", 7 | "flare", 8 | "errors", 9 | "laravel-error-share", 10 | "dev" 11 | ], 12 | "homepage": "https://github.com/spatie/laravel-error-share", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Ruben Van Assche", 17 | "email": "ruben@spatie.be", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": "^8.2", 23 | "illuminate/contracts": "^11.10|^12.0", 24 | "spatie/laravel-package-tools": "^1.16" 25 | }, 26 | "require-dev": { 27 | "larastan/larastan": "^2.9|^3.0", 28 | "laravel/pint": "^1.14", 29 | "nunomaduro/collision": "^8.1.1|^7.10.0", 30 | "orchestra/testbench": "^10.0|^9.0|^8.22", 31 | "pestphp/pest": "^2.34|^3.0", 32 | "pestphp/pest-plugin-arch": "^2.7|^3.0", 33 | "pestphp/pest-plugin-laravel": "^2.3|^3.0", 34 | "phpstan/extension-installer": "^1.3", 35 | "phpstan/phpstan-deprecation-rules": "^1.1|^2.0", 36 | "phpstan/phpstan-phpunit": "^1.3|^2.0", 37 | "spatie/pest-plugin-snapshots": "^2.1" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Spatie\\LaravelErrorShare\\": "src/", 42 | "Spatie\\LaravelErrorShare\\Database\\Factories\\": "database/factories/" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Spatie\\LaravelErrorShare\\Tests\\": "tests/", 48 | "Workbench\\App\\": "workbench/app/" 49 | } 50 | }, 51 | "scripts": { 52 | "post-autoload-dump": "@composer run prepare", 53 | "clear": "@php vendor/bin/testbench package:purge-laravel-error-share --ansi", 54 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 55 | "build": [ 56 | "@composer run prepare", 57 | "@php vendor/bin/testbench workbench:build --ansi" 58 | ], 59 | "start": [ 60 | "Composer\\Config::disableProcessTimeout", 61 | "@composer run build", 62 | "@php vendor/bin/testbench serve" 63 | ], 64 | "analyse": "vendor/bin/phpstan analyse", 65 | "test": "vendor/bin/pest", 66 | "test-coverage": "vendor/bin/pest --coverage", 67 | "format": "vendor/bin/pint" 68 | }, 69 | "config": { 70 | "sort-packages": true, 71 | "allow-plugins": { 72 | "pestphp/pest-plugin": true, 73 | "phpstan/extension-installer": true 74 | } 75 | }, 76 | "extra": { 77 | "laravel": { 78 | "providers": [ 79 | "Spatie\\LaravelErrorShare\\LaravelErrorShareServiceProvider" 80 | ], 81 | "aliases": { 82 | "LaravelErrorShare": "Spatie\\LaravelErrorShare\\Facades\\LaravelErrorShare" 83 | } 84 | } 85 | }, 86 | "minimum-stability": "dev", 87 | "prefer-stable": true 88 | } 89 | -------------------------------------------------------------------------------- /config/error-share.php: -------------------------------------------------------------------------------- 1 | true, 8 | 9 | /** 10 | * This is the URL where the error will be sent to. 11 | * 12 | * In most cases, you will not need to change this value. 13 | */ 14 | 'endpoint' => env('ERROR_SHARE_ENDPOINT', 'https://flareapp.io/'), 15 | ]; 16 | -------------------------------------------------------------------------------- /resources/views/components/error-share.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $properties = app(\Spatie\LaravelErrorShare\Actions\ResolveErrorSharePropertiesAction::class)->execute($exception); 3 | @endphp 4 | @unless(array_key_exists('error', $properties) || config('error-share.enabled') === false) 5 |
108 | 109 | 122 | 123 | 124 | 178 |
179 | @endunless 180 | -------------------------------------------------------------------------------- /resources/views/components/navigation.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 14 | 15 | 16 |
17 | 18 | 19 | {{ $exception->title() }} 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /src/Actions/MapLaravelExceptionAction.php: -------------------------------------------------------------------------------- 1 | 'laravel-error-share', 18 | 'exception_class' => $exception->class(), 19 | 'seen_at' => (new DateTimeImmutable)->getTimestamp(), 20 | 'application_path' => app_path(), 21 | 'message' => $exception->message(), 22 | 'language' => 'PHP', 23 | 'language_version' => phpversion(), 24 | 'framework_version' => app()->version(), 25 | 'application_version' => null, 26 | 'stage' => app()->environment(), 27 | 'message_level' => null, 28 | 'open_frame_index' => $exception->defaultFrame(), 29 | 'glows' => [], 30 | 'solutions' => [], 31 | 'context' => $this->resolveContext($exception), 32 | 'stacktrace' => $this->mapLaravelStackTraceAction->execute($exception), 33 | 'documentation_links' => [], 34 | 'tracking_uuid' => null, 35 | 'handled' => null, 36 | ]; 37 | } 38 | 39 | protected function resolveContext(Exception $exception): array 40 | { 41 | return [ 42 | 'env' => [ 43 | 'laravel_version' => app()->version(), 44 | 'laravel_locale' => app()->getLocale(), 45 | 'laravel_config_cached' => app()->configurationIsCached(), 46 | 'app_debug' => config('app.debug'), 47 | 'app_env' => config('app.env'), 48 | 'php_version' => phpversion(), 49 | ], 50 | 'route' => $this->mapRoute($exception), 51 | 'request' => [ 52 | 'url' => $exception->request()->getUri(), 53 | 'ip' => $exception->request()->getClientIp(), 54 | 'method' => $exception->request()->getMethod(), 55 | 'useragent' => $exception->request()->headers->get('User-Agent'), 56 | ], 57 | 'request_data' => [ 58 | 'body' => $exception->request()->all(), 59 | ], 60 | 'headers' => $exception->requestHeaders(), 61 | 'queries' => $this->mapQueries($exception), 62 | ]; 63 | } 64 | 65 | protected function mapRoute(Exception $exception): array 66 | { 67 | $route = []; 68 | 69 | $routeData = $exception->applicationRouteContext(); 70 | 71 | if (array_key_exists('controller', $routeData)) { 72 | $route['controllerAction'] = $routeData['controller']; 73 | } 74 | 75 | if (array_key_exists('route name', $routeData)) { 76 | $route['route'] = $routeData['route name']; 77 | } 78 | 79 | if (array_key_exists('middleware', $routeData)) { 80 | $route['middleware'] = $exception->request()->route()->gatherMiddleware(); 81 | } 82 | 83 | if ($routeParams = $exception->applicationRouteParametersContext()) { 84 | $route['routeParameters'] = json_decode($routeParams, associative: true); 85 | } 86 | 87 | return $route; 88 | } 89 | 90 | protected function mapQueries(Exception $exception): array 91 | { 92 | $queries = []; 93 | 94 | foreach ($exception->applicationQueries() as $query) { 95 | $queries[] = [ 96 | 'sql' => $query['sql'], 97 | 'time' => $query['time'], 98 | 'connection_name' => $query['connectionName'], 99 | 'bindings' => [], 100 | 'microtime' => null, 101 | ]; 102 | } 103 | 104 | return $queries; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Actions/MapLaravelStackTraceAction.php: -------------------------------------------------------------------------------- 1 | frames(); 18 | $frames = []; 19 | 20 | for ($i = 0; $i < count($laravelFrames); $i++) { 21 | if ($this->maxFrames !== null && $i >= $this->maxFrames) { 22 | break; 23 | } 24 | 25 | $frames[] = [ 26 | 'line_number' => $laravelFrames[$i]->line(), 27 | 'method' => ($laravelFrames[$i + 1] ?? null)?->callable() ?? '/', 28 | 'class' => $laravelFrames[$i]->class(), 29 | 'code_snippet' => $this->mapSnippet($laravelFrames[$i]), 30 | 'file' => $laravelFrames[$i]->file(), 31 | 'is_application_frame' => ! $laravelFrames[$i]->isFromVendor(), 32 | ]; 33 | } 34 | 35 | return $frames; 36 | } 37 | 38 | protected function mapSnippet(Frame $frame): array 39 | { 40 | $from = max($frame->line() - 5, 1); 41 | 42 | $lines = []; 43 | 44 | $spacesToRemove = PHP_INT_MAX; 45 | 46 | foreach (explode(PHP_EOL, $frame->snippet()) as $i => $line) { 47 | $lines[$from + $i] = $line; 48 | 49 | preg_match('/^(\s*)/', $line, $matches); 50 | $initialSpacesCount = strlen($matches[0]); 51 | 52 | if ($initialSpacesCount < $spacesToRemove) { 53 | $spacesToRemove = $initialSpacesCount; 54 | } 55 | } 56 | 57 | if ($spacesToRemove === PHP_INT_MAX) { 58 | return $lines; 59 | } 60 | 61 | foreach ($lines as $i => $line) { 62 | $lines[$i] = substr($line, $spacesToRemove); 63 | } 64 | 65 | return $lines; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Actions/ResolveErrorSharePropertiesAction.php: -------------------------------------------------------------------------------- 1 | laravelExceptionAction->execute($exception); 20 | 21 | return [ 22 | 'url' => Str::of(config('error-share.endpoint'))->rtrim('/')->append('/api/public-reports'), 23 | 'report' => $report, 24 | ]; 25 | } catch (Throwable $exception) { 26 | return [ 27 | 'error' => $exception->getMessage(), 28 | ]; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LaravelErrorShareServiceProvider.php: -------------------------------------------------------------------------------- 1 | canIncludeViews()) { 14 | View::prependNamespace('laravel-exceptions-renderer', [__DIR__.'/../resources/views']); 15 | } 16 | } 17 | 18 | public function configurePackage(Package $package): void 19 | { 20 | $package 21 | ->name('laravel-error-share') 22 | ->hasConfigFile(); 23 | 24 | if ($this->canIncludeViews()) { 25 | $package->hasViews(); 26 | } 27 | } 28 | 29 | protected function canIncludeViews(): bool 30 | { 31 | // Otherwise php artisan optimize may crash 32 | return config('app.debug') === true; 33 | } 34 | } 35 | --------------------------------------------------------------------------------