RESAZIP-PC\resaz

add daitoexceptionnotifier

......@@ -257,6 +257,93 @@ DAITO_GOOGLE_CHAT_VERIFY_SSL=false
Use this only for temporary debugging on local environment. Never disable SSL verification on production.
## DaitoExceptionNotifier (separate reusable module)
### 1) Publish config
```bash
php artisan vendor:publish --tag=daito-exception-notifier-config
```
This creates `config/daito-exception-notifier.php`.
### 2) Minimal `.env` setup
```dotenv
DAITO_EXCEPTION_NOTIFIER_ENABLED=true
DAITO_EXCEPTION_NOTIFIER_SEND_MODE=queue
DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_ENABLED=true
DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_TTL_SECONDS=30
DAITO_EXCEPTION_NOTIFIER_TRACE_MODE=smart
DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LINES=8
DAITO_EXCEPTION_NOTIFIER_TRACE_SKIP_VENDOR=true
DAITO_EXCEPTION_NOTIFIER_TRACE_INCLUDE_FIRST_APP_FRAME=true
```
### 3) Auto notify on exception (HTTP + CLI)
Use Laravel exception handler to automatically send exception cardV2:
```php
<?php
namespace App\Exceptions;
use Daito\Lib\DaitoExceptionNotifier;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
public function register()
{
$this->reportable(function (Throwable $throwable) {
DaitoExceptionNotifier::notify($throwable);
});
}
}
```
`DaitoExceptionNotifier::notify()` supports both route/controller errors and CLI command errors because Laravel routes both through the same exception handler.
If you want explicit mode:
```php
DaitoExceptionNotifier::queue($throwable); // queue
DaitoExceptionNotifier::send($throwable); // sync immediate
```
Exception cardV2 includes:
- `time`
- `action`
- `file`
- `line`
- `message`
- compact `trace` (top useful frames only, configurable)
- `first_app_frame` (when available)
Trace filter strategy (`DAITO_EXCEPTION_NOTIFIER_TRACE_MODE`):
- `smart` (recommended): prefer app frames, then non-vendor frames, then fallback
- `app_only`: only frames that belong to `app/` or class prefix `App\`
- `no_vendor`: remove `vendor` frames
- `class_prefix_only`: only frames by configured class prefixes (`trace_class_prefixes`)
### 4) Built-in loop guard (recommended default)
To avoid recursive notify loops when Google Chat send itself throws exceptions, this module has a built-in circuit-breaker:
- re-entrant guard in the same process/request
- short TTL dedupe by exception fingerprint (file+line+message+action)
- optional cache-based dedupe across workers/processes
Main config keys:
- `loop_guard_enabled`
- `loop_guard_ttl_seconds`
- `loop_guard_use_cache`
- `loop_guard_cache_prefix`
- `loop_guard_skip_if_notifier_in_trace`
## QueryLog (Laravel shared package)
> **Provider registration note**
......
......@@ -21,7 +21,8 @@
"laravel": {
"providers": [
"Daito\\Lib\\DaitoQueryLog\\Providers\\DaitoQueryLogProvider",
"Daito\\Lib\\DaitoGoogleChat\\Providers\\DaitoGoogleChatProvider"
"Daito\\Lib\\DaitoGoogleChat\\Providers\\DaitoGoogleChatProvider",
"Daito\\Lib\\DaitoExceptionNotifier\\Providers\\DaitoExceptionNotifierProvider"
]
}
}
......
<?php
namespace Daito\Lib;
use Daito\Lib\DaitoExceptionNotifier\DaitoExceptionNotifierManager;
use RuntimeException;
use Throwable;
class DaitoExceptionNotifier
{
public static function send(Throwable $throwable, array $arrContext = array(), $webhookUrl = null): array
{
return self::manager()->send($throwable, $arrContext, $webhookUrl);
}
public static function queue(Throwable $throwable, array $arrContext = array(), $webhookUrl = null): void
{
self::manager()->queue($throwable, $arrContext, $webhookUrl);
}
public static function notify(Throwable $throwable, array $arrContext = array(), $webhookUrl = null)
{
return self::manager()->notify($throwable, $arrContext, $webhookUrl);
}
private static function manager(): DaitoExceptionNotifierManager
{
if (!function_exists('app')) {
throw new RuntimeException('Laravel app container is required for DaitoExceptionNotifier.');
}
$manager = app(DaitoExceptionNotifierManager::class);
if (!$manager instanceof DaitoExceptionNotifierManager) {
throw new RuntimeException('Can not resolve DaitoExceptionNotifierManager from container.');
}
return $manager;
}
}
<?php
namespace Daito\Lib\DaitoExceptionNotifier\Providers;
use Daito\Lib\DaitoExceptionNotifier\DaitoExceptionNotifierManager;
use Daito\Lib\DaitoGoogleChat\DaitoGoogleChatManager;
use Illuminate\Support\ServiceProvider;
class DaitoExceptionNotifierProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(
__DIR__ . '/../config/daito-exception-notifier.php',
'daito-exception-notifier'
);
$this->app->singleton(DaitoExceptionNotifierManager::class, function ($app) {
return new DaitoExceptionNotifierManager(
$app->make(DaitoGoogleChatManager::class)
);
});
}
public function boot(): void
{
$this->publishes(
array(
__DIR__ . '/../config/daito-exception-notifier.php' => config_path('daito-exception-notifier.php'),
),
'daito-exception-notifier-config'
);
}
}
<?php
return array(
'enabled' => env('DAITO_EXCEPTION_NOTIFIER_ENABLED', true),
'send_mode' => env('DAITO_EXCEPTION_NOTIFIER_SEND_MODE', 'queue'), // queue|sync
'card_title' => env('DAITO_EXCEPTION_NOTIFIER_CARD_TITLE', 'Exception Alert'),
'loop_guard_enabled' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_ENABLED', true),
'loop_guard_ttl_seconds' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_TTL_SECONDS', 30),
'loop_guard_use_cache' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_USE_CACHE', true),
'loop_guard_cache_prefix' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_CACHE_PREFIX', 'daito-exception-notifier:loop'),
'loop_guard_skip_if_notifier_in_trace' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_SKIP_NOTIFIER_TRACE', true),
'message_max_length' => env('DAITO_EXCEPTION_NOTIFIER_MESSAGE_MAX_LENGTH', 1000),
'trace_mode' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MODE', 'smart'), // smart|app_only|no_vendor|class_prefix_only
'trace_class_prefixes' => array('App\\'),
'trace_include_first_app_frame' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_INCLUDE_FIRST_APP_FRAME', true),
'trace_max_lines' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LINES', 8),
'trace_max_length' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LENGTH', 2500),
'trace_skip_vendor' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_SKIP_VENDOR', true),
);