chore: install laravel telescope as dev-only debugging dashboard
Installs laravel/telescope ^5.0 (v5.12.5) as a dev-dependency.
Three-layer production safety adapted to Laravel 11 layout (no
Kernel.php; routing/schedule in bootstrap/app.php +
routes/console.php):
1. composer.json `extra.laravel.dont-discover` lists
laravel/telescope. After editing, `php artisan package:discover`
regenerates bootstrap/cache/packages.php — without this step
the auto-discovery cache still registers the vendor provider.
2. AppServiceProvider::register() gates registration to local +
testing environments. Registers BOTH the vendor
Laravel\Telescope\TelescopeServiceProvider (routes, migrations,
publishing) AND the project's App\Providers\TelescopeService
Provider (gate + filter) — they're sibling classes that extend
ServiceProvider independently, not parent/child, so both must
register for the dashboard to work. bootstrap/providers.php
deliberately does NOT list either Telescope provider.
3. .env TELESCOPE_ENABLED flag (false in .env.example). Runtime
toggle that disables Telescope even when the providers are
registered.
Production safety verified via simulated APP_ENV=production check:
confirms no Telescope-* providers are loaded.
Authorization: viewTelescope gate restricts dashboard to users
with the super_admin Spatie Permission role. Even in local
environments, only super_admin can view. Default was an email
allow-list stub — replaced with `$user->hasRole('super_admin')`.
Pruning: Schedule::command('telescope:prune --hours=48') added in
routes/console.php (Laravel 11's schedule location), environment-
gated to local + testing only.
Documentation: /dev-docs/TELESCOPE.md added; CLAUDE.md gets a
Development-tooling section. The doc explicitly calls out the
dual-provider registration (vendor + app) which differs from the
single-provider pattern in older Laravel versions.
Migrations applied: telescope_entries, telescope_entries_tags,
telescope_monitoring tables. Route registration verified in local
(42 telescope.* routes).
Tests: 1208/1208 passing — Telescope loads in the testing
environment as well, so the suite exercised it without issues.
Deployment note (flag for separate docs): a production operator
who runs `php artisan migrate` manually will still apply the
Telescope migrations — but because the providers never register
in production, the tables stay empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,12 @@ Design document: `/dev-docs/design-document.md`
|
||||
See `/dev-docs/FRONTEND-TOOLING.md`. New TypeScript code adheres
|
||||
to ts-reset's stricter types automatically.
|
||||
|
||||
## Development tooling
|
||||
|
||||
- Laravel Telescope at `/telescope` — queries, jobs, mails, redis,
|
||||
events. Local + testing only, never production. super_admin role
|
||||
required to view. See `/dev-docs/TELESCOPE.md`.
|
||||
|
||||
## Repository layout
|
||||
|
||||
- `api/` — Laravel backend
|
||||
|
||||
@@ -71,3 +71,10 @@ SANCTUM_STATEFUL_DOMAINS=localhost:5174,localhost:5175
|
||||
# FRONTEND_APP_URL=https://crewli.app
|
||||
# FRONTEND_PORTAL_URL=https://portal.crewli.app
|
||||
# SANCTUM_STATEFUL_DOMAINS=crewli.app,portal.crewli.app
|
||||
|
||||
# Laravel Telescope — dev-only debugging dashboard at /telescope.
|
||||
# Flip to true in your local .env. Production MUST keep this false;
|
||||
# the three-layer safety (composer dont-discover + AppServiceProvider
|
||||
# env-gate + this flag) keeps Telescope out even if one layer is
|
||||
# breached. See /dev-docs/TELESCOPE.md.
|
||||
TELESCOPE_ENABLED=false
|
||||
|
||||
@@ -84,6 +84,19 @@ class AppServiceProvider extends ServiceProvider
|
||||
);
|
||||
|
||||
$this->app->singleton(PurposeRegistry::class);
|
||||
|
||||
// Telescope is a dev-only debugging dashboard. Three-layer
|
||||
// defense keeps it out of production: composer `dont-discover`
|
||||
// suppresses auto-registration, this block gates manual
|
||||
// registration to local/testing, and TELESCOPE_ENABLED in .env
|
||||
// is the runtime toggle (see /dev-docs/TELESCOPE.md). The vendor
|
||||
// service provider registers routes/migrations/publishing;
|
||||
// App\Providers\TelescopeServiceProvider attaches the gate +
|
||||
// filter. Both must register for the dashboard to work.
|
||||
if ($this->app->environment('local', 'testing')) {
|
||||
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
|
||||
$this->app->register(\App\Providers\TelescopeServiceProvider::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
|
||||
63
api/app/Providers/TelescopeServiceProvider.php
Normal file
63
api/app/Providers/TelescopeServiceProvider.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Telescope\IncomingEntry;
|
||||
use Laravel\Telescope\Telescope;
|
||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
|
||||
|
||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Telescope::night();
|
||||
|
||||
$this->hideSensitiveRequestDetails();
|
||||
|
||||
$isLocal = $this->app->environment('local');
|
||||
|
||||
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
|
||||
return $isLocal ||
|
||||
$entry->isReportableException() ||
|
||||
$entry->isFailedRequest() ||
|
||||
$entry->isFailedJob() ||
|
||||
$entry->isScheduledTask() ||
|
||||
$entry->hasMonitoredTag();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sensitive request details from being logged by Telescope.
|
||||
*/
|
||||
protected function hideSensitiveRequestDetails(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telescope::hideRequestParameters(['_token']);
|
||||
|
||||
Telescope::hideRequestHeaders([
|
||||
'cookie',
|
||||
'x-csrf-token',
|
||||
'x-xsrf-token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Telescope gate.
|
||||
*
|
||||
* This gate determines who can access Telescope in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewTelescope', function (?User $user) {
|
||||
return $user !== null && $user->hasRole('super_admin');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
// TelescopeServiceProvider is registered conditionally in
|
||||
// AppServiceProvider::register() (local + testing environments only).
|
||||
// See /dev-docs/TELESCOPE.md for the three-layer production safety.
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
"laravel/telescope": "^5.0",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^11.5.3",
|
||||
@@ -85,7 +86,9 @@
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
"dont-discover": [
|
||||
"laravel/telescope"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
||||
127
api/composer.lock
generated
127
api/composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0bf117afd2eabe5e7f1c5576276666cb",
|
||||
"content-hash": "78c21fb00a5a2db68ad60afeb62382b9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -8141,6 +8141,131 @@
|
||||
},
|
||||
"time": "2025-12-09T13:33:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sentinel",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sentinel.git",
|
||||
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sentinel/zipball/972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/container": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.27",
|
||||
"orchestra/testbench": "^6.47.1|^7.56|^8.37|^9.16|^10.9|^11.0",
|
||||
"phpstan/phpstan": "^2.1.33"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sentinel\\SentinelServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sentinel\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mior Muhammad Zaki",
|
||||
"email": "mior@laravel.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/laravel/sentinel/tree/v1.1.0"
|
||||
},
|
||||
"time": "2026-03-24T14:03:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/telescope",
|
||||
"version": "v5.20.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/telescope.git",
|
||||
"reference": "38ec6e6006a67e05e0c476c5f8ef3550b72e43d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/38ec6e6006a67e05e0c476c5f8ef3550b72e43d8",
|
||||
"reference": "38ec6e6006a67e05e0c476c5f8ef3550b72e43d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
|
||||
"laravel/sentinel": "^1.0",
|
||||
"php": "^8.0",
|
||||
"symfony/console": "^5.3|^6.0|^7.0|^8.0",
|
||||
"symfony/var-dumper": "^5.0|^6.0|^7.0|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"laravel/octane": "^1.4|^2.0",
|
||||
"orchestra/testbench": "^6.47.1|^7.55|^8.36|^9.15|^10.8|^11.0",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Telescope\\TelescopeServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Telescope\\": "src/",
|
||||
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mohamed Said",
|
||||
"email": "mohamed@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "An elegant debug assistant for the Laravel framework.",
|
||||
"keywords": [
|
||||
"debugging",
|
||||
"laravel",
|
||||
"monitoring"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/telescope/issues",
|
||||
"source": "https://github.com/laravel/telescope/tree/v5.20.0"
|
||||
},
|
||||
"time": "2026-04-06T12:52:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.6.12",
|
||||
|
||||
212
api/config/telescope.php
Normal file
212
api/config/telescope.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Telescope\Http\Middleware\Authorize;
|
||||
use Laravel\Telescope\Watchers;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Master Switch
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option may be used to disable all Telescope watchers regardless
|
||||
| of their individual configuration, which simply provides a single
|
||||
| and convenient way to enable or disable Telescope data storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'enabled' => env('TELESCOPE_ENABLED', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the subdomain where Telescope will be accessible from. If the
|
||||
| setting is null, Telescope will reside under the same domain as the
|
||||
| application. Otherwise, this value will be used as the subdomain.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('TELESCOPE_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the URI path where Telescope will be accessible from. Feel free
|
||||
| to change this path to anything you like. Note that the URI will not
|
||||
| affect the paths of its internal API that aren't exposed to users.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('TELESCOPE_PATH', 'telescope'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Storage Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration options determines the storage driver that will
|
||||
| be used to store Telescope's data. In addition, you may set any
|
||||
| custom options as needed by the particular driver you choose.
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('TELESCOPE_DRIVER', 'database'),
|
||||
|
||||
'storage' => [
|
||||
'database' => [
|
||||
'connection' => env('DB_CONNECTION', 'mysql'),
|
||||
'chunk' => 1000,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Queue
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration options determines the queue connection and queue
|
||||
| which will be used to process ProcessPendingUpdate jobs. This can
|
||||
| be changed if you would prefer to use a non-default connection.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => [
|
||||
'connection' => env('TELESCOPE_QUEUE_CONNECTION'),
|
||||
'queue' => env('TELESCOPE_QUEUE'),
|
||||
'delay' => env('TELESCOPE_QUEUE_DELAY', 10),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will be assigned to every Telescope route, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'web',
|
||||
Authorize::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed / Ignored Paths & Commands
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following array lists the URI paths and Artisan commands that will
|
||||
| not be watched by Telescope. In addition to this list, some Laravel
|
||||
| commands, like migrations and queue commands, are always ignored.
|
||||
|
|
||||
*/
|
||||
|
||||
'only_paths' => [
|
||||
// 'api/*'
|
||||
],
|
||||
|
||||
'ignore_paths' => [
|
||||
'livewire*',
|
||||
'nova-api*',
|
||||
'pulse*',
|
||||
'_boost*',
|
||||
'.well-known*',
|
||||
],
|
||||
|
||||
'ignore_commands' => [
|
||||
//
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Watchers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following array lists the "watchers" that will be registered with
|
||||
| Telescope. The watchers gather the application's profile data when
|
||||
| a request or task is executed. Feel free to customize this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'watchers' => [
|
||||
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
|
||||
|
||||
Watchers\CacheWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
|
||||
'hidden' => [],
|
||||
'ignore' => [],
|
||||
],
|
||||
|
||||
Watchers\ClientRequestWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
|
||||
'ignore_hosts' => [],
|
||||
],
|
||||
|
||||
Watchers\CommandWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
|
||||
'ignore' => [],
|
||||
],
|
||||
|
||||
Watchers\DumpWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
|
||||
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
|
||||
],
|
||||
|
||||
Watchers\EventWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
|
||||
'ignore' => [],
|
||||
],
|
||||
|
||||
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
|
||||
|
||||
Watchers\GateWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_GATE_WATCHER', true),
|
||||
'ignore_abilities' => [],
|
||||
'ignore_packages' => true,
|
||||
'ignore_paths' => [],
|
||||
],
|
||||
|
||||
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true),
|
||||
|
||||
Watchers\LogWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
|
||||
'level' => 'error',
|
||||
],
|
||||
|
||||
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true),
|
||||
|
||||
Watchers\ModelWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
|
||||
'events' => ['eloquent.*'],
|
||||
'hydrations' => true,
|
||||
],
|
||||
|
||||
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true),
|
||||
|
||||
Watchers\QueryWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
|
||||
'ignore_packages' => true,
|
||||
'ignore_paths' => [],
|
||||
'slow' => 100,
|
||||
],
|
||||
|
||||
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
|
||||
|
||||
Watchers\RequestWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
|
||||
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
|
||||
'ignore_http_methods' => [],
|
||||
'ignore_status_codes' => [],
|
||||
],
|
||||
|
||||
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
|
||||
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Get the migration connection name.
|
||||
*/
|
||||
public function getConnection(): ?string
|
||||
{
|
||||
return config('telescope.storage.database.connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$schema = Schema::connection($this->getConnection());
|
||||
|
||||
$schema->create('telescope_entries', function (Blueprint $table) {
|
||||
$table->bigIncrements('sequence');
|
||||
$table->uuid('uuid');
|
||||
$table->uuid('batch_id');
|
||||
$table->string('family_hash')->nullable();
|
||||
$table->boolean('should_display_on_index')->default(true);
|
||||
$table->string('type', 20);
|
||||
$table->longText('content');
|
||||
$table->dateTime('created_at')->nullable();
|
||||
|
||||
$table->unique('uuid');
|
||||
$table->index('batch_id');
|
||||
$table->index('family_hash');
|
||||
$table->index('created_at');
|
||||
$table->index(['type', 'should_display_on_index']);
|
||||
});
|
||||
|
||||
$schema->create('telescope_entries_tags', function (Blueprint $table) {
|
||||
$table->uuid('entry_uuid');
|
||||
$table->string('tag');
|
||||
|
||||
$table->primary(['entry_uuid', 'tag']);
|
||||
$table->index('tag');
|
||||
|
||||
$table->foreign('entry_uuid')
|
||||
->references('uuid')
|
||||
->on('telescope_entries')
|
||||
->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('telescope_monitoring', function (Blueprint $table) {
|
||||
$table->string('tag')->primary();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$schema = Schema::connection($this->getConnection());
|
||||
|
||||
$schema->dropIfExists('telescope_entries_tags');
|
||||
$schema->dropIfExists('telescope_entries');
|
||||
$schema->dropIfExists('telescope_monitoring');
|
||||
}
|
||||
};
|
||||
@@ -9,3 +9,10 @@ Artisan::command('inspire', function () {
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
Schedule::command('invitations:expire')->daily();
|
||||
|
||||
// Telescope retention — dev-only (mirrors AppServiceProvider's
|
||||
// environment gate). 48h is enough for debugging without filling the
|
||||
// dev database.
|
||||
Schedule::command('telescope:prune --hours=48')
|
||||
->daily()
|
||||
->environments(['local', 'testing']);
|
||||
|
||||
101
dev-docs/TELESCOPE.md
Normal file
101
dev-docs/TELESCOPE.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Laravel Telescope (dev-only)
|
||||
|
||||
Local debugging dashboard for queries, jobs, mails, redis, events,
|
||||
exceptions. **Never registered in production.**
|
||||
|
||||
## Access
|
||||
|
||||
- Local URL: <http://localhost:8000/telescope>
|
||||
- Auth requirement: authenticated user with `super_admin` role
|
||||
(Spatie Permission). The `viewTelescope` gate is the access
|
||||
control; even in `local`, only super_admin can view.
|
||||
|
||||
## Production safety — three-layer defense
|
||||
|
||||
Crewli runs Laravel 11 (no `Kernel.php`; routing/schedule live in
|
||||
`bootstrap/app.php` + `routes/console.php`). The defense layers
|
||||
adapted to that layout are:
|
||||
|
||||
1. **`composer.json` `extra.laravel.dont-discover`** lists
|
||||
`laravel/telescope`, so Laravel's auto-discovery never registers
|
||||
the vendor `Laravel\Telescope\TelescopeServiceProvider`. After
|
||||
editing this list, run `php artisan package:discover` once to
|
||||
refresh `bootstrap/cache/packages.php`.
|
||||
2. **`AppServiceProvider::register()`** gates manual registration
|
||||
to `local` + `testing` only, and registers BOTH the vendor
|
||||
provider (routes/migrations/publishing) and the project's
|
||||
`App\Providers\TelescopeServiceProvider` (gate + filter):
|
||||
```php
|
||||
if ($this->app->environment('local', 'testing')) {
|
||||
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
|
||||
$this->app->register(\App\Providers\TelescopeServiceProvider::class);
|
||||
}
|
||||
```
|
||||
`bootstrap/providers.php` deliberately does NOT list either
|
||||
Telescope provider — both registrations live behind this
|
||||
environment gate.
|
||||
3. **`.env` `TELESCOPE_ENABLED` flag.** `false` in `.env.example`.
|
||||
Runtime toggle that disables Telescope even when the providers
|
||||
are registered (e.g. to silence Telescope locally during a
|
||||
profiling session).
|
||||
|
||||
If you ever see Telescope on production: revert immediately,
|
||||
audit the three layers above, and treat as a security incident.
|
||||
The dashboard exposes every query, payload, and job — including
|
||||
secrets in payloads.
|
||||
|
||||
### Verifying production safety
|
||||
|
||||
```bash
|
||||
APP_ENV=production php -r "
|
||||
require 'vendor/autoload.php';
|
||||
\$app = require 'bootstrap/app.php';
|
||||
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
foreach (array_keys(\$app->getLoadedProviders()) as \$p) {
|
||||
if (stripos(\$p, 'telescope') !== false) echo 'LOADED: ' . \$p . PHP_EOL;
|
||||
}
|
||||
"
|
||||
```
|
||||
|
||||
Expected output: nothing. Any line means a layer is breached.
|
||||
|
||||
## Pruning
|
||||
|
||||
Scheduled in `routes/console.php` (Laravel 11 layout):
|
||||
|
||||
```php
|
||||
Schedule::command('telescope:prune --hours=48')
|
||||
->daily()
|
||||
->environments(['local', 'testing']);
|
||||
```
|
||||
|
||||
48-hour retention. Adjust if dev DB is filling up.
|
||||
|
||||
## What Telescope captures
|
||||
|
||||
- All Eloquent queries with bindings + execution time (N+1 detection)
|
||||
- Background jobs with payload + status + retry history
|
||||
- Sent mails with rendered HTML + plaintext + recipient
|
||||
- Redis commands
|
||||
- Cache reads/writes
|
||||
- Events fired with listeners
|
||||
- Exceptions with stack traces
|
||||
- HTTP requests with headers + payload
|
||||
|
||||
## What NOT to use Telescope for
|
||||
|
||||
- Production debugging — never enabled there
|
||||
- Long-term audit trails — Spatie ActivityLog handles that
|
||||
(see ARCH-FORM-BUILDER §17.1)
|
||||
- Performance benchmarking — use APM tools designed for it
|
||||
|
||||
## Disabling temporarily
|
||||
|
||||
If Telescope is slowing down a specific dev workflow:
|
||||
|
||||
```bash
|
||||
TELESCOPE_ENABLED=false php artisan serve
|
||||
```
|
||||
|
||||
Or set globally in `.env` and restart your server. Re-enable when
|
||||
done.
|
||||
Reference in New Issue
Block a user