Laravel 7 динамически устанавливает путь к журналу в классе заданий

Я создаю проект на Laravel 7.3 с несколькими заданиями, которые выполняются одновременно. Мне нужно, чтобы каждое задание записывало журналы в разные файлы с ежедневной ротацией. Имя файла журнала должно основываться на модели, которую обрабатывает задание.

Проблема в том, что я не могу найти разумного решения.

Что я пробовал:

1) создание нескольких каналов в config/logging.php.

Это работает, как и ожидалось, но на данный момент существует около 50 различных вакансий, и их количество продолжает расти. Метод уродлив и почти не поддерживается.

2) настройка Config(['logging.channels.CUSTOMCHANNEL.path' => storage_path('logs/platform/'.$this->platform->name.'.log')]);.

Возиться с переменной Config было плохой идеей, потому что за один раз выполнялось много заданий. В результате сообщения от одного задания часто записывались в журнал другого задания.

3) используя Log::useDailyFiles()

Похоже, это перестает работать с laravel 5.5 или 5.6. Просто получаю ошибку Call to undefined method Monolog\Logger::useDailyFiles(). Есть мысли, как заставить работать в laravel 7?

4) используя параметр tap для канала в config/logging.php.

Пример в документации laravel Нет идей, как передать модель name в CustomizeFormatter, чтобы указать имя файла.

Я почти уверен, что есть умное решение, и мне просто что-то не хватает. Есть предложения? Спасибо!


person 966p    schedule 04.04.2020    source источник


Ответы (2)


Вы можете унаследовать диспетчер журналов, чтобы разрешить динамическую конфигурацию

<?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;
    }
}

То же самое с поставщиком службы журналов Laravel, за исключением того, что его можно полностью заменить

<?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);
        });
    }
}

РЕДАКТИРОВАТЬ: Я только что видел, что поставщик службы журналов Laravel отсутствует в config/app.php, потому что он "жестко загружен" приложением. Вы по-прежнему можете заменить его, унаследовав само приложение

<?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));
    }
}

И, наконец, в bootstrap/app.php замените Illuminate\Foundation\Application на App\Foundation\Application

Например, если вы попробуете это

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

Laravel будет использовать конфигурацию канала single и писать в users.log, если ваша логика разрешения

$baseConfig['path'] = $model + '.log';
person Shizzen83    schedule 04.04.2020
comment
Спасибо за ваше решение. Работает, но при тестировании возникла странная проблема. Создан новый пустой контроллер и протестирован с помощью 1) Log :: info ('test'); 2) приложение ('журнал') - ›канал ('myconfigname: testmodel') -› информация ('тест'); Оба теста привели к ошибке Undefined offset: 1 в LogManager (строка разнесения). Есть предположения? Разве не странно, что статический Log :: info () использует тот же LogManager? - person 966p; 04.04.2020
comment
Да, мой пример кода работает, только если в названии канала стоит :, я его отредактировал, теперь он должен работать :) - person Shizzen83; 04.04.2020
comment
С удовольствием, я столкнулся с той же проблемой в отношении баз данных и файловых систем;) - person Shizzen83; 04.04.2020

У меня есть решение, которое я использую с Laravel 4, которое работает, хотя и не соответствует способу работы Laravel.

class UserTrackLogger
{
    /**
     * @var $full_path string
     */
    protected $full_path;
    /**
     * @var $tenant string
     */
    protected $tenant;
    /**
     * @var $user User
     */
    protected $user;
    /**
     * @var $request Request
     */
    protected $request;

    public static function log(string $message, Request $request, User $user, array $data = []): void
    {
        /** @noinspection PhpVariableNamingConventionInspection */
        $userTrack = new static($request, $user);
        $userTrack->write($message, $data);
    }

    protected function __construct(Request $request, User $user)
    {
        $this->request = $request;
        $this->user = $user;
        $this->tenant = app()->make('tenant')->tenant__name;
        $path = storage_path() . "/logs/{$this->tenant}/users";

        $filename = $this->user->username_with_name;
        $this->full_path = Formatter::formatPath("{$path}/{$filename}.log");
        self::makeFolder($this->full_path);
    }

    protected function write(string $message, array $data = []): void
    {
        $formatter = $this->getFormat();
        $record = [
            'message'    => $message,
            'context'    => $data,
            'extra'      => [],
            'datetime'   => date(Utility::DATETIME_FORMAT_DEFAULT),
            'level_name' => 'TRACK',
            'channel'    => '',
        ];
        file_put_contents($this->full_path, $formatter->format($record), FILE_APPEND);
    }

    protected function getFormat(): FormatterInterface
    {
        $ip = $this->request->getClientIp();
        $method = strtoupper($this->request->method());
        $format = "[%datetime%][{$this->tenant}][{$this->user->username}][{$this->user->name}]: $ip $method %message% %context%\n";
        return new LineFormatter($format, null, true);
    }

    protected static function makeFolder(string $full_path): bool
    {
        $path = dirname($full_path);
        if ( !is_dir($path) ) {
            return mkdir($path, 0755, true);
        }

        return false;
    }
}

И когда я хочу что-то записать, я делаю UserTrackLogger::log($request->fullUrl(), $request, $user, $data);

Я бы посоветовал создать регистратор, похожий на этот, но расширяющий RotatingFileHandler.

person Raza    schedule 09.10.2020