Yii does not log with an infinite loop - php

I am not sure if this is the bug in yii or i am doing a wrong approach. I am creating a back-end process that runs without stop and logging is something that is crucial.
I created a console application. My logging works when I do this:
public function actionTest(){
Yii::log( "testing", 'error', 'worker.*');
return;
}
I am able to see "testing" logged into the file worker.log that is located in runtime folder>
However when I create an infinite loop, I don't see anything that is logged:
public function actionTest(){
while(1){
Yii::log( "testing", 'error', 'worker.*');
echo 'running';
usleep(5000000);
}
}
The worker.log file is empty.
Here is the configuration setting for worker.log in console.php:
array(
'class'=>'CFileLogRoute',
'categories' => 'worker.*',
'logFile' => 'worker.log'
)

For performance reasons, by default Yii outputs log records at the end of the request OR when CLogger::autoFlash messages limit is reached IF CLogger::autoDump is set to true.
In your case this event never occurs. To fix it you have several options:
configure CLogger to flush messages as soon as you log them, by setting autoFlash = 1 and autoDump = true
call CLogger::flush() method manually
you can create your own logger class that flushes messages the way you need
or use one of the existing implementations that flushes messages right away, like this one.

Related

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

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.

conditionally create and save separate log file in php/symfony2

On a project i am working we use Symfony2 console commands to run image converting (using LaTeX and some imagick). Due to the nature of project, not all conditions may be met during the console command run so the execution will fail, to be later restarted with a cron job, only if attempts count is not higher that predefined limit.
We already hove logging in our project, we use Monolog logger. What i basically want is to somehow duplicate everything that goes to the main log file in another log file, created specifically for that console command execution and only if attempts limit is reached.
So, if we run command once and it fails - it's ok and nothing should be created.
But if we run command for the 10th time, which is attempt limit, i want to have a separate log file named, say '/logs/failed_commands//fail.log'. That log file should only have messages for the last failed attempt, but not for all the previous ones.
How to do that? Do i need some combination of special logger handler (like FingersCrossed) and proper exceptions handling? Should i rather create additional instance of logger (if so, how can i pass it over to dependent services?)
This is simplified and cleaned piece of command that runs images converting. The attempts limit is checked withing the $this->checkProcessLimit() method
public function execute(InputInterface $input, OutputInterface $output)
{
try {
set_time_limit(0); //loose any time restrictions
$this->checkingDirectories();
$this->checkProcessLimit();
$this->isBuildRunning();
$this->checkingFiles();
try {
$this->startPdfBuilding();
} catch (InternalProjectException $e) {
throw PdfBuildingException::failedStartBuilding($this->pressSheet->getId());
}
} catch (PdfBuildingException $e) {
$this->printError($output, $e);
return;
}
try {
$this->logger->info('Building Image.');
$this->instantiatePdfBuilder();
$buildingResult = $this->pdfBuilder->outputPdf();
$this->generatePreview($buildingResult);
$this->movePDFs($buildingResult);
$this->unlinkLockFile();
$output->writeln('<info>Image successfully built</info>');
} catch (LaTeXException $e) {
$this->unlinkLockFile();
$this->abortPdfBuilding($e->getMessage());
$this->printError($output, $e);
return;
}
}
UPD: It seems that for dumping a bunch of log entries i need to use BufferHandler bundled with Monolog Logger. But i still need to figure out the way to set it up to get dumps only when errors limit (not error level) reached.
UPD2: I've managed to make it work, but i don't like the solution.
Since in Symfony2 you have to define loggers in config.yml and have to rebuild cache for any changes in configuration, i had to resort to dynamically adding a handler to a logger. But the logger itself is considered to be of Psr\Log\LoggerInterface interface, which does not have any means to add handlers. The solution i had to use actually checks if used logger is an instance of Monolog\Logger and then manually adding a BufferHandler to it in Symfony2 Console command's initialize() method.
Then, when it comes to the point where I check for attempts limit, i close buffer handler and delete actual log file (since BufferHandler has no means to removing/closing itself without flushing all it's contents) if limit is not yet reached. If it is, i just let the log file to stay.
This way it works, but it always writes the log, and i have to remove logs if condition (reached attempt limit) is not met.
i think you must create a custom handler.
With Monolog, you can log in a database (see for example https://github.com/Seldaek/monolog/blob/master/doc/04-extending.md)
Thus, it's easy to know how many times an error was raised since x days.
(something like : "select count(*) from monolog where channel='...' and time>...")

how to fix CConsoleApplication.user undefined in a command class in yii framework?

I am having that error , whenever I ran my simple cron script in shell ,
any idea how to fix that thing ?, from the error itself, it says the .user is undefiend,
when I placed the
'user' => array(
// enable cookie-based authentication
'allowAutoLogin' => true,
'loginUrl' => array('myaccount/blah/login'),
in the console config, it is looking for a "Class" ,, what class am i supposed to include in that array? , this user login url is using an LDAP stuff in loggin in and authentication, what should I do ?
A CConsoleApplication is for handling offline tasks. For example, the application starts a cronjob within a linux system. The application checks each day if a registered user of your website should change his password, because it must be changed every 3 month. If the password expired send an email.
To preselecting the users you have set a scope condition to check the status of a user, as well as a scope to restricted signed in users on her own data:
public function scopes(){
return array(...,
'user' => array(
'condition'=>'id='.Yii::app()->user->id,
),
'active' => array(
'condition'=>'status='.self::STATUS_ACTIVE,
), ...
);
}
Now, in your CCA-Code you use the active scope to get all users:
$usersArray = User::model()->active()->findAll(); ...foreach.... The Problem here is in the use of the extended class, the CActiveRecord-class. Mostly used as a class extension in models, which are stored in a database. In this CActiveRecord-class the CActiveRecord->__call function is used to get all stored scopes of a model. After that the class merged the actually requested scopes with the rest of the database criteria. The fact that all scopes are loaded first occures the error in loading the user-scope, include Yii::app()->user->id. The WebUser is called and throws the exception 'CException' with message 'attribute "CConsoleApplication.user is not defined'. You wouldn't call the WebUser, but the automatism arrange this for you :-)
So, do it like schmunk says. Generate in your scope code an exception part where ensures that Yii::app()->user is not called:
public function scopes(){
if (Yii::app() instanceof CConsoleApplication) {
$user = array(''); //no condition
}else{
$user = array(
'condition'=>'id='.Yii::app()->user->id,
);
}
return array(
'user' => $user,
'active' => array(
'condition'=>'status='.self::STATUS_ACTIVE,
), ...
);
}
I hope the explanation helps and perhaps also for other problems.
Short answer: You can't use CWebUser in console application. Don't include it in your config/console.php
Long(er) answer: If you rely on a component, which needs CWebUser, you'll have to detect this in the component and create some kind of workaround for this case. Have a look at this code piece for an example how to detect, if you're running a console app.
Try this
public static $console_user_id;
public function init() {
if (Yii::app() instanceof CConsoleApplication) {
if (self::$console_user_id) $this->id = self::$console_user_id;
return false;
}
parent::init();
}
solved my problem by using update, instead of save in the script...no need to use user array and CWebUser class
I had the same problem. Screened all answers given here and found some good point, but solved my problem my way, although it may not be the best.
First off all I had to figure out that my Cron Jon threw the aforementioned Exception because inside the Cron job I was running a script which had this part of code in it
if(Yii::app()->user->isAdmin()) {...} else {...}
So the console threw the error since the user was not defined. I changed my code in such a way that I tested if the console was running it. The changed code is as follows:
$console = false;
try {
$test = Yii::app()->user->isAdmin();
}
catch (CException $e) {
$console = true;
}
if($console || (!$console && Yii::app()->user->isAdmin()) {...} else {...}
As said, not perfect, but maybe a solution for someone.

Expiration time makes error in caching

I'm using file caching (CFileCache) to show a simple message from a database table.
When page load for the first time it works correct but when I reload page it makes an Error as:
include(CTimestampBehavior.php) [function.include]: failed to open stream: No such file or directory
And This error remains until TIME EXPIRATION which I set in cache->set() and next page load just one time and it makes error again and so on.
Here is my method to handle caching:
public static function getLatest()
{
//see if it is in the cache, if so, just return it
if( ($cache=Yii::app()->cache)!==null)
{
$key='TrackStar.ProjectListing.SystemMessage';
if(($sysMessage=$cache->get($key))!==false)
return $sysMessage;
}
//The system message was either not found in the cache, or
//there is no cache component defined for the application
//retrieve the system message from the database
$sysMessage = SysMessage::model()->find(array(
'order'=>'t.update_time DESC',
));
if($sysMessage != null)
{
//a valid message was found. Store it in cache for future retrievals
if(isset($key))
//$cache->set($key,$sysMessage,300);
$cache->set($key, $sysMessage, 300, new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_sys_message'));
return $sysMessage;
}
else
return null;
}
Error occures in this line:
if(($sysMessage=$cache->get($key))!==false)
I'm new to Yii and caching and have no idea about it.
UPDATE:
behaviors method of AR models:
public function behaviors()
{
return array(
'CTimestampBehavior' => array(
'class' => 'zii.behaviors.CTimestampBehavior',
'createAttribute' => 'create_time',
'updateAttribute' => 'update_time',
'setUpdateOnCreate' => true,
),
);
}
This looks like your issue is either that:
framework/zii/behaviors/CTimestampBehavior.php is missing
framework/zii/behaviors/CTimestampBehavior.php doesn't have correct permissions to be read by your server user
You are using opcode cache (APC?) and there are some issues on that end, (though reports for this seem to be for random occurrences). Try disabling it.
For some unknown reason Yii doesn't import your zii routes
In any event I suggest trying a workaround of adding "zii.behaviors.CTimestampBehavior" to your main.php configuration file "import" section. Or simply calling Yii::import('zii.behaviors.CTimestampBehavior'); in your function. Hopefully that works and you can continue with your work while diving deaper into the issue when you've got the time.
If it doesn't you can investigate the above (and at least people who come here will have more information to work with)

Categories