fastnetksa/laravel-applogger

Configurable database and file log management package for Laravel applications.

Maintainers

Package info

github.com/rehankausar/applogger

pkg:composer/fastnetksa/laravel-applogger

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-04-27 15:46 UTC

This package is auto-updated.

Last update: 2026-04-27 15:51:34 UTC


README

A configurable database and file log management package for Laravel 11/12 applications.

Captures application logs to a database table via a custom Monolog channel, and provides a full-featured admin UI to browse, filter, inspect, and clean up both database logs and Laravel log files.

Features

  • Custom Monolog database channel — logs flow automatically to application_logs table
  • Config-driven log types (exception, payment, auth, etc.)
  • Keyword-based auto-detection of log type from message content
  • Tenant context fields — attach arbitrary columns (e.g. company_id, business_id) via invokable resolver classes
  • Dynamic Eloquent relationships — show related model data in log detail view without touching package code
  • Sensitive key redaction — passwords, tokens, API keys never stored in plain text
  • Admin UI with DataTables, filtering, log detail modal
  • File log management — list, view, download, delete Laravel log files
  • Health status API — JSON endpoint with 24-hour error/warning counts
  • Fully publishable: config, migration, views

Requirements

Dependency Version
PHP ^8.2
Laravel ^11.0 | ^12.0
yajra/laravel-datatables-oracle ^11.0 | ^12.0
monolog/monolog ^3.0

The admin UI also requires Bootstrap 5, Font Awesome, DataTables, and SweetAlert2 to be available in the host application's layout.

Installation

composer require fastnetksa/laravel-applogger

The ServiceProvider is auto-discovered via Laravel's package discovery — no manual registration needed.

Setup

1. Publish config

php artisan vendor:publish --tag=applogger-config

This creates config/applogger.php. Edit it to match your application.

2. Publish & customise migration

php artisan vendor:publish --tag=applogger-migrations

Open the published migration and add your tenant columns before running migrate:

