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