RESAZIP-PC\resaz

add daitoexceptionnotifier

...@@ -257,6 +257,93 @@ DAITO_GOOGLE_CHAT_VERIFY_SSL=false ...@@ -257,6 +257,93 @@ DAITO_GOOGLE_CHAT_VERIFY_SSL=false
257 257
258 Use this only for temporary debugging on local environment. Never disable SSL verification on production. 258 Use this only for temporary debugging on local environment. Never disable SSL verification on production.
259 259
260 +## DaitoExceptionNotifier (separate reusable module)
261 +
262 +### 1) Publish config
263 +```bash
264 +php artisan vendor:publish --tag=daito-exception-notifier-config
265 +```
266 +
267 +This creates `config/daito-exception-notifier.php`.
268 +
269 +### 2) Minimal `.env` setup
270 +```dotenv
271 +DAITO_EXCEPTION_NOTIFIER_ENABLED=true
272 +DAITO_EXCEPTION_NOTIFIER_SEND_MODE=queue
273 +DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_ENABLED=true
274 +DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_TTL_SECONDS=30
275 +DAITO_EXCEPTION_NOTIFIER_TRACE_MODE=smart
276 +DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LINES=8
277 +DAITO_EXCEPTION_NOTIFIER_TRACE_SKIP_VENDOR=true
278 +DAITO_EXCEPTION_NOTIFIER_TRACE_INCLUDE_FIRST_APP_FRAME=true
279 +```
280 +
281 +### 3) Auto notify on exception (HTTP + CLI)
282 +
283 +Use Laravel exception handler to automatically send exception cardV2:
284 +
285 +```php
286 +<?php
287 +
288 +namespace App\Exceptions;
289 +
290 +use Daito\Lib\DaitoExceptionNotifier;
291 +use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
292 +use Throwable;
293 +
294 +class Handler extends ExceptionHandler
295 +{
296 + public function register()
297 + {
298 + $this->reportable(function (Throwable $throwable) {
299 + DaitoExceptionNotifier::notify($throwable);
300 + });
301 + }
302 +}
303 +```
304 +
305 +`DaitoExceptionNotifier::notify()` supports both route/controller errors and CLI command errors because Laravel routes both through the same exception handler.
306 +
307 +If you want explicit mode:
308 +
309 +```php
310 +DaitoExceptionNotifier::queue($throwable); // queue
311 +DaitoExceptionNotifier::send($throwable); // sync immediate
312 +```
313 +
314 +Exception cardV2 includes:
315 +
316 +- `time`
317 +- `action`
318 +- `file`
319 +- `line`
320 +- `message`
321 +- compact `trace` (top useful frames only, configurable)
322 +- `first_app_frame` (when available)
323 +
324 +Trace filter strategy (`DAITO_EXCEPTION_NOTIFIER_TRACE_MODE`):
325 +
326 +- `smart` (recommended): prefer app frames, then non-vendor frames, then fallback
327 +- `app_only`: only frames that belong to `app/` or class prefix `App\`
328 +- `no_vendor`: remove `vendor` frames
329 +- `class_prefix_only`: only frames by configured class prefixes (`trace_class_prefixes`)
330 +
331 +### 4) Built-in loop guard (recommended default)
332 +
333 +To avoid recursive notify loops when Google Chat send itself throws exceptions, this module has a built-in circuit-breaker:
334 +
335 +- re-entrant guard in the same process/request
336 +- short TTL dedupe by exception fingerprint (file+line+message+action)
337 +- optional cache-based dedupe across workers/processes
338 +
339 +Main config keys:
340 +
341 +- `loop_guard_enabled`
342 +- `loop_guard_ttl_seconds`
343 +- `loop_guard_use_cache`
344 +- `loop_guard_cache_prefix`
345 +- `loop_guard_skip_if_notifier_in_trace`
346 +
260 ## QueryLog (Laravel shared package) 347 ## QueryLog (Laravel shared package)
261 348
262 > **Provider registration note** 349 > **Provider registration note**
......
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
21 "laravel": { 21 "laravel": {
22 "providers": [ 22 "providers": [
23 "Daito\\Lib\\DaitoQueryLog\\Providers\\DaitoQueryLogProvider", 23 "Daito\\Lib\\DaitoQueryLog\\Providers\\DaitoQueryLogProvider",
24 - "Daito\\Lib\\DaitoGoogleChat\\Providers\\DaitoGoogleChatProvider" 24 + "Daito\\Lib\\DaitoGoogleChat\\Providers\\DaitoGoogleChatProvider",
25 + "Daito\\Lib\\DaitoExceptionNotifier\\Providers\\DaitoExceptionNotifierProvider"
25 ] 26 ]
26 } 27 }
27 } 28 }
......
1 +<?php
2 +
3 +namespace Daito\Lib;
4 +
5 +use Daito\Lib\DaitoExceptionNotifier\DaitoExceptionNotifierManager;
6 +use RuntimeException;
7 +use Throwable;
8 +
9 +class DaitoExceptionNotifier
10 +{
11 + public static function send(Throwable $throwable, array $arrContext = array(), $webhookUrl = null): array
12 + {
13 + return self::manager()->send($throwable, $arrContext, $webhookUrl);
14 + }
15 +
16 + public static function queue(Throwable $throwable, array $arrContext = array(), $webhookUrl = null): void
17 + {
18 + self::manager()->queue($throwable, $arrContext, $webhookUrl);
19 + }
20 +
21 + public static function notify(Throwable $throwable, array $arrContext = array(), $webhookUrl = null)
22 + {
23 + return self::manager()->notify($throwable, $arrContext, $webhookUrl);
24 + }
25 +
26 + private static function manager(): DaitoExceptionNotifierManager
27 + {
28 + if (!function_exists('app')) {
29 + throw new RuntimeException('Laravel app container is required for DaitoExceptionNotifier.');
30 + }
31 +
32 + $manager = app(DaitoExceptionNotifierManager::class);
33 + if (!$manager instanceof DaitoExceptionNotifierManager) {
34 + throw new RuntimeException('Can not resolve DaitoExceptionNotifierManager from container.');
35 + }
36 +
37 + return $manager;
38 + }
39 +}
1 +<?php
2 +
3 +namespace Daito\Lib\DaitoExceptionNotifier\Providers;
4 +
5 +use Daito\Lib\DaitoExceptionNotifier\DaitoExceptionNotifierManager;
6 +use Daito\Lib\DaitoGoogleChat\DaitoGoogleChatManager;
7 +use Illuminate\Support\ServiceProvider;
8 +
9 +class DaitoExceptionNotifierProvider extends ServiceProvider
10 +{
11 + public function register(): void
12 + {
13 + $this->mergeConfigFrom(
14 + __DIR__ . '/../config/daito-exception-notifier.php',
15 + 'daito-exception-notifier'
16 + );
17 +
18 + $this->app->singleton(DaitoExceptionNotifierManager::class, function ($app) {
19 + return new DaitoExceptionNotifierManager(
20 + $app->make(DaitoGoogleChatManager::class)
21 + );
22 + });
23 + }
24 +
25 + public function boot(): void
26 + {
27 + $this->publishes(
28 + array(
29 + __DIR__ . '/../config/daito-exception-notifier.php' => config_path('daito-exception-notifier.php'),
30 + ),
31 + 'daito-exception-notifier-config'
32 + );
33 + }
34 +}
1 +<?php
2 +
3 +return array(
4 + 'enabled' => env('DAITO_EXCEPTION_NOTIFIER_ENABLED', true),
5 + 'send_mode' => env('DAITO_EXCEPTION_NOTIFIER_SEND_MODE', 'queue'), // queue|sync
6 + 'card_title' => env('DAITO_EXCEPTION_NOTIFIER_CARD_TITLE', 'Exception Alert'),
7 + 'loop_guard_enabled' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_ENABLED', true),
8 + 'loop_guard_ttl_seconds' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_TTL_SECONDS', 30),
9 + 'loop_guard_use_cache' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_USE_CACHE', true),
10 + 'loop_guard_cache_prefix' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_CACHE_PREFIX', 'daito-exception-notifier:loop'),
11 + 'loop_guard_skip_if_notifier_in_trace' => env('DAITO_EXCEPTION_NOTIFIER_LOOP_GUARD_SKIP_NOTIFIER_TRACE', true),
12 + 'message_max_length' => env('DAITO_EXCEPTION_NOTIFIER_MESSAGE_MAX_LENGTH', 1000),
13 + 'trace_mode' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MODE', 'smart'), // smart|app_only|no_vendor|class_prefix_only
14 + 'trace_class_prefixes' => array('App\\'),
15 + 'trace_include_first_app_frame' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_INCLUDE_FIRST_APP_FRAME', true),
16 + 'trace_max_lines' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LINES', 8),
17 + 'trace_max_length' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_MAX_LENGTH', 2500),
18 + 'trace_skip_vendor' => env('DAITO_EXCEPTION_NOTIFIER_TRACE_SKIP_VENDOR', true),
19 +);