From: Tyler Riddle Date: Tue, 16 Oct 2012 01:27:19 +0000 (-0700) Subject: revise log router api to match log::contextual router api change X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=c971f190264cc725abe736dd593a5b11a87e2b84;p=scpubgit%2FObject-Remote.git revise log router api to match log::contextual router api change --- diff --git a/lib/Object/Remote/LogDestination.pm b/lib/Object/Remote/LogDestination.pm index 67f436a..7cdbbd1 100644 --- a/lib/Object/Remote/LogDestination.pm +++ b/lib/Object/Remote/LogDestination.pm @@ -10,7 +10,7 @@ sub select { my ($self, $router, $selector) = @_; my $subscription = $router->subscribe($self->logger, $selector); push(@{ $self->subscriptions }, $subscription); - return $subscription; + return $self; } sub connect { diff --git a/lib/Object/Remote/LogRouter.pm b/lib/Object/Remote/LogRouter.pm index 671856a..3b1f655 100644 --- a/lib/Object/Remote/LogRouter.pm +++ b/lib/Object/Remote/LogRouter.pm @@ -12,53 +12,32 @@ sub before_import { } sub after_import { } sub subscribe { - my ($self, $logger, $selector, $is_temp) = @_; + my ($self, $logger, $selector) = @_; my $subscription_list = $self->subscriptions; - - if(ref $logger ne 'CODE') { - die 'logger was not a CodeRef or a logger object. Please try again.' - unless blessed($logger); - $logger = do { my $l = $logger; sub { $l } } - } - - my $subscription = [ $logger, $selector ]; + + my $subscription = [ $logger, $selector ]; - $is_temp = 0 unless defined $is_temp; - push(@$subscription_list, $subscription); - if ($is_temp) { - #weaken($subscription->[-1]); - } - return $subscription; + push(@$subscription_list, $subscription); + + return $self; } #TODO turn this logic into a role -sub handle_log_message { - my ($self, $caller, $level, $log_meth, @values) = @_; +sub get_loggers { + my ($self, $caller, $level) = @_; my $should_clean = 0; + my @logger_list; foreach(@{ $self->subscriptions }) { - unless(defined($_)) { + unless(defined) { $should_clean = 1; next; } + my ($logger, $selector) = @$_; - #TODO this is not a firm part of the api but providing - #this info to the selector is a good feature - local($_) = { level => $level, package => $caller }; - if ($selector->(@values)) { - #TODO issues with caller_level have not been resolved yet - #when a logger crosses an object::remote::connection so - $logger = $logger->($caller, { caller_level => -1 }); - - #TODO there is a known issue with the interaction of this - #routed logging scheme and objects proxied with Object::Remote. - #Specifically the loggers must be invoked with a calling - #depth of 0 which isn't possible using a logger that has - #been proxied which is what happens with routed logging - #if the logger is created in one Perl interpreter and the - #logging happens in another - $logger->$level($log_meth->(@values)) - if $logger->${\"is_$level"}; + + if ($selector->({ log_level => $level, package => $caller, caller_level => 2 })) { + push(@logger_list, $logger); } } @@ -66,7 +45,7 @@ sub handle_log_message { $self->_remove_dead_subscriptions; } - return; + return @logger_list; } sub _remove_dead_subscriptions { diff --git a/lib/Object/Remote/Logging.pm b/lib/Object/Remote/Logging.pm index b9276e9..f2fec49 100644 --- a/lib/Object/Remote/Logging.pm +++ b/lib/Object/Remote/Logging.pm @@ -36,16 +36,18 @@ sub init_logging_stderr { my ($class, $level) = @_; our $Log_Level = $level; chomp(my $hostname = `hostname`); + our $Log_Output = Object::Remote::LogDestination->new( logger => Log::Contextual::SimpleLogger->new({ levels_upto => $Log_Level, coderef => sub { my @t = localtime(); my $time = sprintf("%0.2i:%0.2i:%0.2i", $t[2], $t[1], $t[0]); - warn "[$hostname $$] $time ", @_ + print STDERR "[$hostname $$] $time ", @_ }, }) ); + $Log_Output->connect($class->arg_router); } @@ -59,153 +61,159 @@ sub init_logging_forwarding { 1; -#__END__ -# -#Hierarchical routed logging concept -# -# Why? -# -# Object::Remote and systems built on it would benefit from a standard model -# for logging that enables simple and transparent log generation and consumption -# that can cross the Perl interpreter instance boundaries transparently. More -# generally CPAN would benefit from a common logging framework that allows all -# log message generators to play nicely with all log message consumers with out -# making the generators or consumers jump through hoops to do what they want to do. -# If these two solutions are the same then all modules built using the -# logging framework will transparently operate properly when run under Object::Remote. -# -# Such a solution needs to be flexible and have a low performance impact when it is not -# actively logging. The hiearchy of log message routers is the way to achieve all of these -# goals. The abstracted message router interface introduced to Log::Contextual allows -# the hierarchical routing system to be built and tested inside Object::Remote with possible -# larger scale deployment in the future. -# -# Hierarchy of log routers -# -# * Each Perl module ideally would use at least a router dedicated -# to that module and may have child routers if the module is complex. -# * Log messages inserted at low levels in the hierarchy -# are available at routers at higher levels in the hierarchy. -# * Each running Perl instance has a root router which receives -# all log messages generated in the Perl instance. -# * The routing hierarchy is available for introspection and connections -# from child routers to parent routers have human readable strings -# * The entire routing system is dynamic -# * Add and remove routers while the system is in operation -# * Add and remove taps into routers while the system is in operation -# * Auto-solves Object::Remote logging by setting the parent router of the -# root router in the remote instance to a router in the local instance the -# log messages will flow into the local router via a proxy object -# * Should probably be two modes of operation for Object::Remote logging -# * forwarding across instances for ease of use during normal operation -# * stderr output by default for debugging cases to limit the usage of -# object::remote -# -# -# Example hiearchy -# -# Root [1] -# * System::Introspector -# * Object::Remote [2] -# * local [3] -# * remote [4] -# * hostname-1.example.com [5] -# * Root -# * System::Introspector -# * Object::Remote -# * local -# * hostname-2.example.com -# * Root -# * System::Introspector -# * Object::Remote -# * local -# -# [1] This router has all logs generated anywhere -# even on remote hosts -# [2] Everything related to Object::Remote including -# log messages from remote nodes for things other -# than Object::Remote -# [3] Log messages generated by Object::Remote on the local -# node only -# [4] All log messages from all remote nodes -# [5] This is the connection from a remote instance to the -# local instance using a proxy object -# -# As a demonstration of the flexibility of the this system consider a CPAN testers GUI -# tool. This hypothetical tool would allow a tester to select a module by name and perform -# the automated tests for that package and all dependent packages. Inside the tool is a pane for -# the output of the process (STDOUT and STDERR), a pane for log messages, and a pane displaying -# the modules that are participating in routed logging. The tester could then click on individual -# packages and enable logging for that package dynamically. If neccassary more than one package -# could be monitored if neccassary. If the GUI is wrapping a program that runs for long periods of -# time or if the application is a daemon then being able to dynamically add and remove logging -# becomes very useful. -# -# Log message selection and output -# -# * Assumptions -# * Modules and packages know how they want to format log messages -# * Consumers of log messages want to know -# * Which Perl module/package generated that message -# * When running with Object::Remote if the log message is from -# a remote node and if so which node -# * Consuming a log message is something the consumer knows how it wants -# to be done; the module/package should not be dictating how to receive -# the log messages -# * Most log messages most of the time will be completely ignored and unused -# * Router taps -# * A consumer of log messages will tap into a router at any arbitrary point -# in the router hierarchy even across machines if Object::Remote is involved -# * The tap is used to access a stream of log data and is not used to select -# which packages/modules should be logged -# * For instance Object::Remote has log messages flowing through it that -# include logs generated on remote nodes even if those logs were generated -# by a module other than Object::Remote -# * Selection -# * The module has defined what the log message format is -# * The tap has defined the scope of messages that will be -# available for selection, ie: all log messages everywhere, -# all logs generated on Object::Remote nodes, etc -# * Selection defines what log messages are going to be delivered -# to a logger object instance -# * Selectors act as a gate between a tap and the logger object -# * Selectors are closures that perform introspection on the log -# message; if the selector returns true the logger will be invoked -# to log this message -# * The logger still has a log level assigned to it and still will have -# the is_$level method invoked to only log at that specific level -# * Destinations -# * A log destination is an instance of a logger object and the associated -# selectors. -# * Consuming logging data from this system is a matter of -# * Constructing an instance of a logging destination object which has -# the following attributes: -# * logger - the logger object, like warnlogger or log4perl instance -# * selectors - a list of closures; the first one that returns true -# causes the logger to be checked for this log_level and -# invoked if neccassary -# * Register selectors with the destination by invoking a method and specifying -# sub refs as an argument -# -# Technical considerations -# * Log contextual likes to have the logger invoked directly inside the exported log -# specific methods because it removes a need to muck with logger caller depths to -# report back the proper caller information for the logger. -# * Because of this the best strategy identified is to return a list of loggers -# to those exported methods which then invoke the loggers inside the method -# * This means that log message forwarding is a process of querying each parent -# router for a list of logger objects that should be invoked. Each router along -# the hierarchy adds to this list and the log_* method will invoke all loggers -# directly. -# * The routing hierarchy has cycles where parent routers hold a reference to the child -# and the child holds a reference to the parent. The cycles are not a problem if weak -# references are used however proxy objects don't seem to currently work with weak -# references. -# * Once a logger hits a proxy object the caller information is totally blown; this -# crossing isn't transparent yet -# -# -# +__END__ + +Hierarchical routed logging concept + + Why? + + Object::Remote and systems built on it would benefit from a standard model + for logging that enables simple and transparent log generation and consumption + that can cross the Perl interpreter instance boundaries. More + generally CPAN would benefit from a common logging framework that allows all + log message generators to play nicely with all log message consumers with out + making the generators or consumers jump through hoops to do what they want to do. + If these two solutions are the same then all modules built using the + logging framework will transparently operate properly when run under Object::Remote. + + Such a solution needs to be flexible and have a low performance impact when it is not + actively logging. The hiearchy of log message routers is the way to achieve all of these + goals. The abstracted message router interface introduced to Log::Contextual allows + the hierarchical routing system to be built and tested inside Object::Remote with possible + larger scale deployment in the future. + + Hierarchy of log routers + + * Each Perl module ideally would use at least a router dedicated + to that module and may have child routers if the module is complex. + + * Log messages inserted at low levels in the hierarchy + are available at routers at higher levels in the hierarchy. + + * Each running Perl instance has a root router which receives + all log messages generated in the Perl instance. + + * The routing hierarchy is available for introspection and connections + from child routers to parent routers have human readable descriptions + + * The entire routing system is dynamic + * Add and remove routers while the system is in operation + * Add and remove subscriptions into routers while the system is in operation + + * Auto-solves Object::Remote logging by setting the parent router of the + root router in the remote instance to a router in the local instance. The + log messages will flow into the local router via a proxy object. + + * There needs to be two modes of operation for routed logging + * forwarding across Perl instances using Object::Remote proxies + for ease of use during normal operation + + * STDERR output by default because not all logs can be forwarded + such as log messages for parts of Object::Remote that relate to + Object::Remote delivering the log. + + + + Example hiearchy + + * Root [1] + * System::Introspector + * Object::Remote [2] + * local [3] + * remote [4] + * connection #1 [5] + * Root + * System::Introspector + * Object::Remote + * local + * connection #2 + * Root + * System::Introspector + * Object::Remote + * local + + [1] This router has all logs generated anywhere + even on remote hosts + [2] Everything related to Object::Remote including + log messages from remote nodes for things other + than Object::Remote + [3] Log messages generated by Object::Remote on the local + node only + [4] All log messages from all remote nodes + [5] This is the connection from a remote instance to the + local instance using a proxy object - the name contains + the Object::Remote::Connection id for the remote node + + As a demonstration of the flexibility of the this system consider a CPAN testers GUI + tool. This hypothetical tool would allow a tester to select a module by name and perform + the automated tests for that package and all dependent packages. Inside the tool is a pane for + the output of the process (STDOUT and STDERR), a pane for log messages, and a pane displaying + the modules that are participating in routed logging. The tester could then click on individual + packages and enable logging for that package dynamically. If neccassary more than one package + could be monitored if neccassary. If the GUI is wrapping a program that runs for long periods of + time or if the application is a daemon then being able to dynamically add and remove logging + becomes very useful. + + Log message selection and output + + * Assumptions + * Modules and packages know how they want to format log messages. + * Consumers of log messages want to know + * Which Perl module/package generated that message. + * When running with Object::Remote if the log message is from + a remote connection and if so which connection. + * Consumers of a log message know how they want to output them. The logger + should not be enforcing a specific type of log output. + * Most log messages most of the time will be completely ignored and unused. + * Router subscriptions + * A consumer of log messages will subscribe to a router at any arbitrary point + in the router hierarchy even across machines if Object::Remote is involved + * The subscription is used to access a stream of log data and is not used to select + which packages/modules should be logged + * For instance the Object::Remote log router has log messages flowing through + it that include logs generated on remote nodes even if those logs were generated + by a module other than Object::Remote + + * Selection + * The module has defined what the log message format is + * The subscription has defined the scope of messages that will be + available for selection, ie: all log messages everywhere, + all logs generated on Object::Remote nodes, etc + * Selection defines what log messages are going to be delivered + to a logger object instance + * Selectors act as a gate between a subscription and the logger object + * Selectors are closures that perform introspection on the log + message and metadata; if the selector returns true the logger + will be invoked to log this message + * The logger still has a log level assigned to it and still will have + the is_$level method invoked to only log at that specific level + + * Destinations + * A log destination is an instance of a logger object and the associated + subscriptions. + * Consuming logging data from this system is a matter of + * Constructing an instance of a logging destination object which has + the following attributes: + * logger - the logger object + * selectors - a list of closures; the first one that returns true + causes the logger to be checked for this log_level and + invoked if needed + * Register selectors with the destination by invoking a method and specifying + sub refs as an argument + + Technical considerations + * The routing hierarchy has cycles where parent routers hold a reference to the child + and the child holds a reference to the parent. The cycles are not a problem if weak + references are used however proxy objects don't seem to currently work with weak + references. + * Once a logger hits a proxy object the caller information is totally blown; this + crossing isn't transparent yet + * If Object::Remote is logging its actions and those logs are being forwarded then + logs can be generated from the forwarding itself creating an infinite loop. Only + a portion of Object::Remote can be forwarded. + + + diff --git a/lib/Object/Remote/Role/LogForwarder.pm b/lib/Object/Remote/Role/LogForwarder.pm index d57a7b8..5be6851 100644 --- a/lib/Object/Remote/Role/LogForwarder.pm +++ b/lib/Object/Remote/Role/LogForwarder.pm @@ -52,7 +52,7 @@ sub add_child_router { # return delete $self->child_routers->{$description}; #} -after handle_log_message => sub { +after get_loggers => sub { my ($self, @args) = @_; my $parent = $self->parent_router;