http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/LruList.java
----------------------------------------------------------------------
diff --git a/log/src/main/java/org/apache/karaf/log/core/internal/LruList.java 
b/log/src/main/java/org/apache/karaf/log/core/internal/LruList.java
new file mode 100644
index 0000000..ea51b34
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/log/core/internal/LruList.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.log.core.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.ops4j.pax.logging.spi.PaxAppender;
+import org.ops4j.pax.logging.spi.PaxLoggingEvent;
+
+/**
+ * A list that only keep the last N elements added
+ */
+public class LruList implements PaxAppender {
+
+    private PaxLoggingEvent[] elements;
+    private transient int start = 0;
+    private transient int end = 0;
+    private transient boolean full = false;
+    private final int maxElements;
+    private final List<PaxAppender> appenders;
+
+    public LruList(int size) {
+        if (size <= 0) {
+            throw new IllegalArgumentException("The size must be greater than 
0");
+        }
+        elements = new PaxLoggingEvent[size];
+        maxElements = elements.length;
+        appenders = new ArrayList<PaxAppender>();
+    }
+
+    public synchronized int size() {
+        int size = 0;
+        if (end < start) {
+            size = maxElements - start + end;
+        } else if (end == start) {
+            size = (full ? maxElements : 0);
+        } else {
+            size = end - start;
+        }
+        return size;
+    }
+
+    public synchronized void clear() {
+        start = 0;
+        end = 0;
+        elements = new PaxLoggingEvent[maxElements];
+    }
+
+    public synchronized void add(PaxLoggingEvent element) {
+        if (null == element) {
+             throw new NullPointerException("Attempted to add null object to 
buffer");
+        }
+        if (size() == maxElements) {
+            Object e = elements[start];
+            if (null != e) {
+                elements[start++] = null;
+                if (start >= maxElements) {
+                    start = 0;
+                }
+                full = false;
+            }
+        }
+        elements[end++] = element;
+        if (end >= maxElements) {
+            end = 0;
+        }
+        if (end == start) {
+            full = true;
+        }
+        for (PaxAppender appender : appenders) {
+            try {
+                appender.doAppend(element);
+            } catch (Throwable t) {
+                // Ignore
+            }
+        }
+    }
+
+    public synchronized Iterable<PaxLoggingEvent> getElements() {
+        return getElements(size());
+    }
+
+    public synchronized Iterable<PaxLoggingEvent> getElements(int nb) {
+        int s = size();
+        nb = Math.min(Math.max(0, nb), s);
+        PaxLoggingEvent[] e = new PaxLoggingEvent[nb];
+        for (int i = 0; i < nb; i++) {
+            e[i] = elements[(i + s - nb + start) % maxElements];
+        }
+        return Arrays.asList(e);
+    }
+
+    public synchronized void addAppender(PaxAppender appender) {
+        this.appenders.add(appender);
+    }
+
+    public synchronized void removeAppender(PaxAppender appender) {
+        this.appenders.remove(appender);
+    }
+    
+    public void doAppend(PaxLoggingEvent event) {
+        event.getProperties(); // ensure MDC properties are copied
+        add(event);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/AbsoluteTimeDateFormat.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/AbsoluteTimeDateFormat.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/AbsoluteTimeDateFormat.java
new file mode 100644
index 0000000..8bb3ff3
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/AbsoluteTimeDateFormat.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Copied from log4j
+ */
+/**
+   Formats a {@link Date} in the format "HH:mm:ss,SSS" for example,
+   "15:49:37,459".
+
+   @since 0.7.5
+*/
+public class AbsoluteTimeDateFormat extends DateFormat {
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.AbsoluteTimeDateFormat} in layouts. Current
+     value is <b>ABSOLUTE</b>.  */
+  public final static String ABS_TIME_DATE_FORMAT = "ABSOLUTE";
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.DateTimeDateFormat} in layouts.  Current
+     value is <b>DATE</b>.
+  */
+  public final static String DATE_AND_TIME_DATE_FORMAT = "DATE";
+
+  /**
+     String constant used to specify {@link
+     org.apache.log4j.helpers.ISO8601DateFormat} in layouts. Current
+     value is <b>ISO8601</b>.
+  */
+  public final static String ISO8601_DATE_FORMAT = "ISO8601";
+
+  public
+  AbsoluteTimeDateFormat() {
+    setCalendar(Calendar.getInstance());
+  }
+
+  public
+  AbsoluteTimeDateFormat(TimeZone timeZone) {
+    setCalendar(Calendar.getInstance(timeZone));
+  }
+
+  private static long   previousTime;
+  private static char[] previousTimeWithoutMillis = new char[9]; // "HH:mm:ss."
+
+  /**
+     Appends to <code>sbuf</code> the time in the format
+     "HH:mm:ss,SSS" for example, "15:49:37,459"
+
+     @param date the date to format
+     @param sbuf the string buffer to write to
+     @param fieldPosition remains untouched
+    */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    long now = date.getTime();
+    int millis = (int)(now % 1000);
+
+    if ((now - millis) != previousTime) {
+      // We reach this point at most once per second
+      // across all threads instead of each time format()
+      // is called. This saves considerable CPU time.
+
+      calendar.setTime(date);
+
+      int start = sbuf.length();
+
+      int hour = calendar.get(Calendar.HOUR_OF_DAY);
+      if(hour < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(hour);
+      sbuf.append(':');
+
+      int mins = calendar.get(Calendar.MINUTE);
+      if(mins < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(mins);
+      sbuf.append(':');
+
+      int secs = calendar.get(Calendar.SECOND);
+      if(secs < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(secs);
+      sbuf.append(',');
+
+      // store the time string for next time to avoid recomputation
+      sbuf.getChars(start, sbuf.length(), previousTimeWithoutMillis, 0);
+
+      previousTime = now - millis;
+    }
+    else {
+      sbuf.append(previousTimeWithoutMillis);
+    }
+
+
+
+    if(millis < 100)
+      sbuf.append('0');
+    if(millis < 10)
+      sbuf.append('0');
+
+    sbuf.append(millis);
+    return sbuf;
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(String s, ParsePosition pos) {
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/DateTimeDateFormat.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/DateTimeDateFormat.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/DateTimeDateFormat.java
new file mode 100644
index 0000000..1aa884d
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/DateTimeDateFormat.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Copied from log4j
+ */
+/**
+   Formats a {@link Date} in the format "dd MMM yyyy HH:mm:ss,SSS" for example,
+   "06 Nov 1994 15:49:37,459".
+
+   @since 0.7.5
+*/
+public class DateTimeDateFormat extends AbsoluteTimeDateFormat {
+
+  String[] shortMonths;
+
+  public
+  DateTimeDateFormat() {
+    super();
+    shortMonths = new DateFormatSymbols().getShortMonths();
+  }
+
+  public
+  DateTimeDateFormat(TimeZone timeZone) {
+    this();
+    setCalendar(Calendar.getInstance(timeZone));
+  }
+
+  /**
+     Appends to <code>sbuf</code> the date in the format "dd MMM yyyy
+     HH:mm:ss,SSS" for example, "06 Nov 1994 08:49:37,459".
+
+     @param sbuf the string buffer to write to
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    calendar.setTime(date);
+
+    int day = calendar.get(Calendar.DAY_OF_MONTH);
+    if(day < 10)
+      sbuf.append('0');
+    sbuf.append(day);
+    sbuf.append(' ');
+    sbuf.append(shortMonths[calendar.get(Calendar.MONTH)]);
+    sbuf.append(' ');
+
+    int year =  calendar.get(Calendar.YEAR);
+    sbuf.append(year);
+    sbuf.append(' ');
+
+    return super.format(date, sbuf, fieldPosition);
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/FormattingInfo.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/FormattingInfo.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/FormattingInfo.java
new file mode 100644
index 0000000..6fd728f
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/FormattingInfo.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+
+/**
+ * Copied from log4j
+ */
+/**
+   FormattingInfo instances contain the information obtained when parsing
+   formatting modifiers in conversion modifiers.
+
+   @since 0.8.2
+ */
+public class FormattingInfo {
+  int min = -1;
+  int max = 0x7FFFFFFF;
+  boolean leftAlign = false;
+
+  void reset() {
+    min = -1;
+    max = 0x7FFFFFFF;
+    leftAlign = false;
+  }
+
+  void dump() {
+    //LogLog.debug("min="+min+", max="+max+", leftAlign="+leftAlign);
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/ISO8601DateFormat.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/ISO8601DateFormat.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/ISO8601DateFormat.java
new file mode 100644
index 0000000..80155a0
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/ISO8601DateFormat.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Copied from log4j
+ */
+// Contributors: Arndt Schoenewald <ar...@ibm23093i821.mc.schoenewald.de>
+/**
+   Formats a {@link Date} in the format "yyyy-MM-dd HH:mm:ss,SSS" for example
+   "1999-11-27 15:49:37,459".
+
+   <p>Refer to the <a
+   href=http://www.cl.cam.ac.uk/~mgk25/iso-time.html>summary of the
+   International Standard Date and Time Notation</a> for more
+   information on this format.
+
+   @since 0.7.5
+*/
+public class ISO8601DateFormat extends AbsoluteTimeDateFormat {
+
+  public
+  ISO8601DateFormat() {
+  }
+
+  public
+  ISO8601DateFormat(TimeZone timeZone) {
+    super(timeZone);
+  }
+
+  static private long   lastTime;
+  static private char[] lastTimeString = new char[20];
+
+  /**
+     Appends a date in the format "YYYY-mm-dd HH:mm:ss,SSS"
+     to <code>sbuf</code>. For example: "1999-11-27 15:49:37,459".
+
+     @param sbuf the <code>StringBuffer</code> to write to
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+                     FieldPosition fieldPosition) {
+
+    long now = date.getTime();
+    int millis = (int)(now % 1000);
+
+    if ((now - millis) != lastTime) {
+      // We reach this point at most once per second
+      // across all threads instead of each time format()
+      // is called. This saves considerable CPU time.
+
+      calendar.setTime(date);
+
+      int start = sbuf.length();
+
+      int year =  calendar.get(Calendar.YEAR);
+      sbuf.append(year);
+
+      String month;
+      switch(calendar.get(Calendar.MONTH)) {
+      case Calendar.JANUARY: month = "-01-"; break;
+      case Calendar.FEBRUARY: month = "-02-";  break;
+      case Calendar.MARCH: month = "-03-"; break;
+      case Calendar.APRIL: month = "-04-";  break;
+      case Calendar.MAY: month = "-05-"; break;
+      case Calendar.JUNE: month = "-06-";  break;
+      case Calendar.JULY: month = "-07-"; break;
+      case Calendar.AUGUST: month = "-08-";  break;
+      case Calendar.SEPTEMBER: month = "-09-"; break;
+      case Calendar.OCTOBER: month = "-10-"; break;
+      case Calendar.NOVEMBER: month = "-11-";  break;
+      case Calendar.DECEMBER: month = "-12-";  break;
+      default: month = "-NA-"; break;
+      }
+      sbuf.append(month);
+
+      int day = calendar.get(Calendar.DAY_OF_MONTH);
+      if(day < 10)
+       sbuf.append('0');
+      sbuf.append(day);
+
+      sbuf.append(' ');
+
+      int hour = calendar.get(Calendar.HOUR_OF_DAY);
+      if(hour < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(hour);
+      sbuf.append(':');
+
+      int mins = calendar.get(Calendar.MINUTE);
+      if(mins < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(mins);
+      sbuf.append(':');
+
+      int secs = calendar.get(Calendar.SECOND);
+      if(secs < 10) {
+       sbuf.append('0');
+      }
+      sbuf.append(secs);
+
+      sbuf.append(',');
+
+      // store the time string for next time to avoid recomputation
+      sbuf.getChars(start, sbuf.length(), lastTimeString, 0);
+      lastTime = now - millis;
+    }
+    else {
+      sbuf.append(lastTimeString);
+    }
+
+
+    if (millis < 100)
+      sbuf.append('0');
+    if (millis < 10)
+      sbuf.append('0');
+
+    sbuf.append(millis);
+    return sbuf;
+  }
+
+  /**
+    This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternConverter.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternConverter.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternConverter.java
new file mode 100644
index 0000000..0d91e8c
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternConverter.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+import org.ops4j.pax.logging.spi.PaxLoggingEvent;
+
+/**
+   <p>PatternConverter is an abtract class that provides the
+   formatting functionality that derived classes need.
+
+   <p>Conversion specifiers in a conversion patterns are parsed to
+   individual PatternConverters. Each of which is responsible for
+   converting a logging event in a converter specific manner.
+
+   @since 0.8.2
+ */
+public abstract class PatternConverter {
+  public PatternConverter next;
+  int min = -1;
+  int max = 0x7FFFFFFF;
+  boolean leftAlign = false;
+
+  protected
+  PatternConverter() {  }
+
+  protected
+  PatternConverter(FormattingInfo fi) {
+    min = fi.min;
+    max = fi.max;
+    leftAlign = fi.leftAlign;
+  }
+
+  /**
+     Derived pattern converters must override this method in order to
+     convert conversion specifiers in the correct way.
+  */
+  abstract
+  protected
+  String convert(PaxLoggingEvent event);
+
+  /**
+     A template method for formatting in a converter specific way.
+   */
+  public
+  void format(StringBuffer sbuf, PaxLoggingEvent e) {
+    String s = convert(e);
+
+    if(s == null) {
+      if(0 < min)
+       spacePad(sbuf, min);
+      return;
+    }
+
+    int len = s.length();
+
+    if(len > max)
+      sbuf.append(s.substring(len-max));
+    else if(len < min) {
+      if(leftAlign) {
+       sbuf.append(s);
+       spacePad(sbuf, min-len);
+      }
+      else {
+       spacePad(sbuf, min-len);
+       sbuf.append(s);
+      }
+    }
+    else
+      sbuf.append(s);
+  }
+
+  static String[] SPACES = {" ", "  ", "    ", "        ", //1,2,4,8 spaces
+                           "                ", // 16 spaces
+                           "                                " }; // 32 spaces
+
+  /**
+     Fast space padding method.
+  */
+  public
+  void spacePad(StringBuffer sbuf, int length) {
+    while(length >= 32) {
+      sbuf.append(SPACES[5]);
+      length -= 32;
+    }
+
+    for(int i = 4; i >= 0; i--) {
+      if((length & (1<<i)) != 0) {
+       sbuf.append(SPACES[i]);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternParser.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternParser.java
 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternParser.java
new file mode 100644
index 0000000..f61b278
--- /dev/null
+++ 
b/log/src/main/java/org/apache/karaf/log/core/internal/layout/PatternParser.java
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.layout;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.ops4j.pax.logging.spi.PaxLocationInfo;
+import org.ops4j.pax.logging.spi.PaxLoggingEvent;
+
+/**
+ * Copied from log4j
+ */
+/**
+   Most of the work of the {@link org.apache.log4j.PatternLayout} class
+   is delegated to the PatternParser class.
+
+   @since 0.8.2
+*/
+public class PatternParser {
+
+  private static final String LINE_SEP = System.getProperty("line.separator");
+
+  private static final char ESCAPE_CHAR = '%';
+
+  private static final int LITERAL_STATE = 0;
+  private static final int CONVERTER_STATE = 1;
+  private static final int MINUS_STATE = 2;
+  private static final int DOT_STATE = 3;
+  private static final int MIN_STATE = 4;
+  private static final int MAX_STATE = 5;
+
+  static final int FULL_LOCATION_CONVERTER = 1000;
+  static final int METHOD_LOCATION_CONVERTER = 1001;
+  static final int CLASS_LOCATION_CONVERTER = 1002;
+  static final int LINE_LOCATION_CONVERTER = 1003;
+  static final int FILE_LOCATION_CONVERTER = 1004;
+
+  static final int RELATIVE_TIME_CONVERTER = 2000;
+  static final int THREAD_CONVERTER = 2001;
+  static final int LEVEL_CONVERTER = 2002;
+  static final int NDC_CONVERTER = 2003;
+  static final int MESSAGE_CONVERTER = 2004;
+
+  int state;
+  protected StringBuffer currentLiteral = new StringBuffer(32);
+  protected int patternLength;
+  protected int i;
+  PatternConverter head;
+  PatternConverter tail;
+  protected FormattingInfo formattingInfo = new FormattingInfo();
+  protected String pattern;
+
+  public
+  PatternParser(String pattern) {
+    this.pattern = pattern;
+    patternLength =  pattern.length();
+    state = LITERAL_STATE;
+  }
+
+  private
+  void  addToList(PatternConverter pc) {
+    if(head == null) {
+      head = tail = pc;
+    } else {
+      tail.next = pc;
+      tail = pc;
+    }
+  }
+
+  protected
+  String extractOption() {
+    if((i < patternLength) && (pattern.charAt(i) == '{')) {
+      int end = pattern.indexOf('}', i);
+      if (end > i) {
+       String r = pattern.substring(i + 1, end);
+       i = end+1;
+       return r;
+      }
+    }
+    return null;
+  }
+
+
+  /**
+     The option is expected to be in decimal and positive. In case of
+     error, zero is returned.  */
+  protected
+  int extractPrecisionOption() {
+    String opt = extractOption();
+    int r = 0;
+    if(opt != null) {
+      try {
+       r = Integer.parseInt(opt);
+       if(r <= 0) {
+           //LogLog.error("Precision option (" + opt + ") isn't a positive 
integer.");
+           r = 0;
+       }
+      }
+      catch (NumberFormatException e) {
+       //LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
+      }
+    }
+    return r;
+  }
+
+  public
+  PatternConverter parse() {
+    char c;
+    i = 0;
+    while(i < patternLength) {
+      c = pattern.charAt(i++);
+      switch(state) {
+      case LITERAL_STATE:
+        // In literal state, the last char is always a literal.
+        if(i == patternLength) {
+          currentLiteral.append(c);
+          continue;
+        }
+        if(c == ESCAPE_CHAR) {
+          // peek at the next char.
+          switch(pattern.charAt(i)) {
+          case ESCAPE_CHAR:
+            currentLiteral.append(c);
+            i++; // move pointer
+            break;
+          case 'n':
+            currentLiteral.append(LINE_SEP);
+            i++; // move pointer
+            break;
+          default:
+            if(currentLiteral.length() != 0) {
+              addToList(new LiteralPatternConverter(
+                                                  currentLiteral.toString()));
+              //LogLog.debug("Parsed LITERAL converter: \""
+              //           +currentLiteral+"\".");
+            }
+            currentLiteral.setLength(0);
+            currentLiteral.append(c); // append %
+            state = CONVERTER_STATE;
+            formattingInfo.reset();
+          }
+        }
+        else {
+          currentLiteral.append(c);
+        }
+        break;
+      case CONVERTER_STATE:
+       currentLiteral.append(c);
+       switch(c) {
+       case '-':
+         formattingInfo.leftAlign = true;
+         break;
+       case '.':
+         state = DOT_STATE;
+         break;
+       default:
+         if(c >= '0' && c <= '9') {
+           formattingInfo.min = c - '0';
+           state = MIN_STATE;
+         }
+         else
+           finalizeConverter(c);
+       } // switch
+       break;
+      case MIN_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9')
+         formattingInfo.min = formattingInfo.min*10 + (c - '0');
+       else if(c == '.')
+         state = DOT_STATE;
+       else {
+         finalizeConverter(c);
+       }
+       break;
+      case DOT_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9') {
+         formattingInfo.max = c - '0';
+          state = MAX_STATE;
+       }
+       else {
+         //LogLog.error("Error occured in position "+i+".\n Was expecting 
digit, instead got char \""+c+"\".");
+         state = LITERAL_STATE;
+       }
+       break;
+      case MAX_STATE:
+       currentLiteral.append(c);
+       if(c >= '0' && c <= '9')
+         formattingInfo.max = formattingInfo.max*10 + (c - '0');
+       else {
+         finalizeConverter(c);
+         state = LITERAL_STATE;
+       }
+       break;
+      } // switch
+    } // while
+    if(currentLiteral.length() != 0) {
+      addToList(new LiteralPatternConverter(currentLiteral.toString()));
+      //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
+    }
+    return head;
+  }
+
+  protected
+  void finalizeConverter(char c) {
+    PatternConverter pc = null;
+    switch(c) {
+    case 'c':
+      pc = new CategoryPatternConverter(formattingInfo,
+                                       extractPrecisionOption());
+      //LogLog.debug("CATEGORY converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'C':
+      pc = new ClassNamePatternConverter(formattingInfo,
+                                        extractPrecisionOption());
+      //LogLog.debug("CLASS_NAME converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'd':
+      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
+      DateFormat df;
+      String dOpt = extractOption();
+      if(dOpt != null)
+       dateFormatStr = dOpt;
+
+      if(dateFormatStr.equalsIgnoreCase(
+                                    
AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
+       df = new  ISO8601DateFormat();
+      else if(dateFormatStr.equalsIgnoreCase(
+                                   
AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
+       df = new AbsoluteTimeDateFormat();
+      else if(dateFormatStr.equalsIgnoreCase(
+                              
AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
+       df = new DateTimeDateFormat();
+      else {
+       try {
+         df = new SimpleDateFormat(dateFormatStr);
+       }
+       catch (IllegalArgumentException e) {
+         //LogLog.error("Could not instantiate SimpleDateFormat with " + 
dateFormatStr, e);
+         df = new ISO8601DateFormat();
+       }
+      }
+      pc = new DatePatternConverter(formattingInfo, df);
+      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'F':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       FILE_LOCATION_CONVERTER);
+      //LogLog.debug("File name converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    /*case 'l':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       FULL_LOCATION_CONVERTER);
+      //LogLog.debug("Location converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;*/
+    case 'L':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       LINE_LOCATION_CONVERTER);
+      //LogLog.debug("LINE NUMBER converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'm':
+      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
+      //LogLog.debug("MESSAGE converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'M':
+      pc = new LocationPatternConverter(formattingInfo,
+                                       METHOD_LOCATION_CONVERTER);
+      //LogLog.debug("METHOD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'p':
+      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
+      //LogLog.debug("LEVEL converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'r':
+      pc = new BasicPatternConverter(formattingInfo,
+                                        RELATIVE_TIME_CONVERTER);
+      //LogLog.debug("RELATIVE time converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 't':
+      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
+      //LogLog.debug("THREAD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+      /*case 'u':
+      if(i < patternLength) {
+       char cNext = pattern.charAt(i);
+       if(cNext >= '0' && cNext <= '9') {
+         pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
+         LogLog.debug("USER converter ["+cNext+"].");
+         formattingInfo.dump();
+         currentLiteral.setLength(0);
+         i++;
+       }
+       else
+         LogLog.error("Unexpected char" +cNext+" at position "+i);
+      }
+      break;*/
+    /*case 'x':
+      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
+      //LogLog.debug("NDC converter.");
+      currentLiteral.setLength(0);
+      break;*/
+    case 'X':
+      String xOpt = extractOption();
+      pc = new MDCPatternConverter(formattingInfo, xOpt);
+      currentLiteral.setLength(0);
+      break;
+    default:
+      //LogLog.error("Unexpected char [" +c+"] at position "+i+" in conversion 
patterrn.");
+      pc = new LiteralPatternConverter(currentLiteral.toString());
+      currentLiteral.setLength(0);
+    }
+
+    addConverter(pc);
+  }
+
+  protected
+  void addConverter(PatternConverter pc) {
+    currentLiteral.setLength(0);
+    // Add the pattern converter to the list.
+    addToList(pc);
+    // Next pattern is assumed to be a literal.
+    state = LITERAL_STATE;
+    // Reset formatting info
+    formattingInfo.reset();
+  }
+
+  // ---------------------------------------------------------------------
+  //                      PatternConverters
+  // ---------------------------------------------------------------------
+
+  private static class BasicPatternConverter extends PatternConverter {
+    int type;
+
+    BasicPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(PaxLoggingEvent event) {
+      switch(type) {
+      case RELATIVE_TIME_CONVERTER:
+       return (Long.toString(event.getTimeStamp() - 
LoggingEvent.getStartTime()));
+      case THREAD_CONVERTER:
+       return event.getThreadName();
+      case LEVEL_CONVERTER:
+       return event.getLevel().toString();
+    //  case NDC_CONVERTER:
+       //return event.getNDC();
+      case MESSAGE_CONVERTER: {
+       return event.getRenderedMessage();
+      }
+      default: return null;
+      }
+    }
+  }
+
+  private static class LiteralPatternConverter extends PatternConverter {
+    private String literal;
+
+    LiteralPatternConverter(String value) {
+      literal = value;
+    }
+
+    public
+    final
+    void format(StringBuffer sbuf, LoggingEvent event) {
+      sbuf.append(literal);
+    }
+
+    public
+    String convert(PaxLoggingEvent event) {
+      return literal;
+    }
+  }
+
+  private static class DatePatternConverter extends PatternConverter {
+    private DateFormat df;
+    private Date date;
+
+    DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
+      super(formattingInfo);
+      date = new Date();
+      this.df = df;
+    }
+
+    public
+    String convert(PaxLoggingEvent event) {
+      date.setTime(event.getTimeStamp());
+      String converted = null;
+      try {
+        converted = df.format(date);
+      }
+      catch (Exception ex) {
+        //LogLog.error("Error occured while converting date.", ex);
+      }
+      return converted;
+    }
+  }
+
+  private class LocationPatternConverter extends PatternConverter {
+    int type;
+
+    LocationPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(PaxLoggingEvent event) {
+      PaxLocationInfo locationInfo = event.getLocationInformation();
+      switch(type) {
+      /*case FULL_LOCATION_CONVERTER:
+       return locationInfo.fullInfo;*/
+      case METHOD_LOCATION_CONVERTER:
+       return locationInfo.getMethodName();
+      case LINE_LOCATION_CONVERTER:
+       return locationInfo.getLineNumber();
+      case FILE_LOCATION_CONVERTER:
+       return locationInfo.getFileName();
+      default: return null;
+      }
+    }
+  }
+
+  private static abstract class NamedPatternConverter extends PatternConverter 
{
+    int precision;
+
+    NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo);
+      this.precision =  precision;
+    }
+
+    abstract
+    String getFullyQualifiedName(PaxLoggingEvent event);
+
+    public
+    String convert(PaxLoggingEvent event) {
+      String n = getFullyQualifiedName(event);
+      if(precision <= 0)
+       return n;
+      else {
+       int len = n.length();
+
+       // We substract 1 from 'len' when assigning to 'end' to avoid out of
+       // bounds exception in return r.substring(end+1, len). This can happen 
if
+       // precision is 1 and the category name ends with a dot.
+       int end = len -1 ;
+       for(int i = precision; i > 0; i--) {
+         end = n.lastIndexOf('.', end-1);
+         if(end == -1)
+           return n;
+       }
+       return n.substring(end+1, len);
+      }
+    }
+  }
+
+  private class ClassNamePatternConverter extends NamedPatternConverter {
+
+    ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(PaxLoggingEvent event) {
+      return event.getLocationInformation().getClassName();
+    }
+  }
+
+  private class CategoryPatternConverter extends NamedPatternConverter {
+
+    CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(PaxLoggingEvent event) {
+      return event.getLoggerName();
+    }
+  }
+
+  private class MDCPatternConverter extends PatternConverter {
+    String key;
+
+    MDCPatternConverter(FormattingInfo formattingInfo, String key) {
+      super(formattingInfo);
+      this.key = key;
+    }
+
+    public
+    String convert(PaxLoggingEvent event) {
+        if (key == null) {
+            StringBuffer buf = new StringBuffer("{");
+            Map properties = event.getProperties();
+            if (properties.size() > 0) {
+              Object[] keys = properties.keySet().toArray();
+              Arrays.sort(keys);
+              for (int i = 0; i < keys.length; i++) {
+                  buf.append('{');
+                  buf.append(keys[i]);
+                  buf.append(',');
+                  buf.append(properties.get(keys[i]));
+                  buf.append('}');
+              }
+            }
+            buf.append('}');
+            return buf.toString();
+        } else {
+          Object val = event.getProperties().get(key);
+          if(val == null) {
+              return null;
+          } else {
+              return val.toString();
+          }
+        }
+    }
+
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/java/org/apache/karaf/log/core/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git 
a/log/src/main/java/org/apache/karaf/log/core/internal/osgi/Activator.java 
b/log/src/main/java/org/apache/karaf/log/core/internal/osgi/Activator.java
new file mode 100644
index 0000000..2e8c4d8
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/log/core/internal/osgi/Activator.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal.osgi;
+
+import java.util.Hashtable;
+
+import org.apache.karaf.log.core.LogEventFormatter;
+import org.apache.karaf.log.core.LogService;
+import org.apache.karaf.log.core.internal.LogEventFormatterImpl;
+import org.apache.karaf.log.core.internal.LogMBeanImpl;
+import org.apache.karaf.log.core.internal.LogServiceImpl;
+import org.apache.karaf.log.core.internal.LruList;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.ops4j.pax.logging.spi.PaxAppender;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ManagedService;
+
+public class Activator extends BaseActivator implements ManagedService {
+
+    @Override
+    protected void doOpen() throws Exception {
+        manage("org.apache.karaf.log");
+        trackService(ConfigurationAdmin.class);
+    }
+
+    protected void doStart() throws Exception {
+        ConfigurationAdmin configurationAdmin = 
getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null) {
+            return;
+        }
+
+        int size = getInt("size", 500);
+        String pattern = getString("pattern", "%d{ABSOLUTE} | %-5.5p | 
%-16.16t | %-32.32c{1} | %-32.32C %4L | %m%n");
+        String fatalColor = getString("fatalColor", "31");
+        String errorColor = getString("errorColor", "31");
+        String warnColor = getString("warnColor", "35");
+        String infoColor = getString("infoColor", "36");
+        String debugColor = getString("debugColor", "39");
+        String traceColor = getString("traceColor", "39");
+
+        LruList events = new LruList(size);
+        Hashtable<String, Object> props = new Hashtable<String, Object>();
+        props.put("org.ops4j.pax.logging.appender.name", "VmLogAppender");
+        register(PaxAppender.class, events, props);
+
+        LogEventFormatterImpl formatter = new LogEventFormatterImpl();
+        formatter.setPattern(pattern);
+        formatter.setFatalColor(fatalColor);
+        formatter.setErrorColor(errorColor);
+        formatter.setWarnColor(warnColor);
+        formatter.setInfoColor(infoColor);
+        formatter.setDebugColor(debugColor);
+        formatter.setTraceColor(traceColor);
+        register(LogEventFormatter.class, formatter);
+
+        LogServiceImpl logService = new LogServiceImpl(configurationAdmin, 
events);
+        register(LogService.class, logService);
+
+        LogMBeanImpl securityMBean = new LogMBeanImpl(logService);
+        registerMBean(securityMBean, "type=log");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/log/src/main/resources/OSGI-INF/bundle.info 
b/log/src/main/resources/OSGI-INF/bundle.info
new file mode 100644
index 0000000..2bd45bd
--- /dev/null
+++ b/log/src/main/resources/OSGI-INF/bundle.info
@@ -0,0 +1,24 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+[mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+The log mbean management bundle exposes a Log MBean that can be used with any 
JMX client (for instance JConsole).
+
+The Log MBean allows quite the same actions that can be performed using log:* 
commands:
+  * display()
+  * display(logger)
+  * get()
+  * get(logger)
+  * set(level)
+  * list(level, logger)
+
+h1. See also
+
+  * Monitoring and Administration using JMX - section of the Karaf User Guide

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/resources/OSGI-INF/metatype/metatype.properties
----------------------------------------------------------------------
diff --git a/log/src/main/resources/OSGI-INF/metatype/metatype.properties 
b/log/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..713283f
--- /dev/null
+++ b/log/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,31 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor
+
+log.name = Apache Karaf Log
+log.description = Configuration of Apache Karaf Log
+
+size.name = Size
+size.description = size of the log to keep in memory
+
+pattern.name = Pattern
+pattern.description = Pattern used to display log entries

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/main/resources/OSGI-INF/metatype/metatype.xml
----------------------------------------------------------------------
diff --git a/log/src/main/resources/OSGI-INF/metatype/metatype.xml 
b/log/src/main/resources/OSGI-INF/metatype/metatype.xml
new file mode 100644
index 0000000..147cb82
--- /dev/null
+++ b/log/src/main/resources/OSGI-INF/metatype/metatype.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"; 
localization="OSGI-INF/metatype/metatype">
+    <OCD id="org.apache.karaf.log" name="%log.name" 
description="%log.description">
+        <AD id="size" type="Integer" default="500" name="%size.name"
+            description="%size.description"/>
+        <AD id="pattern" type="String" default="%d{ABSOLUTE} | %-5.5p | 
%-16.16t | %-32.32c{1} | %-32.32C %4L | %m%n" name="%pattern.name"
+            description="%pattern.description"/>
+    </OCD>
+    <Designate pid="org.apache.karaf.log">
+        <Object ocdref="org.apache.karaf.log"/>
+    </Designate>
+</metatype:MetaData>

http://git-wip-us.apache.org/repos/asf/karaf/blob/7f1463c5/log/src/test/java/org/apache/karaf/log/core/internal/SetLogLevelTest.java
----------------------------------------------------------------------
diff --git 
a/log/src/test/java/org/apache/karaf/log/core/internal/SetLogLevelTest.java 
b/log/src/test/java/org/apache/karaf/log/core/internal/SetLogLevelTest.java
new file mode 100644
index 0000000..73459d3
--- /dev/null
+++ b/log/src/test/java/org/apache/karaf/log/core/internal/SetLogLevelTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.log.core.internal;
+
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+
+import org.apache.karaf.log.core.LogMBean;
+import org.apache.karaf.log.core.LogService;
+import org.easymock.EasyMock;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * Test cases for {@link SetLogLevel}
+ */
+@SuppressWarnings("unchecked")
+public class SetLogLevelTest extends TestCase {
+    
+    private static final String ROOT_LOGGER = "log4j.rootLogger";
+    private static final String PACKAGE_LOGGER = 
"log4j.logger.org.apache.karaf.test";
+    
+    private LogService logService;
+    private LogMBean logMBean;
+    @SuppressWarnings("rawtypes")
+    private Hashtable properties;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        properties = new Hashtable<String, String>();
+        final Configuration configuration = 
EasyMock.createMock(Configuration.class);
+        EasyMock.expect(configuration.getProperties()).andReturn(properties);
+        configuration.update(properties);        
+        ConfigurationAdmin configAdmin = 
EasyMock.createMock(ConfigurationAdmin.class);
+        
EasyMock.expect(configAdmin.getConfiguration(LogServiceImpl.CONFIGURATION_PID, 
null)).andReturn(configuration);
+        logService = new LogServiceImpl(configAdmin, new LruList(100));
+        logMBean = new LogMBeanImpl(logService);
+        EasyMock.replay(configAdmin);
+        EasyMock.replay(configuration);
+    }
+    
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testInvalidLogLevel() throws Exception {
+        try {
+            logMBean.setLevel("INVALID");
+            fail("Exception expected");
+        } catch(IllegalArgumentException e) {
+            // Expected
+        }
+    }
+    
+    public void testSetLogLevel() throws Exception {
+        logMBean.setLevel("org.apache.karaf.test", "INFO");
+        assertEquals("INFO", properties.get(PACKAGE_LOGGER));
+    }
+    
+    public void testSetRootLogLevel() throws Exception {
+        logMBean.setLevel("INFO");
+        assertEquals("INFO", properties.get(ROOT_LOGGER));
+    }
+    
+    public void testSetLogLevelLowerCase() throws Exception {
+        logMBean.setLevel("org.apache.karaf.test", "info");
+        assertEquals("INFO", properties.get(PACKAGE_LOGGER));
+    }
+    
+    public void testSetRootLogLevelLowerCase() throws Exception {
+        logMBean.setLevel("info");
+        assertEquals("INFO", properties.get(ROOT_LOGGER));
+    }
+    
+    public void testChangeLogLevel() throws Exception {
+        properties.put(PACKAGE_LOGGER, "DEBUG");
+        logMBean.setLevel("org.apache.karaf.test", "INFO");
+        assertEquals("INFO", properties.get(PACKAGE_LOGGER));
+    }
+    
+    public void testChangeRootLogLevel() throws Exception {
+        properties.put(ROOT_LOGGER, "DEBUG");
+        logMBean.setLevel("INFO");
+        assertEquals("INFO", properties.get(ROOT_LOGGER));
+    }
+    
+    public void testChangeLogLevelWithAppender() throws Exception {
+        properties.put(PACKAGE_LOGGER, "DEBUG, APPENDER1");
+        logMBean.setLevel("org.apache.karaf.test", "INFO");
+        assertEquals("INFO, APPENDER1", properties.get(PACKAGE_LOGGER));
+    }
+    
+    public void testChangeRootLogLevelWithAppender() throws Exception {
+        properties.put(ROOT_LOGGER, "DEBUG, APPENDER1");
+        logMBean.setLevel("INFO");
+        assertEquals("INFO, APPENDER1", properties.get(ROOT_LOGGER));
+    }
+
+    public void testUnsetLogLevel() throws Exception {
+        properties.put(PACKAGE_LOGGER, "DEBUG");
+        logMBean.setLevel("org.apache.karaf.test", "DEFAULT");
+        assertFalse("Configuration for logger org.apache.karaf.test has been 
removed", properties.containsKey(PACKAGE_LOGGER));
+    }
+
+    public void testUnsetRootLogLevel() throws Exception {
+        properties.put(ROOT_LOGGER, "INFO");
+        logMBean.setLevel("org.apache.karaf.test", "DEFAULT");
+        assertEquals("Configuration for root logger should not be removed", 
"INFO", properties.get(ROOT_LOGGER));
+    }
+    
+}

Reply via email to