How to Turn off Debugging Logs with Monolog - php

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.

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

Hooking into the Error processing cycle

I'm building a monitoring solution for logging PHP errors, uncaught exceptions and anything else the user wants to log to a database table. Kind of a replacement for the Monitoring solution in the commercial Zend Server.
I've written a Monitor class which extends Zend_Log and can handle all the mentioned cases.
My aim is to reduce configuration to one place, which would be the Bootstrap. At the moment I'm initializing the monitor like this:
protected function _initMonitor()
{
$config = Zend_Registry::get('config');
$monitorDb = Zend_Db::factory($config->resources->db->adapter, $config->resources->db->params);
$monitor = new Survey_Monitor(new Zend_Log_Writer_Db($monitorDb, 'logEntries'), $config->projectName);
$monitor->registerErrorHandler()->logExceptions();
}
The registerErrorHandler() method enables php error logging to the DB, the logExceptions() method is an extension and just sets a protected flag.
In the ErrorController errorAction I add the following lines:
//use the monitor to log exceptions, if enabled
$monitor = Zend_Registry::get('monitor');
if (TRUE == $monitor->loggingExceptions)
{
$monitor->log($errors->exception);
}
I would like to avoid adding code to the ErrorController though, I'd rather register a plugin dynamically. That would make integration into existing projects easier for the user.
Question: Can I register a controller plugin that uses the postDispatch hook and achieve the same effect? I don't understand what events trigger the errorAction, if there are multiple events at multiple stages of the circuit, would I need to use several hooks?
Register your plugin with stack index 101. Check for exceptions in response object on routeShutdown and postDispatch.
$response = $this->getResponse();
if ($response->isException()) {
$exceptions = $response->getException();
}
to check if exception was thrown inside error handler loop you must place dispatch() in a try-catch block
The accepted answer by Xerkus got me on the right track. I would like to add some more information about my solution, though.
I wrote a Controller Plugin which looks like that:
class Survey_Controller_Plugin_MonitorExceptions extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse();
$monitor = Zend_Registry::get('monitor');
if ($response->isException())
{
$monitor->log($response);
}
}
}
Note that you get an Array of Zend_Exception instances if you use $response->getException(). After I had understood that, I simply added a foreach loop to my logger method that writes each Exception to log separately.
Now almost everything works as expected. At the moment I still get two identical exceptions logged, which is not what I would expect. I'll have to look into that via another question on SO.

Enable debug mode interactively in symfony

