faustoff/laravel-contextify

Contextual logging with inline notifications for Laravel.

Installs: 1 009

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

pkg:composer/faustoff/laravel-contextify

4.0.0 2025-12-03 12:50 UTC

README

Packagist Version Packagist Downloads Packagist License GitHub Repo stars

Laravel Contextify

Contextual logging with inline notifications for Laravel.

<?php

use Faustoff\Contextify\Facades\Contextify;

Contextify::notice('Order created', ['id' => $id])->notify(['mail']);
// [2025-01-01 12:00:00] production.NOTICE: Order created {"id":1} {"trace_id":"4f9c2a1b"}

Laravel Contextify enhances Laravel's logging with two main features:

  1. Inline Notificationssend notifications alongside logging without splitting code into multiple lines for logging and notifying.
  2. Automatic Context Enrichment — logs and notifications include extra contextual data from configured Context Providers (built-in: Trace ID, Process ID, Hostname, Call file and line, and more), helping you keep messages short and clean by moving additional context out of the message itself into a separate area.

Provides Contextify facade compatible with Laravel's Log facade: same methods (debug, info, notice, warning, error, critical, alert, emergency) with identical parameters, plus a chainable notify() method.

Name origin: “Contextify” combines Context and Notify, reflecting its dual purpose — to enrich logs with contextual data and to send notifications for log events.

Features

Requirements

  • PHP 8.0 or higher
  • Laravel 8.0 or higher
  • Monolog 2.0 or higher

Installation

Install the package via Composer:

composer require faustoff/laravel-contextify

Configuration

Optionally, publish the configuration file:

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

This creates config/contextify.php for configuring Context Providers and notifications.

Environment Variables

Add to .env to configure notification recipients:

CONTEXTIFY_MAIL_ADDRESSES=admin@example.com,team@example.com
CONTEXTIFY_TELEGRAM_CHAT_ID=123456789

Note: Telegram notifications require the laravel-notification-channels/telegram package to be installed manually.

Usage

Writing Logs

Use the Contextify facade like Laravel's Log facade. Logs automatically include extra context from Context Providers configured for logging:

<?php

use Faustoff\Contextify\Facades\Contextify;

Contextify::debug('Debug message', ['key' => 'value']);
// [2025-01-01 12:00:00] local.DEBUG: Debug message {"key":"value"} {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Services/ExampleService.php:42","class":"App\\Services\\ExampleService"}

Contextify::info('User logged in', ['user_id' => 123]);
// [2025-01-01 12:00:00] local.INFO: User logged in {"user_id":123} {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Http/Controllers/Auth/LoginController.php:55","class":"App\\Http\\Controllers\\Auth\\LoginController"}

Contextify::notice('Important notice');
// [2025-01-01 12:00:00] local.NOTICE: Important notice  {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"routes/web.php:10","class":null}

// ... and the same for warning, error, critical, alert and emergency

Sending Notifications

Chain notify() after any logging method to send notifications. Notifications include the log message, context, and extra context from Context Providers configured for notifications.

Filter channels using only and except parameters:

<?php

use Faustoff\Contextify\Facades\Contextify;

Contextify::error('Payment processing failed', ['order_id' => 456])->notify();
// [2025-01-01 12:00:00] local.ERROR: Payment processing failed {"order_id":456} {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Http/Controllers/Api/OrderController.php:133","class":"App\\Http\\Controllers\\Api\\OrderController"}
// Notification with context {"order_id":456} and extra context sent to all configured notification channels

Contextify::critical('Database connection lost')->notify(only: ['mail']);
// [2025-01-01 12:00:00] local.CRITICAL: Database connection lost  {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/MonitorCommand.php:71","class":"App\\Console\\Commands\\MonitorCommand"}
// Notification with extra context sent to a mail channel only

Contextify::alert('Security breach detected')->notify(except: ['telegram']);
// [2025-01-01 12:00:00] local.ALERT: Security breach detected  {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Providers/AppServiceProvider.php:25","class":"App\\Providers\\AppServiceProvider"}
// Notification with extra context sent to all configured notification channels except a Telegram channel

If necessary, you can override the default implementation of the LogNotification:

<?php

namespace App\Notifications;

use Faustoff\Contextify\Notifications\LogNotification;

class CustomLogNotification extends LogNotification
{
    // Override methods as needed
}

Update configuration:

'notifications' => [
    'class' => \App\Notifications\CustomLogNotification::class,
    // ... other notifications settings
],

Exception Notifications

Exceptions are automatically reported via notifications (enabled by default). Notifications include exception details (message and stack trace) and extra context from Context Providers configured for notifications.

If necessary, you can override the default implementation of the ExceptionNotification:

<?php

namespace App\Notifications;

use Faustoff\Contextify\Notifications\ExceptionNotification;

