phplaravellaravel-7laravel-jobs

Laravel 7 set log path dynamically in Job class


Im building project on Laravel 7.3 with multiple Jobs that run at the same time. I need to make each Job write logs to different daily rotated file. The name of the log file should be based on model, that Job is processing.

The issue is I cant find smart solution.

What I have tried:

1) creating multiple channels in config/logging.php.

That works as expected but at the moment there are about 50 different Jobs and amount keeps growing. Method is ugly and hardly maintained.

2) setting up Config(['logging.channels.CUSTOMCHANNEL.path' => storage_path('logs/platform/'.$this->platform->name.'.log')]);.

Messing with Config variable was bad idea because of many Jobs running one time. As a result messages from one job often were written in another Job log.

3) using Log::useDailyFiles()

Seems like this stops working since laravel 5.5 or 5.6. Just getting error Call to undefined method Monolog\Logger::useDailyFiles(). Any thoughts how to make with work in laravel 7?

4) using tap parameter for channel in config/logging.php.

Example in laravel docs No ideas how to pass model name into CustomizeFormatter to setup file name.

Im almost sure there is smart solution and Im just missing something. Any suggests? Thanks!


Solution

  • You could inherit the log manager to allow a dynamic configuration

    <?php
    
    namespace App\Log;
    
    use Illuminate\Support\Str;
    use Illuminate\Log\LogManager as BaseLogManager;
    
    class LogManager extends BaseLogManager
    {
        /**
         * Get the log connection configuration.
         *
         * @param  string  $name
         * @return array
         */
        protected function configurationFor($name)
        {
            if (!Str::contains($name, ':')) {
                return parent::configurationFor($name);
            }
            [$baseName, $model] = explode(':', $name, 2);
            $baseConfig = parent::configurationFor($baseName);
            $baseConfig['path'] = ...; //your logic
            return $baseConfig;
        }
    }
    

    Likewise about Laravel's log service provider except this one can be totally replaced

    <?php
    
    namespace App\Log;
    
    use Illuminate\Support\ServiceProvider;
    
    class LogServiceProvider extends ServiceProvider
    {
        /**
         * Register the service provider.
         *
         * @return void
         */
        public function register()
        {
            $this->app->singleton('log', function ($app) {
                return new LogManager($app);
            });
        }
    }
    

    EDIT: I've just seen that Laravel's log service provider is missing from config/app.php, this is because it's "hard-loaded" by the application. You still can replace it by inheriting the application itself

    <?php
    
    namespace App\Foundation;
    
    use App\Log\LogServiceProvider;
    use Illuminate\Events\EventServiceProvider;
    use Illuminate\Routing\RoutingServiceProvider;
    use Illuminate\Foundation\Application as BaseApplication;
    
    class Application extends BaseApplication
    {
        /**
         * Register all of the base service providers.
         *
         * @return void
         */
        protected function registerBaseServiceProviders()
        {
            $this->register(new EventServiceProvider($this));
            $this->register(new LogServiceProvider($this));
            $this->register(new RoutingServiceProvider($this));
        }
    }
    

    And finally in bootstrap/app.php, replace Illuminate\Foundation\Application with App\Foundation\Application

    For example, if you try this

    app('log')->channel('single:users')->debug('test');
    

    Laravel will use the single channel's config and write to users.log if your resolution logic is

    $baseConfig['path'] = $model + '.log';