Skip to content

Commit fec951f

Browse files
committed
Update docs, tests
1 parent 976c307 commit fec951f

File tree

9 files changed

+385
-157
lines changed

9 files changed

+385
-157
lines changed

README.md

Lines changed: 134 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,41 +108,158 @@ class StripePaymentService
108108

109109
### Controlled Execution Blocks
110110

111-
**What it does:** Monitors critical operations with automatic start/end logging, exception handling, DB transactions, circuit breakers, and failure callbacks.
111+
**What it does:** Monitors critical operations with automatic start/end logging, exception-specific handling, DB transactions, circuit breakers, and true escalation for uncaught exceptions.
112+
113+
#### **Factory & Execution**
112114

113115
```php
114116
use Kirschbaum\Monitor\Facades\Monitor;
115117

118+
// Create and execute controlled block
119+
$result = Monitor::controlled('payment_processing')
120+
->run(function() {
121+
return processPayment($data);
122+
});
123+
```
124+
125+
#### **Context Management**
126+
127+
```php
128+
/*
129+
* Adds additional context to the structured logger.
130+
*/
131+
->addContext([
132+
'transaction_id' => 'txn_456',
133+
'gateway' => 'stripe'
134+
]);
135+
136+
/*
137+
* Will completely replace structured logger context.
138+
* ⚠️ Not recommended unless you have a good reason to do so.
139+
*/
140+
->overrideContext([
141+
'user_id' => 123,
142+
'operation' => 'payment',
143+
'amount' => 99.99
144+
]);
145+
```
146+
147+
#### **Exception Handling**
148+
149+
**Exception-Specific Handlers (`catching`):**
150+
```php
151+
->catching([
152+
DatabaseException::class => function($exception, $meta) {
153+
$cachedData = ExampleModel::getCachedData();
154+
return $cachedData; // Recovery value
155+
},
156+
NetworkException::class => function($exception, $meta) {
157+
$this->exampleRetryLater($meta);
158+
// No return = just handle, don't recover
159+
},
160+
PaymentException::class => function($exception, $meta) {
161+
$this->exampleNotifyFinanceTeam($exception, $meta);
162+
throw $exception; // Re-throw if needed
163+
},
164+
// Other exception types remain uncaught.
165+
])
166+
```
167+
168+
**Uncaught Exception Handling (`onUncaughtException`):**
169+
```php
170+
->onUncaughtException(function($exception, $meta) {
171+
// Example actions, the exception will remain uncaught
172+
$this->alertOpsTeam($exception, $meta);
173+
$this->sendToErrorTracking($exception);
174+
})
175+
```
176+
177+
**Key Behavior:**
178+
- Only specified exception types in `catching()` are handled
179+
- Handlers can return recovery values to prevent re-throwing
180+
- `onUncaughtException()` **only** fires for exceptions not caught by `catching()` handlers
181+
- True separation between expected (caught) and unexpected (uncaught) failures
182+
183+
#### **Circuit Breaker & Database Protection**
184+
185+
```php
186+
->withCircuitBreaker('payment_gateway', 3, 60) // 3 failures, 60s timeout
187+
->withDatabaseTransaction(2, [DeadlockException::class], [ValidationException::class])
188+
```
189+
190+
#### **Tracing & Logging**
191+
192+
```php
193+
->overrideTraceId('custom-trace-12345')
194+
->from('PaymentService') // Custom logger origin
195+
```
196+
197+
#### **Complete Example**
198+
199+
```php
116200
class PaymentService
117201
{
118202
public function processPayment($amount, $userId)
119203
{
120-
return Monitor::controlled('payment_processing')
121-
->with(['amount' => $amount, 'user_id' => $userId]) // Additional log context
122-
->transactioned(3)
123-
->escalated(fn (ControlledFailureMeta $meta) => Slack::notify($meta->toArray()))
124-
->run(function () use ($amount) {
204+
return Monitor::controlled('payment_processing', $this)
205+
->addContext([
206+
'user_id' => $userId,
207+
'amount' => $amount,
208+
'currency' => 'USD'
209+
])
210+
->withCircuitBreaker('payment_gateway', 3, 120)
211+
->withDatabaseTransaction(1, [DeadlockException::class])
212+
->catching([
213+
PaymentDeclinedException::class => function($e, $meta) {
214+
return ['status' => 'declined', 'reason' => $e->getMessage()];
215+
},
216+
InsufficientFundsException::class => function($e, $meta) {
217+
return ['status' => 'insufficient_funds'];
218+
}
219+
])
220+
->onUncaughtException(fn($e, $meta) => Monitor::escalate($e, $meta))
221+
->run(function() use ($amount) {
125222
return $this->chargeCard($amount);
126223
});
127224
}
128225
}
129226
```
130227