class CustomExceptionNotification extends ExceptionNotification
{
    // Override methods as needed
}

Update configuration:

'notifications' => [
    'exception_class' => \App\Notifications\CustomExceptionNotification::class,
    // ... other notifications settings
],

To disable automatic exception notifications, set reportable to null:

'notifications' => [
    'reportable' => null,
    // ... other notifications settings
],

Note: ExceptionNotificationFailedException prevents infinite loops when exception notifications fail.

Context Providers

Context Providers add extra contextual data to logs and notifications, helping you keep log entry and notification messages short and clean by moving additional context out of the message itself into a separate context area. The contextual data is still present in the log entry or notification, but it's separated from the message itself—keeping the message focused while preserving all information for searching and analysis. You no longer need to worry about explicitly passing the required contextual data each time, as it will be added automatically.

Static Context Providers

Static providers return data that remains constant throughout the request/process lifecycle. They implement StaticContextProviderInterface.

Built-in:

Refreshing Static Context

Static context is cached during application boot. Use touch() to refresh it manually, useful when a process is forked (e.g., queue workers) to generate a new trace ID:

<?php

use Faustoff\Contextify\Facades\Contextify;
use Faustoff\Contextify\Context\Providers\TraceIdContextProvider;

// Refresh specific provider (e.g., generate new trace ID)
Contextify::touch(TraceIdContextProvider::class);

// Refresh all static providers
Contextify::touch();

Dynamic Context Providers

Dynamic providers refresh data on each log call. They implement DynamicContextProviderInterface.

Built-in:

Creating Custom Context Providers

Implement StaticContextProviderInterface or DynamicContextProviderInterface:

<?php

namespace App\Context\Providers;

use Faustoff\Contextify\Context\Contracts\StaticContextProviderInterface;

class CustomContextProvider implements StaticContextProviderInterface
{
    public function getContext(): array
    {
        return [
            // implement ...
        ];
    }
}

Registering Custom Providers

Add custom providers to config/contextify.php:

<?php

use App\Context\Providers\CustomContextProvider;
use Faustoff\Contextify\Context\Providers\CallContextProvider;
use Faustoff\Contextify\Context\Providers\EnvironmentContextProvider;
use Faustoff\Contextify\Context\Providers\HostnameContextProvider;
use Faustoff\Contextify\Context\Providers\ProcessIdContextProvider;
use Faustoff\Contextify\Context\Providers\TraceIdContextProvider;

return [
    'logs' => [
        'providers' => [
            // Built-in providers
            ProcessIdContextProvider::class,
            TraceIdContextProvider::class,
            CallContextProvider::class,
            
            // Custom providers
            CustomContextProvider::class,
        ],
        
        // ... other logs settings
    ],

    'notifications' => [
        'providers' => [
            // Built-in providers
            HostnameContextProvider::class,
            ProcessIdContextProvider::class,
            TraceIdContextProvider::class,
            EnvironmentContextProvider::class,
            CallContextProvider::class,
            
            // Custom providers
            CustomContextProvider::class,
        ],
        
        // ... other notifications settings
    ],
];

Group-Based Context

Define separate Context Providers for logs and notifications. If a provider appears in both sets, the same context data is used for both.

Configure in config/contextify.php:

  • logs.providers — providers for log entries
  • notifications.providers — providers for notifications

Example:

<?php

use Faustoff\Contextify\Context\Providers\CallContextProvider;
use Faustoff\Contextify\Context\Providers\EnvironmentContextProvider;
use Faustoff\Contextify\Context\Providers\HostnameContextProvider;
use Faustoff\Contextify\Context\Providers\PeakMemoryUsageContextProvider;
use Faustoff\Contextify\Context\Providers\ProcessIdContextProvider;
use Faustoff\Contextify\Context\Providers\TraceIdContextProvider;

return [
    'logs' => [
        'providers' => [
            ProcessIdContextProvider::class,         // Shared
            TraceIdContextProvider::class,           // Shared
            CallContextProvider::class,              // Logs only
            PeakMemoryUsageContextProvider::class,   // Logs only
        ],
        
        // ... other logs settings
    ],

    'notifications' => [
        'providers' => [
            HostnameContextProvider::class,          // Notifications only
            EnvironmentContextProvider::class,       // Notifications only
            ProcessIdContextProvider::class,         // Shared
            TraceIdContextProvider::class,           // Shared
        ],
        
        // ... other notifications settings
    ],
];

Notifications

Supports mail and telegram channels out of the box. Mail works immediately; Telegram requires the laravel-notification-channels/telegram package.

Configuration

Configure channels in config/contextify.php:

