Modify log format on runtime using Monolog and Laravel - php

I need write a customlog on laravel 5.3, and modify on runtime for add some extra information.
So far I have managed to do it, but the code does not work because what I can do is add a new instance of the log, which duplicates the lines in each modification of the format.
My handicap is that I do not know how to return or if this is possible, the current handler of the logging system, and simply pass it the format modification.
The code below allows me to modify the format line, but only once.
If I run it again, start duplicating the lines, and what is not like, create the handler in another way, or use the existing one that would be appropriate.
$maxFiles = config('app.log_max_files');
$path = storage_path('/logs/cprsync.log');
// Really I don't need this because this are on Laravel App, but with this method y can create a $handler for modify format
$handler = new RotatingFileHandler($path, is_null($maxFiles) ? 5 : $maxFiles, config('app.log_level'));
(is_null(config('cprsync.activejob'))) ? $job = '' : $job=' ['.config('cprsync.activejob').']';
$logFormat = "[%datetime%] [%level_name%]".$job.": %message% %context% %extra%\n";
$formatter = new LineFormatter($logFormat,null,true,true);
$handler->setFormatter($formatter); // What I really want is to make this change ...
// Push handler
$log->getMonolog()->pushHandler($handler);

I don't know if it's the prettiest (like in Symfony you can just put some YAML rules in the services.yml and you've changed your log format), but this seems to be a very valid option and basically builds on what you're doing already:
http://laravel-tricks.com/tricks/monolog-for-custom-logging
TL;DR: Create a custom logging class with a function write() that basically contains the code OP has posted. Then add that class to your facades and you can log with MyCustomLogger::write($message);
EDIT: This isn't really what OP requested, since this doesn't change the format on runtime.

Related

How To Call A Model Or Class Using Custom Directory Path In Codeigniter 4

Guy I need support here For codeigniter 4. Please Help.
I have a folder and file in a root directory with full path :
TestingWebsite\app\CronJob\ExportAlert.php
The File Is Not In Controller.
Inside the ExportAlert.php the code :
<?php
date_default_timezone_set("Asia/Jakarta");
$current_time = date("h:i a");
$begin = "9:00 am";
$end = "3:30 pm";
$now = DateTime::createFromFormat('H:i a', $current_time);
$timebegin = DateTime::createFromFormat('H:i a', $begin);
$timeend = DateTime::createFromFormat('H:i a', $end);
if (!($now > $timebegin && $now < $timeend)) {
return false;
}
$Utility = new \App\Models\Dennis_utility_model(); // Error In Here Class Not Found
$Setting = new \App\Models\Dennis_setting_model(); // Error In Here Class Not Found
$Medoo = new \App\Models\Dennis_medoo_model(); // Error In Here Class Not Found
$Telegram = new \App\Models\Dennis_telegram_model(); // Error In Here Class Not Found
$ExeFile = AmiBroker_Folder.JScript_Main_Folder.Export_Alert;
$Url = $Setting->FPathSetting()['CROSSSERVER']['CURLEXE'];
$Utility->CurlExe($Url, 1, 1, $Setting->FUseEngine64(), $ExeFile);
$myconfig = new \Config\MyConfig();
include 'include/Alert_1.php';
?>
I want to call the model :
$Utility = new \App\Models\Dennis_utility_model(); // Error In Here Class Not Found
$Setting = new \App\Models\Dennis_setting_model(); // Error In Here Class Not Found
$Medoo = new \App\Models\Dennis_medoo_model(); // Error In Here Class Not Found
$Telegram = new \App\Models\Dennis_telegram_model(); // Error In Here Class Not Found
Inside the ExportAlert.php
But I got Error : Class Not Found For Every Model I called.
I have a reason why I don't want to put it inside controller. Because I want to run a schedule task cron job. And to prevent unauthorize user to run it from browser. And spam it.
How I can call a model or class from outside controller use custom directory ?
In the file ExportAlert.php I didn't use the class. And just use a native php with model.
Is it possible ?
What is the correct way to call it ?
Remember this is codeigniter 4 not codeigniter 3.
Thank You
One possible issue I can see is when you use path "\App\Models\Dennis_utility_model()" directly it will not know where that particular path is since Autoloader is not used to load the modules and this is run as vanilla php script.
Besides this, you can certainly use Controllers as queues and not allow users to execute from the browser.
There are two ways to build queue:
Using Controller which restricts the access to the CLI.
Using Command CLI.
By using Second Way i.e Command CLI. You can create command (similiar in the way how spark commands work) and put that command in the CRONJOB to run at particular time. Also, since these are stored in Command folder they are not available to your users as route.
Refer: https://codeigniter4.github.io/userguide/cli/cli_commands.html?highlight=command%20line
This is in my opinion a correct way, however, I have always been using the first way so I cannot provide my own example for this.
Now lets check First Method i.e Via Controller.
The way I do so is to define a Controller with longer and unintutive name as a FunctionalityProcessingQueue and since these are in another module folder, they are not available as route to the users.
Even if they are available to the user as route, you can easily check for it.
by checking for CLI.
You can refer to the following URL :
What is the canonical way to determine commandline vs. http execution of a PHP script?
It shows you on how to do so.
Now as far as the implementation is concerned:
You can set something like FunctionalityProcessingQueue class in your app folder.
So, it will look like app/Controllers/FunctionalityProcessingQueue.php
and your code will be similiar to other Contollers.
One thing you can add is set_time_limit(0) to increase the script time limit to timeout.
Example:
<?php
...
set_time_limit(0);
class FunctionalityProcessingQueue extends Controller {}
Then create a method in your Controller.
such as following :
...
public function process(...$args)
{}
...
This will pass parameters. You can use this args value for some condition check as well. Example token, to verify you are the one hitting it or other useful data.
From the CLI: Goto the route of your project and use following command :
php public/index.php FunctionalityProcessingQueue process some_parameter
Refer :
https://codeigniter4.github.io/userguide/cli/cli.html
https://codeigniter4.github.io/userguide/cli/cli_library.html

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 Turn off Debugging Logs with Monolog

I'm using Monolog in a project, it's not Symfony, just my own application that uses the stand-alone Monolog composer package.
What I'd like to do is programmatically turn off debugging logs. I'm writing to a log file and I'm using the Monolog::StreamHandler. I'm controlling whether the application is in debug mode or not with a Configuration class that gets the debug value from a configuration file. So when someone changes that value to debugging is false, debug logging should turn off.
I felt like the easiest way to do this would be to extend StreamHandler and override StreamHandler's write method like this.
class DurpLogger extends StreamHandler {
protected function write(array $record) {
if ($this->getLevel() == Durp::Debug && !Configuration::debug()) {
return;
}
parent::write($record);
}
}
So if a log request comes in and the log level for the handler is set to DEBUG and the application's Configuration::debug() is FALSE then just return without writing the log message. Otherwise, StreamHandler will do its thing.
I'm wondering if this is the best way to use Monolog or if there's perhaps a cleaner way to do this.
I envision there being a handler in my application for DEBUG, INFO, ERROR and whatever levels I might need for my application. Perhaps it makes sense to not rely on a Configuration::debug() that can only be TRUE or FALSE, but rather a Configuration::logLevel() that will allow me to more granularly control logging output.
But even still, does extending StreamHandler make the most sense when controlling Monolog at the application level?
UPDATE
Now, I'm thinking something like this, that uses level rather than just boolean debug.
class DurpLogger extends StreamHandler {
public function __construct() {
parent::__construct(Configuration::logFile(), Configuration::logLevel());
}
protected function write(array $record) {
if (!($this->getLevel() >= Configuration::logLevel())) {
return;
}
parent::write($record);
}
}
Then I'd use it in the application like this.
class Durp {
private $logger;
public function __construct() {
$this->logger = new Logger('durp-service');
$this->logger->pushHandler(new DurpLogger());
$this->logger->addDebug('Debugging enabled');
$this->logger->addInfo('Starting Durp');
}
}
I figured the StreamHandler handles the file writing stuff, so that's why I'm extending it. And if I turn up the log level in Configuration to Logger::INFO, the "Debugging enabled" message doesn't get logged.
Open to suggestions to make this better.
A common alternative would be to use the NullHandler instead of the StreamHandler.
Maybe switch between them depending on your condition like follows:
if (!Configuration::debug()) {
$logger->pushHandler(new \Monolog\Handler\NullHandler());
}
I would like to give you an example that is more adapted to your usage,
but I need to see some code in order to know how you use it.
Update
For the question about default format, the empty [] at end represent the extra data that can be added with log entries.
From #Seldaek (Monolog's owner) :
The default format of the LineFormatter is:
"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n". the username/age is the context, and extra that is typically empty results in this empty array [].
If you use processors to attach data to log records they typically write it to the extra key to avoid conflicts with context info. If it really is an issue for you you can change the default format and omit %extra%.
Edit: As of Monolog 1.11 the LineFormatter has a $ignoreEmptyContextAndExtra parameter in the constructor that lets you remove these, so you can use this:
// the last "true" here tells it to remove empty []'s
$formatter = new LineFormatter(null, null, false, true);
$handler->setFormatter($formatter);
See How not to show last bracket in a monolog log line? and Symfony2 : use Processors while logging in different files about the processors which #Seldaek is talking about.

Passing parameters to my custom logger in symfony

I am having some troubles using the logger service in Symfony2. I find it quite confusing.
I am trying to use a Rotating File Handler. To this handler zou can pass 3 paramenters: the filename of the log, maxFiles that the log can divide itself and log level.
I want to pass this parameters from the controller, since I load that data in runtime from a .ini configuration file, but I am not sure how to do it.
How could I do this?
Try the following in your controller:
add a use statement: use Monolog\Handler\RotatingFileHandler;
use it like this: $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);

Categories