From 6dcadfa3ea4f2921c29f9d49ab40a2388f06e526 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 2 Oct 2009 20:52:12 +0000 Subject: [PATCH] Adding support for Formatter and MDC. git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@964 7e9141cc-0065-0410-87d8-b60c137991c4 --- .../eclipse/jetty/logging/impl/Appender.java | 3 +- .../jetty/logging/impl/CentralLogger.java | 4 +- .../jetty/logging/impl/CentralMDC.java | 201 ++++++++++++++++++ .../jetty/logging/impl/CentralMDCAdapter.java | 56 +++++ .../jetty/logging/impl/ConsoleAppender.java | 50 +++-- .../jetty/logging/impl/DefaultFormatter.java | 47 ++++ .../eclipse/jetty/logging/impl/Formatter.java | 8 + .../logging/impl/RollingFileAppender.java | 68 +++--- .../jetty/logging/impl/ThreadLocalMap.java | 35 +++ .../java/org/slf4j/impl/StaticMDCBinder.java | 6 +- .../logging/impl/CentralMDCAdapterTest.java | 87 ++++++++ .../jetty/logging/impl/TestAppender.java | 33 ++- 12 files changed, 538 insertions(+), 60 deletions(-) create mode 100644 jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDC.java create mode 100644 jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDCAdapter.java create mode 100644 jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/DefaultFormatter.java create mode 100644 jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Formatter.java create mode 100644 jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ThreadLocalMap.java create mode 100644 jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/CentralMDCAdapterTest.java diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java index e80043ca3d5..f99ddac4d50 100644 --- a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.logging.impl; import java.io.IOException; +import java.util.Date; /** * Appender for log content. @@ -26,7 +27,7 @@ public interface Appender String getId(); - void append(String date, Severity severity, String name, String message, Throwable t) throws IOException; + void append(Date date, Severity severity, String name, String message, Throwable t) throws IOException; void setProperty(String key, String value) throws Exception; diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java index ff9516d8ac8..0d5585ebc7f 100644 --- a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.logging.impl; import java.io.IOException; -import java.text.SimpleDateFormat; import java.util.Date; import org.slf4j.helpers.MarkerIgnoringBase; @@ -28,7 +27,6 @@ import org.slf4j.helpers.MessageFormatter; public class CentralLogger extends MarkerIgnoringBase { private static final long serialVersionUID = 385001265755850685L; - private static final String dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; private Severity level = Severity.INFO; private String name; private Appender appenders[]; @@ -48,7 +46,7 @@ public class CentralLogger extends MarkerIgnoringBase return; } - String now = new SimpleDateFormat(dateFormat).format(new Date()); + Date now = new Date(); for (Appender appender : appenders) { diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDC.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDC.java new file mode 100644 index 00000000000..d1d84b9cb81 --- /dev/null +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDC.java @@ -0,0 +1,201 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.apache.org/licenses/LICENSE-2.0.txt +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.logging.impl; + +import java.util.HashMap; +import java.util.Map; + +public class CentralMDC +{ + private final static CentralMDC mdc = new CentralMDC(); + private ThreadLocalMap local; + + private CentralMDC() + { + local = new ThreadLocalMap(); + } + + public static void put(String key, String value) + { + if (mdc == null) + { + return; + } + mdc.internalPut(key,value); + } + + private void internalPut(String key, String value) + { + if (local == null) + { + return; + } + + HashMap map = local.get(); + if (map == null) + { + map = new HashMap(); + local.set(map); + } + map.put(key,value); + } + + public static String get(String key) + { + if (mdc == null) + { + return null; + } + + return mdc.internalGet(key); + } + + private String internalGet(String key) + { + if (local == null) + { + return null; + } + + if (key == null) + { + return null; + } + + HashMap map = local.get(); + if (map == null) + { + return null; + } + return map.get(key); + } + + public static void remove(String key) + { + if (mdc == null) + { + return; + } + + mdc.internalRemove(key); + } + + private void internalRemove(String key) + { + if (local == null) + { + return; + } + + if (key == null) + { + return; + } + + HashMap map = local.get(); + if (map == null) + { + return; + } + + map.remove(key); + } + + public static void clear() + { + if (mdc == null) + { + return; + } + + mdc.internalClear(); + } + + private void internalClear() + { + if (local == null) + { + return; + } + + HashMap map = local.get(); + if (map == null) + { + return; + } + + map.clear(); + } + + public static Map getContextMap() + { + if (mdc == null) + { + return null; + } + + return mdc.internalGetContextMap(); + } + + private Map internalGetContextMap() + { + if (local == null) + { + return null; + } + + HashMap map = local.get(); + if (map == null) + { + return null; + } + + Map copy = new HashMap(); + copy.putAll(map); + + return copy; + } + + public static void setContextMap(Map contextMap) + { + if (mdc == null) + { + return; + } + + mdc.internalSetContextMap(contextMap); + } + + private void internalSetContextMap(Map contextMap) + { + if (local == null) + { + return; + } + + HashMap map = local.get(); + if (map == null) + { + map = new HashMap(); + local.set(map); + } + else + { + map.clear(); + } + map.putAll(contextMap); + } +} diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDCAdapter.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDCAdapter.java new file mode 100644 index 00000000000..956692e7650 --- /dev/null +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralMDCAdapter.java @@ -0,0 +1,56 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.apache.org/licenses/LICENSE-2.0.txt +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.logging.impl; + +import java.util.Map; + +import org.slf4j.spi.MDCAdapter; + +public class CentralMDCAdapter implements MDCAdapter +{ + + public void clear() + { + CentralMDC.clear(); + } + + public String get(String key) + { + return CentralMDC.get(key); + } + + @SuppressWarnings("unchecked") + public Map getCopyOfContextMap() + { + return CentralMDC.getContextMap(); + } + + public void put(String key, String value) + { + CentralMDC.put(key,value); + } + + public void remove(String key) + { + CentralMDC.remove(key); + } + + @SuppressWarnings("unchecked") + public void setContextMap(Map contextMap) + { + CentralMDC.setContextMap(contextMap); + } +} diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java index 05865cf9d28..da4962742c7 100644 --- a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java @@ -16,33 +16,19 @@ package org.eclipse.jetty.logging.impl; import java.io.IOException; +import java.util.Date; /** * Standard Appender to the STDOUT Console */ public class ConsoleAppender implements Appender { + private Formatter formatter; private String id; - public String getId() + public void append(Date date, Severity severity, String name, String message, Throwable t) { - return id; - } - - public void setId(String id) - { - this.id = id; - } - - public void append(String date, Severity severity, String name, String message, Throwable t) - { - StringBuffer buf = new StringBuffer(); - buf.append(date); - buf.append(':').append(severity.name()).append(':'); - buf.append(name); - buf.append(':').append(message); - - System.out.println(buf.toString()); + System.out.println(getFormatter().format(date,severity,name,message)); if (t != null) { t.printStackTrace(System.out); @@ -50,17 +36,41 @@ public class ConsoleAppender implements Appender System.out.flush(); } - public void setProperty(String key, String value) throws Exception + public void close() throws IOException { /* nothing to do here */ } + public Formatter getFormatter() + { + if (formatter == null) + { + formatter = new DefaultFormatter(); + } + return formatter; + } + + public String getId() + { + return id; + } + public void open() throws IOException { /* nothing to do here */ } - public void close() throws IOException + public void setFormatter(Formatter formatter) + { + this.formatter = formatter; + } + + public void setId(String id) + { + this.id = id; + } + + public void setProperty(String key, String value) throws Exception { /* nothing to do here */ } diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/DefaultFormatter.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/DefaultFormatter.java new file mode 100644 index 00000000000..4426ef2c36c --- /dev/null +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/DefaultFormatter.java @@ -0,0 +1,47 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.apache.org/licenses/LICENSE-2.0.txt +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.logging.impl; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Default log output formatter. + */ +public class DefaultFormatter implements Formatter +{ + private String dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; + + public String format(Date date, Severity severity, String name, String message) + { + StringBuffer buf = new StringBuffer(); + buf.append(new SimpleDateFormat(dateFormat).format(date)); + buf.append(':').append(severity.name()).append(':'); + buf.append(name); + buf.append(':').append(message); + return buf.toString(); + } + + public String getDateFormat() + { + return dateFormat; + } + + public void setDateFormat(String dateFormat) + { + this.dateFormat = dateFormat; + } +} diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Formatter.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Formatter.java new file mode 100644 index 00000000000..8f82c44a760 --- /dev/null +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Formatter.java @@ -0,0 +1,8 @@ +package org.eclipse.jetty.logging.impl; + +import java.util.Date; + +public interface Formatter +{ + String format(Date date, Severity severity, String name, String message); +} diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java index f66755a549f..791d75ab2b5 100644 --- a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; +import java.util.Date; import java.util.TimeZone; import org.eclipse.jetty.logging.impl.io.RolloverFileOutputStream; @@ -28,8 +29,8 @@ import org.eclipse.jetty.logging.impl.io.RolloverFileOutputStream; */ public class RollingFileAppender implements Appender { - private static final byte[] LN = System.getProperty("line.separator","\n").getBytes(); - private RolloverFileOutputStream out; + private RolloverFileOutputStream fileout; + private PrintStream out; private String filename; private File file; private boolean append = true; @@ -38,37 +39,21 @@ public class RollingFileAppender implements Appender private String dateFormat = "yyyy_MM_dd"; private String backupFormat = "HHmmssSSS"; private String id; + private Formatter formatter; - public String getId() + public void append(Date date, Severity severity, String name, String message, Throwable t) throws IOException { - return id; - } - - public void setId(String id) - { - this.id = id; - } - - public void append(String date, Severity severity, String name, String message, Throwable t) throws IOException - { - StringBuffer buf = new StringBuffer(); - buf.append(date); - buf.append(':').append(severity.name()).append(':'); - buf.append(name); - buf.append(':').append(message); - - out.write(buf.toString().getBytes()); - out.write(LN); + out.println(getFormatter().format(date,severity,name,message)); if (t != null) { - t.printStackTrace(new PrintStream(out)); - out.write(LN); + t.printStackTrace(out); } out.flush(); } public void close() throws IOException { + fileout.close(); out.close(); } @@ -82,11 +67,30 @@ public class RollingFileAppender implements Appender return dateFormat; } + public File getFile() + { + return file; + } + public String getFilename() { return filename; } + public Formatter getFormatter() + { + if (formatter == null) + { + formatter = new DefaultFormatter(); + } + return formatter; + } + + public String getId() + { + return id; + } + public int getRetainDays() { return retainDays; @@ -102,11 +106,6 @@ public class RollingFileAppender implements Appender return append; } - public File getFile() - { - return file; - } - public void open() throws IOException { file = new File(PropertyExpansion.expand(filename)); @@ -122,7 +121,8 @@ public class RollingFileAppender implements Appender throw new FileNotFoundException("Logging path exist, but is not a directory: " + logDir); } - out = new RolloverFileOutputStream(file.getAbsolutePath(),append,retainDays,zone,dateFormat,backupFormat); + fileout = new RolloverFileOutputStream(file.getAbsolutePath(),append,retainDays,zone,dateFormat,backupFormat); + out = new PrintStream(fileout); } public void setAppend(boolean append) @@ -145,6 +145,16 @@ public class RollingFileAppender implements Appender this.filename = filename; } + public void setFormatter(Formatter formatter) + { + this.formatter = formatter; + } + + public void setId(String id) + { + this.id = id; + } + public void setProperty(String key, String value) throws Exception { if ("filename".equals(key)) diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ThreadLocalMap.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ThreadLocalMap.java new file mode 100644 index 00000000000..0f40a44dec1 --- /dev/null +++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ThreadLocalMap.java @@ -0,0 +1,35 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.apache.org/licenses/LICENSE-2.0.txt +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.logging.impl; + +import java.util.HashMap; + +final public class ThreadLocalMap extends InheritableThreadLocal> +{ + @Override + @SuppressWarnings("unchecked") + protected HashMap childValue(HashMap parentValue) + { + if (parentValue != null) + { + return (HashMap)parentValue.clone(); + } + else + { + return null; + } + } +} diff --git a/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java index 0c6202a76cf..b214d9bbdab 100644 --- a/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java +++ b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java @@ -15,7 +15,7 @@ // ======================================================================== package org.slf4j.impl; -import org.slf4j.helpers.NOPMakerAdapter; +import org.eclipse.jetty.logging.impl.CentralMDCAdapter; import org.slf4j.spi.MDCAdapter; /** @@ -32,11 +32,11 @@ public class StaticMDCBinder public MDCAdapter getMDCA() { - return new NOPMakerAdapter(); + return new CentralMDCAdapter(); } public String getMDCAdapterClassStr() { - return NOPMakerAdapter.class.getName(); + return CentralMDCAdapter.class.getName(); } } diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/CentralMDCAdapterTest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/CentralMDCAdapterTest.java new file mode 100644 index 00000000000..b1f61908e27 --- /dev/null +++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/CentralMDCAdapterTest.java @@ -0,0 +1,87 @@ +package org.eclipse.jetty.logging.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.apache.log4j.MDC; +import org.eclipse.jetty.logging.impl.TestAppender.LogEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.impl.StaticLoggerBinder; + +import junit.framework.TestCase; + +public class CentralMDCAdapterTest extends TestCase +{ + public void testMDCInfo() throws Exception + { + // Setup Logger Config + Properties props = new Properties(); + props.setProperty("root.level","DEBUG"); + props.setProperty("root.appenders","test"); + props.setProperty("appender.test.class",TestAppender.class.getName()); + + CentralLoggerConfig root = CentralLoggerConfig.load(props); + StaticLoggerBinder.getSingleton().setRoot(root); + + // Generate a few logging events. + Logger logroot = LoggerFactory.getLogger("test.root"); + logroot.info("The Phoenix and the Turtle"); + logroot.info("Let the bird of loudest lay"); + + MDC.put("mood","sad"); + MDC.put("animal","bird"); + Logger logtree = LoggerFactory.getLogger("test.root.tree"); + logtree.info("On the sole Arabian tree,"); + logtree.info("Herald sad and trumpet be,"); + + MDC.put("mood","soaring"); + Logger logwings = LoggerFactory.getLogger("test.root.wings"); + logwings.info("To whose sound chaste wings obey."); + logwings.info("But thou shrieking harbinger,"); + logwings.info("Foul precurrer of the fiend,"); + + MDC.remove("animal"); + Logger logend = LoggerFactory.getLogger("test.root.end"); + logend.info("Augur of the fever's end,"); + + MDC.clear(); + logend.info("To this troop come thou not near."); + + // Assert Events + TestAppender testappender = (TestAppender)root.findAppender(TestAppender.class); + List captured = testappender.getEvents(); + + List expectedMessages = new ArrayList(); + expectedMessages.add("The Phoenix and the Turtle"); + expectedMessages.add("Let the bird of loudest lay"); + expectedMessages.add("On the sole Arabian tree,"); + expectedMessages.add("Herald sad and trumpet be,"); + expectedMessages.add("To whose sound chaste wings obey."); + expectedMessages.add("But thou shrieking harbinger,"); + expectedMessages.add("Foul precurrer of the fiend,"); + expectedMessages.add("Augur of the fever's end,"); + expectedMessages.add("To this troop come thou not near."); + + assertEquals("Captured Messages size",expectedMessages.size(),captured.size()); + + List expectedMdc = new ArrayList(); + expectedMdc.add(""); + expectedMdc.add(""); + expectedMdc.add("animal=bird, mood=sad"); + expectedMdc.add("animal=bird, mood=sad"); + expectedMdc.add("animal=bird, mood=soaring"); + expectedMdc.add("animal=bird, mood=soaring"); + expectedMdc.add("animal=bird, mood=soaring"); + expectedMdc.add("mood=soaring"); + expectedMdc.add(""); + + assertEquals("Captured MDC events size",expectedMdc.size(),captured.size()); + + for(int i=0, n=expectedMessages.size(); i mdcMap = MDC.getCopyOfContextMap(); + if (mdcMap != null) + { + Set keys = new TreeSet(); + keys.addAll(mdcMap.keySet()); + boolean delim = false; + for (String key : keys) + { + if (delim) + { + mdc += ", "; + } + mdc += key + "=" + mdcMap.get(key); + delim = true; + } + } } public LogEvent(Severity severity, String name, String message) @@ -74,13 +99,13 @@ public class TestAppender implements Appender this.id = id; } - public void append(String date, Severity severity, String name, String message, Throwable t) + public void append(Date date, Severity severity, String name, String message, Throwable t) { if (name.equals("org.eclipse.jetty.util.log")) // standard jetty logger { if (t != null) { - // Still interested in seeing throwables (HACK) + // Still interested in seeing throwables t.printStackTrace(System.err); } return; // skip storing it.