topoff/laravel-user-logger

Laravel User Logger

Maintainers

Package info

github.com/topoff/laravel-user-logger

pkg:composer/topoff/laravel-user-logger

Statistics

Installs: 6 573

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v10.2.1 2026-06-12 23:12 UTC

README

Latest Stable Version Latest Unstable Version License Total Downloads

Laravel User Logger with Pennant-based experiment measurement.

Requirements

  • Laravel
  • laravel/pennant

Installation

Using Composer is currently the only supported way to install this package.

composer require topoff/laravel-user-logger

Getting started

Publish the package config:

php artisan vendor:publish --tag=config

If you want to, create a dedicated user-logger database connection in config/database.php:

        'user-logger' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => 'userlogger',
            'username' => env('DB_USERNAME', ''),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],

Run migrations:

php artisan migrate

Set up Pennant (required for experiment variant storage/resolution):

php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"

Then set the Pennant DB connection to your user-logger connection in config/pennant.php:

'stores' => [
    'database' => [
        'driver' => 'database',
        'connection' => 'user-logger',
        'table' => 'features',
    ],
],

Run migrations again so the Pennant features table is created:

php artisan migrate

Experiments

Experiment measurement uses laravel/pennant. Configure tracked features in config/user-logger.php:

'experiments' => [
    'enabled' => true,
    'features' => [
        'landing-page-headline',
        'checkout-flow',
    ],
    'conversion_events' => [
        'conversion',
    ],
    'conversion_entity_types' => [],
    'nova' => [
        'enabled' => true,
    ],
    'pennant' => [
        'store' => 'user-logger',
        'connection' => 'user-logger',
        'table' => 'pennant_features',
        'auto_install' => true,
        'scope' => 'session',
    ],
],

Pennant storage is installed by this package via migrations on the user-logger connection (pennant_features table).
This makes feature resolutions shareable across multiple apps that point to the same user-logger database. With auto_install=true (default), the package also creates the Pennant table automatically at boot if it is missing.

The package only registers its own named Pennant store (user-logger by default). Your app's default database store is left untouched - point your own features at whatever store you like in config/pennant.php.

Flush all measured experiment data (asks for confirmation, use --force to skip):

php artisan user-logger:flush

Nova

When Nova is installed and experiments.nova.enabled is true, the package auto-registers the ExperimentMeasurement Nova resource.

If your app defines a fully custom Nova::mainMenu(...), you must also add the resource manually in that menu.

Testing

composer test

Performance Profiling

You can enable runtime profiling logs in config/user-logger.php:

'performance' => [
    'enabled' => true,
    'log_queries' => true,
    'slow_ms' => 500,
    'sample_rate' => 1.0,      // fraction of requests that persist a row
    'retention_days' => 30,    // used by model:prune, 0 disables pruning
],

When enabled, the package logs:

  • total request duration (request_duration_ms) - server-side time until response
  • user-logger boot duration (boot_duration_ms)
  • user-logger internal segment timings (user_logger.segments)
  • optional query counters (queries_total, queries_user_logger)
  • skip reason (skip_reason) when logging is bypassed

Slow request warnings can be emitted with slow_ms (set 0 to disable warnings). Slow-request warnings are always emitted, regardless of sample_rate.

Privacy & Data Retention

  • Client ips are pseudonymized by default with a keyed HMAC-SHA256 (key: user-logger.ip_salt, falls back to app.key). Use php artisan user-logger:haship to compute the stored value for a given ip. Note this is pseudonymization, not anonymization.
  • Set hash_ip to false to store ips in plain text instead. In that case you should configure retention.ip_days and schedule php artisan user-logger:prune-ips - it removes the stored ip from sessions older than the retention period while keeping the sessions themselves:
Schedule::command('user-logger:prune-ips')->daily();
  • Logs, sessions and performance logs can be pruned via Laravel's model:prune. Configure retention.logs_days, retention.sessions_days and performance.retention_days (all default to disabled except performance logs), then schedule for example:
Schedule::command('model:prune', [
    '--model' => [
        \Topoff\LaravelUserLogger\Models\Log::class,
        \Topoff\LaravelUserLogger\Models\Session::class,
        \Topoff\LaravelUserLogger\Models\PerformanceLog::class,
    ],
])->daily();

Sessions are only pruned once they have no remaining logs, so logs should use an equal or shorter retention than sessions.

Host Header Caution

The domains table is keyed by Request::getHost(). Make sure your web server or Laravel's TrustHosts middleware restricts allowed hosts, otherwise spoofed Host headers create unbounded rows.

Testing the logger in a host app

The logger is disabled in the testing environment by default. Set user-logger.enabled_in_testing to true (plus enabled) in tests that want to exercise it.

User-Agent Parsing Performance

matomo/device-detector supports cache-backed parsing:

'user_agent' => [
    'cache' => true,
],
  • cache: uses Laravel's default cache store to speed up parser internals.

The package automatically skips DeviceDetector bot matching when the request was already classified as a crawler via CrawlerDetect.

Referer Database

Referer detection uses the snowplow referer-parser matching logic, but the bundled database (resources/data/referers.json) is generated from the actively maintained matomo/searchengine-and-social-list definitions - including search engines (with keyword parameters), social networks and AI assistants (medium ai: ChatGPT, Claude, Gemini, ...). Email provider entries are carried over from the snowplow database.

The data refreshes itself on three levels:

  • A scheduled GitHub Action (.github/workflows/update-referers.yml) runs monthly, regenerates the database and - only when it changed - commits, tags the next patch release and pushes. Requires "Read and write permissions" for Actions in the repository settings.
  • Every composer update in this package repository regenerates the file automatically (post-update-cmd); commit the diff if there is one.
  • Manually: composer update-referers (updates the matomo list and regenerates in one step).

A custom database (snowplow json format) can be configured via user-logger.referer_data_path.