How to overload shutdown function? - php

I am using Kohana (v3) framework but I believe it's not tied to a particular framework.
What I have is basically an app with a front-end, where I want to use Kohana's native Kohana::shutdown_handler(), but I also have a part of the - RESTful API - where I don't want colourful and html-encoded exception reporting. I want a plain text reporting.
The way I thought it might work is to register another shutdown function in API's controller abstract class constructor, but then I realised register_shutdown_function() works differently to set_exception_handler() and instead of replacing it adds another function to the shutdown procedure. What's worse PHP doesn't allow "unregistering" shutdown functions and that's where my problem lies.
What to do, if you want to use another shutdown function instead of one already registered?

You can simply overload views/kohana/error by your custom view and set
/**
* Initialize Kohana, setting the default options.
*
* The following options are available:
*
* - string base_url path, and optionally domain, of your application NULL
* - string index_file name of your index file, usually "index.php" index.php
* - string charset internal character set used for input and output utf-8
* - string cache_dir set the internal cache directory APPPATH/cache
* - boolean errors enable or disable error handling TRUE
* - boolean profile enable or disable internal profiling TRUE
* - boolean caching enable or disable internal caching FALSE
*/
Kohana::init(array(
'base_url' => '/',
'errors' => FALSE,
));

The first thing that comes to mind would be to add your function before any other register_shutdown_function() calls and have it call any necessary shutdown functions and then call exit or die to immediately end the script without calling other shutdown functions.
To ensure your call is first, I would make a separate file for it and add it as php.ini's auto_prepend_file.
Call it a hack...maybe. Does it work? I'm 99% sure it should.

Related

Is there a way of using CakeResponse Object in unconstructed Class?

I'm currently doing a custom ErrorHandler for an App in CakePHP.
The reason? Well, bots are always trying to find stuff in your servers and sometimes they provoke exceptions and or errors.
The idea with this ErrorHandler is to filter requests and respond with the appropriate headers and prevent further request damage by handling this type of requests and make it transparent for the user client (because it might affect JavaScript for instance).
And what better way than to use the Framework's functionality, right?
The thing is that since this ErrorHandler is being used statically,
well, there is no constructor so nothing inherits anything, it doesn't
matter if you instantiate any other CakePHP Object.
What would be the appropriate way to use CakeResponse Object?
CakePHP's configuration:
app/Config/bootstrap.php:
App::uses('CustomErrorHandler', 'Lib');
app/Config/core.php:
// Error and exception handlers.
Configure::write('Error', array(
'handler' => 'CustomErrorHandler::handleError',
'level' => E_ALL & ~E_DEPRECATED,
'trace' => true
));
Configure::write('Exception', array(
'handler' => 'CustomErrorHandler::handleException',
'renderer' => 'ExceptionRenderer',
'log' => true
));
app/Lib/CustomErrorHandler.php:
... rest of class code ...
/**
* Named after convention: This method receives all CakePHP's
* errors and exceptions…
*
* #param array $e The exception object.
* #return mixed Returns the error handling or header redirection.
*/
public static function handleException($e)
{
$message = (string) $e->getMessage();
$code = (int) $e->getCode();
$file = (string) $e->getFile();
$line = (string) $e->getLine();
// If it's a Blacklist resource exception it will log it and redirect to home.
if (self::__isResourceException($message))
{
return self::__dismissError();
}
return parent::handleException($e);
}
/**
* This method redirects to home address using CakeResponse Object.
*
* #return mixed
*/
private static function __dismissError()
{
return (new CakeResponse)->header(array(
'Location' => self::$redirectUrl
));
}
}
UPDATE 2:
Will try a small layer over the ExceptionRenderer.
There's not really a point in using a CakeResponse object there... it would work if you'd call send() on it, however with only that one header, there's no advantage over using header() directly.
That being said, you are however in any case ditching the Controller.shutdown and Dispatcher.afterDispatch events. They are being dispatched in ExceptionRenderer::_shutdown(), and are often used to set response headers (CORS related headers are a good example), so you should figure whether its OK to drop them, or maybe even required.
If you need to keep the shutdown and afterDispatch events, then you should either fire them on your own, or maybe even use an extended ExceptionRenderer that handles that specific type of exception and sends an empty reponse with your location header added.
See also
Cookbook > Development > Exceptions > Using a custom renderer with Exception.renderer to handle application exceptions

