view 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 source

/*******************************************************************************

        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;
                   }
        }
}