Commit | Line | Data |
5e2b2229 |
1 | package Object::Remote::Logging; |
2 | |
3 | use strictures 1; |
4 | |
f7ea4120 |
5 | use Log::Contextual qw( :log ); |
6 | use Object::Remote::LogRouter; |
4a9fa1a5 |
7 | use Object::Remote::LogDestination; |
8 | use Log::Contextual::SimpleLogger; |
9 | use Carp qw(cluck); |
5e2b2229 |
10 | |
f7ea4120 |
11 | use base qw(Log::Contextual); |
5e2b2229 |
12 | |
4a9fa1a5 |
13 | sub arg_router { |
14 | return $_[1] if defined $_[1]; |
15 | our $Router_Instance; |
16 | |
17 | return $Router_Instance if defined $Router_Instance; |
18 | |
19 | $Router_Instance = Object::Remote::LogRouter->new( |
20 | description => $_[0], |
21 | ); |
22 | } |
5e2b2229 |
23 | |
4a9fa1a5 |
24 | sub init_logging { |
25 | my ($class) = @_; |
26 | our $Did_Init; |
27 | |
28 | return if $Did_Init; |
29 | $Did_Init = 1; |
30 | |
31 | if ($ENV{OBJECT_REMOTE_LOG_LEVEL}) { |
32 | $class->init_logging_stderr($ENV{OBJECT_REMOTE_LOG_LEVEL}); |
33 | } |
34 | } |
35 | |
36 | sub init_logging_stderr { |
37 | my ($class, $level) = @_; |
38 | our $Log_Level = $level; |
39 | chomp(my $hostname = `hostname`); |
40 | our $Log_Output = Object::Remote::LogDestination->new( |
41 | logger => Log::Contextual::SimpleLogger->new({ |
42 | levels_upto => $Log_Level, |
43 | coderef => sub { warn "[$hostname $$] ", @_ }, |
44 | }) |
45 | ); |
46 | $Log_Output->connect($class->arg_router); |
47 | } |
48 | |
49 | sub init_logging_forwarding { |
50 | my ($class, $remote_parent) = @_; |
51 | chomp(my $host = `hostname`); |
52 | $class->arg_router->description("$$ $host"); |
53 | $class->arg_router->parent_router($remote_parent); |
54 | $remote_parent->add_child_router($class->arg_router); |
55 | } |
5e2b2229 |
56 | |
57 | 1; |
58 | |
4a9fa1a5 |
59 | #__END__ |
60 | # |
61 | #Hierarchical routed logging concept |
62 | # |
63 | # Why? |
64 | # |
65 | # Object::Remote and systems built on it would benefit from a standard model |
66 | # for logging that enables simple and transparent log generation and consumption |
67 | # that can cross the Perl interpreter instance boundaries transparently. More |
68 | # generally CPAN would benefit from a common logging framework that allows all |
69 | # log message generators to play nicely with all log message consumers with out |
70 | # making the generators or consumers jump through hoops to do what they want to do. |
71 | # If these two solutions are the same then all modules built using the |
72 | # logging framework will transparently operate properly when run under Object::Remote. |
73 | # |
74 | # Such a solution needs to be flexible and have a low performance impact when it is not |
75 | # actively logging. The hiearchy of log message routers is the way to achieve all of these |
76 | # goals. The abstracted message router interface introduced to Log::Contextual allows |
77 | # the hierarchical routing system to be built and tested inside Object::Remote with possible |
78 | # larger scale deployment in the future. |
79 | # |
80 | # Hierarchy of log routers |
81 | # |
82 | # * Each Perl module ideally would use at least a router dedicated |
83 | # to that module and may have child routers if the module is complex. |
84 | # * Log messages inserted at low levels in the hierarchy |
85 | # are available at routers at higher levels in the hierarchy. |
86 | # * Each running Perl instance has a root router which receives |
87 | # all log messages generated in the Perl instance. |
88 | # * The routing hierarchy is available for introspection and connections |
89 | # from child routers to parent routers have human readable strings |
90 | # * The entire routing system is dynamic |
91 | # * Add and remove routers while the system is in operation |
92 | # * Add and remove taps into routers while the system is in operation |
93 | # * Auto-solves Object::Remote logging by setting the parent router of the |
94 | # root router in the remote instance to a router in the local instance the |
95 | # log messages will flow into the local router via a proxy object |
96 | # * Should probably be two modes of operation for Object::Remote logging |
97 | # * forwarding across instances for ease of use during normal operation |
98 | # * stderr output by default for debugging cases to limit the usage of |
99 | # object::remote |
100 | # |
101 | # |
102 | # Example hiearchy |
103 | # |
104 | # Root [1] |
105 | # * System::Introspector |
106 | # * Object::Remote [2] |
107 | # * local [3] |
108 | # * remote [4] |
109 | # * hostname-1.example.com [5] |
110 | # * Root |
111 | # * System::Introspector |
112 | # * Object::Remote |
113 | # * local |
114 | # * hostname-2.example.com |
115 | # * Root |
116 | # * System::Introspector |
117 | # * Object::Remote |
118 | # * local |
119 | # |
120 | # [1] This router has all logs generated anywhere |
121 | # even on remote hosts |
122 | # [2] Everything related to Object::Remote including |
123 | # log messages from remote nodes for things other |
124 | # than Object::Remote |
125 | # [3] Log messages generated by Object::Remote on the local |
126 | # node only |
127 | # [4] All log messages from all remote nodes |
128 | # [5] This is the connection from a remote instance to the |
129 | # local instance using a proxy object |
130 | # |
131 | # As a demonstration of the flexibility of the this system consider a CPAN testers GUI |
132 | # tool. This hypothetical tool would allow a tester to select a module by name and perform |
133 | # the automated tests for that package and all dependent packages. Inside the tool is a pane for |
134 | # the output of the process (STDOUT and STDERR), a pane for log messages, and a pane displaying |
135 | # the modules that are participating in routed logging. The tester could then click on individual |
136 | # packages and enable logging for that package dynamically. If neccassary more than one package |
137 | # could be monitored if neccassary. If the GUI is wrapping a program that runs for long periods of |
138 | # time or if the application is a daemon then being able to dynamically add and remove logging |
139 | # becomes very useful. |
140 | # |
141 | # Log message selection and output |
142 | # |
143 | # * Assumptions |
144 | # * Modules and packages know how they want to format log messages |
145 | # * Consumers of log messages want to know |
146 | # * Which Perl module/package generated that message |
147 | # * When running with Object::Remote if the log message is from |
148 | # a remote node and if so which node |
149 | # * Consuming a log message is something the consumer knows how it wants |
150 | # to be done; the module/package should not be dictating how to receive |
151 | # the log messages |
152 | # * Most log messages most of the time will be completely ignored and unused |
153 | # * Router taps |
154 | # * A consumer of log messages will tap into a router at any arbitrary point |
155 | # in the router hierarchy even across machines if Object::Remote is involved |
156 | # * The tap is used to access a stream of log data and is not used to select |
157 | # which packages/modules should be logged |
158 | # * For instance Object::Remote has log messages flowing through it that |
159 | # include logs generated on remote nodes even if those logs were generated |
160 | # by a module other than Object::Remote |
161 | # * Selection |
162 | # * The module has defined what the log message format is |
163 | # * The tap has defined the scope of messages that will be |
164 | # available for selection, ie: all log messages everywhere, |
165 | # all logs generated on Object::Remote nodes, etc |
166 | # * Selection defines what log messages are going to be delivered |
167 | # to a logger object instance |
168 | # * Selectors act as a gate between a tap and the logger object |
169 | # * Selectors are closures that perform introspection on the log |
170 | # message; if the selector returns true the logger will be invoked |
171 | # to log this message |
172 | # * The logger still has a log level assigned to it and still will have |
173 | # the is_$level method invoked to only log at that specific level |
174 | # * Destinations |
175 | # * A log destination is an instance of a logger object and the associated |
176 | # selectors. |
177 | # * Consuming logging data from this system is a matter of |
178 | # * Constructing an instance of a logging destination object which has |
179 | # the following attributes: |
180 | # * logger - the logger object, like warnlogger or log4perl instance |
181 | # * selectors - a list of closures; the first one that returns true |
182 | # causes the logger to be checked for this log_level and |
183 | # invoked if neccassary |
184 | # * Register selectors with the destination by invoking a method and specifying |
185 | # sub refs as an argument |
186 | # |
187 | # Technical considerations |
188 | # * Log contextual likes to have the logger invoked directly inside the exported log |
189 | # specific methods because it removes a need to muck with logger caller depths to |
190 | # report back the proper caller information for the logger. |
191 | # * Because of this the best strategy identified is to return a list of loggers |
192 | # to those exported methods which then invoke the loggers inside the method |
193 | # * This means that log message forwarding is a process of querying each parent |
194 | # router for a list of logger objects that should be invoked. Each router along |
195 | # the hierarchy adds to this list and the log_* method will invoke all loggers |
196 | # directly. |
197 | # * The routing hierarchy has cycles where parent routers hold a reference to the child |
198 | # and the child holds a reference to the parent. The cycles are not a problem if weak |
199 | # references are used however proxy objects don't seem to currently work with weak |
200 | # references. |
201 | # * Once a logger hits a proxy object the caller information is totally blown; this |
202 | # crossing isn't transparent yet |
203 | # |
204 | # |
205 | # |
206 | |
207 | |
208 | |
209 | |