Custom HandlerWrapper with MonologBundle

I am using Symfony 3.1 and I try to configure Monolog in such a way, that requests from the Googlebot are not logged. For this I wrote a UserAgentProcessorwhich already works as intended. In the next step I tried to write BotFilter which looks like this:
<?php
namespace AppBundle\Handler;
use Monolog\Handler\HandlerWrapper;
class FilterBotsHandler extends HandlerWrapper
{
/**
* {#inheritdoc}
*/
public function isHandling(array $record)
{
if (stripos($record['extra']['userAgent'], 'bot') !== false){
return false;
} else {
return $this->handler->isHandling($record);
}
}
}
This was inspired by the comments in the HandlerWrapper abstract class (take a look here).
Now I want to add that filter to my monolog yml-configuration. I tried adding it to my services but this was not possible as HandlerWrapper needs a Handler instance for its constructor. I researched how I could use the filter without a service but as far as I can see, the monolog bundle only accepts built-in types and the generic service type.
Now the question is: How can I use the filter in my configuration?
I am using Symfony 3.1 and I try to configure Monolog in such a way, that requests from the GoogleBot are not logged...
The quick way to prevent robots visiting your site is put these two lines into the /robots.txt file on your server. Create a robots.txt file in 'web' directory and paste the follows content:
User-agent: *
Disallow: /
https://support.google.com/webmasters/answer/6062608?hl=en&visit_id=1-636097099675465769-3677253464&rd=1
It's the recommended option when you need avoid access fully, meaning your sites will not longer be index by search engines and other bots. You don't need to configure/implement anything in your application to achieve it.
Now, if you need the bot to enter, but you don't want register it in logs. Instead of writing log files somewhere, some handlers are used to filter or modify log entries before sending them to other handlers. One powerful, built-in handler called fingers_crossed is used in the prod environment by default. It stores all log messages during a request but only passes them to a second handler if one of the messages reaches an action_level:
# app/config/config.yml
monolog:
handlers:
filter_for_errors:
type: fingers_crossed
# if *one* log is error or higher, pass *all* to file_log
action_level: error
handler: file_log
# now passed *all* logs, but only if one log is error or higher
file_log:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
Thus, in your prod.log file just will register the messages/requests that contains some error, so the bots don't have effect in this level.
More details about this http://symfony.com/doc/current/logging.html
What you try to do is not advisable, because the handler will depend from http request instead of log records, which will be out of context, however you can register its own handler in Symfony easily:
Let's create the custom handler class:
namespace AppBundle\Monolog\Handler;
use Monolog\Handler\AbstractHandler;
class StopBotLogHandler extends AbstractHandler
{
public function isBotRequestDetected()
{
// here your code to detect Bot requests, return true or false
// something like this:
// return isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/bot|crawl|slurp|spider/i', $_SERVER['HTTP_USER_AGENT']);
}
/**
* Checks whether the given record will be handled by this handler.
*
* This is mostly done for performance reasons, to avoid calling processors for nothing.
*
* Handlers should still check the record levels within handle(), returning false in isHandling()
* is no guarantee that handle() will not be called, and isHandling() might not be called
* for a given record.
*
* #param array $record Partial log record containing only a level key (e.g: array('level' => 100) for DEBUG level)
*
* #return bool
*/
public function isHandling(array $record)
{
return $this->isBotRequestDetected();
}
/**
* Handles a record.
*
* All records may be passed to this method, and the handler should discard
* those that it does not want to handle.
*
* The return value of this function controls the bubbling process of the handler stack.
* Unless the bubbling is interrupted (by returning true), the Logger class will keep on
* calling further handlers in the stack with a given log record.
*
* #param array $record The record to handle
*
* #return bool true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record)
{
// do nothing, just returns true whether the request is detected as "bot", this will break the handlers loop.
// else returns false and other handler will handle the record.
return $this->isBotRequestDetected();
}
}
Whenever you add a record to the logger, it traverses the handler stack. Each handler decides whether it fully handled the record, and if so, the propagation of the record ends there.
Important: Read the phpdoc from isHandling() and handle() methods for more details.
Next, let's register the class as service "without tags":
# app/config/services.yml
services:
monolog.handler.stop_bot_log:
class: AppBundle\Monolog\Handler\StopBotLogHandler
public: false
Then, add its handler to handlers list:
# app/config/config_prod.yml
monolog:
handlers:
# ...
stopbotlog:
type: service
id: monolog.handler.stop_bot_log
priority: 1
Note the type property must be equal to service, id must be the service name before defined and priority must be greater than 0 to ensure that its handler will be executed before that any other handler.
When the GoogleBot performs a request to website application the stopbotlog handler stops all handlers after him and don't register any log message.
Remember it's not the recommended way to do that! According to your needs, implementing option 1 or 2 should be enough.
If you want ignore bot requests for handlers group, you can override the monolog.handler.group.class container parameter and override the group handler behavior:
namespace AppBundle\Handler;
use Monolog\Handler\GroupHandler;
class NoBotGroupHandler extends GroupHandler
{
public function isBotRequestDetected()
{
// here your code to detect Bot requests, return true or false
}
public function handle(array $record)
{
if ($this->isBotRequestDetected()) {
// ignore bot request for handlers list
return false === $this->bubble;
}
return parent::handle($record);
}
}
in your config_prod.yml or services.yml:
parameters:
monolog.handler.group.class: AppBundle\Handler\NoBotGroupHandler
That's it! Now, you can stop bot logs for custom handles list:
# config_prod.yml
monolog:
handlers:
grouped:
type: group
members: [main, console, chromephp]
Finally, if you have difficulty to analyze your logs files I recommend using this amazing tool: https://github.com/EasyCorp/easy-log-handler
It's quite a dirty trick, but if you really need it, you may make it like this.
Supposing you want to wrap a handler with a type stream:
Add a constructor in you FilterBotsHandler:
public function __constructor($path, $level, $bubble, $permissions) {
$this->handler = new Monolog\Handler\StreamHandler($path, $level, $bubble, $permissions);
}
And then redefine a parameter monolog.handler.stream.class:
parameters:
monolog.handler.stream.class: AppBundle\Handler\FilterBotsHandler
Make sure that this parameter will be defined after it was defined by MonologBundle.
That's it. Should work.
You may write CompilerPass in your AppBundle which adds configurator to monolog service. Such configurator can be also a request event listener which can replace all handlers dynamically on request and bot detection and push empty handlers array to Logger which can be hold on configurator call.
In other words configurator added to DI by CompilerPass and added to EventDispatcher as Listener to Kernel events which onRequest check User-Agent header looking for bot and then clears Monolog\Logger (passed in configurator) all handlers (or putting an NullHandler if empty handlers array fails).
DI configurator is only way to change your services during runtime which can be applied as service definition level. Such definition can be attached or detached if not needed and it doesn't really change anything in your application.

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.

Laravel 5 > Using monolog introspection processor

I have configured Laravel 5 to use a custom logging configuration (default is way too simple). I've added monolog's IntrospectionProcessor to log the file name and line number of the log call.
The problem is that all lines get the same file and line number:
[2015-06-29 17:31:46] local.DEBUG (/home/vagrant/project/vendor/laravel/framework/src/Illuminate/Log/Writer.php#201): Loading view... [192.168.10.1 - GET /loans/create]
Is there a way to config the IntrospectionProcessor to print the actual lines and not the facade ones?
If I do Log::getMonolog()->info('Hello'); it works and prints the correct file and line number... but I don't know how safe is to avoid calling the Writer.writeLog function because it fires a log event (is it safe to not fire that event?).
(Only tried in Laravel 4.2!)
When pushing the Introspection Processor to Monolog it is possible to give an skipClassesPartial array as second parameter in the IntrospectionProcessor contructor. With this array it is possible to skip the Laravel Illuminate classes and the logger logs the class calling the log method.
$log->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, array('Illuminate\\')));
also see: https://github.com/Seldaek/monolog/blob/master/src/Monolog/Processor/IntrospectionProcessor.php
I know this is an old question but I thought I'd give a quick update because it's pretty easy to get this done now.
I haven't tried with Laravel but My own logging mechanism is within a LoggingService wrapper class. As such the introspection was only giving details about the service rather than the caller.
after reading Matt Topolski's answer, I had a look in the IntrospectionProcessor.php. the constructor looks like this:
__construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0)
All I had to do was add the processor like this:
log->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, array(), 1));
This is actually the expected functionality unless you're having the handler process the logs directly (check out the comments at the top of IntrospectionProcessor.php). My guess is you have a wrapper function around the logger and you're calling it from Writer.php -- BUT
If you look at the code for IntrospectionProcessor.php you'll see a bit of code on lines 81 to 87 that decides how to format that stack trace, and it still has access to the stack. If you bump the $i values for $trace[$i - 1] / $trace[$i] up one (aka $trace[$i]/$trace[$i + 1] respectively) you can 'climb' the stack back to where you want.
It's important to note that the 'class' and 'function' parts of the trace need to be one level of the stack higher than the 'file' and 'line.'
On a personal (plz dont mod me bruhs) note, I'd like to see functionality to include a stack offset when throwing the log in. I know what function I want to blame if an error shoots out when I write the error_log('ut oh') but I might(will) forget that by the time the 'ut oh' comes.

Load PHP Script Outside of Zend Framework Before Bootstrapping

I need to run a php script once before my zend framework application is bootstrapped and run. This works now by calling this initial script in my /public/index.php, however the script is run for all subsequent zend framework page requests as well. I need this script to only be run once on the initial request and not again when additional pages are loaded via ajax.
My initial attempts were to set some php constants in the /public/index.php file like so:
if (!defined('SOME_VAR')) require_once 'path/to/script/to/run/once.php';
define('SOME_VAR', '1);
However, when another page is loaded via ajax, even though I've defined 'SOME_VAR', it doesn't persist and stay as defined and the script is executed again.
I'm using Zend Framework 1.11, Apache (Xampp).
Executing this script AFTER Zend has been bootstrapped and run (inside Zend Framework) is not an option.
Constants don't work because they only exist for the duration of the request.
It sounds like you want to figure out if the current request is a regular request or an XmlHttpRequest (AJAX) request.
You could try something like this at the top of your index.php:
<?php
define('IS_AJAX_REQUEST', isset($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
if (! IS_AJAX_REQUEST){
// run your code.
}
If you're using sessions, you could also just set a flag in the session once you special code has been run, and test for that.
According to the Zend Framework Documentation:
However, should custom initialization be necessary, you have two choices. First, you can write methods prefixed with _init to specify discrete code to bootstrap. These methods will be called by bootstrap(), and can also be called as if they were public methods: bootstrap(). They should accept an optional array of options.
If your resource method returns a value, it will be stored in a container in the bootstrap. This can be useful when different resources need to interact (such as one resource injecting itself into another). The method getResource() can then be used to retrieve those values.
The other option is to use resource plugins. Resource plugins are objects that perform specific initializations, and may be specified:
When instantiating the Zend_Application object
During initialization of the bootstrap object
By explicitly enabling them via method calls to the bootstrap object
Resource plugins implement Zend_Application_Resource_ResourceAbstract, which defines simply that they allow injection of the caller and options, and that they have an init() method.

Categories