131-
**What it logs:**
228+
#### **📊 What it logs:**
229+
230+
**Success:**
132231
```json
133-
// Success
134-
{"message": "[PaymentService] STARTED", "controlled_block": "payment_processing", "block_id": "01HK..."}
135-
{"message": "[PaymentService] ENDED", "status": "ok", "duration_ms": 1250}
232+
{"message": "[PaymentProcessor] STARTED", "controlled_block": "payment_processing", "controlled_block_id": "01HK..."}
233+
{"message": "[PaymentProcessor] ENDED", "status": "ok", "duration_ms": 1250}
234+
```
136235

137-
// Failure
138-
{"message": "[PaymentService] STARTED", "controlled_block": "payment_processing"}
139-
{"message": "[PaymentService] FAILED", "exception": "RuntimeException", "trace": [...]}
236+
**Caught Exception (Recovery):**
237+
```json
238+
{"message": "[PaymentProcessor] STARTED", "controlled_block": "payment_processing"}
239+
{"message": "[PaymentProcessor] CAUGHT", "exception": "PaymentDeclinedException", "duration_ms": 500}
240+
{"message": "[PaymentProcessor] RECOVERED", "recovery_value": "array"}
241+
```
242+
243+
**Uncaught Exception (Escalation):**
244+
```json
245+
{"message": "[PaymentProcessor] STARTED", "controlled_block": "payment_processing"}
246+
{"message": "[PaymentProcessor] UNCAUGHT", "exception": "RuntimeException", "uncaught": true, "duration_ms": 300}
140247
```
141248

142-
**Advanced Features:**
143-
- **Circuit Breakers:** `->breaker('service_name', threshold, decaySeconds)` - Automatically opens/closes breaker based on failures
144-
- **Database Transactions:** `->transactioned(retries, onlyExceptions, excludeExceptions)`. This allows to only retry transaction on specific exceptions, or otherwise ignore specific exception.
145-
- **Failure Escalation Path:** `->escalated($callback)` for critical business processes
249+
#### **🎯 API Reference**
250+
251+
| Method | Purpose | Returns |
252+
|--------|---------|---------|
253+
| `Controlled::for(string $name)` | Create controlled block | `self` |
254+
| `->overrideContext(array $context)` | Replace entire context | `self` |
255+
| `->addContext(array $context)` | Merge additional context | `self` |
256+
| `->catching(array $handlers)` | Define exception-specific handlers | `self` |
257+
| `->onUncaughtException(Closure $callback)` | Handle uncaught exceptions only | `self` |
258+
| `->withCircuitBreaker(string $name, int $threshold, int $decay)` | Configure circuit breaker | `self` |
259+
| `->withDatabaseTransaction(int $retries, array $only, array $exclude)` | Wrap in DB transaction with retry | `self` |
260+
| `->overrideTraceId(string $traceId)` | Set custom trace ID | `self` |
261+
| `->from(string\|object $origin)` | Set logger origin | `self` |
262+
| `->run(Closure $callback)` | Execute the controlled block | `mixed` |
146263

147264
### Distributed Tracing
148265

config/monitor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@
317317
'attempt',
318318
'status',
319319
'breaker_tripped',
320-
'escalated',
320+
'uncaught',
321321

322322
'title',
323323
'type',

0 commit comments

Comments
 (0)