Можем ли мы создать пользовательский файл журнала для разных целей в laravel 5.2, как и для записей журнала, связанных с порядком, которые должны быть в файле order.log и для связанных с оплатой материалов запись должна войти в файл payments.log
Я хочу найти лучший способ Ларавеля.
В настоящее время мы можем изменять частоту файла журнала (например, ежедневно, одиночно), или мы можем изменить имя файла журнала, отличного от значения по умолчанию, т.е. laravel.log
Здесь вы идете … Я потратил так много времени, чтобы добавить пользовательскую функциональность к Monolog, которая способна сделать ТОТ надлежащим образом. Я пробовал много разных способов, но все было немного взломанным. Наконец, я нашел хороший способ заставить эту функциональность работать …
Поскольку приложение является большим, мне нужны отдельные файлы журналов и максимально поддерживать существующий интерфейс журнала Laravel. Мне нужно было что-то вроде:
Log::write('audit', 'User logged in to the app.');
Log::info('event', 'User sent out 2 emails.');
Решение:
App \ Providers \ AppServiceProvider.php (добавить в функцию регистрации)
//Facade to Object binding $this->app->bind('chanellog', 'App\Helpers\ChannelWriter');
config \ app.php (добавить в псевдонимы)
//Custom Alias Class 'ChannelLog' => App\Contracts\Facades\ChannelLog::class,
App \ КОНТРАКТЫ \ Фасады \ ChannelLog.php
<?php namespace App\Contracts\Facades; use Illuminate\Support\Facades\Facade; /** * @see \Illuminate\Log\Writer */ class ChannelLog extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'chanellog'; } }
App \ Helpers \ ChannelWriter.php
<?php namespace App\Helpers; use Monolog\Logger; use App\Helpers\ChannelStreamHandler; class ChannelWriter { /** * The Log channels. * * @var array */ protected $channels = [ 'event' => [ 'path' => 'logs/audit.log', 'level' => Logger::INFO ], 'audit' => [ 'path' => 'logs/audit.log', 'level' => Logger::INFO ] ]; /** * The Log levels. * * @var array */ protected $levels = [ 'debug' => Logger::DEBUG, 'info' => Logger::INFO, 'notice' => Logger::NOTICE, 'warning' => Logger::WARNING, 'error' => Logger::ERROR, 'critical' => Logger::CRITICAL, 'alert' => Logger::ALERT, 'emergency' => Logger::EMERGENCY, ]; public function __construct() {} /** * Write to log based on the given channel and log level set * * @param type $channel * @param type $message * @param array $context * @throws InvalidArgumentException */ public function writeLog($channel, $level, $message, array $context = []) { //check channel exist if( !in_array($channel, array_keys($this->channels)) ){ throw new InvalidArgumentException('Invalid channel used.'); } //lazy load logger if( !isset($this->channels[$channel]['_instance']) ){ //create instance $this->channels[$channel]['_instance'] = new Logger($channel); //add custom handler $this->channels[$channel]['_instance']->pushHandler( new ChannelStreamHandler( $channel, storage_path() .'/'. $this->channels[$channel]['path'], $this->channels[$channel]['level'] ) ); } //write out record $this->channels[$channel]['_instance']->{$level}($message, $context); } public function write($channel, $message, array $context = []){ //get method name for the associated level $level = array_flip( $this->levels )[$this->channels[$channel]['level']]; //write to log $this->writeLog($channel, $level, $message, $context); } //alert('event','Message'); function __call($func, $params){ if(in_array($func, array_keys($this->levels))){ return $this->writeLog($params[0], $func, $params[1]); } } }
App \ Helpers \ ChannelStreamHandler.php
<?php namespace App\Helpers; use Monolog\Handler\StreamHandler; /** * Use channels to log into separate files * * @author Peter Feher */ class ChannelStreamHandler extends StreamHandler { /** * Channel name * * @var String */ protected $channel; /** * @param String $channel Channel name to write * @see parent __construct for params */ public function __construct($channel, $stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) { $this->channel = $channel; parent::__construct($stream, $level, $bubble); } /** * When to handle the log record. * * @param array $record * @return type */ public function isHandling(array $record) { //Handle if Level high enough to be handled (default mechanism) //AND CHANNELS MATCHING! if( isset($record['channel']) ){ return ( $record['level'] >= $this->level && $record['channel'] == $this->channel ); } else { return ( $record['level'] >= $this->level ); } } }
После этого вы можете сделать в любом файле:
use ChannelLog as Log; ... function myFunction(){ //Recommended (writes INFO to logs/event.log) Log::write('event', 'User sent out 3 voucher.') //Possible to use (writes ALERT to logs/audit.log) Log::alert('audit', 'User modified xyz entry.') //Or even: Log::write('audit', 'User modified xyz entry.', ['user'=>1]) }
Существует простой способ:
use Monolog\Logger; use Monolog\Handler\StreamHandler; $log = ['orderId' => 10, 'description' => 'Some description']; $orderLog = new Logger(order); $orderLog->pushHandler(new StreamHandler(storage_path('logs/order.log')), Logger::INFO); $orderLog->info('OrderLog', $log);
Выход в журналах / order.log:
[2017-04-30 00:00:00] order.INFO: OrderLog {"orderId":10, "description":"Some description"} []
Вы можете попробовать перепрограммировать функции журнала для записи разных типов журналов в разные файлы. Это можно сделать, отредактировав файл bootstrap/app.php
:
$app->configureMonologUsing(function($monolog) { $bubble = false; $infoStreamHandler = new Monolog\Handler\StreamHandler( storage_path("/logs/orders.log"), Monolog\Logger::INFO, $bubble); $monolog->pushHandler($infoStreamHandler); $warningStreamHandler = new Monolog\Handler\StreamHandler( storage_path("/logs/logins.log"), Monolog\Logger::WARNING, $bubble); $monolog->pushHandler($warningStreamHandler); });
Затем в вашем коде вы можете:
Log::info('Order was created', ['ORDER-123']); Log::warning('User login', ['USER-1']);
Вы можете использовать этот метод для редактирования всех доступных функций журнала:
Чтобы расширить ответ ShQ:
Одна из проблем, которую я заметил, это то, что журнал будет добавлен с [] []
, которые представляют собой пустые значения массива для $context
и $extra
в LineFormatter.format();
т.е. vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php
Существует два способа обойти это: либо предоставить формат, который не включает дополнительный или контекст для конструктора LineFormatter
, либо предоставить 4-й аргумент $ignoreEmptyContextAndExtra
= true
.
Все файлы в ответе ShQ остаются теми же, но ChannelStreamHandler
должен измениться.
ChannelStreamHandler:
<?php namespace App\Helpers; use Monolog\Formatter\LineFormatter; use Monolog\Handler\StreamHandler; use Monolog\Logger; /** * Use channels to log into separate files * */ class ChannelStreamHandler extends StreamHandler { /** * Channel name * * @var String */ protected $channel; /** * @param String $channel Channel name to write * @param bool|int $stream * @param bool|int $level * @param bool $bubble * @param null $filePermission * @param bool $useLocking * @see parent __construct for params */ public function __construct( $channel, $stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false ) { $this->channel = $channel; $formatter = new LineFormatter(null, null, false, true); $this->setFormatter($formatter); parent::__construct($stream, $level, $bubble); } /** * When to handle the log record. * * @param array $record * @return bool */ public function isHandling(array $record) { //Handle if Level high enough to be handled (default mechanism) //AND CHANNELS MATCHING! if (isset($record['channel'])) { return ($record['level'] >= $this->level && $record['channel'] == $this->channel); } else { return ($record['level'] >= $this->level); } } }
Важным изменением является предоставление 4-го параметра истины, который является $ignoreEmptyContextAndExtra
. Этот параметр указывает, что LineFormatter
игнорирует любой context
extra
массивов, если он пуст:
$formatter = new LineFormatter(null, null, false, true); $this->setFormatter($formatter);
Вы также должны убедиться, что ваш монолог 1.22 работает, потому что он включает исправление ошибки в отношении ignoreEmptyContextAndExtra
.
Я также добавил переопределение для info () в класс ChannelWritter
:
public function info($channel, $message, array $context = []) { $level = array_flip($this->levels)[$this->channels[$channel]['level']]; $this->writeLog($channel, $level, $message, $context); }
Кроме того, я не был доволен «ленивым регистратором нагрузки» в решении ShQ, поэтому был изменен для использования поставщика услуг / IoC
Заменить ChannelWriter.writeLog()
:
public function writeLog(string $channel, string $level, string $message, array $context = []) { if (!in_array($channel, array_keys($this->channels))) { throw new InvalidArgumentException('Invalid channel used.'); } $logger = \App::make("{$channel}log"); $channelHandler = new ChannelStreamHandler( $channel, storage_path() . '/' . $this->channels[$channel]['path'], $this->channels[$channel]['level'] ); $logger->pushHandler($channelHandler); $logger->{$level}($message); }
и в вашем AppServiceProvider
:
$this->app->bind('eventlog', function () { return new Logger('event'); }); $this->app->bind('auditlog', function () { return new Logger('audit'); });
Я попробую собрать это вместе в пакет.
Для меня в Laravel 5.3 я не уверен, что это была моя установка ранее, но я обнаружил, что bootstrap / app.php не работает для меня.
Мне нужно было поместить это в app / Providers / AppServiceProvider.php.
nb Это то, где раньше у меня была настройка уровня журнала из конфигурации, поэтому в итоге я получил 3 обработчика журналов.
public function register() { $monolog = Log::getMonolog(); foreach ($monolog->getHandlers() as $handler) { $handler->setLevel(Config::get('app.log_level')); } $bubble = false; $infoStreamHandler = new \Monolog\Handler\StreamHandler( storage_path("logs/info.log"), \Monolog\Logger::INFO, $bubble); $monolog->pushHandler($infoStreamHandler); $warningStreamHandler = new \Monolog\Handler\StreamHandler( storage_path("logs/warning.log"), \Monolog\Logger::WARNING, $bubble); $monolog->pushHandler($warningStreamHandler); }
Основанный на ответе ShQ, более короткий и простой помощник журнала, который позволяет вам входить в пользовательский файл «на лету». Вы также можете добавить свой собственный обработчик и задать путь к файлу.
App \ Helper
<?php /** * Logger helper to log into different files * * @package App\Helpers * @author Romain Laneuville <romain.laneuville@hotmail.fr> */ namespace App\Helpers; use Monolog\Logger; use Monolog\Handler\HandlerInterface; use Monolog\Handler\StreamHandler; /** * Class LogToChannels * * @package App\Helpers */ class LogToChannels { /** * The LogToChannels channels. * * @var Logger[] */ protected $channels = []; /** * LogToChannels constructor. */ public function __construct() { } /** * @param string $channel The channel to log the record in * @param int $level The error level * @param string $message The error message * @param array $context Optional context arguments * * @return bool Whether the record has been processed */ public function log(string $channel, int $level, string $message, array $context = []): bool { // Add the logger if it doesn't exist if (!isset($this->channels[$channel])) { $handler = new StreamHandler( storage_path() . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . $channel . '.log' ); $this->addChannel($channel, $handler); } // LogToChannels the record return $this->channels[$channel]->{Logger::getLevelName($level)}($message, $context); } /** * Add a channel to log in * * @param string $channelName The channel name * @param HandlerInterface $handler The channel handler * @param string|null $path The path of the channel file, DEFAULT storage_path()/logs * * @throws \Exception When the channel already exists */ public function addChannel(string $channelName, HandlerInterface $handler, string $path = null) { if (isset($this->channels[$channelName])) { throw new \Exception('This channel already exists'); } $this->channels[$channelName] = new Logger($channelName); $this->channels[$channelName]->pushHandler( new $handler( $path === null ? storage_path() . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . $channelName . '.log' : $path . DIRECTORY_SEPARATOR . $channelName . '.log' ) ); } /** * Adds a log record at the DEBUG level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function debug(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::DEBUG, $message, $context); } /** * Adds a log record at the INFO level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function info(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::INFO, $message, $context); } /** * Adds a log record at the NOTICE level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function notice(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::NOTICE, $message, $context); } /** * Adds a log record at the WARNING level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function warn(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::WARNING, $message, $context); } /** * Adds a log record at the WARNING level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function warning(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::WARNING, $message, $context); } /** * Adds a log record at the ERROR level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function err(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::ERROR, $message, $context); } /** * Adds a log record at the ERROR level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function error(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::ERROR, $message, $context); } /** * Adds a log record at the CRITICAL level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function crit(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::CRITICAL, $message, $context); } /** * Adds a log record at the CRITICAL level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return Boolean Whether the record has been processed */ public function critical(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::CRITICAL, $message, $context); } /** * Adds a log record at the ALERT level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function alert(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::ALERT, $message, $context); } /** * Adds a log record at the EMERGENCY level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function emerg(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::EMERGENCY, $message, $context); } /** * Adds a log record at the EMERGENCY level. * * @param string $channel The channel name * @param string $message The log message * @param array $context The log context * * @return bool Whether the record has been processed */ public function emergency(string $channel, string $message, array $context = []): bool { return $this->log($channel, Logger::EMERGENCY, $message, $context); } }
App \ Providers \ AppServiceProvider.php (добавить в функцию регистрации)
//Facade to Object binding $this->app->bind('LogToChannels', 'App\Helpers\LogToChannels');
config \ app.php (добавить в псевдонимы)
// Custom Alias Class 'Log' => App\Contracts\Facades\LogToChannels::class
Тогда в любом месте вашего приложения вы можете позвонить
Log::info('logger_name', 'Log message'); Log::error('other_logger_name', 'Log message', $someContext);
Вы даже можете настроить вывод журнала, вызывая
Log::addChannel('channel_name', $customHandler);
И он будет доступен, когда вы будете называть его имя в любом месте вашего приложения.