I'm using symfony 1.4 with Doctrine.
I'm trying to find a way to enable debug mode only if the current sfUser has a special debugger credential.
I already created a filter that deactivates the symfony debug bar if the sfUser has not this credential (the web_debug is set to true in my settings.yml file):
class checkWebDebugFilter extends sfFilter
{
public function execute($filterChain)
{
if(!$this->getContext()->getUser()->hasCredential('debugger'))
{
sfConfig::set('sf_web_debug', false);
}
$filterChain->execute();
}
}
The code of my index.php file is:
require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false));
sfContext::createInstance($configuration)->dispatch();
The problem is, as the debug mode is hardcoded to false in my index.php, it is also disabled for debuggers; therefore the Web debug bar does not show Doctrine statements nor timing indications.
Is there a way to enable debug mode only if the current sfUser has a precise credential?
I tried to add sfConfig::set('sf_debug', true); to my checkWebDebugFilter::execute() method but as the filter is executed after Doctrine statements, they are not recorded.
I also tried to add session_start(); in my index.php file, then browsing through the $_SESSION variable to check whether the current user has the debugger credential, but it did not work (and it was not in the spirit of symfony either).
Thanks in advance for your answers.
When you pass the debug parameter in the index.php file, it actually is passed down to the sfApplicationConfiguration class of your application. In your case it can be found in the /apps/frontend/config/frontendConfiguration.class.php file. frontendConfiguration class extends sfApplicationConfiguration, and here you can add your code.
Debug parameter is stored in a protected variable of this class, so you wont be able to change it from filter, but you can create a function for example:
setDebug($mode) {
$this->debug = $mode;
}
And call it in your filter:
$this->context->getConfiguration()->setDebug(true);
You also could override isDebug() function in frontendConfiguration class, because that is used in the initConfiguration() function to initialize timing indicators and other debugging stuff.
if ($this->isDebug() && !sfWebDebugPanelTimer::isStarted())
{
sfWebDebugPanelTimer::startTime();
}
But you won't be able to check user permissions here, as sfUser class won't be initialized in this stage yet. But you can check $_COOKIES or $_SESSION global variables for a value that you can set when user is logging in. Or you can call sfWebDebugPanelTimer::startTime() in your Filter, but will miss a few microseconds.
I have not tested this, but that's how I would do it.
Try this
if you want to enable web_debug panel (dev) mode then
http://host_url/frontend_dev.php
or
write in your index.php 'frontend','dev',true.
require_once(dirname(FILE).'/../config/ProjectConfiguration.class.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true));
sfContext::createInstance($configuration)->dispatch();

How to design error reporting in PHP

How should I write error reporting modules in PHP?
Say, I want to write a function in PHP: 'bool isDuplicateEmail($email)'.
In that function, I want to check if the $email is already present in the database.
It will return 'true', if exists. Else 'false'.
Now, the query execution can also fail, In that time I want to report 'Internal Error' to the user.
The function should not die with typical mysql error: die(mysql_error(). My web app has two interfaces: browser and email(You can perform certain actions by sending an email).
In both cases it should report error in good aesthetic.
Do I really have to use exception handling for this?
Can anyone point me to some good PHP project where I can learn how to design robust PHP web-app?
In my PHP projects, I have tried several different tacts. I've come to the following solution which seems to work well for me:
First, any major PHP application I write has some sort of central singleton that manages application-level data and behaviors. The "Application" object. I mention that here because I use this object to collect generated feedback from every other module. The rendering module can query the application object for the feedback it deems should be displayed to the user.
On a lower-level, every class is derived from some base class that contains error management methods. For example an "AddError(code,string,global)" and "GetErrors()" and "ClearErrors". The "AddError" method does two things: stores a local copy of that error in an instance-specific array for that object and (optionally) notifies the application object of this error ("global" is a boolean) which then stores that error for future use in rendering.
So now here's how it works in practice:
Note that 'Object' defines the following methods: AddError ClearErrors GetErrorCodes GetErrorsAsStrings GetErrorCount and maybe HasError for convenience
// $GLOBALS['app'] = new Application();
class MyObject extends Object
{
/**
* #return bool Returns false if failed
*/
public function DoThing()
{
$this->ClearErrors();
if ([something succeeded])
{
return true;
}
else
{
$this->AddError(ERR_OP_FAILED,"Thing could not be done");
return false;
}
}
}
$ob = new MyObject();
if ($ob->DoThing())
{
echo 'Success.';
}
else
{
// Right now, i may not really care *why* it didn't work (the user
// may want to know about the problem, though (see below).
$ob->TrySomethingElse();
}
// ...LATER ON IN THE RENDERING MODULE
echo implode('<br/>',$GLOBALS['app']->GetErrorsAsStrings());
The reason I like this is because:
I hate exceptions because I personally believe they make code more convoluted that it needs to be
Sometimes you just need to know that a function succeeded or failed and not exactly what went wrong
A lot of times you don't need a specific error code but you need a specific error string and you don't want to create an error code for every single possible error condition. Sometimes you really just want to use an "opfailed" code but go into some detail for the user's sake in the string itself. This allows for that flexibility
Having two error collection locations (the local level for use by the calling algorithm and global level for use by rendering modules for telling the user about them) has really worked for me to give each functional area exactly what it needs to get things done.
Using MVC, i always use some sort of default error/exception handler, where actions with exceptions (and no own error-/exceptionhandling) will be caught.
There you could decide to answer via email or browser-response, and it will always have the same look :)
I'd use a framework like Zend Framework that has a thorough exception handling mechanism built all through it.
Look into exception handling and error handling in the php manual. Also read the comments at the bottom, very useful.
There's aslo a method explained in those page how to convert PHP errors into exceptions, so you only deal with exceptions (for the most part).

Categories