00001 /******************************************************************************* 00002 00003 @file Hierarchy.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version, October 2004 00034 @author Kris 00035 00036 00037 *******************************************************************************/ 00038 00039 module mango.log.Hierarchy; 00040 00041 private import mango.log.Appender; 00042 00043 /******************************************************************************* 00044 00045 Pull in additional functions from the C library 00046 00047 *******************************************************************************/ 00048 00049 extern (C) 00050 { 00051 int memcmp (void *, void *, int); 00052 } 00053 00054 /******************************************************************************* 00055 00056 This is the real Logger implementation, hidden behind the public 00057 abstract frontage. 00058 00059 *******************************************************************************/ 00060 00061 private class LoggerInstance : Logger 00062 { 00063 private LoggerInstance next, 00064 parent; 00065 00066 private char[] name; 00067 private Level level; 00068 private Appender appender; 00069 private Hierarchy hierarchy; 00070 private bool additive, 00071 breakpoint; 00072 00073 /*********************************************************************** 00074 00075 Construct a LoggerInstance with the specified name for the 00076 given hierarchy. By default, logger instances are additive 00077 and are prohibited from emitting events. 00078 00079 ***********************************************************************/ 00080 00081 protected this (Hierarchy hierarchy, char[] name) 00082 { 00083 this.hierarchy = hierarchy; 00084 this.level = Level.None; 00085 this.additive = true; 00086 this.name = name; 00087 } 00088 00089 /*********************************************************************** 00090 00091 Is this logger enabed for the specified Level? 00092 00093 ***********************************************************************/ 00094 00095 final bool isEnabled (Level level) 00096 { 00097 return level >= this.level; 00098 } 00099 00100 /*********************************************************************** 00101 00102 Is this a breakpoint Logger? 00103 00104 ***********************************************************************/ 00105 00106 final bool isBreakpoint () 00107 { 00108 return breakpoint; 00109 } 00110 00111 /*********************************************************************** 00112 00113 Is this logger additive? That is, should we walk ancestors 00114 looking for more appenders? 00115 00116 ***********************************************************************/ 00117 00118 final bool isAdditive () 00119 { 00120 return additive; 00121 } 00122 00123 /*********************************************************************** 00124 00125 Append a trace message 00126 00127 ***********************************************************************/ 00128 00129 final void trace (char[] msg) 00130 { 00131 append (Level.Trace, msg); 00132 } 00133 00134 /*********************************************************************** 00135 00136 Append an info message 00137 00138 ***********************************************************************/ 00139 00140 final void info (char[] msg) 00141 { 00142 append (Level.Info, msg); 00143 } 00144 00145 /*********************************************************************** 00146 00147 Append a warning message 00148 00149 ***********************************************************************/ 00150 00151 final void warn (char[] msg) 00152 { 00153 append (Level.Warn, msg); 00154 } 00155 00156 /*********************************************************************** 00157 00158 Append an error message 00159 00160 ***********************************************************************/ 00161 00162 final void error (char[] msg) 00163 { 00164 append (Level.Error, msg); 00165 } 00166 00167 /*********************************************************************** 00168 00169 Append a fatal message 00170 00171 ***********************************************************************/ 00172 00173 final void fatal (char[] msg) 00174 { 00175 append (Level.Fatal, msg); 00176 } 00177 00178 /*********************************************************************** 00179 00180 Return the name of this Logger (sans the appended dot). 00181 00182 ***********************************************************************/ 00183 00184 final char[] getName () 00185 { 00186 int i = name.length; 00187 if (i > 0) 00188 --i; 00189 return name[0 .. i]; 00190 } 00191 00192 /*********************************************************************** 00193 00194 Return the Level this logger is set to 00195 00196 ***********************************************************************/ 00197 00198 final Level getLevel () 00199 { 00200 return level; 00201 } 00202 00203 /*********************************************************************** 00204 00205 Set the current level for this logger (and only this logger). 00206 00207 ***********************************************************************/ 00208 00209 final void setLevel (Level level) 00210 { 00211 setLevel (level, false); 00212 } 00213 00214 /*********************************************************************** 00215 00216 Set the current level for this logger, and (optionally) all 00217 of its descendents. 00218 00219 ***********************************************************************/ 00220 00221 final void setLevel (Level level, bool force) 00222 { 00223 this.level = level; 00224 hierarchy.updateLoggers (this, force); 00225 } 00226 00227 /*********************************************************************** 00228 00229 Set the breakpoint status of this logger. 00230 00231 ***********************************************************************/ 00232 00233 final void setBreakpoint (bool enabled) 00234 { 00235 breakpoint = enabled; 00236 hierarchy.updateLoggers (this, false); 00237 } 00238 00239 /*********************************************************************** 00240 00241 Set the additive status of this logger. See isAdditive(). 00242 00243 ***********************************************************************/ 00244 00245 final void setAdditive (bool enabled) 00246 { 00247 additive = enabled; 00248 } 00249 00250 /*********************************************************************** 00251 00252 Add (another) appender to this logger. Appenders are each 00253 invoked for log events as they are produced. At most, one 00254 instance of each appender will be invoked. 00255 00256 ***********************************************************************/ 00257 00258 final void addAppender (Appender next) 00259 { 00260 if (appender) 00261 next.setNext (appender); 00262 appender = next; 00263 } 00264 00265 /*********************************************************************** 00266 00267 Remove all appenders from this Logger 00268 00269 ***********************************************************************/ 00270 00271 final void clearAppenders () 00272 { 00273 appender = null; 00274 } 00275 00276 00277 /*********************************************************************** 00278 00279 Get number of milliseconds since this application started 00280 00281 ***********************************************************************/ 00282 00283 final ulong getUptime () 00284 { 00285 return Event.getUptime(); 00286 } 00287 00288 /*********************************************************************** 00289 00290 Append a message to this logger via its appender list. 00291 00292 ***********************************************************************/ 00293 00294 private final void append (Level level, char[] s) 00295 { 00296 if (isEnabled (level)) 00297 { 00298 uint masks; 00299 LoggerInstance l = this; 00300 Event event = Event.allocate (); 00301 00302 try { 00303 // set the event attributes 00304 event.set (hierarchy, level, s, name[0..name.length-1]); 00305 00306 // combine appenders from all ancestors 00307 do { 00308 Appender appender = l.appender; 00309 00310 // this level have an appender? 00311 while (appender) 00312 { 00313 uint mask = appender.getMask(); 00314 00315 // have we used this appender already? 00316 if ((masks & mask) == 0) 00317 { 00318 // no - append message and update mask 00319 event.scratch.length = 0; 00320 appender.append (event); 00321 masks |= mask; 00322 } 00323 // process all appenders for this node 00324 appender = appender.getNext (); 00325 } 00326 // process all ancestors 00327 } while (l.additive && ((l = l.parent) !== null)); 00328 00329 // make sure we release the event ... 00330 } finally 00331 Event.deallocate (event); 00332 } 00333 } 00334 00335 /*********************************************************************** 00336 00337 See if the provided Logger is a good match as a parent of 00338 this one. Note that each Logger name has a '.' appended to 00339 the end, such that name segments will not partially match. 00340 00341 ***********************************************************************/ 00342 00343 private final bool isCloserAncestor (LoggerInstance other) 00344 { 00345 int length = other.name.length; 00346 00347 // possible parent if length is shorter 00348 if (length < name.length) 00349 // does the prefix match? Note we append a "." to each 00350 if (length == 0 || 00351 memcmp (&other.name[0], &name[0], length) == 0) 00352 // is this a better (longer) match than prior parent? 00353 if ((parent is null) || (length >= parent.name.length)) 00354 return true; 00355 return false; 00356 } 00357 } 00358 00359 00360 /******************************************************************************* 00361 00362 The Logger hierarchy implementation. We keep a reference to each 00363 logger in a hash-table for convenient lookup purposes, plus keep 00364 each logger linked to the others in an ordered chain. Ordering 00365 places shortest names at the head and longest ones at the tail, 00366 making the job of identifying ancestors easier in an orderly 00367 fashion. For example, when propogating levels across descendents 00368 it would be a mistake to propogate to a child before all of its 00369 ancestors were taken care of. 00370 00371 *******************************************************************************/ 00372 00373 class Hierarchy 00374 { 00375 private char[] name, 00376 address; 00377 00378 private LoggerInstance root; 00379 private LoggerInstance[char[]] loggers; 00380 00381 /*********************************************************************** 00382 00383 Construct a hierarchy with the given name. 00384 00385 ***********************************************************************/ 00386 00387 this (char[] name) 00388 { 00389 this.name = name; 00390 this.address = "network"; 00391 00392 // insert a root node; the root has an empty name 00393 root = new LoggerInstance (this, ""); 00394 } 00395 00396 /********************************************************************** 00397 00398 Return the name of this Hierarchy 00399 00400 **********************************************************************/ 00401 00402 char[] getName () 00403 { 00404 return name; 00405 } 00406 00407 /********************************************************************** 00408 00409 Return the address of this Hierarchy. This is typically 00410 attached when sending events to remote monitors. 00411 00412 **********************************************************************/ 00413 00414 char[] getAddress () 00415 { 00416 return address; 00417 } 00418 00419 /********************************************************************** 00420 00421 Set the name of this Hierarchy 00422 00423 **********************************************************************/ 00424 00425 void setName (char[] name) 00426 { 00427 this.name = name; 00428 } 00429 00430 /********************************************************************** 00431 00432 Set the address of this Hierarchy. The address is attached 00433 used when sending events to remote monitors. 00434 00435 **********************************************************************/ 00436 00437 void setAddress (char[] address) 00438 { 00439 this.address = address; 00440 } 00441 00442 /*********************************************************************** 00443 00444 Return the root node. 00445 00446 ***********************************************************************/ 00447 00448 LoggerInstance getRootLogger () 00449 { 00450 return root; 00451 } 00452 00453 /*********************************************************************** 00454 00455 Return the instance of a Logger with the provided name. If 00456 the instance does not exist, it is created at this time. 00457 00458 ***********************************************************************/ 00459 00460 synchronized LoggerInstance getLogger (char[] name) 00461 { 00462 name ~= "."; 00463 00464 LoggerInstance l = loggers [name]; 00465 00466 if (l is null) 00467 { 00468 // create a new logger 00469 l = new LoggerInstance (this, name); 00470 00471 // insert into linked list 00472 insertLogger (l); 00473 00474 // look for and adjust children 00475 updateLoggers (l, true); 00476 00477 // insert into map 00478 loggers [name] = l; 00479 } 00480 00481 return l; 00482 } 00483 00484 /********************************************************************** 00485 00486 Iterate over all Loggers in list 00487 00488 **********************************************************************/ 00489 00490 int opApply (int delegate(inout Logger) dg) 00491 { 00492 int result = 0; 00493 LoggerInstance curr = root; 00494 00495 while (curr) 00496 { 00497 // BUG: this uncovers a cast() issue in the 'inout' delegation 00498 Logger l = curr; 00499 if ((result = dg (l)) != 0) 00500 break; 00501 curr = curr.next; 00502 } 00503 return result; 00504 } 00505 00506 /*********************************************************************** 00507 00508 Loggers are maintained in a sorted linked-list. The order 00509 is maintained such that the shortest name is at the root, 00510 and the longest at the tail. 00511 00512 This is done so that updateLoggers() will always have a 00513 known environment to manipulate, making it much faster. 00514 00515 ***********************************************************************/ 00516 00517 private void insertLogger (LoggerInstance l) 00518 { 00519 LoggerInstance prev, 00520 curr = root; 00521 00522 while (curr) 00523 { 00524 // insert here if the new name is shorter 00525 if (l.name.length < curr.name.length) 00526 if (prev is null) 00527 throw new Exception ("invalid hierarchy"); 00528 else 00529 { 00530 l.next = prev.next; 00531 prev.next = l; 00532 return; 00533 } 00534 else 00535 // find best match for parent of new entry 00536 propogate (l, curr, true); 00537 00538 // remember where insertion point should be 00539 prev = curr; 00540 curr = curr.next; 00541 } 00542 00543 // add to tail 00544 prev.next = l; 00545 } 00546 00547 /*********************************************************************** 00548 00549 Propogate hierarchical changes across known loggers. 00550 This includes changes in the hierarchy itself, and to 00551 the various settings of child loggers with respect to 00552 their parent(s). 00553 00554 ***********************************************************************/ 00555 00556 private void updateLoggers (LoggerInstance changed, bool force) 00557 { 00558 LoggerInstance logger = root; 00559 00560 // scan all loggers 00561 while (logger) 00562 { 00563 propogate (logger, changed, force); 00564 00565 // try next entry 00566 logger = logger.next; 00567 } 00568 } 00569 00570 /*********************************************************************** 00571 00572 Propogate changes in the hierarchy downward to child Loggers. 00573 Note that while 'parent' and 'breakpoint' are always forced 00574 to update, the update of 'level' is selectable. 00575 00576 ***********************************************************************/ 00577 00578 private void propogate (LoggerInstance logger, LoggerInstance changed, bool force) 00579 { 00580 // is the changed instance a better match for our parent? 00581 if (logger.isCloserAncestor (changed)) 00582 { 00583 // update parent (might actually be current parent) 00584 logger.parent = changed; 00585 00586 // if we don't have an explicit level set, inherit it 00587 if (logger.level == Logger.Level.None || force) 00588 logger.level = changed.level; 00589 00590 // always force breakpoints to follow parent settings 00591 logger.breakpoint = changed.breakpoint; 00592 } 00593 } 00594 } 00595 00596 00597