'notifications' => [
    /*
     * Use associative array format ['channel' => 'queue'] to specify
     * queue per channel. Simple array ['channel'] uses 'default' queue.
     */
    'channels' => [
        'mail' => 'mail-queue',
        'telegram' => 'telegram-queue',
    ],
    
    'mail_addresses' => explode(',', env('CONTEXTIFY_MAIL_ADDRESSES', '')),
    
    // ... other notifications settings
],

Custom Notification Channels

For example, to add Slack notifications, you need to:

  1. Create a custom notification class with a toSlack() method implemented:
<?php

namespace App\Notifications;

use Faustoff\Contextify\Notifications\LogNotification;
use Illuminate\Notifications\Messages\SlackMessage;

class CustomLogNotification extends LogNotification
{
    public function toSlack($notifiable): SlackMessage
    {
        // See https://laravel.com/docs/12.x/notifications#formatting-slack-notifications
        
        return (new SlackMessage())
            ->content(ucfirst($this->level) . ': ' . $this->message);
    }
}
  1. Create a custom notifiable class with a routeNotificationForSlack() method implemented:
<?php

namespace App\Notifications;

use Faustoff\Contextify\Notifications\Notifiable;

class CustomNotifiable extends Notifiable
{
    public function routeNotificationForSlack($notification): string
    {
        // See https://laravel.com/docs/12.x/notifications#routing-slack-notifications
    
        return config('services.slack.notifications.channel');
    }
}
  1. Configure Slack in config/services.php.

  2. Update config/contextify.php:

'notifications' => [
    'class' => \App\Notifications\CustomLogNotification::class,
    'notifiable' => \App\Notifications\CustomNotifiable::class,
    'channels' => [
        'mail',
        'telegram',
        'slack'
    ],
    
    // ... other notifications settings
],

Note: For exception notifications, extend ExceptionNotification and add the toSlack() method similarly.

Want more notification channels? You are welcome to Laravel Notifications Channels.

Console Commands

Tracking

Use Faustoff\Contextify\Console\Trackable trait to log command start, finish, and execution time:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Faustoff\Contextify\Console\Trackable;
use Faustoff\Contextify\Facades\Contextify;

class SyncData extends Command
{
    use Trackable;

    protected $signature = 'data:sync';

    public function handle(): int
    {
        // Your business logic here
        
        Contextify::notice('Data was synced');

        return self::SUCCESS;
    }
}

Log:

[2025-01-01 12:00:00] local.DEBUG: Run with arguments {"command":"data:sync"} {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/SyncData.php:42","class":"App\\Console\\Commands\\SyncData"}
[2025-01-01 12:00:00] local.NOTICE: Data was synced {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/SyncData.php:42","class":"App\\Console\\Commands\\SyncData"}
[2025-01-01 12:00:00] local.DEBUG: Execution time: 1 second {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/SyncData.php:42","class":"App\\Console\\Commands\\SyncData"}

Output Capturing

Use Faustoff\Contextify\Console\Outputable trait to capture Laravel console output from info()-like methods and store it in logs:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Faustoff\Contextify\Console\Outputable;

class SyncData extends Command
{
    use Outputable;

    protected $signature = 'data:sync';

    public function handle(): int
    {
        // You business logic here
        
        $this->info('Data was synced');

        return self::SUCCESS;
    }
}

Log:

[2025-01-01 12:00:00] local.NOTICE: Data was synced {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/SyncData.php:42","class":"App\\Console\\Commands\\SyncData"}

Handling Shutdown Signals

Handle shutdown signals (SIGQUIT, SIGINT, SIGTERM by default) for graceful shutdown. Use the appropriate trait with SignalableCommandInterface:

  • TerminatableV62 for symfony/console:<6.3 (Laravel 9, 10)
  • TerminatableV63 for symfony/console:^6.3 (Laravel 9, 10)
  • TerminatableV70 for symfony/console:^7.0 (Laravel 11+)
<?php

namespace App\Console\Commands;

use Faustoff\Contextify\Console\TerminatableV62;
use Illuminate\Console\Command;
use Symfony\Component\Console\Command\SignalableCommandInterface;

class ConsumeStats extends Command implements SignalableCommandInterface
{
    use TerminatableV62;

    protected $signature = 'stats:consume';

    public function handle(): void
    {
        while (true) {
            // ...

            if ($this->shouldTerminate) {
                // Execution terminated by handle shutdown signal
                break;
            }
        }
    }
}

Log:

[2025-01-01 12:00:00] local.WARNING: Received SIGTERM (15) shutdown signal {"pid":12345,"trace_id":"4f9c2a1bd3e7a8f0","file":"app/Console/Commands/ConsumeStats.php:42","class":"App\\Console\\Commands\\ConsumeStats"}

License

This package is open-sourced software licensed under the MIT license.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues, questions, or contributions, please visit the GitHub repository.