How to extend laravel logger? - php

I have created different files for different level of logging e.g Log::info, Log::error etc. Now I also want to send a message to slack only if an error comes. I have done this too by writing following code in app/bootstrap.php
$app->configureMonologUsing( function($monolog) {
$slackHandler = new Monolog\Handler\SlackHandler(env('SLACK_TOKEN'), env('SLACK_CHANNEL'), 'Monolog', true, null, Monolog\Logger::ERROR);
$monolog->pushHandler($slackHandler);
});
but it has overrided the default logger and now only slack logger is working and no logs are adding in storage/logs/ folder

The docs are pretty vague on what exactly this does but it looks like configureMonologUsing configures Monolog to use only the handlers which you specifically add to it. This means the handlers (I believe StreamHandler) which Laravel sets up to log to the laravel.log file are never added.
I'd recommend not using that and dropping this code into your AppServiceProvider which will add the new handler while keeping all the default handlers Laravel likes to add...
$monolog = \Log::getMonolog();
$slackHandler = new \Monolog\Handler\SlackHandler(env('SLACK_TOKEN'), env('SLACK_CHANNEL'), 'Monolog', true, null, Monolog\Logger::ERROR);
$monolog->pushHandler($slackHandler);

Related

Laravel: multiple log providers using `configureMonologUsing()`?

I'm using configureMonologUsing() to add in two custom loggers. Doing the standard SOLID principal, I have two providers: ConsoleLoggerProvider and MailLogProvider.
Both of these have a register similar to:
public function register()
{
app()->configureMonologUsing(function(\Monolog\Logger $monolog) {
$monolog->pushHandler(new HandlerClass());
});
}
However, I have noticed over logger will overwrite another logger... How do I stack these?
I've tried to use boot() as well, and that didn't work. I couldn't find any other way to add to the Monolog stack.
Preferable, I want to stack onto Laravel's built-in logger as well.
I (finally) found the answer my question:
Within my providers, instead of using configureMonologUsing(), I used Log::getMonolog()->pushHandler([..])
That works! All loggers, including built-in Laravel file logger, are firing. Finally!
(I've honestly been looking for days for a way to add onto the Monolog stack; I apparently wasn't searching by the right terms)
According to the Laravel documentation:
You should place a call to the configureMonologUsing method in your bootstrap/app.php file right before the $app variable is returned by the file.
In that case, thus should work for you: create two handler classes and add them to monolog this way (in your bootstrap/app.php):
$app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(new EmailLogHandler);
$monolog->pushHandler(new ConsoleLogHandler);
});
return $app;
Following Laravel 5.2 docs, in bootstrap/app.php, I added the following code right before return $app;:
$app->configureMonologUsing(function($monolog) {//IMPORTANT: I think the order of pushHandler matters, and the ones defined last here will be the first to be called, which affects anything where bubble=false
if (config('services.slack.send_errors_to_slack')) {
$bubble = false; //I think that if I set the 'bubble' argument to false and handle the most severe logging levels first (which counterintuitively means lower in this function), less severe logging levels don't bother reporting the same message.
$useShortAttachment = false;
$includeContextAndExtra = true; //This is important because otherwise 404 errors wouldn't report the URL, give how 'report' function is coded within App\Exceptions\Handler.php.
$handlerForWarningsToNotifyPhone = new \Monolog\Handler\SlackHandler(config('services.slack.token'), config('services.slack.channel_warnings'), 'Monolog', true, null, \Monolog\Logger::WARNING, $bubble, $useShortAttachment, $includeContextAndExtra);
$monolog->pushHandler($handlerForWarningsToNotifyPhone);
$handlerForErrorsToNotifyPhone = new \Monolog\Handler\SlackHandler(config('services.slack.token'), config('services.slack.channel_errors'), 'Monolog', true, null, \Monolog\Logger::ERROR, $bubble, $useShortAttachment, $includeContextAndExtra);
$monolog->pushHandler($handlerForErrorsToNotifyPhone);
}
if (config('app.send_logs_to_loggy')) {
$logglyHandler = new \Monolog\Handler\LogglyHandler(config('services.loggly.token'), config('app.send_logs_to_loggy')); //See \Monolog\Logger::INFO. Log level 200 is "info".
$logglyHandler->setTag(config('services.loggly.tag'));
$monolog->pushHandler($logglyHandler);
}
if (config('app.log_to_local_disk')) {
$localHandler = new \Monolog\Handler\StreamHandler(storage_path("/logs/laravel.log"));
$monolog->pushHandler($localHandler);
}
});
It's just an example that may help you.
Be sure to edit your config files accordingly (e.g. so that app.log_to_local_disk, services.slack.send_errors_to_slack, etc are available).
http://stackoverflow.com/a/36259944/470749 was helpful.
Here is how I able to configure on Laravel Lumen v5.4
in app.php:
$publisher = new \Gelf\Publisher(new \Gelf\Transport\HttpTransport(env('GRAYLOG_HOST'), env('GRAYLOG_PORT'), env('GRAYLOG_PATH')));
//WhatFailureGroupHandler does not break app execution
//if some exceptions happen happens while logging
$failureHandler = new \Monolog\Handler\WhatFailureGroupHandler([
new \Monolog\Handler\GelfHandler($publisher)
]);
\Log::pushHandler($failureHandler);
\Log::getMonolog() as on accepted answer threw error.
Also tried to configure using $app->configureMonologUsing() which threw A facade root has not been set. error. But at the end, I found out that was because we need to return logger:
$app->configureMonologUsing(function ($monolog) {
$publisher = new \Gelf\Publisher(new \Gelf\Transport\HttpTransport(env('GRAYLOG_HOST'), env('GRAYLOG_PORT'), env('GRAYLOG_PATH')));
$failureHandler = new \Monolog\Handler\WhatFailureGroupHandler([new \Monolog\Handler\GelfHandler($publisher)]);
$monolog->pushHandler($failureHandler);
//fixes error: A facade root has not been set
return $monolog;
});
All the examples of $app->configureMonologUsing() usage I have seen do not have a return statement, even in the other answers, which did not work for me.

