NAME
Hook::Filter - A runtime filtering layer on top of subroutine calls
SYNOPSIS
Imagine you have a big program using a logging library that exports 3 functions called mydebug, myinfo and mywarn. Those functions generate far too much log, so you want to skip calling them except in some specific circumstances.
In your main program, write:
use Hook::Filter hook => ["mydebug","myinfo","mywarn"];
In all modules making use of the logging library, write:
use Hook::Filter;
Then create a file called ./hook_filter.rules. This file contains boolean expressions that specify when calls to the filtered subroutines should be allowed:
# allow calls to 'mydebug' only inside package 'My::Filthy:Attempt'
is_sub('mydebug') && from_pkg('My::Filthy::Attempt')
# allow all calls to 'myinfo' except from inside packages under the namespace My::Test::
is_sub('myinfo') && !from_pkg(/^My::Test/)
# allow calls to 'mywarn' from function 'do_stuff' in package 'main'
# whose third argument is a message that does not match the string 'invalid login name'
is_sub('mywarn') && from_sub('do_stuff') && from_pkg('main') && !has_arg(3,/invalid login name/)
# all other calls to 'myinfo', 'mydebug' or 'mywarn' will be skipped
SYNOPSIS, Log::Dispatch
Your program uses Log::Dispatch
. You want to enable Hook::Filter on top of the methods log
and log_to
from Log::Dispatch
everywhere at once. And you want to use the filter rules located in /etc/myconf/filter_rules.conf
. Easy: in main
, write:
use Hook::Filter rules => '/etc/myconf/filter_rules.conf', hook => ['Log::Dispatch::log','Log::Dispatch::log_to'];
That's all!
DESCRIPTION
Hook::Filter is a runtime firewall for subroutine calls.
Hook::Filter lets you hook some subroutines and define rules to specify when calls to those subroutines should be allowed or skipped. Those rules are very flexible and are eval-ed during runtime each time a call to one of the hooked subroutine is made.
RULES
A rule is one line of valid perl code that returns either true or false when eval-ed. This line of code is usually made of boolean operators combining functions that are exported by the modules located under Hook::Filter::Plugins::.
See Hook::Filter::Plugins::Location for details.
Rules are loaded from a file. See the import parameter rules
for a description of how to specify the rules file.
Rules are parsed from the rules file only once, when the module inits.
This file follows a standard syntax: a line starting with #
is a comment, any other line is considered to be a rule, ie a valid line of perl code.
Each time one of the filtered subroutines is called, all loaded rules are eval-ed until one returns true or all returned false. If one returns true, the filtered subroutine is called transparantly, otherwise it is skipped and its return value is set to either undef or an empty list, depending on the context.
If a rule dies/croaks/confess upon being eval-ed (f.ex. when you left a syntax error in your rules file), it will be assumed to have returned true. This is a form of fail-safe policy. A warning message with a complete diagnostic will be emitted with warn
.
CREATING NEW RULE TESTS
Filter rules are made of perl code mixed with test functions that are imported from the modules located under Hook::Filter::Plugins::
. Hook::Filter comes with a number of default plugin modules that implement the default rule tests (such as from_sub
, is_sub
, has_arg
, etc.). Those modules are loaded into Hook::Filter using Module:Pluggable. It is therefore quite easy to extend the existing set of test functions by writing your own Hook::Filter plugins. See Hook::Filter::Plugins::Location for an exemple on how to do that.
CAVEATS
When a call to a subroutine is allowed, the input and output arguments of the subroutine are forwarded without modification. But when the call is blocked, the subroutine response is simulated and will be undef
in SCALAR context and an empty list in ARRAY context. So do not filter subroutines whose return values are significant for the rest of your code.
Time. Hook::Filter evaluates all filter rules for each call to a filtered subroutine. It would therefore be very unappropriate to filter a heavily used subroutine.
USE CASE
Why the hell would one want to do such a creepy thing to his code?
Well, the main use case if that of easily building a log policy on top of a logging library in a large application. You want a dynamic and flexible way to define what should be logged and in which circumstances, without having to actually edit the aplication's code. Just use Hook::Filter like in the SYNOPSIS and define a system wide rules file.
Or you have a large application that is crashing and you decide to turn on debug verbosity system wide. You do so and get gazillions of log messages. Instead of greping your way through them or starting your debugger, you use Hook::Filter at the relevant places in your application and filter away all irrelevant debug messages with a tailored set of rules that allow only the right information to be logged.
Your application is managed during runtime by an awe inspiring AI engine that continuously produces filter rules to dynamically alter the call flow inside you application (don't ask why! this sounds like sick design anyway...).
The concept of a dynamic subroutine call filter being somewhat mind bobbling, you will surely imagine some new twisted use cases (and let me know then!).
INTERFACE
Hook::Filter exports no functions. It only has the following import parameters:
rules => $rules_file
-
Specify the complete path to the rules file. This import parameter can be used only once in a program (usually in package
main
) independently of how many timesuse Hook::Filter
is written. If it is not specified anywhere, Hook::Filter will by default search for a file namedhook_filter.rules
located in./
or under~/.hook_filter/
. If no file is found or if the rules file contains no valid rules, no subroutines will be filtered. hook => $subname1
orhook => [$subname1,$subname2...]
-
Specify which subroutines should be filtered in the current module. If Hook::Filter is used without specifying
hook
, the same function names as specified in packagemain
are taken.
DIAGNOSTICS
- Passing wrong arguments to Hook::Filter's import parameters will cause it to croak.
- The import parameter
hook
must be used at least in packagemain
otherwise Hook::Filter croaks with an error message. - An IO error when opening the rules file causes Hook::Filter to die.
- An error in a filter rule will be reported with a perl warning.
SECURITY
Hook::Filter gives anybody who has the rights to create or manipulate a rules file the possibility to inject code into your running application at runtime. This can be highly dangerous! Protect your filesystem.
THREADS
Hook::Filter is not thread safe.
SEE ALSO
See Hook::WrapSub, Log::Localized, Log::Log4perl, Log::Dispatch.
BUGS AND LIMITATIONS
Please report any bugs or feature requests to bug-hook-filter@rt.cpan.org
, or through the web interface at http://rt.cpan.org.
AUTHOR
Written by Erwan Lemonnier <erwan@cpan.org>
based on inspiration received during the 2005 perl Nordic Workshops. Kind thanks to Claes Jacobsson & Jerker Montelius for their suggestions and support!
COPYRIGHT AND LICENSE
Copyright (C) 2005 by Erwan Lemonnier <erwan@cpan.org>
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
DISCLAIMER OF WARRANTY
Because this software is licensed free of charge, there is no warranty for the software, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or other parties provide the software "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the software is with you. Should the software prove defective, you assume the cost of all necessary servicing, repair, or correction.
In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the software as permitted by the above licence, be liable to you for damages, including any general, special, incidental, or consequential damages arising out of the use or inability to use the software (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the software to operate with any other software), even if such holder or other party has been advised of the possibility of such damages.