-
-
Notifications
You must be signed in to change notification settings - Fork 1
Example API Debugging
Learn how to use ChronoTrace to debug external API integrations, identify failures, optimize response times, and implement robust error handling.
Your payment processing system is experiencing intermittent failures and slow response times, causing checkout failures and poor user experience.
- Payment API calls failing randomly (5-10% failure rate)
- Slow payment processing (3-8 seconds)
- Timeout errors during peak hours
- Inconsistent error responses
- Users getting charged but orders not completing
Configure ChronoTrace to capture detailed HTTP events for payment integration:
# Target payment-related routes
CHRONOTRACE_MODE=targeted// config/chronotrace.php
'targeted_routes' => [
'checkout/*',
'api/payments/*',
'api/orders/*/payment',
'webhooks/payment/*',
],
'http' => [
'enabled' => true,
'include_request_body' => true,
'include_response_body' => true,
'max_body_size' => 128 * 1024, // 128KB for payment data
'timeout_threshold' => 5000, // Flag requests >5s
],Let's generate some payment transactions to capture both successful and failed scenarios:
# Start recording payment API calls
php artisan chronotrace:record --routes="*payment*" --duration=1h --sample-rate=1.0
# Monitor in real-time
tail -f storage/logs/laravel.log | grep -i paymentList traces to identify payment failures:
# Find failed payment attempts
php artisan chronotrace:list --route="*payment*" --status=error --limit=10
# Example output showing various failure types:
ββββββββββββββ¬ββββββββββββββββββββββ¬βββββββββ¬βββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββ
β Trace ID β Timestamp β Method β Status β Route β Duration β
ββββββββββββββΌββββββββββββββββββββββΌβββββββββΌβββββββββββΌββββββββββββββββββββββββββΌβββββββββββ€
β fail_001 β 2024-08-06 14:30:15 β POST β 500 β api/payments/process β 30,000ms β
β fail_002 β 2024-08-06 14:32:42 β POST β 422 β api/payments/process β 156ms β
β fail_003 β 2024-08-06 14:35:18 β POST β 500 β api/payments/process β 8,456ms β
β timeout1 β 2024-08-06 14:37:25 β POST β 504 β api/payments/process β 30,000ms β
ββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββ΄βββββββββββ΄ββββββββββββββββββββββββββ΄βββββββββββLet's analyze a timeout failure in detail:
# Analyze timeout failure focusing on HTTP events
php artisan chronotrace:replay timeout1 --filter=httpββ REQUEST INFORMATION βββββββββββββββββββββββββββββββββββββββββ
β Trace ID: timeout1 β
β Method: POST /api/payments/process β
β Status: 504 Gateway Timeout β
β Duration: 30,000ms β
β Memory: 25MB β
β User: customer_789 β
β Order ID: ORD-2024-08-06-12345 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββ HTTP EVENTS (4 requests) ββββββββββββββββββββββββββββββββββββ
β [+0ms] POST https://api.stripe.com/v1/payment_intents (30,000ms) β TIMEOUT
β Request Headers:
β Authorization: Bearer sk_live_[REDACTED]
β Content-Type: application/x-www-form-urlencoded
β Idempotency-Key: idem_2024080614301512345
β Request Body:
β amount=29999¤cy=usd&payment_method=pm_1234567890
β &confirm=true&return_url=https://example.com/return
β Response: TIMEOUT_ERROR
β Connection: timeout after 30.000s
β
β [+30,100ms] POST https://api.stripe.com/v1/payment_intents (15,234ms) β οΈ RETRY
β Request: Same as above (automatic retry)
β Response Status: 429 Too Many Requests
β Response Body: {"error": {"type": "rate_limit_error",
β "message": "Too many requests"}}
β
β [+45,500ms] GET https://api.stripe.com/v1/payment_intents/pi_1234567890 (2,345ms)
β Request: GET payment status after timeout
β Response Status: 200 OK
β Response Body: {"status": "succeeded", "amount": 29999,
β "charges": {"data": [{"paid": true}]}}
β β οΈ Payment actually succeeded but we didn't know!
β
β [+48,000ms] POST https://webhook.site/test-endpoint (567ms)
β Request: Error notification webhook
β Response Status: 200 OK
β Note: Notifying about "failed" payment that actually succeeded
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Timeout Configuration: 30-second timeout too long for user experience
- No Retry Strategy: Automatic retry hitting rate limits
- Status Checking: No proper status verification after timeout
- Double Charging Risk: Payment succeeded but system thought it failed
- Poor Error Handling: Generic timeout response to user
Before (problematic code):
// PaymentService.php - Poor timeout handling
public function processPayment($paymentData)
{
$response = Http::timeout(30) // Too long!
->post('https://api.stripe.com/v1/payment_intents', $paymentData);
if ($response->failed()) {
throw new PaymentException('Payment failed');
}
return $response->json();
}After (optimized code):
// PaymentService.php - Proper timeout and retry strategy
public function processPayment($paymentData)
{
return $this->withRetry(function () use ($paymentData) {
return Http::timeout(10) // Shorter timeout
->retry(2, 1000, function ($exception, $request) {
// Only retry on specific conditions
return $exception instanceof ConnectException ||
$this->isRetryableError($exception);
})
->withHeaders([
'Idempotency-Key' => $this->generateIdempotencyKey($paymentData),
])
->post('https://api.stripe.com/v1/payment_intents', $paymentData);
});
}
private function withRetry($callback, $maxAttempts = 3)
{
$attempt = 1;
while ($attempt <= $maxAttempts) {
try {
$response = $callback();
if ($response->successful()) {
return $response->json();
}
// Handle specific error cases
if ($response->status() === 429) {
// Rate limit - wait and retry
sleep(pow(2, $attempt)); // Exponential backoff
$attempt++;
continue;
}
if ($this->isRetryableError($response)) {
$attempt++;
continue;
}
// Non-retryable error
throw new PaymentException($response->json()['error']['message'] ?? 'Payment failed');
} catch (ConnectException $e) {
if ($attempt === $maxAttempts) {
return $this->handleTimeout($paymentData);
}
$attempt++;
sleep(1);
}
}
}// Handle timeout scenarios properly
private function handleTimeout($paymentData)
{
// Check if payment actually succeeded
$paymentIntent = $this->checkPaymentStatus($paymentData['payment_intent_id']);
if ($paymentIntent && $paymentIntent['status'] === 'succeeded') {
Log::info('Payment succeeded despite timeout', [
'payment_intent_id' => $paymentIntent['id'],
'amount' => $paymentIntent['amount']
]);
return $paymentIntent;
}
// Payment truly failed or is still processing
throw new PaymentTimeoutException(
'Payment processing timeout. Please check your payment status.',
['payment_intent_id' => $paymentData['payment_intent_id']]
);
}
private function checkPaymentStatus($paymentIntentId)
{
try {
$response = Http::timeout(5)
->get("https://api.stripe.com/v1/payment_intents/{$paymentIntentId}");
return $response->successful() ? $response->json() : null;
} catch (Exception $e) {
Log::error('Failed to check payment status', [
'payment_intent_id' => $paymentIntentId,
'error' => $e->getMessage()
]);
return null;
}
}// Prevent cascading failures
class PaymentCircuitBreaker
{
private $failureThreshold = 5;
private $timeoutSeconds = 60;
public function execute($callback)
{
$failures = Cache::get('payment_failures', 0);
$lastFailure = Cache::get('payment_last_failure');
// Circuit is open - fast fail
if ($failures >= $this->failureThreshold) {
if ($lastFailure && now()->diffInSeconds($lastFailure) < $this->timeoutSeconds) {
throw new CircuitBreakerOpenException('Payment service temporarily unavailable');
}
// Reset circuit breaker after timeout
Cache::forget('payment_failures');
Cache::forget('payment_last_failure');
}
try {
$result = $callback();
// Success - reset failure count
Cache::forget('payment_failures');
Cache::forget('payment_last_failure');
return $result;
} catch (Exception $e) {
// Increment failure count
Cache::put('payment_failures', $failures + 1, 300);
Cache::put('payment_last_failure', now(), 300);
throw $e;
}
}
}// PaymentController.php - Async processing for better UX
public function processPayment(Request $request)
{
$paymentData = $this->validatePaymentData($request);
// Start async payment processing
$job = ProcessPaymentJob::dispatch($paymentData)
->onQueue('payments')
->delay(now()->addSeconds(1));
return response()->json([
'status' => 'processing',
'job_id' => $job->getJobId(),
'polling_url' => route('payment.status', ['job' => $job->getJobId()]),
'estimated_time' => 30 // seconds
]);
}
// ProcessPaymentJob.php
class ProcessPaymentJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(PaymentService $paymentService)
{
try {
$result = $paymentService->processPayment($this->paymentData);
// Broadcast success
broadcast(new PaymentProcessedEvent($this->paymentData['order_id'], $result));
} catch (PaymentTimeoutException $e) {
// Handle timeout - check status later
CheckPaymentStatusJob::dispatch($this->paymentData)->delay(now()->addMinutes(2));
} catch (Exception $e) {
// Handle failure
broadcast(new PaymentFailedEvent($this->paymentData['order_id'], $e->getMessage()));
throw $e; // Re-throw to trigger retry mechanism
}
}
}After implementing fixes, let's test the improved payment system:
# Record new payment traces with fixes
php artisan chronotrace:record --routes="*payment*" --duration=30m
# Simulate various payment scenarios
php artisan payment:test --scenarios=success,timeout,rate-limit,network-error# Check improved payment processing
php artisan chronotrace:list --route="*payment*" --since="30 minutes ago"
# Results showing improvements:
ββββββββββββββ¬ββββββββββββββββββββββ¬βββββββββ¬βββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββ
β Trace ID β Timestamp β Method β Status β Route β Duration β
ββββββββββββββΌββββββββββββββββββββββΌβββββββββΌβββββββββββΌββββββββββββββββββββββββββΌβββββββββββ€
β good_001 β 2024-08-06 16:30:15 β POST β 200 β api/payments/process β 1,245ms β
β good_002 β 2024-08-06 16:32:42 β POST β 200 β api/payments/process β 2,156ms β
β timeout2 β 2024-08-06 16:35:18 β POST β 200 β api/payments/process β 10,567ms β
ββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββ΄βββββββββββ΄ββββββββββββββββββββββββββ΄βββββββββββphp artisan chronotrace:replay timeout2 --filter=httpββ HTTP EVENTS (3 requests) ββββββββββββββββββββββββββββββββββββ
β [+0ms] POST https://api.stripe.com/v1/payment_intents (10,000ms) β οΈ TIMEOUT
β Request: Same payment data with proper idempotency
β Response: TIMEOUT_ERROR (expected with 10s timeout)
β
β [+10,100ms] GET https://api.stripe.com/v1/payment_intents/pi_1234567890 (234ms) β
β Request: Status check after timeout
β Response Status: 200 OK
β Response: {"status": "succeeded", "amount": 29999}
β β
Payment verified as successful!
β
β [+10,400ms] POST https://webhook.site/success-endpoint (123ms) β
β Request: Success notification
β Response Status: 200 OK
β β
Correct success handling despite initial timeout
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Metric | Before | After | Improvement |
|---|---|---|---|
| Failure Rate | 10% | 2% | 80% reduction |
| Average Response Time | 5,500ms | 1,800ms | 67% faster |
| Timeout Recovery | 0% | 95% | 95% success rate |
| User Experience | Poor | Good | Async processing |
| Double Charges | 15 cases | 0 cases | 100% eliminated |
# Analyze API response patterns
php artisan chronotrace:replay trace_id --filter=http --format=json | jq '.http_events[] | {url, status, duration, response_size}'
# Find slow API endpoints
php artisan chronotrace:list --min-duration=2000 | \
xargs -I {} php artisan chronotrace:replay {} --filter=http --min-duration=1000// Analyze error patterns across traces
class APIErrorAnalyzer
{
public function analyzeErrorPatterns($timeframe = '24 hours')
{
$traces = ChronoTrace::getTraces()
->where('created_at', '>=', now()->sub($timeframe))
->where('status', '>=', 400);
$errorPatterns = [];
foreach ($traces as $trace) {
$httpEvents = $trace->getHttpEvents();
foreach ($httpEvents as $event) {
if ($event['status'] >= 400) {
$pattern = [
'endpoint' => $this->extractEndpoint($event['url']),
'status' => $event['status'],
'error_type' => $this->categorizeError($event),
'frequency' => 1
];
$key = md5(json_encode($pattern));
if (isset($errorPatterns[$key])) {
$errorPatterns[$key]['frequency']++;
} else {
$errorPatterns[$key] = $pattern;
}
}
}
}
return collect($errorPatterns)->sortByDesc('frequency');
}
}// Monitor API performance metrics
class APIPerformanceMonitor
{
public function trackAPIMetrics($trace)
{
$httpEvents = $trace->getHttpEvents();
foreach ($httpEvents as $event) {
$metrics = [
'endpoint' => $this->normalizeEndpoint($event['url']),
'method' => $event['method'],
'status' => $event['status'],
'duration' => $event['duration'],
'request_size' => $event['request_size'] ?? 0,
'response_size' => $event['response_size'] ?? 0,
'timestamp' => $event['timestamp'],
];
// Store in time-series database
InfluxDB::write('api_metrics', $metrics);
// Check for performance alerts
if ($event['duration'] > 5000) {
$this->sendSlowAPIAlert($metrics);
}
if ($event['status'] >= 500) {
$this->sendAPIErrorAlert($metrics);
}
}
}
}- Define timeout strategies for each API
- Plan retry logic and exponential backoff
- Implement idempotency for critical operations
- Design circuit breaker patterns
- Plan async processing for slow APIs
- Test timeout scenarios
- Verify retry behavior
- Test error response handling
- Validate status checking mechanisms
- Test rate limit handling
- Monitor API response times
- Track error rates by endpoint
- Set up alerting for failures
- Monitor circuit breaker states
- Track timeout recovery success
- Analyze API usage patterns
- Optimize frequently used endpoints
- Review and adjust timeout values
- Update retry strategies based on data
- Implement caching where appropriate
// Log all API interactions
Http::macro('withLogging', function () {
return $this->beforeSending(function ($request) {
Log::info('API Request', [
'url' => $request->url(),
'method' => $request->method(),
'headers' => $request->headers(),
'body' => $request->body()
]);
})->withMiddleware(function ($response) {
Log::info('API Response', [
'status' => $response->status(),
'headers' => $response->headers(),
'body' => $response->body(),
'duration' => $response->handlerStats()['total_time'] ?? null
]);
});
});// API health monitoring
class APIHealthChecker
{
public function checkPaymentGateway()
{
try {
$response = Http::timeout(5)->get('https://api.stripe.com/v1/account');
return $response->successful();
} catch (Exception $e) {
return false;
}
}
}// Mock external APIs during testing
class PaymentServiceTest extends TestCase
{
public function test_payment_timeout_handling()
{
Http::fake([
'api.stripe.com/*' => Http::response([], 408), // Timeout
]);
$this->expectException(PaymentTimeoutException::class);
$paymentService = new PaymentService();
$paymentService->processPayment($this->getTestPaymentData());
}
}- Configuration - HTTP event capture configuration
- Performance Analysis - General performance optimization
- Security - API security and PII protection
- Production Monitoring - API monitoring in production
Result: Payment system reliability improved from 90% to 98% success rate, with 67% faster response times and 100% elimination of double-charging incidents!
- Getting Started - Install and configure ChronoTrace
- Examples - Real-world debugging scenarios
- Production Guide - Deploy safely in production
- Troubleshooting - Solve common issues
| Section | Page | Description |
|---|---|---|
| π Basics | Your First Trace | Step-by-step beginner guide |
| ποΈ Config | Recording Modes | Choose when to record traces |
| π‘οΈ Security | Security & PII | Protect sensitive data |
| π§ Tools | Commands | Complete command reference |
- π¬ GitHub Discussions - Community support
- π Report Issues - Bug reports & feature requests
- π§ Contact - Direct support
- π‘ Feature Requests - Suggest improvements
ChronoTrace helps Laravel developers debug applications faster with intelligent request tracing.
Made with β€οΈ by Grazulex β’ Documentation updated August 2024