Laravel API request logging with idempotency support
prahsys/laravel-api-logs is a Laravel package for laravel api request logging with idempotency support.
It currently has 0 GitHub stars and 213 downloads on Packagist (latest version 0.2.5).
Install it with composer require prahsys/laravel-api-logs.
Discover more Laravel packages by prahsys
or browse all Laravel packages to compare alternatives.
Last updated
A comprehensive Laravel package for logging API requests and responses with idempotency support, model tracking, and configurable data redaction.
The package follows a clean event-driven architecture with a lightweight database design:
HTTP Request → Middleware → Event → Listener → Pipeline → Log Channels
↓
Model Tracking → Association → Database (Lightweight References)
ApiLogItem is designed as a lightweight reference to API requests, not a full data store. This approach:
The actual request/response data is processed through configurable log channels where it can be:
composer require prahsys/laravel-api-logs
php artisan vendor:publish --provider="Prahsys\ApiLogs\ApiLogsServiceProvider"
php artisan migrate
# API Logs Settings
API_LOGS_TTL=86400
# Logging Channels (configure in config/logging.php)
API_LOGS_RAW_CHANNEL=api_logs_raw
API_LOGS_REDACTED_CHANNEL=api_logs_redacted
Add to your config/logging.php:
'channels' => [
// Raw logs - restricted access, complete data
'api_logs_raw' => [
'driver' => 'daily',
'path' => storage_path('logs/api_logs_raw.log'),
'level' => 'info',
'days' => 14,
'permission' => 0600, // Restricted access
],
// Redacted logs - general monitoring and analytics
'api_logs_redacted' => [
'driver' => 'daily',
'path' => storage_path('logs/api_logs_redacted.log'),
'level' => 'info',
'days' => 90,
],
// External monitoring services with tailored redaction
'api_logs_sentry' => [
'driver' => 'sentry',
'level' => 'error',
'bubble' => true,
],
'api_logs_axiom' => [
'driver' => 'custom',
'via' => App\Logging\AxiomLogger::class,
'level' => 'info',
'dataset' => 'api_logs',
],
// Stack multiple channels for comprehensive monitoring
'api_logs_monitoring' => [
'driver' => 'stack',
'channels' => ['api_logs_redacted', 'api_logs_sentry'],
'ignore_exceptions' => false,
],
],
Add to your app/Http/Kernel.php:
protected $middlewareGroups = [
'api' => [
// ... other middleware
\Prahsys\ApiLogs\Http\Middleware\ApiLogMiddleware::class,
],
];
Once configured, the package automatically logs API requests. Include an Idempotency-Key header for request correlation:
$response = Http::withHeaders([
'Idempotency-Key' => 'unique-key-123',
])->post('/api/users', ['name' => 'John Doe']);
The package includes Guzzle middleware to log outbound HTTP requests your application makes to external APIs.
Add the middleware to your Guzzle client's handler stack:
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Prahsys\ApiLogs\Http\Middleware\GuzzleApiLogMiddleware;
$stack = HandlerStack::create();
$stack->push(app(GuzzleApiLogMiddleware::class));
$client = new Client([
'handler' => $stack,
'base_uri' => 'https://api.example.com',
'timeout' => 30,
]);
If you already have a handler stack with other middleware:
// Get your existing handler stack
$stack = $existingClient->getConfig('handler');
// Add API logging middleware
$stack->push(app(GuzzleApiLogMiddleware::class));
// Skip logging by adding request option
$response = $client->get('/status', [
'prahsys_api_logs_skip' => true
]);
Configure outbound logging in your environment:
# Enable/disable outbound API logging
API_LOGS_OUTBOUND_ENABLED=true
Or in config/api-logs.php:
'outbound' => [
'enabled' => true,
'exclude_hosts' => [
'localhost',
'*.internal.company.com',
'monitoring.example.com',
],
],
Add the HasApiLogItems trait to models you want to track:
use Prahsys\ApiLogs\Models\HasApiLogItems;
class User extends Model
{
use HasApiLogItems;
// ... model code
}
Models created or updated during API requests are automatically associated with the API log item. This is particularly useful for:
// Get all API requests for a model
$user = User::find(1);
$requests = $user->apiLogItems;
// Get the latest API request for a model
$latestRequest = $user->latestApiLogItem();
// Get all models associated with an API request
$apiLogItem = ApiLogItem::where('request_id', $requestId)->first();
$users = $apiLogItem->getRelatedModels(User::class)->get();
// Example: Track all models affected by a single API request
$apiLogItem = ApiLogItem::where('request_id', 'abc-123-def')->first();
// Get all users modified in this request
$affectedUsers = $apiLogItem->getRelatedModels(User::class)->get();
// Get all orders created/updated in this request
$affectedOrders = $apiLogItem->getRelatedModels(Order::class)->get();
// Get all affected models regardless of type
$allAffectedModels = $apiLogItem->relatedModels; // Returns collection of all associated models
// Example output for debugging or audit purposes
foreach ($allAffectedModels as $model) {
echo "Modified {$model->getMorphClass()}: ID {$model->id}";
}
Configure different redaction pipelines for different channels in config/api-logs.php. Each channel can have its own redaction strategy based on the destination's requirements:
'channels' => [
// Raw logs - no redaction for internal secure storage
'api_logs_raw' => [],
// General monitoring - basic redaction for security
'api_logs_redacted' => [
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
],
// External monitoring services - tailored redaction
'api_logs_sentry' => [
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['**.email', '**.phone', '**.ssn'],
],
],
'api_logs_axiom' => [
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['request.body.internal_id', 'response.body.debug_info'],
],
],
],
CommonHeaderFieldsRedactor: Redacts authentication headers (extends DotNotationRedactor)CommonBodyFieldsRedactor: Redacts password fields (extends DotNotationRedactor)DotNotationRedactor: Base redactor using dot notation (supports * and ** wildcards)The DotNotationRedactor supports powerful wildcard patterns:
Single wildcard (*): Matches one level
'users.*.email' // Matches users.1.email, users.john.email, etc.
Deep wildcard (**): Matches any level of nesting
'**.card.number' // Matches card.number anywhere in the data structure
'**.password' // Matches password fields at any depth
Examples:
// Traditional specific paths
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['request.body.users.0.password', 'request.body.users.1.password'],
]
// Using single wildcards
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['request.body.users.*.password'],
]
// Using deep wildcards for complex nested data
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['**.password', '**.card.number', '**.ssn'],
]
The default ApiLogItem stores lightweight references. You can extend it to store additional data:
// Create custom model
class CustomApiLogItem extends \Prahsys\ApiLogs\Models\ApiLogItem
{
protected $fillable = [
// Default fields
'request_id', 'path', 'method', 'api_version',
'request_at', 'response_at', 'response_status', 'is_error',
// Custom fields
'request_payload', 'response_payload', 'user_id', 'client_ip'
];
protected $casts = [
// Default casts
'request_at' => 'datetime:Y-m-d H:i:s.v',
'response_at' => 'datetime:Y-m-d H:i:s.v',
'response_status' => 'integer',
'is_error' => 'boolean',
// Custom casts
'request_payload' => 'json',
'response_payload' => 'json',
];
}
// Create custom migration
Schema::table('api_log_items', function (Blueprint $table) {
$table->json('request_payload')->nullable();
$table->json('response_payload')->nullable();
$table->string('user_id')->nullable();
$table->string('client_ip')->nullable();
});
// Update config
'models' => [
'api_log_item' => \App\Models\CustomApiLogItem::class,
],
Alternative approaches:
The easiest way to create custom redactors is by extending DotNotationRedactor, just like the built-in CommonHeaderFieldsRedactor and CommonBodyFieldsRedactor:
<?php
namespace App\Redactors;
use Prahsys\ApiLogs\Redactors\DotNotationRedactor;
class PciRedactor extends DotNotationRedactor
{
public function __construct(array $additionalPaths = [], string|\Closure $replacement = '[REDACTED]')
{
$pciPaths = [
// Credit card numbers
'**.card.number',
'**.card_number',
'**.cc_number',
// CVV codes
'**.card.cvv',
'**.card.cvc',
'**.cvv',
'**.cvc',
// Expiry dates
'**.card.expiry',
'**.card.exp_month',
'**.card.exp_year',
// Track data
'**.track1',
'**.track2',
'**.magnetic_stripe',
];
parent::__construct(
array_merge($pciPaths, $additionalPaths),
$replacement
);
}
}
<?php
namespace App\Redactors;
use Prahsys\ApiLogs\Redactors\DotNotationRedactor;
class HipaaRedactor extends DotNotationRedactor
{
public function __construct(array $additionalPaths = [], string|\Closure $replacement = '[REDACTED]')
{
$hipaaPaths = [
// Patient identifiers
'**.patient.ssn',
'**.patient.medical_record_number',
'**.patient.account_number',
'**.patient.insurance_id',
// Biometric data
'**.biometric',
'**.fingerprint',
'**.voice_print',
// Health information
'**.diagnosis',
'**.medical_condition',
'**.treatment',
'**.medication',
// Deep wildcard patterns for nested patient data
'**.patient.**.personal_id',
'**.health_record.**',
];
parent::__construct(
array_merge($hipaaPaths, $additionalPaths),
$replacement
);
}
}
<?php
namespace App\Redactors;
use Prahsys\ApiLogs\Redactors\DotNotationRedactor;
class PiiRedactor extends DotNotationRedactor
{
public function __construct(array $additionalPaths = [], string|\Closure $replacement = '[REDACTED]')
{
$piiPaths = [
// Personal identifiers
'**.ssn',
'**.social_security_number',
'**.sin',
'**.national_id',
'**.passport_number',
'**.drivers_license',
// Contact information
'**.email',
'**.phone',
'**.phone_number',
'**.mobile',
'**.address',
'**.street_address',
'**.postal_code',
'**.zip_code',
// Financial information
'**.bank_account',
'**.routing_number',
'**.iban',
'**.account_number',
// Deep patterns for user objects
'**.user.email',
'**.user.phone',
'**.users.*.email',
'**.users.*.phone',
];
parent::__construct(
array_merge($piiPaths, $additionalPaths),
$replacement
);
}
}
You can also provide custom replacement logic using closures:
<?php
namespace App\Redactors;
use Prahsys\ApiLogs\Redactors\DotNotationRedactor;
class SmartRedactor extends DotNotationRedactor
{
public function __construct()
{
$paths = ['**.card.number', '**.email'];
$customReplacement = function ($value, $path) {
if (str_contains($path, 'card.number')) {
// Show only last 4 digits of card numbers
return '****-****-****-' . substr($value, -4);
}
if (str_contains($path, 'email')) {
// Partially redact email addresses
[$local, $domain] = explode('@', $value);
return substr($local, 0, 2) . '***@' . $domain;
}
return '[REDACTED]';
};
parent::__construct($paths, $customReplacement);
}
}
Once created, use your custom redactors in your channel configuration:
// config/api-logs.php
'channels' => [
'api_logs_pci_compliant' => [
\App\Redactors\PciRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
],
'api_logs_healthcare' => [
\App\Redactors\HipaaRedactor::class,
\App\Redactors\PiiRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
],
],
You can listen to CompleteApiLogItemEvent to add custom processing:
use Prahsys\ApiLogs\Events\CompleteApiLogItemEvent;
// In your EventServiceProvider
protected $listen = [
CompleteApiLogItemEvent::class => [
YourCustomListener::class,
],
];
The event contains:
requestId: The correlation IDapiLogItemId: Database ID of the ApiLogItemmodels: Array of associated modelsapiLogData: Complete API log data objectThis package provides features that are generally useful for compliance requirements:
The package integrates seamlessly with external monitoring services:
Sentry Integration:
// config/prahsys-api-logs.php
'channels' => [
'api_logs_sentry' => [
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
\App\Redactors\PiiRedactor::class, // Custom PII redactor
],
],
// config/logging.php
'api_logs_sentry' => [
'driver' => 'sentry',
'level' => 'error',
'bubble' => true,
],
Axiom Integration:
// Custom logger for Axiom
class AxiomLogger
{
public function __invoke(array $config)
{
return new AxiomHandler($config['dataset']);
}
}
// Channel configuration with Axiom-specific redaction
'api_logs_axiom' => [
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\DotNotationRedactor::class => [
'paths' => ['request.body.internal_metrics'],
],
],
Other Services:
ApiLogItems are designed for long-term retention (365 days by default) but can be pruned using Laravel's built-in model pruning:
# Run model pruning manually
php artisan model:prune
# Schedule in your app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('model:prune')->daily();
}
Configure retention in your environment:
# Retain database references for 1 year (default)
API_LOGS_TTL_HOURS=8760
# Or configure in config file
'database' => [
'pruning' => [
'ttl_hours' => 24 * 365, // 365 days
],
],
Separate from database retention, configure log rotation per channel:
// config/logging.php
'api_logs_raw' => [
'driver' => 'daily',
'path' => storage_path('logs/api_logs_raw.log'),
'days' => 14, // Rotate log files every 14 days
],
'api_logs_redacted' => [
'driver' => 'daily',
'days' => 90, // Keep redacted logs longer for analytics
],
Best practices:
Run the test suite:
vendor/bin/pest
For environments handling payment card data, configure appropriate redaction and retention:
// Enhanced channel configuration for PCI environments
'channels' => [
'api_logs_pci_raw' => [
\App\Redactors\PciRedactor::class, // Custom PCI redactor
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
],
'api_logs_pci_monitoring' => [
\App\Redactors\PciRedactor::class, // Custom PCI redactor
\Prahsys\ApiLogs\Redactors\CommonHeaderFieldsRedactor::class,
\Prahsys\ApiLogs\Redactors\CommonBodyFieldsRedactor::class,
],
],
// Logging channel with appropriate retention
// config/logging.php
'api_logs_pci_raw' => [
'driver' => 'daily',
'path' => storage_path('logs/pci/api_logs_raw.log'),
'level' => 'info',
'days' => 365, // Extended retention for audit requirements
'permission' => 0600, // Restricted access
],
For SOC 2 environments, configure comprehensive logging with security controls:
// SIEM integration for SOC 2 requirements
// config/logging.php
'channels' => [
'api_logs_soc2' => [
'driver' => 'stack',
'channels' => ['syslog', 'siem_service'],
'ignore_exceptions' => false,
],
'syslog' => [
'driver' => 'syslog',
'level' => 'info',
'facility' => LOG_USER,
],
],
Configure retention policies based on your compliance requirements:
// Example retention configuration
// config/logging.php
'api_logs_compliance' => [
'driver' => 'daily',
'path' => storage_path('logs/compliance/api_logs.log'),
'days' => 2555, // Extended retention as needed
'permission' => 0600,
],
This package is open-sourced software licensed under the MIT license.
For support, please open an issue on the GitHub repository or contact the maintainers.