From 305f5717c6fe4ad6596433673d27c4831b382d1e Mon Sep 17 00:00:00 2001 From: lindzh Date: Fri, 2 Mar 2018 16:07:41 +0800 Subject: [PATCH] [ROCKETMQ-367] Add logging component. (#224) --- logging/pom.xml | 50 + .../rocketmq/logging/InnerLoggerFactory.java | 478 +++++++ .../rocketmq/logging/InternalLogger.java | 63 + .../logging/InternalLoggerFactory.java | 93 ++ .../rocketmq/logging/Slf4jLoggerFactory.java | 153 ++ .../rocketmq/logging/inner/Appender.java | 228 +++ .../apache/rocketmq/logging/inner/Layout.java | 39 + .../apache/rocketmq/logging/inner/Level.java | 156 +++ .../apache/rocketmq/logging/inner/Logger.java | 467 +++++++ .../logging/inner/LoggingBuilder.java | 1230 +++++++++++++++++ .../rocketmq/logging/inner/LoggingEvent.java | 121 ++ .../rocketmq/logging/inner/SysLogger.java | 89 ++ .../apache/rocketmq/logging/package-info.java | 35 + .../rocketmq/logging/BasicloggerTest.java | 70 + .../logging/InnerLoggerFactoryTest.java | 92 ++ .../rocketmq/logging/InternalLoggerTest.java | 72 + .../logging/Slf4jLoggerFactoryTest.java | 80 ++ .../rocketmq/logging/inner/AppenderTest.java | 160 +++ .../rocketmq/logging/inner/LayoutTest.java | 54 + .../rocketmq/logging/inner/LevelTest.java | 37 + .../logging/inner/LoggerRepositoryTest.java | 49 + .../rocketmq/logging/inner/LoggerTest.java | 115 ++ .../logging/inner/LoggingBuilderTest.java | 113 ++ .../logging/inner/MessageFormatterTest.java | 40 + logging/src/test/resources/logback_test.xml | 46 + pom.xml | 6 + 26 files changed, 4136 insertions(+) create mode 100644 logging/pom.xml create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java create mode 100755 logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java create mode 100755 logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java create mode 100755 logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java create mode 100755 logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java create mode 100644 logging/src/main/java/org/apache/rocketmq/logging/package-info.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/BasicloggerTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java create mode 100644 logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java create mode 100644 logging/src/test/resources/logback_test.xml diff --git a/logging/pom.xml b/logging/pom.xml new file mode 100644 index 00000000..baad6ad0 --- /dev/null +++ b/logging/pom.xml @@ -0,0 +1,50 @@ + + + + + org.apache.rocketmq + rocketmq-all + 4.3.0-SNAPSHOT + + + 4.0.0 + jar + rocketmq-logging + rocketmq-logging ${project.version} + + + 1.6 + 1.6 + + + + + org.slf4j + slf4j-api + true + + + ch.qos.logback + logback-classic + test + + + + \ No newline at end of file diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java new file mode 100644 index 00000000..7714640f --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java @@ -0,0 +1,478 @@ +/* + * 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.rocketmq.logging; + +import org.apache.rocketmq.logging.inner.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class InnerLoggerFactory extends InternalLoggerFactory { + + @Override + protected InternalLogger getLoggerInstance(String name) { + return new InnerLogger(name); + } + + @Override + protected String getLoggerType() { + return LOGGER_INNER; + } + + @Override + protected void shutdown() { + Logger.getRepository().shutdown(); + } + + public static class InnerLogger implements InternalLogger { + + private Logger logger; + + public InnerLogger(String name) { + logger = Logger.getLogger(name); + } + + @Override + public String getName() { + return logger.getName(); + } + + @Override + public void debug(String var1) { + logger.debug(var1); + } + + @Override + public void debug(String var1, Throwable var2) { + logger.debug(var1, var2); + } + + @Override + public void info(String var1) { + logger.info(var1); + } + + @Override + public void info(String var1, Throwable var2) { + logger.info(var1, var2); + } + + @Override + public void warn(String var1) { + logger.warn(var1); + } + + @Override + public void warn(String var1, Throwable var2) { + logger.warn(var1, var2); + } + + @Override + public void error(String var1) { + logger.error(var1); + } + + @Override + public void error(String var1, Throwable var2) { + logger.error(var1, var2); + } + + @Override + public void debug(String var1, Object var2) { + FormattingTuple format = MessageFormatter.format(var1, var2); + logger.debug(format.getMessage(), format.getThrowable()); + } + + @Override + public void debug(String var1, Object var2, Object var3) { + FormattingTuple format = MessageFormatter.format(var1, var2, var3); + logger.debug(format.getMessage(), format.getThrowable()); + } + + @Override + public void debug(String var1, Object... var2) { + FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); + logger.debug(format.getMessage(), format.getThrowable()); + } + + @Override + public void info(String var1, Object var2) { + FormattingTuple format = MessageFormatter.format(var1, var2); + logger.info(format.getMessage(), format.getThrowable()); + } + + @Override + public void info(String var1, Object var2, Object var3) { + FormattingTuple format = MessageFormatter.format(var1, var2, var3); + logger.info(format.getMessage(), format.getThrowable()); + } + + @Override + public void info(String var1, Object... var2) { + FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); + logger.info(format.getMessage(), format.getThrowable()); + } + + @Override + public void warn(String var1, Object var2) { + FormattingTuple format = MessageFormatter.format(var1, var2); + logger.warn(format.getMessage(), format.getThrowable()); + } + + @Override + public void warn(String var1, Object... var2) { + FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); + logger.warn(format.getMessage(), format.getThrowable()); + } + + @Override + public void warn(String var1, Object var2, Object var3) { + FormattingTuple format = MessageFormatter.format(var1, var2, var3); + logger.warn(format.getMessage(), format.getThrowable()); + } + + @Override + public void error(String var1, Object var2) { + FormattingTuple format = MessageFormatter.format(var1, var2); + logger.warn(format.getMessage(), format.getThrowable()); + } + + @Override + public void error(String var1, Object var2, Object var3) { + FormattingTuple format = MessageFormatter.format(var1, var2, var3); + logger.warn(format.getMessage(), format.getThrowable()); + } + + @Override + public void error(String var1, Object... var2) { + FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); + logger.warn(format.getMessage(), format.getThrowable()); + } + + public Logger getLogger() { + return logger; + } + } + + + public static class FormattingTuple { + private String message; + private Throwable throwable; + private Object[] argArray; + + public FormattingTuple(String message) { + this(message, null, null); + } + + public FormattingTuple(String message, Object[] argArray, Throwable throwable) { + this.message = message; + this.throwable = throwable; + if (throwable == null) { + this.argArray = argArray; + } else { + this.argArray = trimmedCopy(argArray); + } + + } + + static Object[] trimmedCopy(Object[] argArray) { + if (argArray != null && argArray.length != 0) { + int trimemdLen = argArray.length - 1; + Object[] trimmed = new Object[trimemdLen]; + System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); + return trimmed; + } else { + throw new IllegalStateException("non-sensical empty or null argument array"); + } + } + + public String getMessage() { + return this.message; + } + + public Object[] getArgArray() { + return this.argArray; + } + + public Throwable getThrowable() { + return this.throwable; + } + } + + public static class MessageFormatter { + + public MessageFormatter() { + } + + public static FormattingTuple format(String messagePattern, Object arg) { + return arrayFormat(messagePattern, new Object[]{arg}); + } + + public static FormattingTuple format(String messagePattern, Object arg1, Object arg2) { + return arrayFormat(messagePattern, new Object[]{arg1, arg2}); + } + + static Throwable getThrowableCandidate(Object[] argArray) { + if (argArray != null && argArray.length != 0) { + Object lastEntry = argArray[argArray.length - 1]; + return lastEntry instanceof Throwable ? (Throwable) lastEntry : null; + } else { + return null; + } + } + + public static FormattingTuple arrayFormat(String messagePattern, Object[] argArray) { + Throwable throwableCandidate = getThrowableCandidate(argArray); + if (messagePattern == null) { + return new FormattingTuple(null, argArray, throwableCandidate); + } else if (argArray == null) { + return new FormattingTuple(messagePattern); + } else { + int i = 0; + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + + int len; + for (len = 0; len < argArray.length; ++len) { + int j = messagePattern.indexOf("{}", i); + if (j == -1) { + if (i == 0) { + return new FormattingTuple(messagePattern, argArray, throwableCandidate); + } + + sbuf.append(messagePattern.substring(i, messagePattern.length())); + return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); + } + + if (isEscapeDelimeter(messagePattern, j)) { + if (!isDoubleEscaped(messagePattern, j)) { + --len; + sbuf.append(messagePattern.substring(i, j - 1)); + sbuf.append('{'); + i = j + 1; + } else { + sbuf.append(messagePattern.substring(i, j - 1)); + deeplyAppendParameter(sbuf, argArray[len], null); + i = j + 2; + } + } else { + sbuf.append(messagePattern.substring(i, j)); + deeplyAppendParameter(sbuf, argArray[len], null); + i = j + 2; + } + } + + sbuf.append(messagePattern.substring(i, messagePattern.length())); + if (len < argArray.length - 1) { + return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); + } else { + return new FormattingTuple(sbuf.toString(), argArray, null); + } + } + } + + static boolean isEscapeDelimeter(String messagePattern, int delimeterStartIndex) { + if (delimeterStartIndex == 0) { + return false; + } else { + char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); + return potentialEscape == 92; + } + } + + static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { + return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == 92; + } + + private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { + if (o == null) { + sbuf.append("null"); + } else { + if (!o.getClass().isArray()) { + safeObjectAppend(sbuf, o); + } else if (o instanceof boolean[]) { + booleanArrayAppend(sbuf, (boolean[]) o); + } else if (o instanceof byte[]) { + byteArrayAppend(sbuf, (byte[]) o); + } else if (o instanceof char[]) { + charArrayAppend(sbuf, (char[]) o); + } else if (o instanceof short[]) { + shortArrayAppend(sbuf, (short[]) o); + } else if (o instanceof int[]) { + intArrayAppend(sbuf, (int[]) o); + } else if (o instanceof long[]) { + longArrayAppend(sbuf, (long[]) o); + } else if (o instanceof float[]) { + floatArrayAppend(sbuf, (float[]) o); + } else if (o instanceof double[]) { + doubleArrayAppend(sbuf, (double[]) o); + } else { + objectArrayAppend(sbuf, (Object[]) o, seenMap); + } + + } + } + + private static void safeObjectAppend(StringBuilder sbuf, Object o) { + try { + String t = o.toString(); + sbuf.append(t); + } catch (Throwable var3) { + System.err.println("RocketMQ InnerLogger: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]"); + var3.printStackTrace(); + sbuf.append("[FAILED toString()]"); + } + + } + + private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { + if (seenMap == null) { + seenMap = new HashMap(); + } + sbuf.append('['); + if (!seenMap.containsKey(a)) { + seenMap.put(a, null); + int len = a.length; + + for (int i = 0; i < len; ++i) { + deeplyAppendParameter(sbuf, a[i], seenMap); + if (i != len - 1) { + sbuf.append(", "); + } + } + + seenMap.remove(a); + } else { + sbuf.append("..."); + } + + sbuf.append(']'); + } + + private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void charArrayAppend(StringBuilder sbuf, char[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void shortArrayAppend(StringBuilder sbuf, short[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void intArrayAppend(StringBuilder sbuf, int[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void longArrayAppend(StringBuilder sbuf, long[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void floatArrayAppend(StringBuilder sbuf, float[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + + private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { + sbuf.append('['); + int len = a.length; + + for (int i = 0; i < len; ++i) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + + sbuf.append(']'); + } + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java b/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java new file mode 100644 index 00000000..fae69dda --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java @@ -0,0 +1,63 @@ +/* + * 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.rocketmq.logging; + +public interface InternalLogger { + + String getName(); + + void debug(String var1); + + void debug(String var1, Object var2); + + void debug(String var1, Object var2, Object var3); + + void debug(String var1, Object... var2); + + void debug(String var1, Throwable var2); + + void info(String var1); + + void info(String var1, Object var2); + + void info(String var1, Object var2, Object var3); + + void info(String var1, Object... var2); + + void info(String var1, Throwable var2); + + void warn(String var1); + + void warn(String var1, Object var2); + + void warn(String var1, Object... var2); + + void warn(String var1, Object var2, Object var3); + + void warn(String var1, Throwable var2); + + void error(String var1); + + void error(String var1, Object var2); + + void error(String var1, Object var2, Object var3); + + void error(String var1, Object... var2); + + void error(String var1, Throwable var2); +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java new file mode 100644 index 00000000..ec176ce6 --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java @@ -0,0 +1,93 @@ +/* + * 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.rocketmq.logging; + +import java.util.concurrent.ConcurrentHashMap; + +public abstract class InternalLoggerFactory { + + public static final String LOGGER_SLF4J = "slf4j"; + + public static final String LOGGER_INNER = "inner"; + + public static final String DEFAULT_LOGGER = LOGGER_SLF4J; + + private static String loggerType = null; + + private static ConcurrentHashMap loggerFactoryCache = new ConcurrentHashMap(); + + public static InternalLogger getLogger(Class clazz) { + return getLogger(clazz.getName()); + } + + public static InternalLogger getLogger(String name) { + return getLoggerFactory().getLoggerInstance(name); + } + + private static InternalLoggerFactory getLoggerFactory() { + InternalLoggerFactory internalLoggerFactory = null; + if (loggerType != null) { + internalLoggerFactory = loggerFactoryCache.get(loggerType); + } + if (internalLoggerFactory == null) { + internalLoggerFactory = loggerFactoryCache.get(DEFAULT_LOGGER); + } + if (internalLoggerFactory == null) { + internalLoggerFactory = loggerFactoryCache.get(LOGGER_INNER); + } + if (internalLoggerFactory == null) { + throw new RuntimeException("[RocketMQ] Logger init failed, please check logger"); + } + return internalLoggerFactory; + } + + public static void setCurrentLoggerType(String type) { + loggerType = type; + } + + static { + try { + new Slf4jLoggerFactory(); + } catch (Throwable e) { + //ignore + } + try { + new InnerLoggerFactory(); + } catch (Throwable e) { + //ignore + } + } + + public InternalLoggerFactory() { + doRegister(); + } + + protected void doRegister() { + String loggerType = getLoggerType(); + if (loggerFactoryCache.get(loggerType) != null) { + return; + } + loggerFactoryCache.put(loggerType, this); + } + + protected abstract void shutdown(); + + protected abstract InternalLogger getLoggerInstance(String name); + + protected abstract String getLoggerType(); +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java new file mode 100644 index 00000000..1a246847 --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java @@ -0,0 +1,153 @@ +/* + * 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.rocketmq.logging; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Slf4jLoggerFactory extends InternalLoggerFactory { + + @Override + protected String getLoggerType() { + return InternalLoggerFactory.LOGGER_SLF4J; + } + + @Override + protected InternalLogger getLoggerInstance(String name) { + return new Slf4jLogger(name); + } + + @Override + protected void shutdown() { + + } + + public static class Slf4jLogger implements InternalLogger { + + private Logger logger = null; + + public Slf4jLogger(String name) { + logger = LoggerFactory.getLogger(name); + } + + @Override + public String getName() { + return logger.getName(); + } + + @Override + public void debug(String s) { + logger.debug(s); + } + + @Override + public void debug(String s, Object o) { + logger.debug(s, o); + } + + @Override + public void debug(String s, Object o, Object o1) { + logger.debug(s, o, o1); + } + + @Override + public void debug(String s, Object... objects) { + logger.debug(s, objects); + } + + @Override + public void debug(String s, Throwable throwable) { + logger.debug(s, throwable); + } + + @Override + public void info(String s) { + logger.info(s); + } + + @Override + public void info(String s, Object o) { + logger.info(s, o); + } + + @Override + public void info(String s, Object o, Object o1) { + logger.info(s, o, o1); + } + + @Override + public void info(String s, Object... objects) { + logger.info(s, objects); + } + + @Override + public void info(String s, Throwable throwable) { + logger.info(s, throwable); + } + + @Override + public void warn(String s) { + logger.warn(s); + } + + @Override + public void warn(String s, Object o) { + logger.warn(s, o); + } + + @Override + public void warn(String s, Object... objects) { + logger.warn(s, objects); + } + + @Override + public void warn(String s, Object o, Object o1) { + logger.warn(s, o, o1); + } + + @Override + public void warn(String s, Throwable throwable) { + logger.warn(s, throwable); + } + + @Override + public void error(String s) { + logger.error(s); + } + + @Override + public void error(String s, Object o) { + logger.error(s, o); + } + + @Override + public void error(String s, Object o, Object o1) { + logger.error(s, o, o1); + } + + @Override + public void error(String s, Object... objects) { + logger.error(s, objects); + } + + @Override + public void error(String s, Throwable throwable) { + logger.error(s, throwable); + } + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java new file mode 100755 index 00000000..c0615631 --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java @@ -0,0 +1,228 @@ +/* + * 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.rocketmq.logging.inner; + + +import java.io.InterruptedIOException; +import java.util.Enumeration; +import java.util.Vector; + +public abstract class Appender { + + public static final int CODE_WRITE_FAILURE = 1; + public static final int CODE_FLUSH_FAILURE = 2; + public static final int CODE_CLOSE_FAILURE = 3; + public static final int CODE_FILE_OPEN_FAILURE = 4; + + public final static String LINE_SEP = System.getProperty("line.separator"); + + boolean firstTime = true; + + protected Layout layout; + + protected String name; + + protected boolean closed = false; + + public void activateOptions() { + } + + abstract protected void append(LoggingEvent event); + + public void finalize() { + try { + super.finalize(); + } catch (Throwable throwable) { + SysLogger.error("Finalizing appender named [" + name + "]. error", throwable); + } + if (this.closed) { + return; + } + + SysLogger.debug("Finalizing appender named [" + name + "]."); + close(); + } + + public Layout getLayout() { + return layout; + } + + public final String getName() { + return this.name; + } + + public synchronized void doAppend(LoggingEvent event) { + if (closed) { + SysLogger.error("Attempted to append to closed appender named [" + name + "]."); + return; + } + this.append(event); + } + + public void setLayout(Layout layout) { + this.layout = layout; + } + + public void setName(String name) { + this.name = name; + } + + public abstract void close(); + + public void handleError(String message, Exception e, int errorCode) { + if (e instanceof InterruptedIOException || e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + if (firstTime) { + SysLogger.error(message + " code:" + errorCode, e); + firstTime = false; + } + } + + public void handleError(String message) { + if (firstTime) { + SysLogger.error(message); + firstTime = false; + } + } + + + public interface AppenderPipeline { + + void addAppender(Appender newAppender); + + Enumeration getAllAppenders(); + + Appender getAppender(String name); + + boolean isAttached(Appender appender); + + void removeAllAppenders(); + + void removeAppender(Appender appender); + + void removeAppender(String name); + } + + + public static class AppenderPipelineImpl implements AppenderPipeline { + + + protected Vector appenderList; + + public void addAppender(Appender newAppender) { + if (newAppender == null) { + return; + } + + if (appenderList == null) { + appenderList = new Vector(1); + } + if (!appenderList.contains(newAppender)) { + appenderList.addElement(newAppender); + } + } + + public int appendLoopOnAppenders(LoggingEvent event) { + int size = 0; + Appender appender; + + if (appenderList != null) { + size = appenderList.size(); + for (int i = 0; i < size; i++) { + appender = appenderList.elementAt(i); + appender.doAppend(event); + } + } + return size; + } + + public Enumeration getAllAppenders() { + if (appenderList == null) { + return null; + } else { + return appenderList.elements(); + } + } + + public Appender getAppender(String name) { + if (appenderList == null || name == null) { + return null; + } + + int size = appenderList.size(); + Appender appender; + for (int i = 0; i < size; i++) { + appender = appenderList.elementAt(i); + if (name.equals(appender.getName())) { + return appender; + } + } + return null; + } + + public boolean isAttached(Appender appender) { + if (appenderList == null || appender == null) { + return false; + } + + int size = appenderList.size(); + Appender a; + for (int i = 0; i < size; i++) { + a = appenderList.elementAt(i); + if (a == appender) { + return true; + } + } + return false; + } + + public void removeAllAppenders() { + if (appenderList != null) { + int len = appenderList.size(); + for (int i = 0; i < len; i++) { + Appender a = appenderList.elementAt(i); + a.close(); + } + appenderList.removeAllElements(); + appenderList = null; + } + } + + public void removeAppender(Appender appender) { + if (appender == null || appenderList == null) { + return; + } + appenderList.removeElement(appender); + } + + public void removeAppender(String name) { + if (name == null || appenderList == null) { + return; + } + int size = appenderList.size(); + for (int i = 0; i < size; i++) { + if (name.equals((appenderList.elementAt(i)).getName())) { + appenderList.removeElementAt(i); + break; + } + } + } + + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java new file mode 100644 index 00000000..7ea3561d --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java @@ -0,0 +1,39 @@ +/* + * 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.rocketmq.logging.inner; + +public abstract class Layout { + + public abstract String format(LoggingEvent event); + + public String getContentType() { + return "text/plain"; + } + + public String getHeader() { + return null; + } + + public String getFooter() { + return null; + } + + + abstract public boolean ignoresThrowable(); + +} \ No newline at end of file diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java new file mode 100755 index 00000000..487682cd --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java @@ -0,0 +1,156 @@ +/* + * 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.rocketmq.logging.inner; + +import java.io.Serializable; + +public class Level implements Serializable { + + transient int level; + transient String levelStr; + transient int syslogEquivalent; + + public final static int OFF_INT = Integer.MAX_VALUE; + public final static int ERROR_INT = 40000; + public final static int WARN_INT = 30000; + public final static int INFO_INT = 20000; + public final static int DEBUG_INT = 10000; + public final static int ALL_INT = Integer.MIN_VALUE; + + + private static final String ALL_NAME = "ALL"; + + private static final String DEBUG_NAME = "DEBUG"; + + private static final String INFO_NAME = "INFO"; + + private static final String WARN_NAME = "WARN"; + + private static final String ERROR_NAME = "ERROR"; + + private static final String OFF_NAME = "OFF"; + + final static public Level OFF = new Level(OFF_INT, OFF_NAME, 0); + + final static public Level ERROR = new Level(ERROR_INT, ERROR_NAME, 3); + + final static public Level WARN = new Level(WARN_INT, WARN_NAME, 4); + + final static public Level INFO = new Level(INFO_INT, INFO_NAME, 6); + + final static public Level DEBUG = new Level(DEBUG_INT, DEBUG_NAME, 7); + + final static public Level ALL = new Level(ALL_INT, ALL_NAME, 7); + + static final long serialVersionUID = 3491141966387921974L; + + protected Level(int level, String levelStr, int syslogEquivalent) { + this.level = level; + this.levelStr = levelStr; + this.syslogEquivalent = syslogEquivalent; + } + + public static Level toLevel(String sArg) { + return toLevel(sArg, Level.DEBUG); + } + + public static Level toLevel(int val) { + return toLevel(val, Level.DEBUG); + } + + public static Level toLevel(int val, Level defaultLevel) { + switch (val) { + case ALL_INT: + return ALL; + case DEBUG_INT: + return Level.DEBUG; + case INFO_INT: + return Level.INFO; + case WARN_INT: + return Level.WARN; + case ERROR_INT: + return Level.ERROR; + case OFF_INT: + return OFF; + default: + return defaultLevel; + } + } + + public static Level toLevel(String sArg, Level defaultLevel) { + if (sArg == null) { + return defaultLevel; + } + String s = sArg.toUpperCase(); + + if (s.equals(ALL_NAME)) { + return Level.ALL; + } + if (s.equals(DEBUG_NAME)) { + return Level.DEBUG; + } + if (s.equals(INFO_NAME)) { + return Level.INFO; + } + if (s.equals(WARN_NAME)) { + return Level.WARN; + } + if (s.equals(ERROR_NAME)) { + return Level.ERROR; + } + if (s.equals(OFF_NAME)) { + return Level.OFF; + } + + if (s.equals(INFO_NAME)) { + return Level.INFO; + } + return defaultLevel; + } + + + public boolean equals(Object o) { + if (o instanceof Level) { + Level r = (Level) o; + return this.level == r.level; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = level; + result = 31 * result + (levelStr != null ? levelStr.hashCode() : 0); + result = 31 * result + syslogEquivalent; + return result; + } + + public boolean isGreaterOrEqual(Level r) { + return level >= r.level; + } + + final public String toString() { + return levelStr; + } + + public final int toInt() { + return level; + } + +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java new file mode 100755 index 00000000..470ed41d --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java @@ -0,0 +1,467 @@ +/* + * 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.rocketmq.logging.inner; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + + +public class Logger implements Appender.AppenderPipeline { + + private static final String FQCN = Logger.class.getName(); + + private static final DefaultLoggerRepository REPOSITORY = new DefaultLoggerRepository(new RootLogger(Level.DEBUG)); + + public static LoggerRepository getRepository() { + return REPOSITORY; + } + + private String name; + + volatile private Level level; + + volatile private Logger parent; + + Appender.AppenderPipelineImpl appenderPipeline; + + private boolean additive = true; + + private Logger(String name) { + this.name = name; + } + + static public Logger getLogger(String name) { + return getRepository().getLogger(name); + } + + static public Logger getLogger(Class clazz) { + return getRepository().getLogger(clazz.getName()); + } + + public static Logger getRootLogger() { + return getRepository().getRootLogger(); + } + + synchronized public void addAppender(Appender newAppender) { + if (appenderPipeline == null) { + appenderPipeline = new Appender.AppenderPipelineImpl(); + } + appenderPipeline.addAppender(newAppender); + } + + public void callAppenders(LoggingEvent event) { + int writes = 0; + + for (Logger logger = this; logger != null; logger = logger.parent) { + synchronized (logger) { + if (logger.appenderPipeline != null) { + writes += logger.appenderPipeline.appendLoopOnAppenders(event); + } + if (!logger.additive) { + break; + } + } + } + + if (writes == 0) { + getRepository().emitNoAppenderWarning(this); + } + } + + synchronized void closeNestedAppenders() { + Enumeration enumeration = this.getAllAppenders(); + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + Appender a = (Appender) enumeration.nextElement(); + if (a instanceof Appender.AppenderPipeline) { + a.close(); + } + } + } + } + + public void debug(Object message) { + if (getRepository().isDisabled(Level.DEBUG_INT)) { + return; + } + if (Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.DEBUG, message, null); + } + } + + + public void debug(Object message, Throwable t) { + if (getRepository().isDisabled(Level.DEBUG_INT)) { + return; + } + if (Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.DEBUG, message, t); + } + } + + + public void error(Object message) { + if (getRepository().isDisabled(Level.ERROR_INT)) { + return; + } + if (Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.ERROR, message, null); + } + } + + public void error(Object message, Throwable t) { + if (getRepository().isDisabled(Level.ERROR_INT)) { + return; + } + if (Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.ERROR, message, t); + } + + } + + + protected void forcedLog(String fqcn, Level level, Object message, Throwable t) { + callAppenders(new LoggingEvent(fqcn, this, level, message, t)); + } + + + synchronized public Enumeration getAllAppenders() { + if (appenderPipeline == null) { + return null; + } else { + return appenderPipeline.getAllAppenders(); + } + } + + synchronized public Appender getAppender(String name) { + if (appenderPipeline == null || name == null) { + return null; + } + + return appenderPipeline.getAppender(name); + } + + public Level getEffectiveLevel() { + for (Logger c = this; c != null; c = c.parent) { + if (c.level != null) { + return c.level; + } + } + return null; + } + + public final String getName() { + return name; + } + + final public Level getLevel() { + return this.level; + } + + + public void info(Object message) { + if (getRepository().isDisabled(Level.INFO_INT)) { + return; + } + if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.INFO, message, null); + } + } + + public void info(Object message, Throwable t) { + if (getRepository().isDisabled(Level.INFO_INT)) { + return; + } + if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.INFO, message, t); + } + } + + public boolean isAttached(Appender appender) { + return appender != null && appenderPipeline != null && appenderPipeline.isAttached(appender); + } + + synchronized public void removeAllAppenders() { + if (appenderPipeline != null) { + appenderPipeline.removeAllAppenders(); + appenderPipeline = null; + } + } + + synchronized public void removeAppender(Appender appender) { + if (appender == null || appenderPipeline == null) { + return; + } + appenderPipeline.removeAppender(appender); + } + + synchronized public void removeAppender(String name) { + if (name == null || appenderPipeline == null) { + return; + } + appenderPipeline.removeAppender(name); + } + + public void setAdditivity(boolean additive) { + this.additive = additive; + } + + public void setLevel(Level level) { + this.level = level; + } + + public void warn(Object message) { + if (getRepository().isDisabled(Level.WARN_INT)) { + return; + } + + if (Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.WARN, message, null); + } + } + + public void warn(Object message, Throwable t) { + if (getRepository().isDisabled(Level.WARN_INT)) { + return; + } + if (Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) { + forcedLog(FQCN, Level.WARN, message, t); + } + } + + public interface LoggerRepository { + + boolean isDisabled(int level); + + void setLogLevel(Level level); + + void emitNoAppenderWarning(Logger cat); + + Level getLogLevel(); + + Logger getLogger(String name); + + Logger getRootLogger(); + + Logger exists(String name); + + void shutdown(); + + Enumeration getCurrentLoggers(); + } + + public static class ProvisionNode extends Vector { + + ProvisionNode(Logger logger) { + super(); + addElement(logger); + } + } + + public static class DefaultLoggerRepository implements LoggerRepository { + + final Hashtable ht = new Hashtable(); + Logger root; + + int logLevelInt; + Level logLevel; + + boolean emittedNoAppenderWarning = false; + + public DefaultLoggerRepository(Logger root) { + this.root = root; + setLogLevel(Level.ALL); + } + + public void emitNoAppenderWarning(Logger cat) { + if (!this.emittedNoAppenderWarning) { + SysLogger.warn("No appenders could be found for logger (" + cat.getName() + ")."); + SysLogger.warn("Please initialize the logger system properly."); + this.emittedNoAppenderWarning = true; + } + } + + public Logger exists(String name) { + Object o = ht.get(new CategoryKey(name)); + if (o instanceof Logger) { + return (Logger) o; + } else { + return null; + } + } + + public void setLogLevel(Level l) { + if (l != null) { + logLevelInt = l.level; + logLevel = l; + } + } + + public Level getLogLevel() { + return logLevel; + } + + + public Logger getLogger(String name) { + CategoryKey key = new CategoryKey(name); + Logger logger; + + synchronized (ht) { + Object o = ht.get(key); + if (o == null) { + logger = makeNewLoggerInstance(name); + ht.put(key, logger); + updateParents(logger); + return logger; + } else if (o instanceof Logger) { + return (Logger) o; + } else if (o instanceof ProvisionNode) { + logger = makeNewLoggerInstance(name); + ht.put(key, logger); + updateChildren((ProvisionNode) o, logger); + updateParents(logger); + return logger; + } else { + return null; + } + } + } + + public Logger makeNewLoggerInstance(String name) { + return new Logger(name); + } + + public Enumeration getCurrentLoggers() { + Vector loggers = new Vector(ht.size()); + + Enumeration elems = ht.elements(); + while (elems.hasMoreElements()) { + Object o = elems.nextElement(); + if (o instanceof Logger) { + Logger logger = (Logger)o; + loggers.addElement(logger); + } + } + return loggers.elements(); + } + + + public Logger getRootLogger() { + return root; + } + + public boolean isDisabled(int level) { + return logLevelInt > level; + } + + + public void shutdown() { + Logger root = getRootLogger(); + root.closeNestedAppenders(); + + synchronized (ht) { + Enumeration cats = this.getCurrentLoggers(); + while (cats.hasMoreElements()) { + Logger c = (Logger) cats.nextElement(); + c.closeNestedAppenders(); + } + root.removeAllAppenders(); + } + } + + + private void updateParents(Logger cat) { + String name = cat.name; + int length = name.length(); + boolean parentFound = false; + + for (int i = name.lastIndexOf('.', length - 1); i >= 0; + i = name.lastIndexOf('.', i - 1)) { + String substr = name.substring(0, i); + + CategoryKey key = new CategoryKey(substr); + Object o = ht.get(key); + if (o == null) { + ht.put(key, new ProvisionNode(cat)); + } else if (o instanceof Logger) { + parentFound = true; + cat.parent = (Logger) o; + break; + } else if (o instanceof ProvisionNode) { + ((ProvisionNode) o).addElement(cat); + } else { + Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht."); + e.printStackTrace(); + } + } + if (!parentFound) { + cat.parent = root; + } + } + + private void updateChildren(ProvisionNode pn, Logger logger) { + final int last = pn.size(); + + for (int i = 0; i < last; i++) { + Logger l = pn.elementAt(i); + if (!l.parent.name.startsWith(logger.name)) { + logger.parent = l.parent; + l.parent = logger; + } + } + } + + private class CategoryKey { + + String name; + int hashCache; + + CategoryKey(String name) { + this.name = name; + hashCache = name.hashCode(); + } + + final public int hashCode() { + return hashCache; + } + + final public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o != null && o instanceof CategoryKey) { + CategoryKey cc = (CategoryKey) o; + return name.equals(cc.name); + } else { + return false; + } + } + } + + } + + public static class RootLogger extends Logger { + + public RootLogger(Level level) { + super("root"); + setLevel(level); + } + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java new file mode 100644 index 00000000..469cb52b --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java @@ -0,0 +1,1230 @@ +/* + * 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.rocketmq.logging.inner; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilterWriter; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public class LoggingBuilder { + + public static final String SYSTEM_OUT = "System.out"; + public static final String SYSTEM_ERR = "System.err"; + + public static final String LOGGING_ENCODING = "rocketmq.logging.inner.encoding"; + public static final String ENCODING = System.getProperty(LOGGING_ENCODING, "UTF-8"); + + public static AppenderBuilder newAppenderBuilder() { + return new AppenderBuilder(); + } + + public static class AppenderBuilder { + private AsyncAppender asyncAppender; + + private Appender appender = null; + + private AppenderBuilder() { + + } + + public AppenderBuilder withLayout(Layout layout) { + appender.setLayout(layout); + return this; + } + + public AppenderBuilder withName(String name) { + appender.setName(name); + return this; + } + + public AppenderBuilder withConsoleAppender(String target) { + ConsoleAppender consoleAppender = new ConsoleAppender(); + consoleAppender.setTarget(target); + consoleAppender.activateOptions(); + this.appender = consoleAppender; + return this; + } + + public AppenderBuilder withFileAppender(String file) { + FileAppender appender = new FileAppender(); + appender.setFile(file); + appender.setAppend(true); + appender.setBufferedIO(false); + appender.setEncoding(ENCODING); + appender.setImmediateFlush(true); + appender.activateOptions(); + this.appender = appender; + return this; + } + + public AppenderBuilder withRollingFileAppender(String file, String maxFileSize, int maxFileIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setFile(file); + appender.setAppend(true); + appender.setBufferedIO(false); + appender.setEncoding(ENCODING); + appender.setImmediateFlush(true); + appender.setMaximumFileSize(Integer.parseInt(maxFileSize)); + appender.setMaxBackupIndex(maxFileIndex); + appender.activateOptions(); + this.appender = appender; + return this; + } + + public AppenderBuilder withDailyFileRollingAppender(String file, String datePattern) { + DailyRollingFileAppender appender = new DailyRollingFileAppender(); + appender.setFile(file); + appender.setAppend(true); + appender.setBufferedIO(false); + appender.setEncoding(ENCODING); + appender.setImmediateFlush(true); + appender.setDatePattern(datePattern); + appender.activateOptions(); + this.appender = appender; + return this; + } + + public AppenderBuilder withAsync(boolean blocking, int buffSize) { + AsyncAppender asyncAppender = new AsyncAppender(); + asyncAppender.setBlocking(blocking); + asyncAppender.setBufferSize(buffSize); + this.asyncAppender = asyncAppender; + return this; + } + + public Appender build() { + if (appender == null) { + throw new RuntimeException("please specify appender first"); + } + if (asyncAppender != null) { + asyncAppender.addAppender(appender); + return asyncAppender; + } else { + return appender; + } + } + } + + public static class AsyncAppender extends Appender implements Appender.AppenderPipeline { + + public static final int DEFAULT_BUFFER_SIZE = 128; + + private final List buffer = new ArrayList(); + + private final Map discardMap = new HashMap(); + + private int bufferSize = DEFAULT_BUFFER_SIZE; + + private final AppenderPipelineImpl appenderPipeline; + + private final Thread dispatcher; + + private boolean blocking = true; + + public AsyncAppender() { + appenderPipeline = new AppenderPipelineImpl(); + + dispatcher = new Thread(new Dispatcher(this, buffer, discardMap, appenderPipeline)); + + dispatcher.setDaemon(true); + + dispatcher.setName("AsyncAppender-Dispatcher-" + dispatcher.getName()); + dispatcher.start(); + } + + public void addAppender(final Appender newAppender) { + synchronized (appenderPipeline) { + appenderPipeline.addAppender(newAppender); + } + } + + public void append(final LoggingEvent event) { + if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) { + synchronized (appenderPipeline) { + appenderPipeline.appendLoopOnAppenders(event); + } + + return; + } + + event.getThreadName(); + event.getRenderedMessage(); + + synchronized (buffer) { + while (true) { + int previousSize = buffer.size(); + + if (previousSize < bufferSize) { + buffer.add(event); + + if (previousSize == 0) { + buffer.notifyAll(); + } + + break; + } + + boolean discard = true; + if (blocking + && !Thread.interrupted() + && Thread.currentThread() != dispatcher) { + try { + buffer.wait(); + discard = false; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if (discard) { + String loggerName = event.getLoggerName(); + DiscardSummary summary = discardMap.get(loggerName); + + if (summary == null) { + summary = new DiscardSummary(event); + discardMap.put(loggerName, summary); + } else { + summary.add(event); + } + + break; + } + } + } + } + + public void close() { + + synchronized (buffer) { + closed = true; + buffer.notifyAll(); + } + + try { + dispatcher.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + SysLogger.error( + "Got an InterruptedException while waiting for the " + + "dispatcher to finish.", e); + } + + synchronized (appenderPipeline) { + Enumeration iter = appenderPipeline.getAllAppenders(); + if (iter != null) { + while (iter.hasMoreElements()) { + Object next = iter.nextElement(); + if (next instanceof Appender) { + ((Appender) next).close(); + } + } + } + } + } + + public Enumeration getAllAppenders() { + synchronized (appenderPipeline) { + return appenderPipeline.getAllAppenders(); + } + } + + public Appender getAppender(final String name) { + synchronized (appenderPipeline) { + return appenderPipeline.getAppender(name); + } + } + + public boolean isAttached(final Appender appender) { + synchronized (appenderPipeline) { + return appenderPipeline.isAttached(appender); + } + } + + public void removeAllAppenders() { + synchronized (appenderPipeline) { + appenderPipeline.removeAllAppenders(); + } + } + + public void removeAppender(final Appender appender) { + synchronized (appenderPipeline) { + appenderPipeline.removeAppender(appender); + } + } + + public void removeAppender(final String name) { + synchronized (appenderPipeline) { + appenderPipeline.removeAppender(name); + } + } + + public void setBufferSize(final int size) { + if (size < 0) { + throw new NegativeArraySizeException("size"); + } + + synchronized (buffer) { + bufferSize = (size < 1) ? 1 : size; + buffer.notifyAll(); + } + } + + public int getBufferSize() { + return bufferSize; + } + + public void setBlocking(final boolean value) { + synchronized (buffer) { + blocking = value; + buffer.notifyAll(); + } + } + + public boolean getBlocking() { + return blocking; + } + + private final class DiscardSummary { + + private LoggingEvent maxEvent; + + private int count; + + public DiscardSummary(final LoggingEvent event) { + maxEvent = event; + count = 1; + } + + public void add(final LoggingEvent event) { + if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) { + maxEvent = event; + } + count++; + } + + public LoggingEvent createEvent() { + String msg = + MessageFormat.format( + "Discarded {0} messages due to full event buffer including: {1}", + count, maxEvent.getMessage()); + + return new LoggingEvent( + "AsyncAppender.DONT_REPORT_LOCATION", + Logger.getLogger(maxEvent.getLoggerName()), + maxEvent.getLevel(), + msg, + null); + } + } + + private class Dispatcher implements Runnable { + + private final AsyncAppender parent; + + private final List buffer; + + private final Map discardMap; + + private final AppenderPipelineImpl appenderPipeline; + + public Dispatcher( + final AsyncAppender parent, final List buffer, final Map discardMap, + final AppenderPipelineImpl appenderPipeline) { + + this.parent = parent; + this.buffer = buffer; + this.appenderPipeline = appenderPipeline; + this.discardMap = discardMap; + } + + public void run() { + boolean isActive = true; + + try { + while (isActive) { + LoggingEvent[] events = null; + + synchronized (buffer) { + int bufferSize = buffer.size(); + isActive = !parent.closed; + + while ((bufferSize == 0) && isActive) { + buffer.wait(); + bufferSize = buffer.size(); + isActive = !parent.closed; + } + + if (bufferSize > 0) { + events = new LoggingEvent[bufferSize + discardMap.size()]; + buffer.toArray(events); + + int index = bufferSize; + Collection values = discardMap.values(); + for (DiscardSummary value : values) { + events[index++] = value.createEvent(); + } + + buffer.clear(); + discardMap.clear(); + + buffer.notifyAll(); + } + } + if (events != null) { + for (LoggingEvent event : events) { + synchronized (appenderPipeline) { + appenderPipeline.appendLoopOnAppenders(event); + } + } + } + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + private static class QuietWriter extends FilterWriter { + + protected Appender appender; + + public QuietWriter(Writer writer, Appender appender) { + super(writer); + this.appender = appender; + } + + public void write(String string) { + if (string != null) { + try { + out.write(string); + } catch (Exception e) { + appender.handleError("Failed to write [" + string + "].", e, + Appender.CODE_WRITE_FAILURE); + } + } + } + + public void flush() { + try { + out.flush(); + } catch (Exception e) { + appender.handleError("Failed to flush writer,", e, + Appender.CODE_FLUSH_FAILURE); + } + } + } + + public static class WriterAppender extends Appender { + + + protected boolean immediateFlush = true; + + protected String encoding; + + + protected QuietWriter qw; + + public WriterAppender() { + + } + + public void setImmediateFlush(boolean value) { + immediateFlush = value; + } + + + public boolean getImmediateFlush() { + return immediateFlush; + } + + public void activateOptions() { + } + + + public void append(LoggingEvent event) { + if (!checkEntryConditions()) { + return; + } + subAppend(event); + } + + protected boolean checkEntryConditions() { + if (this.closed) { + SysLogger.warn("Not allowed to write to a closed appender."); + return false; + } + + if (this.qw == null) { + handleError("No output stream or file set for the appender named [" + + name + "]."); + return false; + } + + if (this.layout == null) { + handleError("No layout set for the appender named [" + name + "]."); + return false; + } + return true; + } + + public synchronized void close() { + if (this.closed) { + return; + } + this.closed = true; + writeFooter(); + reset(); + } + + protected void closeWriter() { + if (qw != null) { + try { + qw.close(); + } catch (IOException e) { + handleError("Could not close " + qw, e, CODE_CLOSE_FAILURE); + } + } + } + + protected OutputStreamWriter createWriter(OutputStream os) { + OutputStreamWriter retval = null; + + String enc = getEncoding(); + if (enc != null) { + try { + retval = new OutputStreamWriter(os, enc); + } catch (IOException e) { + SysLogger.warn("Error initializing output writer."); + SysLogger.warn("Unsupported encoding?"); + } + } + if (retval == null) { + retval = new OutputStreamWriter(os); + } + return retval; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String value) { + encoding = value; + } + + + public synchronized void setWriter(Writer writer) { + reset(); + this.qw = new QuietWriter(writer, this); + writeHeader(); + } + + protected void subAppend(LoggingEvent event) { + this.qw.write(this.layout.format(event)); + + if (layout.ignoresThrowable()) { + String[] s = event.getThrowableStr(); + if (s != null) { + for (String s1 : s) { + this.qw.write(s1); + this.qw.write(LINE_SEP); + } + } + } + + if (shouldFlush(event)) { + this.qw.flush(); + } + } + + protected void reset() { + closeWriter(); + this.qw = null; + } + + protected void writeFooter() { + if (layout != null) { + String f = layout.getFooter(); + if (f != null && this.qw != null) { + this.qw.write(f); + this.qw.flush(); + } + } + } + + protected void writeHeader() { + if (layout != null) { + String h = layout.getHeader(); + if (h != null && this.qw != null) { + this.qw.write(h); + } + } + } + + protected boolean shouldFlush(final LoggingEvent event) { + return event != null && immediateFlush; + } + } + + + public static class FileAppender extends WriterAppender { + + protected boolean fileAppend = true; + + protected String fileName = null; + + protected boolean bufferedIO = false; + + protected int bufferSize = 8 * 1024; + + public FileAppender() { + } + + public FileAppender(Layout layout, String filename, boolean append) + throws IOException { + this.layout = layout; + this.setFile(filename, append, false, bufferSize); + } + + public void setFile(String file) { + fileName = file.trim(); + } + + public boolean getAppend() { + return fileAppend; + } + + public String getFile() { + return fileName; + } + + public void activateOptions() { + if (fileName != null) { + try { + setFile(fileName, fileAppend, bufferedIO, bufferSize); + } catch (IOException e) { + handleError("setFile(" + fileName + "," + fileAppend + ") call failed.", + e, CODE_FILE_OPEN_FAILURE); + } + } else { + SysLogger.warn("File option not set for appender [" + name + "]."); + SysLogger.warn("Are you using FileAppender instead of ConsoleAppender?"); + } + } + + protected void closeFile() { + if (this.qw != null) { + try { + this.qw.close(); + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + SysLogger.error("Could not close " + qw, e); + } + } + } + + public boolean getBufferedIO() { + return this.bufferedIO; + } + + public int getBufferSize() { + return this.bufferSize; + } + + public void setAppend(boolean flag) { + fileAppend = flag; + } + + public void setBufferedIO(boolean bufferedIO) { + this.bufferedIO = bufferedIO; + if (bufferedIO) { + immediateFlush = false; + } + } + + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) + throws IOException { + SysLogger.debug("setFile called: " + fileName + ", " + append); + + if (bufferedIO) { + setImmediateFlush(false); + } + + reset(); + FileOutputStream ostream; + try { + ostream = new FileOutputStream(fileName, append); + } catch (FileNotFoundException ex) { + String parentName = new File(fileName).getParent(); + if (parentName != null) { + File parentDir = new File(parentName); + if (!parentDir.exists() && parentDir.mkdirs()) { + ostream = new FileOutputStream(fileName, append); + } else { + throw ex; + } + } else { + throw ex; + } + } + Writer fw = createWriter(ostream); + if (bufferedIO) { + fw = new BufferedWriter(fw, bufferSize); + } + this.setQWForFiles(fw); + this.fileName = fileName; + this.fileAppend = append; + this.bufferedIO = bufferedIO; + this.bufferSize = bufferSize; + writeHeader(); + SysLogger.debug("setFile ended"); + } + + protected void setQWForFiles(Writer writer) { + this.qw = new QuietWriter(writer, this); + } + + protected void reset() { + closeFile(); + this.fileName = null; + super.reset(); + } + } + + + public static class RollingFileAppender extends FileAppender { + + protected long maxFileSize = 10 * 1024 * 1024; + + protected int maxBackupIndex = 1; + + private long nextRollover = 0; + + public RollingFileAppender() { + super(); + } + + public int getMaxBackupIndex() { + return maxBackupIndex; + } + + public long getMaximumFileSize() { + return maxFileSize; + } + + public void rollOver() { + File target; + File file; + + if (qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + SysLogger.debug("rolling over count=" + size); + nextRollover = size + maxFileSize; + } + SysLogger.debug("maxBackupIndex=" + maxBackupIndex); + + boolean renameSucceeded = true; + if (maxBackupIndex > 0) { + file = new File(fileName + '.' + maxBackupIndex); + if (file.exists()) { + renameSucceeded = file.delete(); + } + + for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) { + file = new File(fileName + "." + i); + if (file.exists()) { + target = new File(fileName + '.' + (i + 1)); + SysLogger.debug("Renaming file " + file + " to " + target); + renameSucceeded = file.renameTo(target); + } + } + + if (renameSucceeded) { + target = new File(fileName + "." + 1); + + this.closeFile(); // keep windows happy. + + file = new File(fileName); + SysLogger.debug("Renaming file " + file + " to " + target); + renameSucceeded = file.renameTo(target); + + if (!renameSucceeded) { + try { + this.setFile(fileName, true, bufferedIO, bufferSize); + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + SysLogger.error("setFile(" + fileName + ", true) call failed.", e); + } + } + } + } + + if (renameSucceeded) { + try { + this.setFile(fileName, false, bufferedIO, bufferSize); + nextRollover = 0; + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + SysLogger.error("setFile(" + fileName + ", false) call failed.", e); + } + } + } + + public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) + throws IOException { + super.setFile(fileName, append, this.bufferedIO, this.bufferSize); + if (append) { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + public void setMaxBackupIndex(int maxBackups) { + this.maxBackupIndex = maxBackups; + } + + public void setMaximumFileSize(long maxFileSize) { + this.maxFileSize = maxFileSize; + } + + protected void setQWForFiles(Writer writer) { + this.qw = new CountingQuietWriter(writer, this); + } + + protected void subAppend(LoggingEvent event) { + super.subAppend(event); + if (fileName != null && qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + if (size >= maxFileSize && size >= nextRollover) { + rollOver(); + } + } + } + + protected class CountingQuietWriter extends QuietWriter { + + protected long count; + + public CountingQuietWriter(Writer writer, Appender appender) { + super(writer, appender); + } + + public void write(String string) { + try { + out.write(string); + count += string.length(); + } catch (IOException e) { + appender.handleError("Write failure.", e, Appender.CODE_WRITE_FAILURE); + } + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + } + } + + + public static class DailyRollingFileAppender extends FileAppender { + + static final int TOP_OF_TROUBLE = -1; + static final int TOP_OF_MINUTE = 0; + static final int TOP_OF_HOUR = 1; + static final int HALF_DAY = 2; + static final int TOP_OF_DAY = 3; + static final int TOP_OF_WEEK = 4; + static final int TOP_OF_MONTH = 5; + + + /** + * The date pattern. By default, the pattern is set to + * "'.'yyyy-MM-dd" meaning daily rollover. + */ + private String datePattern = "'.'yyyy-MM-dd"; + + private String scheduledFilename; + + private long nextCheck = System.currentTimeMillis() - 1; + + Date now = new Date(); + + SimpleDateFormat sdf; + + RollingCalendar rc = new RollingCalendar(); + + final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); + + + public void setDatePattern(String pattern) { + datePattern = pattern; + } + + public String getDatePattern() { + return datePattern; + } + + public void activateOptions() { + super.activateOptions(); + if (datePattern != null && fileName != null) { + now.setTime(System.currentTimeMillis()); + sdf = new SimpleDateFormat(datePattern); + int type = computeCheckPeriod(); + printPeriodicity(type); + rc.setType(type); + File file = new File(fileName); + scheduledFilename = fileName + sdf.format(new Date(file.lastModified())); + + } else { + SysLogger.error("Either File or DatePattern options are not set for appender [" + name + "]."); + } + } + + void printPeriodicity(int type) { + switch (type) { + case TOP_OF_MINUTE: + SysLogger.debug("Appender [" + name + "] to be rolled every minute."); + break; + case TOP_OF_HOUR: + SysLogger.debug("Appender [" + name + "] to be rolled on top of every hour."); + break; + case HALF_DAY: + SysLogger.debug("Appender [" + name + "] to be rolled at midday and midnight."); + break; + case TOP_OF_DAY: + SysLogger.debug("Appender [" + name + "] to be rolled at midnight."); + break; + case TOP_OF_WEEK: + SysLogger.debug("Appender [" + name + "] to be rolled at start of week."); + break; + case TOP_OF_MONTH: + SysLogger.debug("Appender [" + name + "] to be rolled at start of every month."); + break; + default: + SysLogger.warn("Unknown periodicity for appender [" + name + "]."); + } + } + + int computeCheckPeriod() { + RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault()); + // set sate to 1970-01-01 00:00:00 GMT + Date epoch = new Date(0); + if (datePattern != null) { + for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern); + simpleDateFormat.setTimeZone(gmtTimeZone); + String r0 = simpleDateFormat.format(epoch); + rollingCalendar.setType(i); + Date next = new Date(rollingCalendar.getNextCheckMillis(epoch)); + String r1 = simpleDateFormat.format(next); + if (r0.equals(r1)) { + return i; + } + } + } + return TOP_OF_TROUBLE; + } + + void rollOver() throws IOException { + + if (datePattern == null) { + handleError("Missing DatePattern option in rollOver()."); + return; + } + + String datedFilename = fileName + sdf.format(now); + + if (scheduledFilename.equals(datedFilename)) { + return; + } + this.closeFile(); + + File target = new File(scheduledFilename); + if (target.exists() && !target.delete()) { + SysLogger.error("Failed to delete [" + scheduledFilename + "]."); + } + + File file = new File(fileName); + boolean result = file.renameTo(target); + if (result) { + SysLogger.debug(fileName + " -> " + scheduledFilename); + } else { + SysLogger.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "]."); + } + + try { + this.setFile(fileName, true, this.bufferedIO, this.bufferSize); + } catch (IOException e) { + handleError("setFile(" + fileName + ", true) call failed."); + } + scheduledFilename = datedFilename; + } + + protected void subAppend(LoggingEvent event) { + long n = System.currentTimeMillis(); + if (n >= nextCheck) { + now.setTime(n); + nextCheck = rc.getNextCheckMillis(now); + try { + rollOver(); + } catch (IOException ioe) { + if (ioe instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + SysLogger.error("rollOver() failed.", ioe); + } + } + super.subAppend(event); + } + } + + private static class RollingCalendar extends GregorianCalendar { + private static final long serialVersionUID = -3560331770601814177L; + + int type = DailyRollingFileAppender.TOP_OF_TROUBLE; + + RollingCalendar() { + super(); + } + + RollingCalendar(TimeZone tz, Locale locale) { + super(tz, locale); + } + + void setType(int type) { + this.type = type; + } + + public long getNextCheckMillis(Date now) { + return getNextCheckDate(now).getTime(); + } + + public Date getNextCheckDate(Date now) { + this.setTime(now); + + switch (type) { + case DailyRollingFileAppender.TOP_OF_MINUTE: + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + this.add(Calendar.MINUTE, 1); + break; + case DailyRollingFileAppender.TOP_OF_HOUR: + this.set(Calendar.MINUTE, 0); + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + this.add(Calendar.HOUR_OF_DAY, 1); + break; + case DailyRollingFileAppender.HALF_DAY: + this.set(Calendar.MINUTE, 0); + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + int hour = get(Calendar.HOUR_OF_DAY); + if (hour < 12) { + this.set(Calendar.HOUR_OF_DAY, 12); + } else { + this.set(Calendar.HOUR_OF_DAY, 0); + this.add(Calendar.DAY_OF_MONTH, 1); + } + break; + case DailyRollingFileAppender.TOP_OF_DAY: + this.set(Calendar.HOUR_OF_DAY, 0); + this.set(Calendar.MINUTE, 0); + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + this.add(Calendar.DATE, 1); + break; + case DailyRollingFileAppender.TOP_OF_WEEK: + this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek()); + this.set(Calendar.HOUR_OF_DAY, 0); + this.set(Calendar.MINUTE, 0); + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + this.add(Calendar.WEEK_OF_YEAR, 1); + break; + case DailyRollingFileAppender.TOP_OF_MONTH: + this.set(Calendar.DATE, 1); + this.set(Calendar.HOUR_OF_DAY, 0); + this.set(Calendar.MINUTE, 0); + this.set(Calendar.SECOND, 0); + this.set(Calendar.MILLISECOND, 0); + this.add(Calendar.MONTH, 1); + break; + default: + throw new IllegalStateException("Unknown periodicity type."); + } + return getTime(); + } + } + + public static class ConsoleAppender extends WriterAppender { + + protected String target = SYSTEM_OUT; + + public ConsoleAppender() { + } + + public void setTarget(String value) { + String v = value.trim(); + + if (SYSTEM_OUT.equalsIgnoreCase(v)) { + target = SYSTEM_OUT; + } else if (SYSTEM_ERR.equalsIgnoreCase(v)) { + target = SYSTEM_ERR; + } else { + targetWarn(value); + } + } + + public String getTarget() { + return target; + } + + void targetWarn(String val) { + SysLogger.warn("[" + val + "] should be System.out or System.err."); + SysLogger.warn("Using previously set target, System.out by default."); + } + + public void activateOptions() { + if (target.equals(SYSTEM_ERR)) { + setWriter(createWriter(System.err)); + } else { + setWriter(createWriter(System.out)); + } + super.activateOptions(); + } + + protected final void closeWriter() { + + } + } + + public static LayoutBuilder newLayoutBuilder() { + return new LayoutBuilder(); + } + + public static class LayoutBuilder { + + private Layout layout; + + public LayoutBuilder withSimpleLayout() { + layout = new SimpleLayout(); + return this; + } + + public LayoutBuilder withDefaultLayout() { + layout = new DefaultLayout(); + return this; + } + + public Layout build() { + if (layout == null) { + layout = new SimpleLayout(); + } + return layout; + } + } + + public static class SimpleLayout extends Layout { + + @Override + public String format(LoggingEvent event) { + + StringBuilder sb = new StringBuilder(); + sb.append(event.getLevel().toString()); + sb.append(" - "); + sb.append(event.getRenderedMessage()); + sb.append("\r\n"); + return sb.toString(); + } + + @Override + public boolean ignoresThrowable() { + return false; + } + } + + + /** + * %d{yyy-MM-dd HH:mm:ss,SSS} %p %c{1}%L - %m%n + */ + public static class DefaultLayout extends Layout { + @Override + public String format(LoggingEvent event) { + + StringBuilder sb = new StringBuilder(); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,sss"); + String format = simpleDateFormat.format(new Date(event.timeStamp)); + sb.append(format); + sb.append(" "); + sb.append(event.getLevel()); + sb.append(" "); + sb.append(event.getLoggerName()); + sb.append(" - "); + sb.append(event.getMessage()); + String[] throwableStr = event.getThrowableStr(); + if (throwableStr != null) { + sb.append("\r\n"); + for (String s : throwableStr) { + sb.append(s); + sb.append("\r\n"); + } + } + sb.append("\r\n"); + return sb.toString(); + } + + @Override + public boolean ignoresThrowable() { + return false; + } + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java new file mode 100644 index 00000000..1b3e9553 --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java @@ -0,0 +1,121 @@ +/* + * 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.rocketmq.logging.inner; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; + +public class LoggingEvent implements java.io.Serializable { + + transient public final String fqnOfCategoryClass; + + transient private Object message; + + transient private Level level; + + transient private Logger logger; + + private String renderedMessage; + + private String threadName; + + public final long timeStamp; + + private Throwable throwable; + + public LoggingEvent(String fqnOfCategoryClass, Logger logger, + Level level, Object message, Throwable throwable) { + this.fqnOfCategoryClass = fqnOfCategoryClass; + this.message = message; + this.logger = logger; + this.throwable = throwable; + this.level = level; + timeStamp = System.currentTimeMillis(); + } + + public Object getMessage() { + if (message != null) { + return message; + } else { + return getRenderedMessage(); + } + } + + public String getRenderedMessage() { + if (renderedMessage == null && message != null) { + if (message instanceof String) { + renderedMessage = (String) message; + } else { + renderedMessage = message.toString(); + } + } + return renderedMessage; + } + + public String getThreadName() { + if (threadName == null) { + threadName = (Thread.currentThread()).getName(); + } + return threadName; + } + + public Level getLevel() { + return level; + } + + public String getLoggerName() { + return logger.getName(); + } + + public String[] getThrowableStr() { + if (throwable == null) { + return null; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + try { + throwable.printStackTrace(pw); + } catch (RuntimeException ex) { + SysLogger.warn("InnerLogger print stack trace error", ex); + } + pw.flush(); + LineNumberReader reader = new LineNumberReader( + new StringReader(sw.toString())); + ArrayList lines = new ArrayList(); + try { + String line = reader.readLine(); + while (line != null) { + lines.add(line); + line = reader.readLine(); + } + } catch (IOException ex) { + if (ex instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + lines.add(ex.toString()); + } + String[] tempRep = new String[lines.size()]; + lines.toArray(tempRep); + return tempRep; + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java new file mode 100755 index 00000000..b6d10497 --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java @@ -0,0 +1,89 @@ +/* + * 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.rocketmq.logging.inner; + +public class SysLogger { + + protected static boolean debugEnabled = false; + + private static boolean quietMode = false; + + private static final String PREFIX = "RocketMQLog: "; + private static final String ERR_PREFIX = "RocketMQLog:ERROR "; + private static final String WARN_PREFIX = "RocketMQLog:WARN "; + + public static void setInternalDebugging(boolean enabled) { + debugEnabled = enabled; + } + + public static void debug(String msg) { + if (debugEnabled && !quietMode) { + System.out.printf("%s", PREFIX + msg); + } + } + + public static void debug(String msg, Throwable t) { + if (debugEnabled && !quietMode) { + System.out.printf("%s", PREFIX + msg); + if (t != null) { + t.printStackTrace(System.out); + } + } + } + + public static void error(String msg) { + if (quietMode) { + return; + } + System.err.println(ERR_PREFIX + msg); + } + + public static void error(String msg, Throwable t) { + if (quietMode) { + return; + } + + System.err.println(ERR_PREFIX + msg); + if (t != null) { + t.printStackTrace(); + } + } + + public static void setQuietMode(boolean quietMode) { + SysLogger.quietMode = quietMode; + } + + public static void warn(String msg) { + if (quietMode) { + return; + } + + System.err.println(WARN_PREFIX + msg); + } + + public static void warn(String msg, Throwable t) { + if (quietMode) { + return; + } + + System.err.println(WARN_PREFIX + msg); + if (t != null) { + t.printStackTrace(); + } + } +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/package-info.java b/logging/src/main/java/org/apache/rocketmq/logging/package-info.java new file mode 100644 index 00000000..7cb0645d --- /dev/null +++ b/logging/src/main/java/org/apache/rocketmq/logging/package-info.java @@ -0,0 +1,35 @@ +/* + * 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.rocketmq.logging; + +/* + This package is a minimal logger on the basis of Apache Log4j without + file configuration and pattern layout configuration. Main forked files are + followed as below: + 1. LoggingEvent + 2. Logger + 3. Layout + 4. Level + 5. AsyncAppender + 6. FileAppender + 7. RollingFileAppender + 8. DailyRollingFileAppender + 9. ConsoleAppender + + For more information about Apache Log4j, please go to https://github.com/apache/log4j. + */ \ No newline at end of file diff --git a/logging/src/test/java/org/apache/rocketmq/logging/BasicloggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/BasicloggerTest.java new file mode 100644 index 00000000..28496dd5 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/BasicloggerTest.java @@ -0,0 +1,70 @@ +/* + * 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.rocketmq.logging; + +import org.apache.rocketmq.logging.inner.Level; +import org.apache.rocketmq.logging.inner.Logger; +import org.apache.rocketmq.logging.inner.LoggingEvent; +import org.junit.After; +import org.junit.Before; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +public class BasicLoggerTest { + + protected Logger logger = Logger.getLogger("test"); + + protected LoggingEvent loggingEvent; + + protected String loggingDir = System.getProperty("user.home") + "/logs/rocketmq-test"; + + @Before + public void createLoggingEvent() { + loggingEvent = new LoggingEvent(Logger.class.getName(), logger, Level.INFO, + "junit test error", new RuntimeException("createLogging error")); + } + + public String readFile(String file) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + FileInputStream fileInputStream = new FileInputStream(file); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream)); + String line = bufferedReader.readLine(); + while (line != null) { + stringBuilder.append(line); + stringBuilder.append("\r\n"); + line = bufferedReader.readLine(); + } + bufferedReader.close(); + return stringBuilder.toString(); + } + + @After + public void clean() { + File file = new File(loggingDir); + if (file.exists()) { + File[] files = file.listFiles(); + for (File file1 : files) { + file1.delete(); + } + } + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java new file mode 100644 index 00000000..c47dba68 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java @@ -0,0 +1,92 @@ +/* + * 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.rocketmq.logging; + +import org.apache.rocketmq.logging.inner.Appender; +import org.apache.rocketmq.logging.inner.Level; +import org.apache.rocketmq.logging.inner.Logger; +import org.apache.rocketmq.logging.inner.LoggingBuilder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class InnerLoggerFactoryTest extends BasicLoggerTest { + + private ByteArrayOutputStream byteArrayOutputStream; + + public static final String LOGGER = "ConsoleLogger"; + + private PrintStream out; + + @Before + public void initLogger() { + out = System.out; + byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + + Appender consoleAppender = LoggingBuilder.newAppenderBuilder() + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + Logger consoleLogger = Logger.getLogger("ConsoleLogger"); + consoleLogger.setAdditivity(false); + consoleLogger.addAppender(consoleAppender); + consoleLogger.setLevel(Level.INFO); + } + + @After + public void fixConsole() { + System.setOut(out); + } + + @Test + public void testInnerLoggerFactory() { + InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_INNER); + + InternalLogger logger1 = InnerLoggerFactory.getLogger(LOGGER); + InternalLogger logger = InternalLoggerFactory.getLogger(LOGGER); + + Assert.assertTrue(logger.getName().equals(logger1.getName())); + + InternalLogger logger2 = InnerLoggerFactory.getLogger(InnerLoggerFactoryTest.class); + InnerLoggerFactory.InnerLogger logger3 = (InnerLoggerFactory.InnerLogger) logger2; + + logger.info("innerLogger inner info Message"); + logger.error("innerLogger inner error Message", new RuntimeException()); + logger.debug("innerLogger inner debug message"); + logger3.info("innerLogger info message"); + logger3.error("logback error message"); + logger3.info("info {}", "hahahah"); + logger3.warn("warn {}", "hahahah"); + logger3.warn("logger3 warn"); + logger3.error("error {}", "hahahah"); + logger3.debug("debug {}", "hahahah"); + + String content = new String(byteArrayOutputStream.toByteArray()); + System.out.println(content); + + Assert.assertTrue(content.contains("InnerLoggerFactoryTest")); + Assert.assertTrue(content.contains("info")); + Assert.assertTrue(content.contains("RuntimeException")); + Assert.assertTrue(!content.contains("debug")); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java new file mode 100644 index 00000000..04b9f06e --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java @@ -0,0 +1,72 @@ +/* + * 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.rocketmq.logging; + +import org.apache.rocketmq.logging.inner.Appender; +import org.apache.rocketmq.logging.inner.Level; +import org.apache.rocketmq.logging.inner.Logger; +import org.apache.rocketmq.logging.inner.LoggingBuilder; +import org.apache.rocketmq.logging.inner.SysLogger; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + + +public class InternalLoggerTest { + + + @Test + public void testInternalLogger() { + SysLogger.setQuietMode(false); + SysLogger.setInternalDebugging(true); + PrintStream out = System.out; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + + Appender consoleAppender = LoggingBuilder.newAppenderBuilder() + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + + Logger consoleLogger = Logger.getLogger("ConsoleLogger"); + consoleLogger.setAdditivity(false); + consoleLogger.addAppender(consoleAppender); + consoleLogger.setLevel(Level.INFO); + + Logger.getRootLogger().addAppender(consoleAppender); + + InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_INNER); + InternalLogger logger = InternalLoggerFactory.getLogger(InternalLoggerTest.class); + InternalLogger consoleLogger1 = InternalLoggerFactory.getLogger("ConsoleLogger"); + + consoleLogger1.warn("simple warn {}", 14555); + + logger.info("testInternalLogger"); + consoleLogger1.info("consoleLogger1"); + + System.setOut(out); + consoleAppender.close(); + + String result = new String(byteArrayOutputStream.toByteArray()); + Assert.assertTrue(result.contains("consoleLogger1")); + Assert.assertTrue(result.contains("testInternalLogger")); + } + +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java new file mode 100644 index 00000000..ba6ec3ba --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java @@ -0,0 +1,80 @@ +/* + * 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.rocketmq.logging; + +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.joran.spi.JoranException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; + +public class Slf4jLoggerFactoryTest extends BasicLoggerTest { + + public static final String LOGGER = "Slf4jTestLogger"; + + @Before + public void initLogback() throws JoranException { + InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_SLF4J); + System.setProperty("loggingDir", loggingDir); + ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); + JoranConfigurator joranConfigurator = new JoranConfigurator(); + joranConfigurator.setContext((Context) iLoggerFactory); + URL logbackConfigFile = Slf4jLoggerFactoryTest.class.getClassLoader().getResource("logback_test.xml"); + if (logbackConfigFile == null) { + throw new RuntimeException("can't find logback_test.xml"); + } else { + joranConfigurator.doConfigure(logbackConfigFile); + } + } + + @Test + public void testSlf4j() throws IOException { + InternalLogger logger1 = Slf4jLoggerFactory.getLogger(LOGGER); + InternalLogger logger = InternalLoggerFactory.getLogger(LOGGER); + Assert.assertTrue(logger.getName().equals(logger1.getName())); + InternalLogger logger2 = Slf4jLoggerFactory.getLogger(Slf4jLoggerFactoryTest.class); + Slf4jLoggerFactory.Slf4jLogger logger3 = (Slf4jLoggerFactory.Slf4jLogger) logger2; + + String file = loggingDir + "/logback_test.log"; + + logger.info("logback slf4j info Message"); + logger.error("logback slf4j error Message", new RuntimeException()); + logger.debug("logback slf4j debug message"); + logger3.info("logback info message"); + logger3.error("logback error message"); + logger3.info("info {}", "hahahah"); + logger3.warn("warn {}", "hahahah"); + logger3.warn("logger3 warn"); + logger3.error("error {}", "hahahah"); + logger3.debug("debug {}", "hahahah"); + String content = readFile(file); + System.out.println(content); + + Assert.assertTrue(content.contains("Slf4jLoggerFactoryTest")); + Assert.assertTrue(content.contains("info")); + Assert.assertTrue(content.contains("RuntimeException")); + Assert.assertTrue(!content.contains("debug")); + } + +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java new file mode 100644 index 00000000..37ff8bd4 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java @@ -0,0 +1,160 @@ +/* + * 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.rocketmq.logging.inner; + +import org.apache.rocketmq.logging.BasicLoggerTest; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class AppenderTest extends BasicLoggerTest { + + @Test + public void testConsole() { + SysLogger.setQuietMode(false); + SysLogger.setInternalDebugging(true); + PrintStream out = System.out; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + + Appender consoleAppender = LoggingBuilder.newAppenderBuilder() + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + LoggingBuilder.ConsoleAppender consoleAppender1 = (LoggingBuilder.ConsoleAppender) consoleAppender; + String target = consoleAppender1.getTarget(); + Assert.assertTrue(target.equals(LoggingBuilder.SYSTEM_OUT)); + + Layout layout = consoleAppender.getLayout(); + Assert.assertTrue(layout instanceof LoggingBuilder.DefaultLayout); + + Logger consoleLogger = Logger.getLogger("ConsoleLogger"); + consoleLogger.setAdditivity(false); + consoleLogger.addAppender(consoleAppender); + consoleLogger.setLevel(Level.INFO); + + Logger.getRootLogger().addAppender(consoleAppender); + Logger.getLogger(AppenderTest.class).info("this is a AppenderTest log"); + + Logger.getLogger("ConsoleLogger").info("console info Message"); + Logger.getLogger("ConsoleLogger").error("console error Message", new RuntimeException()); + Logger.getLogger("ConsoleLogger").debug("console debug message"); + System.setOut(out); + consoleAppender.close(); + + String result = new String(byteArrayOutputStream.toByteArray()); + + Assert.assertTrue(result.contains("info")); + Assert.assertTrue(result.contains("RuntimeException")); + Assert.assertTrue(!result.contains("debug")); + Assert.assertTrue(result.contains("AppenderTest")); + } + + @Test + public void testInnerFile() throws IOException { + String file = loggingDir + "/logger.log"; + + Logger fileLogger = Logger.getLogger("fileLogger"); + + Appender myappender = LoggingBuilder.newAppenderBuilder() + .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") + .withName("myappender") + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + fileLogger.addAppender(myappender); + + Logger.getLogger("fileLogger").setLevel(Level.INFO); + + Logger.getLogger("fileLogger").info("fileLogger info Message"); + Logger.getLogger("fileLogger").error("fileLogger error Message", new RuntimeException()); + Logger.getLogger("fileLogger").debug("fileLogger debug message"); + + myappender.close(); + + String content = readFile(file); + + System.out.println(content); + + Assert.assertTrue(content.contains("info")); + Assert.assertTrue(content.contains("RuntimeException")); + Assert.assertTrue(!content.contains("debug")); + } + + + + @Test + public void asyncAppenderTest() { + Appender appender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + Assert.assertTrue(appender instanceof LoggingBuilder.AsyncAppender); + LoggingBuilder.AsyncAppender asyncAppender = (LoggingBuilder.AsyncAppender) appender; + Assert.assertTrue(!asyncAppender.getBlocking()); + Assert.assertTrue(asyncAppender.getBufferSize() > 0); + } + + @Test + public void testWriteAppender() { + LoggingBuilder.WriterAppender writerAppender = new LoggingBuilder.WriterAppender(); + writerAppender.setImmediateFlush(true); + Assert.assertTrue(writerAppender.getImmediateFlush()); + } + + @Test + public void testFileAppender() throws IOException { + LoggingBuilder.FileAppender fileAppender = new LoggingBuilder.FileAppender( + new LoggingBuilder.SimpleLayout(), loggingDir + "/simple.log", true); + fileAppender.setBufferSize(1024); + int bufferSize = fileAppender.getBufferSize(); + boolean bufferedIO = fileAppender.getBufferedIO(); + Assert.assertTrue(!bufferedIO); + Assert.assertTrue(bufferSize > 0); + Assert.assertTrue(fileAppender.getAppend()); + + LoggingBuilder.RollingFileAppender rollingFileAppender = new LoggingBuilder.RollingFileAppender(); + rollingFileAppender.setImmediateFlush(true); + rollingFileAppender.setMaximumFileSize(1024 * 1024); + rollingFileAppender.setMaxBackupIndex(10); + rollingFileAppender.setAppend(true); + rollingFileAppender.setFile(loggingDir + "/rolling_file.log"); + rollingFileAppender.setName("myRollingFileAppender"); + + rollingFileAppender.activateOptions(); + + Assert.assertTrue(rollingFileAppender.getMaximumFileSize() > 0); + Assert.assertTrue(rollingFileAppender.getMaxBackupIndex() == 10); + } + + @Test + public void testDailyRollingAppender() { + LoggingBuilder.DailyRollingFileAppender dailyRollingFileAppender = new LoggingBuilder.DailyRollingFileAppender(); + dailyRollingFileAppender.setFile(loggingDir + "/daily.log"); + dailyRollingFileAppender.setName("dailyAppender"); + dailyRollingFileAppender.setAppend(true); + dailyRollingFileAppender.setDatePattern("'.'yyyy-mm-dd"); + String datePattern = dailyRollingFileAppender.getDatePattern(); + Assert.assertTrue(datePattern != null); + dailyRollingFileAppender.activateOptions(); + } + +} + + diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java new file mode 100644 index 00000000..66ef18ea --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java @@ -0,0 +1,54 @@ +/* + * 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.rocketmq.logging.inner; + +import org.apache.rocketmq.logging.BasicLoggerTest; +import org.junit.Assert; +import org.junit.Test; + +public class LayoutTest extends BasicLoggerTest { + + @Test + public void testSimpleLayout() { + Layout layout = LoggingBuilder.newLayoutBuilder().withSimpleLayout().build(); + String format = layout.format(loggingEvent); + Assert.assertTrue(format.contains("junit")); + } + + @Test + public void testDefaultLayout() { + Layout layout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build(); + String format = layout.format(loggingEvent); + String contentType = layout.getContentType(); + Assert.assertTrue(contentType.contains("text")); + Assert.assertTrue(format.contains("createLoggingEvent")); + Assert.assertTrue(format.contains("createLogging error")); + Assert.assertTrue(format.contains(Thread.currentThread().getName())); + } + + @Test + public void testLogFormat() { + Layout innerLayout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build(); + + LoggingEvent loggingEvent = new LoggingEvent(Logger.class.getName(), logger, org.apache.rocketmq.logging.inner.Level.INFO, + "junit test error", null); + String format = innerLayout.format(loggingEvent); + + System.out.println(format); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java new file mode 100644 index 00000000..21667e14 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java @@ -0,0 +1,37 @@ +/* + * 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.rocketmq.logging.inner; + +import org.junit.Assert; +import org.junit.Test; + +public class LevelTest { + + @Test + public void levelTest() { + Level info = Level.toLevel("info"); + Level error = Level.toLevel(3); + Assert.assertTrue(error != null && info != null); + } + + @Test + public void loggerLevel(){ + Level level = Logger.getRootLogger().getLevel(); + Assert.assertTrue(level!=null); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java new file mode 100644 index 00000000..6a56c20f --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java @@ -0,0 +1,49 @@ +/* + * 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.rocketmq.logging.inner; + +import org.apache.rocketmq.logging.BasicLoggerTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Enumeration; + +public class LoggerRepositoryTest extends BasicLoggerTest { + + @Test + public void testLoggerRepository() { + Logger.getRepository().setLogLevel(Level.INFO); + + String file = loggingDir + "/repo.log"; + Logger fileLogger = Logger.getLogger("repoLogger"); + + Appender myappender = LoggingBuilder.newAppenderBuilder() + .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") + .withName("repoAppender") + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + fileLogger.addAppender(myappender); + Logger.getLogger("repoLogger").setLevel(Level.INFO); + Logger repoLogger = Logger.getRepository().exists("repoLogger"); + Assert.assertTrue(repoLogger != null); + Enumeration currentLoggers = Logger.getRepository().getCurrentLoggers(); + Level logLevel = Logger.getRepository().getLogLevel(); + Assert.assertTrue(logLevel.equals(Level.INFO)); +// Logger.getRepository().shutdown(); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java new file mode 100644 index 00000000..4e738e23 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java @@ -0,0 +1,115 @@ +/* + * 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.rocketmq.logging.inner; + +import org.apache.rocketmq.logging.BasicLoggerTest; +import org.apache.rocketmq.logging.InnerLoggerFactory; +import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class LoggerTest extends BasicLoggerTest { + + + @Before + public void init() { + InternalLoggerFactory.setCurrentLoggerType(InnerLoggerFactory.LOGGER_INNER); + } + + @Test + public void testInnerConsoleLogger() throws IOException { + PrintStream out = System.out; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + + Appender consoleAppender = LoggingBuilder.newAppenderBuilder() + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + Logger.getLogger("ConsoleLogger").addAppender(consoleAppender); + Logger.getLogger("ConsoleLogger").setLevel(Level.INFO); + + InternalLogger consoleLogger1 = InternalLoggerFactory.getLogger("ConsoleLogger"); + consoleLogger1.info("console info Message"); + consoleLogger1.error("console error Message", new RuntimeException()); + consoleLogger1.debug("console debug message"); + + consoleLogger1.info("console {} test", "simple"); + consoleLogger1.info("[WATERMARK] Send Queue Size: {} SlowTimeMills: {}", 1, 300); + consoleLogger1.info("new consumer connected, group: {} {} {} channel: {}", "mygroup", "orderly", + "broudcast", new RuntimeException("simple object")); + + System.setOut(out); + consoleAppender.close(); + + String result = new String(byteArrayOutputStream.toByteArray()); + + System.out.println(result); + + Assert.assertTrue(result.contains("info")); + Assert.assertTrue(result.contains("RuntimeException")); + Assert.assertTrue(result.contains("WATERMARK")); + Assert.assertTrue(result.contains("consumer")); + Assert.assertTrue(result.contains("broudcast")); + Assert.assertTrue(result.contains("simple test")); + Assert.assertTrue(!result.contains("debug")); + } + + @Test + public void testInnerFileLogger() throws IOException { + String file = loggingDir + "/inner.log"; + + Logger fileLogger = Logger.getLogger("innerLogger"); + + Appender myappender = LoggingBuilder.newAppenderBuilder() + .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") + .withName("innerAppender") + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + fileLogger.addAppender(myappender); + fileLogger.setLevel(Level.INFO); + + InternalLogger innerLogger = InternalLoggerFactory.getLogger("innerLogger"); + + innerLogger.info("fileLogger info Message"); + innerLogger.error("fileLogger error Message", new RuntimeException()); + innerLogger.debug("fileLogger debug message"); + + myappender.close(); + + String content = readFile(file); + + System.out.println(content); + + Assert.assertTrue(content.contains("info")); + Assert.assertTrue(content.contains("RuntimeException")); + Assert.assertTrue(!content.contains("debug")); + } + + @After + public void close() { + InternalLoggerFactory.setCurrentLoggerType(null); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java new file mode 100644 index 00000000..977e553b --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java @@ -0,0 +1,113 @@ +/* + * 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.rocketmq.logging.inner; + +import org.apache.rocketmq.logging.BasicLoggerTest; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FilenameFilter; +import java.io.PrintStream; + +public class LoggingBuilderTest extends BasicLoggerTest { + + @Test + public void testConsole() { + PrintStream out = System.out; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + + Appender consoleAppender = LoggingBuilder.newAppenderBuilder() + .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + consoleAppender.doAppend(loggingEvent); + String result = new String(byteArrayOutputStream.toByteArray()); + System.setOut(out); + + Assert.assertTrue(result.contains(loggingEvent.getMessage().toString())); + + } + + @Test + public void testFileAppender() throws InterruptedException { + String logFile = loggingDir + "/file.log"; + Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 102400) + .withFileAppender(logFile).withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + for (int i = 0; i < 10; i++) { + rollingFileAppender.doAppend(loggingEvent); + } + rollingFileAppender.close(); + + File file = new File(logFile); + Assert.assertTrue(file.length() > 0); + } + + @Test + public void testRollingFileAppender() throws InterruptedException { + + String rollingFile = loggingDir + "/rolling.log"; + Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) + .withRollingFileAppender(rollingFile, "1024", 5) + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + for (int i = 0; i < 100; i++) { + rollingFileAppender.doAppend(loggingEvent); + } + rollingFileAppender.close(); + + int cc = 0; + for (int i = 0; i < 5; i++) { + File file; + if (i == 0) { + file = new File(rollingFile); + } else { + file = new File(rollingFile + "." + i); + } + if (file.exists() && file.length() > 0) { + cc += 1; + } + } + Assert.assertTrue(cc >= 2); + } + + @Test + public void testDailyRollingFileAppender() throws InterruptedException { + String rollingFile = loggingDir + "/daily-rolling--222.log"; + Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) + .withDailyFileRollingAppender(rollingFile, "'.'yyyy-MM-dd_HH-mm-ss-SSS") + .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); + + for (int i = 0; i < 100; i++) { + rollingFileAppender.doAppend(loggingEvent); + } + + rollingFileAppender.close(); + + File file = new File(loggingDir); + String[] list = file.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith("daily-rolling--222.log"); + } + }); + Assert.assertTrue(list.length > 0); + } +} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java new file mode 100644 index 00000000..5fa80ad5 --- /dev/null +++ b/logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java @@ -0,0 +1,40 @@ +/* + * 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.rocketmq.logging.inner; + + +import org.apache.rocketmq.logging.InnerLoggerFactory; +import org.junit.Assert; +import org.junit.Test; + +public class MessageFormatterTest { + + @Test + public void formatTest(){ + InnerLoggerFactory.FormattingTuple logging = InnerLoggerFactory.MessageFormatter.format("this is {},and {}", "logging", 6546); + String message = logging.getMessage(); + Assert.assertTrue(message.contains("logging")); + + InnerLoggerFactory.FormattingTuple format = InnerLoggerFactory.MessageFormatter.format("cause exception {}", 143545, new RuntimeException()); + String message1 = format.getMessage(); + Throwable throwable = format.getThrowable(); + System.out.println(message1); + Assert.assertTrue(throwable != null); + } + +} diff --git a/logging/src/test/resources/logback_test.xml b/logging/src/test/resources/logback_test.xml new file mode 100644 index 00000000..c1ab2004 --- /dev/null +++ b/logging/src/test/resources/logback_test.xml @@ -0,0 +1,46 @@ + + + + + + ${loggingDir}/logback_test.log + true + + ${loggingDir}/logback_test.%i.log + + 1 + 5 + + + 10MB + + + %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n + UTF-8 + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 4326b466..4be64a6f 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,7 @@ test distribution openmessaging + logging @@ -528,6 +529,11 @@ rocketmq-example ${project.version} + + ${project.groupId} + rocketmq-logging + ${project.version} + org.slf4j slf4j-api -- GitLab