Laravel 5.3 changing logfiles for specific console commands

There are two noisy console commands in my Laravel 5.3 app that I want to keep logs for but would prefer to have them write to a different log file from the rest of the system.
Currently my app writes logs to a file configured in bootstrap/app.php using $app->configureMonologUsing(function($monolog) { ...
Second prize is writing all console commands to another log file, but ideally just these two.
I tried following these instructions (https://blog.muya.co.ke/configure-custom-logging-in-laravel-5/ and https://laracasts.com/discuss/channels/general-discussion/advance-logging-with-laravel-and-monolog) to reroute all console logs to another file but it did not work and just caused weird issues in the rest of the code.
If this is still the preferred method in 5.3 then I will keep trying, but was wondering if there was newer method or a method to only change the file for those two console commands.
They are two approaches you could take
First, you could use Log::useFiles or Log::useDailyFiles like suggests here.
Log::useDailyFiles(storage_path().'/logs/name-of-log.log');
Log::info([info to log]);
The downside of this approach is that everything will still be log in your default log file because the default Monolog is executed before your code.
Second, to avoid to have everything in your default log, you could overwrite the default logging class. An exemple of this is given here. You could have a specific log file for let's say Log::info() and all the others logs could be written in your default file. The obvious downside of this approach is that it requires more work and code maintenance.
This is possible but first you need to remove existing handlers.
Monolog already has had some logging handlers set, so you need to get rid of those with $monolog->popHandler();. Then using Wistar's suggestion a simple way of adding a new log is with $log->useFiles('/var/log/nginx/ds.console.log', $level='info');.
public function fire (Writer $log)
{
$monolog = $log->getMonolog();
$monolog->popHandler();
$log->useFiles('/var/log/nginx/ds.console.log', $level='info');
$log->useFiles('/var/log/nginx/ds.console.log', $level='error');
...
For multiple handlers
If you have more than one log handler set (if for example you are using Sentry) you may need to pop more than one before the handlers are clear. If you want to keep a handler, you need to loop through all of them and then readd the ones you wanted to keep.
$monolog->popHandler() will throw an exception if you try to pop a non-existant handler so you have to jump through hoops to get it working.
public function fire (Writer $log)
{
$monolog = $log->getMonolog();
$handlers = $monolog->getHandlers();
$numberOfHandlers = count($handlers);
$saveHandlers = [];
for ($idx=0; $idx<$numberOfHandlers; $idx++)
{
$handler = $monolog->popHandler();
if (get_class($handler) !== 'Monolog\Handler\StreamHandler')
{
$saveHandlers[] = $handler;
}
}
foreach ($saveHandlers as $handler)
{
$monolog->pushHandler($handler);
}
$log->useFiles('/var/log/nginx/ds.console.log', $level='info');
$log->useFiles('/var/log/nginx/ds.console.log', $level='error');
...
For more control over the log file, instead of $log->useFiles() you can use something like this:
$logStreamHandler = new \Monolog\Handler\StreamHandler('/var/log/nginx/ds.console.log');
$pid = getmypid();
$logFormat = "%datetime% $pid [%level_name%]: %message%\n";
$formatter = new \Monolog\Formatter\LineFormatter($logFormat, null, true);
$logStreamHandler->setFormatter($formatter);
$monolog->pushHandler($logStreamHandler);

How to add a new exception handler to Laravel without disable the default one?

I'm using Sentry to keep tracking of exceptions from a Laravel application.
Sentry's docs say I should use the following code in my application bootstrap to setup the client:
$app->configureMonologUsing(function($monolog) {
$client = new Raven_Client('your dsn');
$handler = new Monolog\Handler\RavenHandler($client);
$handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n"));
$monolog->pushHandler($handler);
});
And that works fine!
The side effect is that Laravel's default exception handler, which writes the exceptions to the file at /storage/logs/laravel.log, stopped to work after adding the new exception handler.
How can I keep both handlers?
UPDATE for Laravel 5.6+
Starting on Laravel 5.6 log stacks are available. It now allows devs to set up multiple log channels with ease. Refer to the docs for more info on that.
You can look at Illuminate\Foundation\Bootstrap\ConfigureLogging to see how Laravel sets up its native logging. Then in your own bootstrap, just push another handler that does the same:
$app->configureMonologUsing(function($monolog) {
// Keep your existing Sentry configuration
$client = new Raven_Client('your dsn');
$handler = new Monolog\Handler\RavenHandler($client);
$handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n"));
$monolog->pushHandler($handler);
// Add another handler that writes to laravel.log
$handler = new Monolog\Handler\StreamHandler(storage_path('logs/laravel.log'));
$handler->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true, true));
$monolog->pushHandler($handler);
});
Edit:
If you don't need to reproduce Laravel's behaviour exactly, you could use the default formatter for a one-liner addition instead:
$monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path('logs/laravel.log')));
This differs from the native Laravel implementation in that it'll use the default constructor arguments for LineFormatter rather than those we were setting explicitly before.
A third option that reuses Laravel's logic for configuring Monolog would be the addition of this line instead:
with(new Illuminate\Log\Writer($monolog))->useFiles(storage_path('logs/laravel.log'));

Laravel Log useFiles method is making Log write in multiple files

I am using Laravel Log Facade in my app. And I have several services like Mandrill, Twilio, Stripe, etc., that need to be logged in separate file. But when I use Log::useFiles() to set separate file for one of the service wrapper class, like this:
Class Mailer
{
static function init()
{
Log::useFiles(storage_path('logs/mandrill-'.date("Y-m-d").'.log'));
}
static function send()
{
// some code here...
Log::error("Email not sent");
}
}
And I am ending up with log being written in both Laravel log file, and this Mandrill log file.
Is there a way to tell Log to write logs only in one file?
It's generally strange that it does that, because when I use directly Monolog, it writes only in one file, as it should. As far as I know Log Facade is using Monolog.
First of all, keep in mind that if you change the log handlers in your Mailer class you'll change them for the whole application.
Secondly, the reason that after your changes you get logs written to 2 files is that useFiles() does not overwrite the default log handler but adds a new handler to the handlers that Monolog will use. Therefore, you just add a second handler to the list and both of them handle the log message by saving them into different files.
Thirdly, Laravel's Log facade does not provide a way to replace the default handler - if you want to use it you need to use Monolog directly. You can access it by calling Log::getMonolog().

Laravel 5 different log levels for development and production

I'm using Laravel 5.1 and trying to set different logging logic for a development and production environment.
Throughout my application I am using the Log facade with most of the following different methods:
Log::emergency($error);
Log::alert($error);
Log::critical($error);
Log::error($error);
Log::warning($error);
Log::notice($error);
Log::info($error);
Log::debug($error);
However, in my production environment, I would like to only log anything that is an Error, Critical, Alert or Emergency priority and ignore log requests with lower priority.
I couldn't find anything in the documentation or by exploring the code (both Log facade and the Monolog class).
My current thought is to create a custom wrapper around the Log facade that simply checks the environment and ignores anything below 400 (Monolog level for Error). Basically I would create a threshold variable in the environment file and anything below it will simply not be logged to the files.
Before I do so, I wanted to ask the community if there is an existing method/configuration for that which I could use, so that I don't re-invent the wheel.
If not - what would be the best approach?
This gist shows a more comfortable answer, as is not dependent on the
chosen handler.
I'm just providing the essential part in an answer here in case the above link gets deleted in some time.
In the AppServiceProviders' register method:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
$monolog = Log::getMonolog();
foreach($monolog->getHandlers() as $handler) {
$handler->setLevel(Config::get('app.log-level'));
}
}
Then just add an additional key to your config/app.php:
'log-level' => 'info', // or whatever minimum log level you would like.
Add the following code to your AppServiceProvider::register():
$this->app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(
$handler = new RotatingFileHandler(
$this->app->storagePath() . '/logs/laravel.log',
$this->app->make('config')->get('app.log_max_files', 5),
$this->app->make('config')->get('app.level', 'debug')
)
);
$handler->setFormatter(new LineFormatter(null, null, true, true));
});
This recreates the logic that Laravel does when setting up the daily handler, but adds passing level to the handler.
You can set your minimum logging level by setting level value in your config/app.php:
'level' => 'debug', //debug, info, notice, warning, error, critical, alert, emergency
This is a bit of a workaround and each type of handler would need to be set up separately. I'm currently working on a pull-request to Laravel that would add setting minimum debug level from the config file without writing a line of code in your AppServiceProvider.
The code above hasn't been tested, so let me know if you see any typos or something doesn't work properly and I'll be more than happy to make that work for you.

Categories