      }
    }
  ?>
-Index: skeleton/util/log/BufferedAppender.class.php
-===================================================================
---- skeleton/util/log/BufferedAppender.class.php	(revision 13884)
-+++ skeleton/util/log/BufferedAppender.class.php	(working copy)
-@@ -13,19 +13,15 @@
-    * @purpose  Appender
-    */  
-   class BufferedAppender extends LogAppender {
--    public 
--      $buffer = '';
-+    public $buffer= '';
- 
-     /**
--     * Appends log data to the buffer
-+     * Append data
-      *
--     * @param  mixed* args variables
--     */
--    public function append() {
--      foreach (func_get_args() as $arg) {
--        $this->buffer.= $this->varSource($arg).' ';
--      }
--      $this->buffer.= "\n";
+Index: skeleton/util/log/DefaultLayout.class.php
+===================================================================
+--- skeleton/util/log/DefaultLayout.class.php	(revision 0)
++++ skeleton/util/log/DefaultLayout.class.php	(revision 0)
+@@ -0,0 +1,43 @@
++<?php
++/* This class is part of the XP framework
++ *
++ * $Id$ 
++ */
++
++  uses('util.log.Layout');
++
++  /**
++   * Default layout
++   *
++   */
++  class DefaultLayout extends utillogLayout {
++  
++    /**
++     * Creates a string representation of the given argument. For any 
++     * string given, the result is the string itself, for any other type,
++     * the result is the xp::stringOf() output.
++     *
++     * @param   var arg
++     * @return  string
++     */
++    protected function stringOf($arg) {
++      return is_string($arg) ? $arg : xp::stringOf($arg);
++    }
++
++    /**
++     * Formats a logging event according to this layout
++     *
 +     * @param   util.log.LoggingEvent event
-+     */ 
-+    public function append(LoggingEvent $event) {
-+      $this->buffer.= $this->layout->format($event);
-     }
-     
-     /**
-Index: skeleton/util/log/SmtpAppender.class.php
-===================================================================
---- skeleton/util/log/SmtpAppender.class.php	(revision 13884)
-+++ skeleton/util/log/SmtpAppender.class.php	(working copy)
-@@ -43,24 +43,18 @@
-     }
-     
-     /**
--     * Sends log data to the specified email address
-+     * Append data
-      *
--     * @param   mixed args variables
--     */
--    public function append() {
--      $body= '';
--      
--      with ($args= func_get_args()); {
--        foreach ($args as $idx => $arg) {
--          $body.= $this->varSource($arg).($idx < sizeof($args)-1 ? ' ' : '');
--        }
--      }
--      
-+     * @param   util.log.LoggingEvent event
-+     */ 
-+    public function append(LoggingEvent $event) {
-+      $body= $this->layout->format($event);
-       if ($this->sync) {
-         mail($this->email, $this->prefix, $body);
-       } else {
-         $this->_data[]= $body;
-       }
-+
-     }
-     
-     /**
++     * @return  string
++     */
++    public function format(LoggingEvent $event) {
++      return sprintf(
++        '[%s %5d %5s] %s', 
++        date('H:i:s', $event->getTimestamp()),
++        $event->getProcessId(),
++        strtolower(LogLevel::nameOf($event->getLevel())),
++        implode(' ', array_map(array($this, 'stringOf'), $event->getArguments()))
++      );
++    }
++  }
++?>
+
+Property changes on: skeleton/util/log/DefaultLayout.class.php
+___________________________________________________________________
+Added: svn:keywords
+   + Id
+
+Index: ports/classes/net/xp_framework/unittest/logging/LoggingEventTest.class.php
+===================================================================
+--- ports/classes/net/xp_framework/unittest/logging/LoggingEventTest.class.php	(revision 0)
++++ ports/classes/net/xp_framework/unittest/logging/LoggingEventTest.class.php	(revision 0)
+@@ -0,0 +1,79 @@
++<?php
++/* This class is part of the XP framework
++ *
++ * $Id$ 
++ */
++
++  uses(
++    'unittest.TestCase',
++    'util.log.LoggingEvent'
++  );
++
++  /**
++   * TestCase
++   *
++   * @see      xp://util.log.LoggingEvent
++   */
++  class LoggingEventTest extends TestCase {
++    protected $fixture= NULL;
++  
++    /**
++     * Creates fixture
++     *
++     */
++    public function setUp() {
++      $this->fixture= new LoggingEvent(
++        new LogCategory('default', NULL, NULL, 0), 
++        1258733284, 
++        1, 
++        LogLevel::INFO, 
++        array('Hello')
++      );
++    }
++  
++    /**
++     * Test getCategory() method
++     *
++     */
++    #[@test]
++    public function getCategory() {
++      $this->assertEquals(new LogCategory('default', NULL, NULL, 0), $this->fixture->getCategory());
++    }
++ 
++    /**
++     * Test getTimestamp() method
++     *
++     */
++    #[@test]
++    public function getTimestamp() {
++      $this->assertEquals(1258733284, $this->fixture->getTimestamp());
++    }
++
++    /**
++     * Test getProcessId() method
++     *
++     */
++    #[@test]
++    public function getProcessId() {
++      $this->assertEquals(1, $this->fixture->getProcessId());
++    }
++
++    /**
++     * Test getLevel() method
++     *
++     */
++    #[@test]
++    public function getLevel() {
++      $this->assertEquals(LogLevel::INFO, $this->fixture->getLevel());
++    }
++
++    /**
++     * Test getArguments() method
++     *
++     */
++    #[@test]
++    public function getArguments() {
++      $this->assertEquals(array('Hello'), $this->fixture->getArguments());
++    }
++  }
++?>
+
+Property changes on: ports/classes/net/xp_framework/unittest/logging/LoggingEventTest.class.php
+___________________________________________________________________
+Added: svn:keywords
+   + Id
+
+Index: ports/classes/net/xp_framework/unittest/logging/PatternLayoutTest.class.php
+===================================================================
+--- ports/classes/net/xp_framework/unittest/logging/PatternLayoutTest.class.php	(revision 0)
++++ ports/classes/net/xp_framework/unittest/logging/PatternLayoutTest.class.php	(revision 0)
+@@ -0,0 +1,92 @@
++<?php
++/* This class is part of the XP framework
++ *
++ * $Id$ 
++ */
++
++  uses(
++    'unittest.TestCase',
++    'util.log.PatternLayout'
++  );
++
++  /**
++   * TestCase
++   *
++   * @see      xp://util.log.PatternLayout
++   */
++  class PatternLayoutTest extends TestCase {
++
++    /**
++     * Test illegal format token %Q
++     * 
++     */
++    #[@test, @expect('lang.IllegalArgumentException')]
++    public function illegalFormatToken() {
++      new PatternLayout('%Q');
++    }
++ 
++    /**
++     * Test unterminated format token
++     * 
++     */
++    #[@test, @expect('lang.IllegalArgumentException')]
++    public function unterminatedFormatToken() {
++      new PatternLayout('%');
++    }
++    
++    /**
++     * Creates a new logging event
++     *
++     * @return  util.log.LoggingEvent
++     */
++    protected function newLoggingEvent() {
++      return new LoggingEvent(
++        new LogCategory('default'), 
++        1258733284, 
++        1214, 
++        LogLevel::WARN, 
++        array('Hello')
++      );   
++    }
++
++    /**
++     * Test literal percent
++     * 
++     */
++    #[@test]
++    public function literalPercent() {
++      $this->assertEquals(
++        '100%',
++        create(new PatternLayout('100%%'))->format($this->newLoggingEvent())
++      );
++    }
++
++    /**
++     * Test simple format:
++     * <pre>
++     *   INFO [default] Hello
++     * </pre>
++     */
++    #[@test]
++    public function simpleFormat() {
++      $this->assertEquals(
++        'WARN [default] Hello',
++        create(new PatternLayout('%L [%c] %m'))->format($this->newLoggingEvent())
++      );
++    }
++
++    /**
++     * Test default format:
++     * <pre>
++     *   [16:08:04 1214 warn] Hello
++     * </pre>
++     */
++    #[@test]
++    public function defaultFormat() {
++      $this->assertEquals(
++        '[16:08:04 1214 warn] Hello',
++        create(new PatternLayout('[%t %p %l] %m'))->format($this->newLoggingEvent())
++      );
++    }
++  }
++?>
+
+Property changes on: ports/classes/net/xp_framework/unittest/logging/PatternLayoutTest.class.php
+___________________________________________________________________
+Added: svn:keywords
+   + Id
+
 Index: ports/classes/net/xp_framework/unittest/logging/LogCategoryTest.class.php
 ===================================================================
 --- ports/classes/net/xp_framework/unittest/logging/LogCategoryTest.class.php	(revision 13884)

       fwrite(STDERR, "\x1b[0m\n");
     }
   }
Index: skeleton/util/log/LogLevel.class.php
===================================================================
--- skeleton/util/log/LogLevel.class.php	(revision 13884)
+++ skeleton/util/log/LogLevel.class.php	(working copy)
@@ -4,6 +4,12 @@
  * $Id$ 
  */
 
+  define('LOGGER_FLAG_INFO',    0x0001);
+  define('LOGGER_FLAG_WARN',    0x0002);
+  define('LOGGER_FLAG_ERROR',   0x0004);
+  define('LOGGER_FLAG_DEBUG',   0x0008);
+  define('LOGGER_FLAG_ALL',     LOGGER_FLAG_INFO | LOGGER_FLAG_WARN | LOGGER_FLAG_ERROR | LOGGER_FLAG_DEBUG);
+
   /**
    * Log levels
    *
@@ -45,5 +51,26 @@
       }
       return $map[$key];
     }
+
+    /**
+     * Retrieve a loglevel name for a given level
+     *
+     * @param   int level
+     * @return  string
+     * @throws  lang.IllegalArgumentException
+     */
+    public static function nameOf($level) {
+      static $map= array(
+        self::INFO  => 'INFO',
+        self::WARN  => 'WARN',
+        self::ERROR => 'ERROR',
+        self::DEBUG => 'DEBUG',
+      );
+    
+      if (!isset($map[$level])) {
+        throw new IllegalArgumentException('No such loglevel '.$level);
+      }
+      return $map[$level];
+    }
   }
 ?>
