A Laravel package that ships your application logs to a central server.
This package is designed to work with Logger — a centralized log aggregation service built with Laravel.
Logger provides:
- Web Dashboard — Search, filter, and browse logs with pagination
- Multi-Project Support — Manage logs from multiple applications with isolated project keys
- Failing Controllers Report — Identify error hotspots by controller
- Retention Policies — Configurable per-project log retention (7, 14, 30, 90 days, or infinite)
- Webhook Notifications — Slack/Discord/Mattermost-compatible alerts for errors and critical events
- Dark Mode — Full dark theme support
Note: You can also use this package with any HTTP endpoint that accepts JSON log payloads.
- PHP 8.1 or higher
- Laravel 10, 11, or 12
- A queue driver (recommended: Redis, database, or SQS)
composer require adminintelligence/laravel-log-shipperPublish the configuration file:
php artisan vendor:publish --tag=log-shipper-configAdd the following to your .env file:
LOG_SHIPPER_ENABLED=true
LOG_SHIPPER_ENDPOINT=https://your-log-server.com/api/ingest
LOG_SHIPPER_KEY=your-project-api-key
LOG_SHIPPER_QUEUE=redis
LOG_SHIPPER_QUEUE_NAME=logs
If you're upgrading from a previous version and want to use the new features (IP obfuscation, enhanced status metrics, etc.), republish the configuration file:
php artisan vendor:publish --tag=log-shipper-config --force
⚠️ Warning: This will overwrite your existingconfig/log-shipper.phpfile. Make sure to back up any custom configurations first, or manually merge the new options from the published config.
New configuration options in recent versions:
ip_obfuscation- Privacy-compliant IP address obfuscationmax_payload_size- Payload size limiting for securitystatus.metrics.foldersize- Folder size monitoringstatus.metrics.node_npm- Node.js and npm version detectionstatus.metrics.dependency_checks- Outdated package detectionstatus.metrics.security_audits- Security vulnerability scanningstatus.monitored_folders- Folders to monitor for size metrics
Add the log shipper channel to your config/logging.php:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'log_shipper'],
'ignore_exceptions' => false,
],
'log_shipper' => [
'driver' => 'custom',
'via' => \AdminIntelligence\LogShipper\Logging\CreateCustomLogger::class,
'level' => 'error', // Only ship errors and above
],
// ... other channels
],Now any log at the configured level or above will be shipped to your central server:
Log::error('Something went wrong', ['order_id' => 123]);| Option | Description | Default |
|---|---|---|
enabled |
Enable/disable log shipping | true |
api_endpoint |
The URL of your log server | - |
api_key |
Your project's API key | - |
queue_connection |
Queue connection to use | default |
queue_name |
Queue name for log jobs | default |
retries |
Number of attempts before failing | 3 |
backoff |
Wait time between retries (seconds) | [2, 5, 10] |
circuit_breaker |
Circuit breaker configuration | enabled |
fallback_channel |
Local channel to use if shipping fails | null |
sanitize_fields |
Fields to redact from logs | See config |
send_context |
Context data to include | See config |
When a log event is shipped, the following data is sent:
| Field | Type | Description |
|---|---|---|
level |
string | Log level (error, warning, info, debug, etc.) |
message |
string | The log message |
context |
array | Any context array passed to the log call |
datetime |
string | Timestamp in Y-m-d H:i:s.u format |
channel |
string | The logging channel name |
extra |
array | Additional Monolog processor data |
These fields can be toggled via the send_context configuration:
| Field | Type | Default | Description |
|---|---|---|---|
user_id |
int|null | enabled | Authenticated user's ID |
ip_address |
string|null | enabled | Client IP address |
user_agent |
string|null | enabled | Browser/client user agent string |
request_method |
string|null | enabled | HTTP method (GET, POST, PUT, DELETE, etc.) |
request_url |
string|null | enabled | Full request URL including query string |
route_name |
string|null | enabled | Laravel route name |
controller_action |
string|null | enabled | Controller and action handling the request |
app_env |
string|null | enabled | Application environment (local, staging, production) |
app_debug |
bool|null | enabled | Whether debug mode is enabled |
referrer |
string|null | enabled | HTTP Referer header |
To disable specific context fields, update your config/log-shipper.php:
'send_context' => [
'user_id' => true,
'ip_address' => true,
'user_agent' => false, // disabled
// ...
],Here is an example of the JSON payload sent for a log event:
{
"level": "error",
"message": "Order processing failed",
"context": {
"order_id": 12345,
"reason": "Payment declined"
},
"datetime": "2025-12-11 14:30:45.123456",
"channel": "local",
"extra": {},
"user_id": "1",
"ip_address": "127.0.0.1",
"user_agent": "Mozilla/5.0...",
"request_method": "POST",
"request_url": "https://api.example.com/orders",
"route_name": "orders.store",
"controller_action": "App\\Http\\Controllers\\OrderController@store",
"app_env": "production",
"app_debug": false
}The package can automatically push system health metrics to your log server on a configurable interval. This helps you monitor the health of your application instances.
Add the following to your .env file:
LOG_SHIPPER_STATUS_ENABLED=true
LOG_SHIPPER_STATUS_ENDPOINT=https://your-log-server.com/api/stats
LOG_SHIPPER_STATUS_INTERVAL=5 # minutes (default: 5)You can also configure the disk path to monitor:
LOG_SHIPPER_DISK_PATH=/The LOG_SHIPPER_STATUS_INTERVAL setting supports flexible scheduling:
| Interval | Behavior | Example |
|---|---|---|
| 1-59 | Every N minutes | 5 = Every 5 minutes |
| 60-1439 | Every N hours | 120 = Every 2 hours, 180 = Every 3 hours |
| 1440+ | Daily | 1440 = Daily at midnight |
The scheduler automatically selects the appropriate cron expression based on your interval value.
Note: Status pushing relies on Laravel's scheduler. Ensure you have the scheduler running:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Here is an example of the JSON payload sent for a status update:
{
"timestamp": "2025-12-11T14:30:45+00:00",
"app_name": "Laravel",
"app_env": "production",
"app_debug": false,
"instance_id": "web-server-01",
"log_shipper_version": "1.0.0",
"system": {
"memory_usage": 25165824,
"memory_peak": 26214400,
"server_memory": {
"total": 17179869184,
"free": 4294967296,
"used": 12884901888,
"percent_used": 75.0
},
"cpu_usage": 12.5,
"php_version": "8.3.0",
"laravel_version": "11.0.0",
"uptime": 86400,
"disk_space": {
"total": 100000000000,
"free": 60000000000,
"used": 40000000000,
"percent_used": 40.0
}
},
"queue": {
"size": 15,
"connection": "redis",
"queue": "default"
},
"database": {
"status": "connected",
"latency_ms": 1.2
},
"filesize": {
"laravel.log": 102400
},
"foldersize": {
"logs": 5242880
}
}The status payload includes the following system metrics:
| Field | Type | Description |
|---|---|---|
memory_usage |
int | Current memory usage of the PHP process (bytes) |
memory_peak |
int | Peak memory usage of the PHP process (bytes) |
server_memory |
object | Server-level RAM metrics (see below) |
cpu_usage |
float|null | CPU usage percentage or load average |
php_version |
string | PHP version |
laravel_version |
string | Laravel framework version |
uptime |
int|null | System uptime in seconds (null if unavailable) |
disk_space |
object | Disk space metrics for monitored path |
node_version |
string|null | Node.js version (if enabled) |
npm_version |
string|null | npm version (if enabled) |
composer_outdated |
int | Count of outdated Composer packages (if enabled) |
npm_outdated |
int | Count of outdated npm packages (if enabled) |
composer_audit |
int | Count of Composer security advisories (if enabled) |
npm_audit |
int | Count of npm security vulnerabilities (if enabled) |
| Field | Type | Description |
|---|---|---|
total |
int|null | Total system RAM (bytes) |
free |
int|null | Available system RAM (bytes) |
used |
int|null | Used system RAM (bytes) |
percent_used |
float|null | Percentage of RAM in use |
Note on macOS: The
percent_usedincludes filesystem cache. This is normal macOS behavior and does not indicate memory pressure.
- Size: Queue jobs count (excluding the status job itself)
- Connection: Active queue connection
- Status: Connection status (
connectedordisconnected) - Latency: Query latency in milliseconds
- Driver: Active cache driver
- Files: Sizes of specific monitored files (configurable)
- Folders: Total sizes of specific monitored folders, including all nested files (configurable)
To configure which metrics are sent or to monitor specific files/folders, publish the config and update the status section:
'status' => [
'metrics' => [
'system' => true,
'queue' => true,
'database' => true,
'cache' => false,
'filesize' => true,
'foldersize' => true,
// Optional expensive metrics (disabled by default)
'node_npm' => false, // Node.js and npm versions
'dependency_checks' => false, // Outdated package counts
'security_audits' => false, // Security vulnerability counts
],
'monitored_files' => [
storage_path('logs/laravel.log'),
storage_path('logs/worker.log'),
],
'monitored_folders' => [
storage_path('logs'),
storage_path('app/cache'),
],
],Performance Note: The optional metrics (
node_npm,dependency_checks,security_audits) can be slow as they execute shell commands. Enable these only if you need them and have increased the job timeout accordingly (default: 120 seconds).
Note: Status pushing relies on Laravel's scheduler. Ensure you have the scheduler running:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
The package automatically redacts sensitive fields from your logs. The following field patterns are redacted by default:
| Field Pattern | Examples |
|---|---|
password |
password, user_password, password_hash |
password_confirmation |
password_confirmation |
credit_card |
credit_card, credit_card_number |
card_number |
card_number |
cvv |
cvv, card_cvv |
api_key |
api_key, stripe_api_key |
secret |
secret, client_secret |
token |
token, access_token, refresh_token |
authorization |
authorization |
Add additional fields to sanitize in the config file:
'sanitize_fields' => [
'password',
'credit_card',
'ssn', // add your own
// ...
],For privacy compliance (GDPR, CCPA), you can enable IP address obfuscation to anonymize client IP addresses before they are logged.
Add the following to your .env file:
LOG_SHIPPER_IP_OBFUSCATION_ENABLED=true
LOG_SHIPPER_IP_OBFUSCATION_METHOD=mask # or 'hash'Zeroes out the last octet of IPv4 addresses and the last 64 bits of IPv6 addresses:
- IPv4:
192.168.1.100→192.168.1.0 - IPv6:
2001:db8:85a3::8a2e:370:7334→2001:db8:85a3::
This method preserves geographic information while protecting individual privacy.
Generates a one-way hash of the IP address:
- IPv4:
192.168.1.100→ip_a3f5c8e91d2b4f67 - IPv6:
2001:db8:85a3::8a2e:370:7334→ip_b9e4d1c72a8f3e56
This method provides maximum privacy while maintaining consistency (the same IP always produces the same hash).
// config/log-shipper.php
'ip_obfuscation' => [
'enabled' => env('LOG_SHIPPER_IP_OBFUSCATION_ENABLED', false),
'method' => env('LOG_SHIPPER_IP_OBFUSCATION_METHOD', 'mask'), // 'mask' or 'hash'
],In production environments, the package automatically enforces HTTPS for the API endpoint. HTTP endpoints will be rejected to prevent credential leaks.
# Accepted in production
LOG_SHIPPER_ENDPOINT=https://secure.example.com/api/ingest
# Rejected in production (only allowed in local/staging/testing)
LOG_SHIPPER_ENDPOINT=http://insecure.example.com/api/ingestTo prevent denial-of-service attacks via oversized log payloads, the package limits the maximum payload size:
// config/log-shipper.php
'max_payload_size' => env('LOG_SHIPPER_MAX_PAYLOAD_SIZE', 1048576), // 1MB defaultIf a payload exceeds this limit, the context will be truncated and marked with _truncated: true.
By default, logs are shipped via queued jobs for better performance. If you prefer synchronous shipping (useful for debugging or simple setups), set:
LOG_SHIPPER_QUEUE=syncNote: Sync mode will block your application until the HTTP request completes. Use queued mode in production for better performance.
For high-traffic applications, dispatching a job for every log event can create significant queue pressure. You can enable batch shipping to buffer logs in Redis and ship them in chunks.
Add the following to your .env file:
LOG_SHIPPER_BATCH_ENABLED=true
LOG_SHIPPER_BATCH_SIZE=100
LOG_SHIPPER_BATCH_INTERVAL=1 # Run every minute- Redis connection (configured in
config/database.php) - Laravel Scheduler running
- Logs are pushed to a Redis list (
log_shipper_buffer) instead of dispatching a job immediately. - A scheduled command (
log-shipper:ship-batch) runs every minute. - The command pops logs in batches (default: 100) and dispatches a single job per batch.
- The job ships the batch to the log server in a single HTTP request.
This package includes built-in mechanisms to ensure your application remains stable even if the log server is down.
If the log server returns a 4xx/5xx error or is unreachable, the job will automatically retry based on your configuration.
// config/log-shipper.php
'retries' => 3,
'backoff' => [2, 5, 10], // Wait 2s, then 5s, then 10sTo prevent your queue from being flooded with failing jobs during an outage, the Circuit Breaker will temporarily stop dispatching new log jobs.
// config/log-shipper.php
'circuit_breaker' => [
'enabled' => true,
'failure_threshold' => 5, // Open circuit after 5 consecutive failures
'duration' => 300, // Keep circuit open for 5 minutes
],When the circuit is open, logs are skipped entirely to protect your application's performance.
If the log shipper fails to send logs (after all retries are exhausted), you can configure a fallback channel to ensure logs are not lost.
Add the following to your .env file:
LOG_SHIPPER_FALLBACK=dailyWhen a failure occurs, the original log payload will be written to the specified channel, along with failure details in the context.
Important: Do not set the fallback channel to
log_shipperitself, as this will create an infinite loop. The package automatically prevents this scenario.
Please see CHANGELOG.md for more information on what has changed recently.
Please see CONTRIBUTING.md for details.
If you discover a security vulnerability, please see SECURITY.md for reporting instructions.
The MIT License (MIT). Please see LICENSE for more information.