// Inside Schema::create('application_logs', ...) — add after user_id:
$table->foreignId('company_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('business_id')->nullable()->constrained()->nullOnDelete();

3. Run migration

php artisan migrate

Monolog Channel Setup

Add a database channel to config/logging.php:

'channels' => [
    // ... existing channels ...

    'database' => [
        'driver' => 'custom',
        'via'    => \FastnetKSA\AppLogger\Logging\CreateDatabaseLogger::class,
        'level'  => 'debug',
    ],
],

Then set your default or stack channel to include database:

// Option 1 — add to your stack
'stack' => [
    'driver'   => 'stack',
    'channels' => ['single', 'database'],
],

// Option 2 — set as default
'default' => env('LOG_CHANNEL', 'database'),

All log calls (Log::error(...), Log::info(...), etc.) will now be persisted to the database automatically.

Layout Configuration

Set layout.value in config/applogger.php to the Blade view path of your application's main layout:

'layout' => [
    'value' => 'layouts.admin',   // → resources/views/layouts/admin.blade.php
],

The layout must contain @yield('content') where page content should appear.

If your layout also uses {{ $slot }}

When a layout dual-supports both component usage (<x-your-layout>) and extends usage (@extends), the $slot variable is only defined in the component context. To prevent an "Undefined variable $slot" error when the package uses @extends, change:

{{-- Before --}}
{{ $slot }}

{{-- After --}}
{{ $slot ?? '' }}

Route Configuration

Option A — Package-managed routes (default)

The package registers its own routes when routes.enabled = true:

'routes' => [
    'enabled'    => true,
    'prefix'     => 'admin/system/logs',
    'name'       => 'applogger.',
    'middleware' => ['web', 'auth'],
],

Routes available:

Method URI Name Action
GET {prefix}/ {name}index Log list & dashboard
GET {prefix}/{id} {name}show Log detail (JSON)
POST {prefix}/cleanup {name}cleanup Delete old logs
GET {prefix}/health/status {name}health Health check (JSON)
GET {prefix}/files/list {name}files File log list
GET {prefix}/files/view {name}files.view View file content (JSON)
GET {prefix}/files/download {name}files.download Download log file
POST {prefix}/files/delete {name}files.delete Delete a log file
POST {prefix}/files/clear-all {name}files.clear-all Clear all log files

Option B — Host-app-managed routes

If the host app has its own route group (e.g. with auth guards, permission middleware, locale prefix), disable the package routes and define them manually:

// config/applogger.php
'routes' => [
    'enabled' => false,
],
// routes/admin.php
use FastnetKSA\AppLogger\Http\Controllers\ApplicationLogsController;

Route::prefix('system/logs')->name('admin.system.logs.')->group(function () {
    Route::get('/',                 [ApplicationLogsController::class, 'index'])->name('index');
    Route::get('/{log}',            [ApplicationLogsController::class, 'show'])->name('show');
    Route::post('/cleanup',         [ApplicationLogsController::class, 'cleanup'])->name('cleanup');
    Route::get('/health/status',    [ApplicationLogsController::class, 'health'])->name('health');
    Route::get('/files/list',       [ApplicationLogsController::class, 'fileLogs'])->name('files');
    Route::get('/files/view',       [ApplicationLogsController::class, 'viewFile'])->name('files.view');
    Route::get('/files/download',   [ApplicationLogsController::class, 'downloadFile'])->name('files.download');
    Route::post('/files/delete',    [ApplicationLogsController::class, 'deleteFile'])->name('files.delete');
    Route::post('/files/clear-all', [ApplicationLogsController::class, 'clearAllFiles'])->name('files.clear-all');
});

Tenant Context Fields

Attach extra columns (e.g. company_id) to every log record automatically.

1. Ensure the column exists in your migration

$table->foreignId('company_id')->nullable()->constrained()->nullOnDelete();

2. Create an invokable resolver class

// app/Logging/Resolvers/CompanyIdResolver.php
namespace App\Logging\Resolvers;

class CompanyIdResolver
{
    public function __invoke(): ?int
    {
        return auth()->user()?->company_id;
    }
}

Important: Use an invokable class (not a closure). Closures cannot be serialised by php artisan config:cache.

3. Register in config

// config/applogger.php
'context_fields' => [
    [
        'column'   => 'company_id',
        'resolver' => \App\Logging\Resolvers\CompanyIdResolver::class,
    ],
    [
        'column'   => 'business_id',
        'resolver' => \App\Logging\Resolvers\BusinessIdResolver::class,
    ],
],

Dynamic Relationships

Display related model data in the log detail modal without modifying package code.

// config/applogger.php
'relationships' => [
    'company' => [
        'model'   => \App\Models\Company::class,
        'foreign' => 'company_id',   // FK column on application_logs
        'display' => 'name',         // attribute to show in the detail view
    ],
    'business' => [
        'model'   => \App\Models\Business::class,
        'foreign' => 'business_id',
        'display' => 'name',
    ],
],

The ServiceProvider registers these as runtime belongsTo relationships on ApplicationLog using resolveRelationUsing(). The controller loads them with with([...]) and renders the display attribute in the detail modal.

Log Types

Define the type vocabulary for your application:

// config/applogger.php
'types' => [
    'exception' => 'Exception',
    'auth'      => 'Authentication',
    'system'    => 'System',
    'database'  => 'Database',
    'invoice'   => 'Invoice',
    'payment'   => 'Payment',
],

Keyword auto-detection

When a log entry has no explicit type in its context, the handler scans the message for keywords:

'type_keywords' => [
    'payment'  => 'payment',
    'invoice'  => 'invoice',
    'auth'     => 'auth',
    'login'    => 'auth',
    'database' => 'database',
    'query'    => 'database',
],

'default_type' => 'system',  // fallback when no keyword matches

First match wins (top-to-bottom). To set an explicit type, pass it in log context:

Log::error('Payment gateway timeout', ['type' => 'payment']);

Static Logging Helpers

ApplicationLog provides static helpers for direct programmatic logging (bypassing Monolog):

use FastnetKSA\AppLogger\Models\ApplicationLog;

ApplicationLog::logError('payment', 'Stripe charge failed', ['order_id' => 42], $exception);
ApplicationLog::logWarning('invoice', 'PDF generation slow', ['duration_ms' => 3200]);
ApplicationLog::logInfo('auth', 'User logged in', ['user_id' => 1]);
ApplicationLog::logDebug('system', 'Cache miss for key settings.theme');

All helpers call logEntry() internally, which resolves tenant context fields automatically.

Sensitive Key Redaction

Context keys listed in sensitive_keys are replaced with [REDACTED] before being stored:

'sensitive_keys' => [
    'password',
    'token',
    'secret',
    'api_key',
    'credit_card',
],

View Customisation

Publish views to override them individually:

php artisan vendor:publish --tag=applogger-views

Files are copied to resources/views/vendor/applogger/logs/. Laravel automatically uses these instead of the package originals. You can override one file without touching the other.

Health Status API

GET {prefix}/health/status

Returns JSON:

{
    "status": "healthy",
    "total_logs_24h": 142,
    "errors_24h": 3,
    "warnings_24h": 11,
    "last_error": { ... }
}

Status values: healthy / warning / critical — determined by thresholds:

'health' => [
    'critical_errors' => 100,   // errors/24h to trigger critical
    'warning_errors'  => 10,    // errors/24h to trigger warning
],

Eloquent Query Scopes

use FastnetKSA\AppLogger\Models\ApplicationLog;

ApplicationLog::errors()->get();                          // level = error
ApplicationLog::warnings()->get();                        // level = warning
ApplicationLog::level('info')->get();                     // any level
ApplicationLog::type('payment')->get();                   // by type
ApplicationLog::dateRange('2026-01-01', '2026-01-31')->get(); // date range

Security Notes

  • File path traversal protection: viewFile, downloadFile, and deleteFile validate that the resolved real path starts with storage_path('logs'). Requests with ../ style paths are rejected with 404.
  • laravel.log is protected from deletion by deleteFile. clearAllFiles truncates it to empty rather than deleting.
  • Sensitive context keys are redacted at write time — they are never stored even in the context JSON column.

Package Structure

laravel-applogger/
├── composer.json
├── config/
│   └── applogger.php                    ← default config (publish to override)
├── database/
│   └── migrations/
│       └── ..._create_application_logs_table.php
├── resources/
│   └── views/
│       └── logs/
│           ├── index.blade.php          ← database log list + dashboard
│           └── files.blade.php          ← file log manager
├── routes/
│   └── applogger.php
└── src/
    ├── AppLoggerServiceProvider.php
    ├── Http/
    │   └── Controllers/
    │       └── ApplicationLogsController.php
    ├── Logging/
    │   ├── CreateDatabaseLogger.php     ← Monolog factory
    │   └── DatabaseLogHandler.php       ← Monolog handler → DB
    └── Models/
        └── ApplicationLog.php

Publishable Tags

Tag What it publishes
applogger-config config/applogger.php
applogger-migrations database/migrations/..._create_application_logs_table.php
applogger-views resources/views/vendor/applogger/logs/

License

MIT