Index: skeleton/util/log/StreamAppender.class.php
===================================================================
--- skeleton/util/log/StreamAppender.class.php	(revision 13884)
+++ skeleton/util/log/StreamAppender.class.php	(working copy)
@@ -13,30 +13,24 @@
    * @purpose  Appender
    */  
   class StreamAppender extends LogAppender {
-    public 
-      $stream = NULL;
+    public $stream= NULL;
     
     /**
      * Constructor
      *
      * @param   io.streams.OutputStream stream
      */
-    public function __construct($stream) {
+    public function __construct(OutputStream $stream) {
       $this->stream= $stream;
     }
     
     /**
-     * Appends log data to the file
+     * Append data
      *
-     * @param   mixed* args variables
-     */
-    public function append() {
-      with ($args= func_get_args()); {
-        foreach ($args as $idx => $arg) {
-          $this->stream->write($this->varSource($arg). ($idx < sizeof($args)-1 ? ' ' : ''));
-        }
-      }
-      $this->stream->write("\n");
+     * @param   util.log.LoggingEvent event
+     */ 
+    public function append(LoggingEvent $event) {
+      $this->stream->write($this->layout->format($event));
     }
   }
 ?>
Index: skeleton/util/log/FileAppender.class.php
===================================================================
--- skeleton/util/log/FileAppender.class.php	(revision 13884)

     }
     
     /**
Index: ports/classes/net/xp_framework/unittest/logging/LogAppenderTest.class.php
===================================================================
--- ports/classes/net/xp_framework/unittest/logging/LogAppenderTest.class.php	(revision 0)
+++ ports/classes/net/xp_framework/unittest/logging/LogAppenderTest.class.php	(revision 0)
@@ -0,0 +1,97 @@
+<?php
+/* This class is part of the XP framework
+ *
+ * $Id$ 
+ */
+
+  uses(
+    'unittest.TestCase',
+    'util.log.LogAppender',
+    'util.log.LogCategory',
+    'util.log.PatternLayout',
+    'util.collections.Vector'
+  );
+
+  /**
+   * TestCase
+   *
+   * @see      reference
+   * @purpose  purpose
+   */
+  class LogAppenderTest extends TestCase {
+    protected $fixture= NULL;
+    protected $events= NULL;
+  
+    /**
+     * Sets up test case
+     *
+     */
+    public function setUp() {
+      $this->events= create('new Vector<String>()');
+      $appender= newinstance('util.log.LogAppender', array($this->events), '{
+        private $events= NULL;
+
+        public function __construct($events) {
+          $this->events= $events;
+        }
+
+        public function append(LoggingEvent $event) {
+          $this->events[]= new String($this->layout->format($event));
+        }
+      }');
+      $this->fixture= create(new LogCategory('default'))
+        ->withAppender($appender->withLayout(new PatternLayout('[%l] %m')))
+      ;
+    }
+    
+    /**
+     * Test
+     *
+     */
+    #[@test]
+    public function info() {
+      $this->fixture->info('Hello');
+      $this->assertEquals(new String('[info] Hello'), $this->events[0]);
+    }
+
+    /**
+     * Test
+     *
+     */
+    #[@test]
+    public function infoWithMultipleArguments() {
+      $this->fixture->info('Hello', 'World');
+      $this->assertEquals(new String('[info] Hello World'), $this->events[0]);
+    }
+
+    /**
+     * Test
+     *
+     */
+    #[@test]
+    public function warn() {
+      $this->fixture->warn('Hello');
+      $this->assertEquals(new String('[warn] Hello'), $this->events[0]);
+    }
+
+    /**
+     * Test
+     *
+     */
+    #[@test]
+    public function debug() {
+      $this->fixture->debug('Hello');
+      $this->assertEquals(new String('[debug] Hello'), $this->events[0]);
+    }
+
+    /**
+     * Test
+     *
+     */
+    #[@test]
+    public function error() {
+      $this->fixture->error('Hello');
+      $this->assertEquals(new String('[error] Hello'), $this->events[0]);
+    }
+  }
+?>

Property changes on: ports/classes/net/xp_framework/unittest/logging/LogAppenderTest.class.php
___________________________________________________________________
Added: svn:keywords
   + Id

Index: ports/unittest/logging.ini
===================================================================
--- ports/unittest/logging.ini	(revision 13884)
