diff tango/tango/util/log/Hierarchy.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tango/tango/util/log/Hierarchy.d	Fri Jan 11 17:57:40 2008 +0100
@@ -0,0 +1,721 @@
+/*******************************************************************************
+
+        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
+
+        license:        BSD style: $(LICENSE)
+      
+        version:        Oct 2004: Initial release
+        version:        Feb 2007: Switched to lazy expr
+        
+        author:         Kris
+
+*******************************************************************************/
+
+module tango.util.log.Hierarchy;
+
+private import  tango.time.Clock;
+
+private import  tango.core.Exception;
+
+private import  tango.util.log.Logger,
+                tango.util.log.Appender;
+
+private import  tango.text.convert.Layout;
+
+private import  tango.util.log.model.IHierarchy;
+
+/*******************************************************************************
+
+        Pull in additional functions from the C library
+
+*******************************************************************************/
+
+extern (C)
+{
+        int memcmp (void *, void *, int);
+}
+
+/*******************************************************************************
+
+        Hack to sidestep linux linker errors (big thanks to Keinfarbton)
+
+*******************************************************************************/
+
+private Layout!(char) format;
+
+static this()
+{
+        .format = new Layout!(char);
+}
+
+/*******************************************************************************
+
+        Loggers are named entities, sometimes shared, sometimes specific to 
+        a particular portion of code. The names are generally hierarchical in 
+        nature, using dot notation (with '.') to separate each named section. 
+        For example, a typical name might be something like "mail.send.writer"
+        ---
+        import tango.util.log.Log;
+        
+        auto log = Log.getLogger ("mail.send.writer");
+
+        log.info  ("an informational message");
+        log.error ("an exception message: " ~ exception.toString);
+
+        etc ...
+        ---
+        
+        It is considered good form to pass a logger instance as a function or 
+        class-ctor argument, or to assign a new logger instance during static 
+        class construction. For example: if it were considered appropriate to 
+        have one logger instance per class, each might be constructed like so:
+        ---
+        private Logger log;
+        
+        static this()
+        {
+            log = Log.getLogger (nameOfThisClassOrStructOrModule);
+        }
+        ---
+
+        Messages passed to a Logger are assumed to be pre-formatted. You 
+        may find that the format() methos is handy for collating various 
+        components of the message: 
+        ---
+        char tmp[128] = void;
+        ...
+        log.warn (log.format (tmp, "temperature is {} degrees!", 101));
+        ---
+
+        Note that a provided workspace is used to format the message, which 
+        should generally be located on the stack so as to support multiple
+        threads of execution. In the example above we indicate assignment as 
+        "tmp = void", although this is an optional attribute (see the language 
+        manual for more information).
+
+        To avoid overhead when constructing formatted messages, the logging
+        system employs lazy expressions such that the message is not constructed
+        unless the logger is actually active. You can also explicitly check to
+        see whether a logger is active or not:
+        ---
+        if (log.isEnabled (log.Level.Warn))
+            log.warn (log.format (tmp, "temperature is {} degrees!", 101));
+        ---
+
+        You might optionally configure various layout & appender implementations
+        to support specific rendering needs.
+        
+        tango.log closely follows both the API and the behaviour as documented 
+        at the official Log4J site, where you'll find a good tutorial. Those 
+        pages are hosted over 
+        <A HREF="http://logging.apache.org/log4j/docs/documentation.html">here</A>.
+
+*******************************************************************************/
+
+private class LoggerInstance : Logger
+{
+        private LoggerInstance  next,
+                                parent;
+
+        private char[]          name_;
+        private Level           level_;
+        private Appender        appender;
+        private Hierarchy       hierarchy;
+        private bool            additive,
+                                breakpoint;
+
+
+        /***********************************************************************
+        
+                Construct a LoggerInstance with the specified name for the 
+                given hierarchy. By default, logger instances are additive
+                and are set to emit all events.
+
+        ***********************************************************************/
+
+        protected this (Hierarchy hierarchy, char[] name)
+        {
+                this.hierarchy = hierarchy;
+                this.level_ = Level.Trace;
+                this.additive = true;
+                this.name_ = name;
+        }
+
+        /***********************************************************************
+        
+                No, you should not delete or 'scope' these entites
+
+        ***********************************************************************/
+
+        private ~this()
+        {
+        }
+
+        /***********************************************************************
+        
+                Is this logger enabed for the specified Level?
+
+        ***********************************************************************/
+
+        final bool isEnabled (Level level = Level.Fatal)
+        {
+                return hierarchy.context.isEnabled (level_, level);
+        }
+
+        /***********************************************************************
+        
+                Is this a breakpoint Logger?
+                
+        ***********************************************************************/
+
+        final bool isBreakpoint ()
+        {
+                return breakpoint;
+        }
+
+        /***********************************************************************
+        
+                Is this logger additive? That is, should we walk ancestors
+                looking for more appenders?
+
+        ***********************************************************************/
+
+        final bool isAdditive ()
+        {
+                return additive;
+        }
+
+        /***********************************************************************
+
+                Append a trace message
+
+        ***********************************************************************/
+
+        final Logger trace (lazy char[] msg)
+        {
+                return append (Level.Trace, msg);
+        }
+
+        /***********************************************************************
+
+                Append an info message
+
+        ***********************************************************************/
+
+        final Logger info (lazy char[] msg)
+        {
+                return append (Level.Info, msg);
+        }
+
+        /***********************************************************************
+
+                Append a warning message
+
+        ***********************************************************************/
+
+        final Logger warn (lazy char[] msg)
+        {
+                return append (Level.Warn, msg);
+        }
+
+        /***********************************************************************
+
+                Append an error message
+
+        ***********************************************************************/
+
+        final Logger error (lazy char[] msg)
+        {
+                return append (Level.Error, msg);
+        }
+
+        /***********************************************************************
+
+                Append a fatal message
+
+        ***********************************************************************/
+
+        final Logger fatal (lazy char[] msg)
+        {
+                return append (Level.Fatal, msg);
+        }
+
+        /***********************************************************************
+
+                Return the name of this Logger (sans the appended dot).
+       
+        ***********************************************************************/
+
+        final char[] name ()
+        {
+                int i = name_.length;
+                if (i > 0)
+                    --i;
+                return name_[0 .. i];     
+        }
+
+        /***********************************************************************
+        
+                Return the Level this logger is set to
+
+        ***********************************************************************/
+
+        final Level level ()
+        {
+                return level_;     
+        }
+
+        /***********************************************************************
+        
+                Set the current level for this logger (and only this logger).
+
+        ***********************************************************************/
+
+        final Logger setLevel (Level level = Level.Trace)
+        {
+                return setLevel (level, false);
+        }
+
+        /***********************************************************************
+        
+                Set the current level for this logger, and (optionally) all
+                of its descendents.
+
+        ***********************************************************************/
+
+        final Logger setLevel (Level level, bool propagate)
+        {
+                this.level_ = level;     
+                hierarchy.updateLoggers (this, propagate);
+                return this;
+        }
+
+        /***********************************************************************
+        
+                Set the breakpoint status of this logger.
+
+        ***********************************************************************/
+
+        final Logger setBreakpoint (bool enabled)
+        {
+                breakpoint = enabled;     
+                hierarchy.updateLoggers (this, false);
+                return this;
+        }
+
+        /***********************************************************************
+        
+                Set the additive status of this logger. See isAdditive().
+
+        ***********************************************************************/
+
+        final Logger setAdditive (bool enabled)
+        {
+                additive = enabled;     
+                return this;
+        }
+
+        /***********************************************************************
+        
+                Add (another) appender to this logger. Appenders are each
+                invoked for log events as they are produced. At most, one
+                instance of each appender will be invoked.
+
+        ***********************************************************************/
+
+        final Logger addAppender (Appender next)
+        {
+                if (appender)
+                    next.setNext (appender);
+                appender = next;
+                return this;
+        }
+
+        /***********************************************************************
+        
+                Remove all appenders from this Logger
+
+        ***********************************************************************/
+
+        final Logger clearAppenders ()
+        {
+                appender = null;     
+                return this;
+        }
+
+
+        /***********************************************************************
+        
+                Get time since this application started
+
+        ***********************************************************************/
+
+        final TimeSpan runtime ()
+        {
+                return Clock.now - Event.startedAt;
+        }
+
+        /***********************************************************************
+        
+                Append a message to this logger via its appender list.
+
+        ***********************************************************************/
+
+        final Logger append (Level level, lazy char[] exp)
+        {
+                if (hierarchy.context.isEnabled (level_, level))
+                   {
+                   auto event = Event.allocate;
+                   scope (exit)
+                          Event.deallocate (event);
+
+                   // set the event attributes
+                   event.set (hierarchy, level, exp, name.length ? name_[0..$-1] : "root");
+
+                   // combine appenders from all ancestors
+                   auto links = this;
+                   Appender.Mask masks = 0;                 
+                   do {
+                      auto appender = links.appender;
+
+                      // this level have an appender?
+                      while (appender)
+                            { 
+                            auto mask = appender.getMask;
+
+                            // have we used this appender already?
+                            if ((masks & mask) is 0)
+                               {
+                               // no - append message and update mask
+                               event.scratch.length = 0;
+                               appender.append (event);
+                               masks |= mask;
+                               }
+                            // process all appenders for this node
+                            appender = appender.getNext;
+                            }
+                        // process all ancestors
+                      } while (links.additive && ((links = links.parent) !is null));
+                   }
+                return this;
+        }
+
+        /***********************************************************************
+
+                Format text using the formatter configured in the associated
+                hierarchy (see Hierarchy.setFormat)
+
+        ***********************************************************************/
+
+        final char[] format (char[] buffer, char[] formatStr, ...)
+        {
+                return .format.vprint (buffer, formatStr, _arguments, _argptr);     
+        }
+
+        /***********************************************************************
+        
+                See if the provided Logger is a good match as a parent of
+                this one. Note that each Logger name has a '.' appended to
+                the end, such that name segments will not partially match.
+
+        ***********************************************************************/
+
+        private final bool isCloserAncestor (LoggerInstance other)
+        {
+                auto length = other.name_.length;
+
+                // possible parent if length is shorter
+                if (length < name_.length)
+                    // does the prefix match? Note we append a "." to each 
+                    if (length is 0 || 
+                        memcmp (&other.name_[0], &name_[0], length) is 0)
+                        // is this a better (longer) match than prior parent?
+                        if ((parent is null) || (length >= parent.name_.length))
+                             return true;
+                return false;
+        }
+}
+
+
+/*******************************************************************************
+ 
+        The Logger hierarchy implementation. We keep a reference to each
+        logger in a hash-table for convenient lookup purposes, plus keep
+        each logger linked to the others in an ordered chain. Ordering
+        places shortest names at the head and longest ones at the tail, 
+        making the job of identifying ancestors easier in an orderly
+        fashion. For example, when propagating levels across descendents
+        it would be a mistake to propagate to a child before all of its
+        ancestors were taken care of.
+
+*******************************************************************************/
+
+class Hierarchy : IHierarchy, IHierarchy.Context
+{
+        private char[]                  name,
+                                        address;      
+        private LoggerInstance          root;
+        private LoggerInstance[char[]]  loggers;
+        private Context                 context_;
+
+        /***********************************************************************
+        
+                Construct a hierarchy with the given name.
+
+        ***********************************************************************/
+
+        this (char[] name)
+        {
+                this.name = name;
+                this.address = "network";
+
+                // insert a root node; the root has an empty name
+                root = new LoggerInstance (this, "");
+                context = this;
+        }
+
+        /**********************************************************************
+
+        **********************************************************************/
+
+        final char[] label ()
+        {
+                return "";
+        }
+                
+        /**********************************************************************
+
+
+        **********************************************************************/
+
+        final bool isEnabled (ILevel.Level level, ILevel.Level test)
+        {
+                return test >= level;
+        }
+
+        /**********************************************************************
+
+                Return the name of this Hierarchy
+
+        **********************************************************************/
+
+        final char[] getName ()
+        {
+                return name;
+        }
+
+        /**********************************************************************
+
+                Return the address of this Hierarchy. This is typically
+                attached when sending events to remote monitors.
+
+        **********************************************************************/
+
+        final char[] getAddress ()
+        {
+                return address;
+        }
+
+        /**********************************************************************
+
+                Set the name of this Hierarchy
+
+        **********************************************************************/
+
+        final void setName (char[] name)
+        {
+                this.name = name;
+        }
+
+        /**********************************************************************
+
+                Set the address of this Hierarchy. The address is attached
+                used when sending events to remote monitors.
+
+        **********************************************************************/
+
+        final void setAddress (char[] address)
+        {
+                this.address = address;
+        }
+
+        /**********************************************************************
+
+                Set the diagnostic context.  Not usually necessary, as a 
+                default was created.  Useful when you need to provide a 
+                different implementation, such as a ThreadLocal variant.
+
+        **********************************************************************/
+        
+        final void context (Context context)
+        {
+        	this.context_ = context;
+        }
+        
+        /**********************************************************************
+
+                Return the diagnostic context.  Useful for setting an 
+                override logging level.
+
+        **********************************************************************/
+        
+        final Context context ()
+        {
+        	return context_;
+        }
+        
+        /***********************************************************************
+        
+                Return the root node.
+
+        ***********************************************************************/
+
+        final LoggerInstance getRootLogger ()
+        {
+                return root;
+        }
+
+        /***********************************************************************
+        
+                Return the instance of a Logger with the provided label. If
+                the instance does not exist, it is created at this time.
+
+        ***********************************************************************/
+
+        final synchronized LoggerInstance getLogger (char[] label)
+        {
+                auto name = label ~ ".";
+                auto l = name in loggers;
+
+                if (l is null)
+                   {
+                   // create a new logger
+                   auto li = new LoggerInstance (this, name);
+                   l = &li;
+
+                   // insert into linked list
+                   insertLogger (li);
+
+                   // look for and adjust children
+                   updateLoggers (li, true);
+
+                   // insert into map
+                   loggers [name] = li;
+                   }
+               
+                return *l;
+        }
+
+        /**********************************************************************
+
+                Iterate over all Loggers in list
+
+        **********************************************************************/
+
+        final int opApply (int delegate(inout Logger) dg)
+        {
+                int result = 0;
+                LoggerInstance curr = root;
+
+                while (curr)
+                      {
+                      // BUG: this uncovers a cast() issue in the 'inout' delegation
+                      Logger logger = curr;
+                      if ((result = dg (logger)) != 0)
+                           break;
+                      curr = curr.next;
+                      }
+                return result;
+        }
+
+        /***********************************************************************
+        
+                Loggers are maintained in a sorted linked-list. The order 
+                is maintained such that the shortest name is at the root, 
+                and the longest at the tail.
+
+                This is done so that updateLoggers() will always have a
+                known environment to manipulate, making it much faster.
+
+        ***********************************************************************/
+
+        private void insertLogger (LoggerInstance l)
+        {
+                LoggerInstance prev,
+                               curr = root;
+
+                while (curr)
+                      {
+                      // insert here if the new name is shorter
+                      if (l.name.length < curr.name.length)
+                          if (prev is null)
+                              throw new IllegalElementException ("invalid hierarchy");
+                          else                                 
+                             {
+                             l.next = prev.next;
+                             prev.next = l;
+                             return;
+                             }
+                      else
+                         // find best match for parent of new entry
+                         propagate (l, curr, true);
+
+                      // remember where insertion point should be
+                      prev = curr;  
+                      curr = curr.next;  
+                      }
+
+                // add to tail
+                prev.next = l;
+        }
+
+        /***********************************************************************
+        
+                Propagate hierarchical changes across known loggers. 
+                This includes changes in the hierarchy itself, and to
+                the various settings of child loggers with respect to 
+                their parent(s).              
+
+        ***********************************************************************/
+
+        private void updateLoggers (LoggerInstance changed, bool force)
+        {
+                LoggerInstance logger = root;
+
+                // scan all loggers 
+                while (logger)
+                      {
+                      propagate (logger, changed, force);
+
+                      // try next entry
+                      logger = logger.next;
+                      }                
+        }
+
+        /***********************************************************************
+        
+                Propagate changes in the hierarchy downward to child Loggers.
+                Note that while 'parent' and 'breakpoint' are always forced
+                to update, the update of 'level' is selectable.
+
+        ***********************************************************************/
+
+        private void propagate (LoggerInstance logger, LoggerInstance changed, bool force)
+        {
+                // is the changed instance a better match for our parent?
+                if (logger.isCloserAncestor (changed))
+                   {
+                   // update parent (might actually be current parent)
+                   logger.parent = changed;
+
+                   // if we don't have an explicit level set, inherit it
+                   if ((logger.level is Logger.Level.None) || force)
+                        logger.setLevel (changed.level);
+
+                   // always force breakpoints to follow parent settings
+                   logger.breakpoint = changed.breakpoint;
+                   }
+        }
+}
+
+