code-distortion / clarity-control
A Laravel package to catch and log exceptions with a fluent interface
Installs: 1 252
Dependents: 1
Suggesters: 2
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Requires
- php: 8.0.* | 8.1.* | 8.2.* | 8.3.*
- code-distortion/clarity-context: ^0.1.0
- code-distortion/staticall: ^0.1.0
Requires (Dev)
- infection/infection: ^0.10 | ^0.11 | ^0.12 | ^0.13 | ^0.14 | ^0.15 | ^0.16 | ^0.17 | ^0.18 | ^0.19 | ^0.20 | ^0.21 | ^0.22 | ^0.23 | ^0.24 | ^0.25 | ^0.26 | ^0.27
- orchestra/testbench: ^6.12 | ^7.0 | ^8.0
- phpstan/phpstan: ^0.9 | ^0.10 | ^0.11 | ^0.12 | ^1.0
- phpunit/phpunit: ~4.8 | ^5.0 | ^6.0 | ^7.0 | ^8.4 | ^9.0 | ^10.0
- squizlabs/php_codesniffer: ^3.8.0
Suggests
- code-distortion/clarity-context: Understand Your Exceptions. Part of the Clarity Suite
- code-distortion/clarity-logger: Useful Exception Logs. Part of the Clarity Suite
README
code-distortion/clarity-control is a Laravel package that lets you catch and log exceptions with a fluent interface.
// run $callable - will catch + report() any exceptions Control::run($callable); // do the same, but with extra optional configuration Control::prepare($callable) ->channel('slack') ->level(Settings::REPORTING_LEVEL_WARNING) ->debug() … ->emergency() ->default('some-value') ->catch(DivisionByZeroError::class) ->match('Undefined variable $a') ->matchRegex('/^Undefined variable /') ->known('https://company.atlassian.net/browse/ISSUE-1234') ->report() or ->dontReport() ->rethrow() or ->dontRethrow() or ->rethrow($callable) ->callback($callable) ->finally($callable) ->execute();
Clarity Suite
Clarity Control is a part of the Clarity Suite, designed to let you manage exceptions more easily:
- Clarity Context - Understand Your Exceptions
- Clarity Logger - Useful Exception Logs
- Clarity Control - Handle Your Exceptions
Table of Contents
- Installation
- Catching Exceptions
- Configuring The Way Exceptions Are Caught
- Log Channel
- Log Level
- Default Return Value
- Catching Selectively
- Recording "Known" Issues
- Disabling Logging
- Re-throwing Exceptions
- Suppressing Exceptions
- Callbacks
- Advanced Catching
- Retrieving Exceptions
Installation
Install the package via composer:
composer require code-distortion/clarity-control
Config File
Use the following command if you would like to publish the config/code_distortion.clarity_control.php
config file:
php artisan vendor:publish --provider="CodeDistortion\ClarityControl\ServiceProvider" --tag="config"
Catching Exceptions
Clarity Control deals with flow control. Which is to say, it provides ways to catch and manage exceptions.
Whilst you can still use try / catch statements of course, you can write them this way instead:
use CodeDistortion\ClarityControl\Control; Control::run($callable); // is equivalent to: try { $callable(); // … do something } catch (Exception $e) { report($e); }
Control will run the callable passed to Control::run(…)
. If an exception occurs, it will be caught and reported using Laravel's report()
helper.
The code following it will continue to run afterwards.
Tip: Laravel's dependency injection system is used to run your callable. Just type-hint your parameters and they'll be resolved for you.
Configuring The Way Exceptions Are Caught
Another way to write this is to call Control::prepare($callable)
, and then ->execute()
afterwards.
use CodeDistortion\ClarityControl\Control; Control::prepare($callable)->execute(); // is equivalent to: Control::run($callable);
These are the same, except that when using Control::prepare($callable)
and ->execute()
, there's an opportunity to set some configuration values in-between…
Here is the list of the methods you can use to configure the way Control catches exceptions (they're explained in more detail below):
use CodeDistortion\ClarityControl\Control; use CodeDistortion\ClarityControl\Settings; Control::prepare($callable) ->channel('slack') ->level(Settings::REPORTING_LEVEL_WARNING) ->debug() … ->emergency() ->default('default') ->catch(DivisionByZeroError::class) ->match('Undefined variable $a') ->matchRegex('/^Undefined variable /') ->known('https://company.atlassian.net/browse/ISSUE-1234') ->report() or ->dontReport() ->rethrow() or ->dontRethrow() or ->rethrow($callable) ->callback($callable) ->finally($callable) ->execute();
Note About Logging
This package uses Clarity Context, and some settings require a logger that's also aware of Clarity Context (such as Clarity Logger) to work. Specifically, the channel, log level, and "known" issue settings won't appear anywhere otherwise.
These details are added to the Context
object that Clarity Context produces when an exception occurs. And it's up to the logger to use its values.
See Clarity Context for more information about this Context
object.
See Clarity Logger for a logger that understands the Context
object.
Log Channel
You can specify which Laravel log-channel you'd like to log to. The possible values come from your projects' config/logging.php
file.
Control::prepare($callable)->channel('slack')->execute();
You can specify more than one if you'd like.
Control::prepare($callable)->channel(['stack', 'slack'])->execute();
Note: This setting requires a logging tool that's aware of Clarity. See the Note About Logging for more information.
See Laravel's documentation about logging for more information about Log Channels.
Log Level
You can specify the reporting level you'd like to use when logging.
use CodeDistortion\ClarityControl\Settings; Control::prepare($callable)->debug()->execute(); Control::prepare($callable)->info()->execute(); Control::prepare($callable)->notice()->execute(); Control::prepare($callable)->warning()->execute(); Control::prepare($callable)->error()->execute(); Control::prepare($callable)->critical()->execute(); Control::prepare($callable)->alert()->execute(); Control::prepare($callable)->emergency()->execute(); // or Control::prepare($callable)->level(Settings::REPORTING_LEVEL_WARNING)->execute(); // etc
Note: This setting requires a logging tool that's aware of Clarity. See the Note About Logging for more information.
See Laravel's documentation about logging for more information about Log Levels.
Default Return Value
You can specify the default value to return when an exception occurs by passing a second parameter to Control::run()
or Control::prepare()
.
$result = Control::run($callable, $default); // or $result = Control::prepare($callable, $default)->execute();
You can also call ->default()
after calling Control::prepare()
$result = Control::prepare($callable)->default($default)->execute();
Tip: If the default value is callable, Control will run it (when needed) to resolve the value.
Catching Selectively
You can choose to only catch certain types of exceptions. Other exceptions will ignored.
use DivisionByZeroError; Control::prepare($callable) ->catch(DivisionByZeroError::class) // only catch this type of exception ->execute();
Control::prepare($callable) ->match('Undefined variable $a') // exact string match of $e->getMessage() ->matchRegex('/^Undefined variable /') // regex string match of $e->getMessage() ->execute();
You can specify multiple exception classes, match-strings or regexes.
When you specify match()
and matchRegex()
, only one of them needs to match the exception message.
Recording "Known" Issues
If you use an issue management system like Jira, you can make a note of the issue/task the exception relates to:
Control::prepare($callable) ->known('https://company.atlassian.net/browse/ISSUE-1234') ->execute();
This gives you an opportunity to label exceptions while the fix is being worked on.
Note: This setting requires a logging tool that's aware of Clarity. See the Note About Logging for more information.
Disabling Logging
You can disable the reporting of exceptions once caught. This will stop report()
from being triggered.
Control::prepare($callable)->dontReport()->execute(); // or Control::prepare($callable)->report(false)->execute();
Re-throwing Exceptions
If you'd like caught exceptions to be detected and reported, but re-thrown again afterwards, you can tell Control to rethrow them:
Control::prepare($callable)->rethrow()->execute();
If you'd like to rethrow a different exception, you can pass a closure to make the decision. It must return a Throwable / Exception, or true / false.
$closure = fn(Throwable $e) => new MyException('Something happened', 0, $e); Control::prepare($callable)->rethrow($closure)->execute();
Suppressing Exceptions
If you'd like to stop exceptions from being reported and rethrown once caught, you can suppress them altogether.
Control::prepare($callable)->suppress()->execute(); // is equivalent to: Control::prepare($callable)->dontReport()->dontRethrow()->execute();
Callbacks
You can add a custom callback to be run when an exception is caught. This can be used to either do something when an exception occurs, or to decide if the the exception should be "suppressed".
You can add multiple callbacks if you like.
use CodeDistortion\ClarityControl\Context; use Illuminate\Http\Request; use Throwable; $callback = fn(Throwable $e, Context $context, Request $request) => …; // do something Control::prepare($callable)->callback($callback)->execute();
Tip: Laravel's dependency injection is used to run your callback. Just type-hint your parameters, like in the example above.
Extra parameters you can use are:
- The exception: when the parameter is named
$e
or$exception
- The
Context
object: when type-hinted withCodeDistortion\ClarityContext\Context
Using The Context Object in Callbacks
When you type-hint a callback parameter with CodeDistortion\ClarityContext\Context
, you'll receive the Context
object populated with details about the exception.
This is the same Context object from the Clarity Context package, and is designed to be used in app/Exceptions/Handler.php
when reporting an exception. See Clarity Context's documentation for more information.
As well as reading values from the Context object, you can update some of its values inside your callback. This lets you alter what happens on-the-fly.
use CodeDistortion\ClarityControl\Context; use CodeDistortion\ClarityControl\Settings; $callback = function (Context $context) { // the exception that occurred $context->getException(); // manage the log channels $context->getChannels(); $context->setChannels(['slack']); // manage the log reporting level $context->getLevel(); $context->setLevel(Settings::REPORTING_LEVEL_WARNING); $context->debug() … $context->emergency(); // manage the default return value $context->getDefault(); $context->setDefault('default'); // manage the request's trace identifiers $context->getTraceIdentifiers(); $context->setTraceIdentifiers([$traceId]); // manage the known issues $context->hasKnown(); $context->getKnown(); $context->setKnown(['https://company.atlassian.net/browse/ISSUE-1234']); // manage the report setting $context->getReport(); $context->setReport(true/false); $context->dontReport(); // manage the rethrow setting $context->getRethrow(); $context->setRethrow(true/false); $context->setRethrow($exception); // a new exception to rethrow $context->setRethrow($callable); // a closure that decides which exception to rethrow $context->dontRethrow(); // turn both report and rethrow off $context->suppress(); };
Suppressing Exceptions On The Fly
You can suppress an exception by calling $context->suppress()
inside your callback.
This will also happen if your callback sets $context->setReport(false)
and $context->setRethrow(false)
.
Tip: Callbacks are run in the order they were specified. Subsequent callbacks won't be called when the exception is suppressed.
use CodeDistortion\ClarityControl\Context; use Illuminate\Http\Request; $callback = function (Context $context, Request $request) { // suppress the exception when the user-agent is 'test-agent' if ($request->userAgent == 'test-agent') { $context->suppress() // or $context->setReport(false)->setRethrow(false); } }; Control::prepare($callable)->callback($callback)->execute();
Global Callbacks
You can tell Control to run a "global" callback whenever it catches an exception. You can add as many as you need.
These callbacks are run before the regular (non-global) callbacks.
Control::globalCallback($callable);
A good place to set one up would be in a service provider. See Laravel's documentation for more information about service providers.
namespace App\Providers; use Illuminate\Support\ServiceProvider; use CodeDistortion\ClarityControl\Control; class MyServiceProvider extends ServiceProvider { public function boot() { $callback = function () { … }; // do something Control::globalCallback($callback); // <<< } }
Finally
You can specify a callable to run after the execution of the main $callable
by passing a third parameter to Control::run()
or Control::prepare()
.
$finally = fn() => …; // do something Control::run($callable, 'default', $finally); // or Control::prepare($callable, 'default', $finally)->execute();
You can also call ->finally()
after calling Control::prepare()
Control::prepare($callable)->finally($finally)->execute();
Tip: Laravel's dependency injection system is used to run your callable. Just type-hint your parameters and they'll be resolved for you.
Advanced Catching
You can choose to do different things when different exceptions are caught.
To do this, configure a CodeDistortion\ClarityControl\CatchType
object, and pass that to $clarity->catch()
instead of
passing the exception class string.
CatchType
objects can be customised with the same settings as the Control
object. They're all optional, and can be called in any order.
use CodeDistortion\ClarityControl\CatchType; use CodeDistortion\ClarityControl\Settings; $catchType1 = CatchType::channel('slack') ->level(Settings::REPORTING_LEVEL_WARNING) ->debug() … ->emergency() ->default('default') ->catch(DivisionByZeroError::class) ->match('Undefined variable $a') ->matchRegex('/^Undefined variable /') ->known('https://company.atlassian.net/browse/ISSUE-1234') ->report() or ->dontReport() ->rethrow() or ->dontRethrow() or ->rethrow($callable) ->suppress() ->callback($callable) ->finally($callable); $catchType2 = …; Control::prepare($callable) ->catch($catchType1) ->catch($catchType2) ->execute();
CatchTypes are checked in the order they were specified. The first one that matches the exception is used.
Retrieving Exceptions
If you'd like to obtain the exception, you can call getException()
and pass a variable by reference. When an exception occurs, it will contain the exception afterwards. Otherwise it's set to null.
The exception will be set, even if the exception was suppressed.
Control::prepare($callable)->getException($e)->execute(); dump($e); // will contain the exception, or null
Testing This Package
- Clone this package:
git clone https://github.com/code-distortion/clarity-control.git .
- Run
composer install
to install dependencies - Run the tests:
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
SemVer
This library uses SemVer 2.0.0 versioning. This means that changes to X
indicate a breaking change: 0.0.X
, 0.X.y
, X.y.z
. When this library changes to version 1.0.0, 2.0.0 and so forth, it doesn't indicate that it's necessarily a notable release, it simply indicates that the changes were breaking.
Treeware
This package is Treeware. If you use it in production, then we ask that you buy the world a tree to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.
Contributing
Please see CONTRIBUTING for details.
Code of Conduct
Please see CODE_OF_CONDUCT for details.
Security
If you discover any security related issues, please email tim@code-distortion.net instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.