From 8661d9abc12fdb13940f98751a9beb99c039b827 Mon Sep 17 00:00:00 2001 From: cdfive <31885791+cdfive@users.noreply.github.com> Date: Wed, 19 Jun 2019 09:46:13 +0800 Subject: [PATCH] Add support for logging into console for common logs (#836) * Add a ConsoleHandler to support logging into stdout and stderr. * Add a `csp.sentinel.log.output.type` property to configure for output type of record logs (only a temporary design) * Add millisecond to the format of CspFormatter --- .../csp/sentinel/log/ConsoleHandler.java | 85 +++++++++++++++++++ .../csp/sentinel/log/CspFormatter.java | 3 +- .../com/alibaba/csp/sentinel/log/LogBase.java | 60 ++++++++++--- .../csp/sentinel/log/ConsoleHandlerTest.java | 81 ++++++++++++++++++ 4 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java create mode 100644 sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java new file mode 100644 index 00000000..7654fa2a --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.log; + +import java.io.UnsupportedEncodingException; +import java.util.logging.*; + +/** + * This Handler publishes log records to console by using {@link java.util.logging.StreamHandler}. + * + * Print log of WARNING level or above to System.err, + * and print log of INFO level or below to System.out. + * + * To use this handler, add the following VM argument: + *
+ * -Dcsp.sentinel.log.output.type=console
+ * 
+ * + * @author cdfive + */ +class ConsoleHandler extends Handler { + + /** + * A Handler which publishes log records to System.out. + */ + private StreamHandler stdoutHandler; + + /** + * A Handler which publishes log records to System.err. + */ + private StreamHandler stderrHandler; + + public ConsoleHandler() { + this.stdoutHandler = new StreamHandler(System.out, new CspFormatter()); + this.stderrHandler = new StreamHandler(System.err, new CspFormatter()); + } + + @Override + public synchronized void setFormatter(Formatter newFormatter) throws SecurityException { + this.stdoutHandler.setFormatter(newFormatter); + this.stderrHandler.setFormatter(newFormatter); + } + + @Override + public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException { + this.stdoutHandler.setEncoding(encoding); + this.stderrHandler.setEncoding(encoding); + } + + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= Level.WARNING.intValue()) { + stderrHandler.publish(record); + stderrHandler.flush(); + } else { + stdoutHandler.publish(record); + stdoutHandler.flush(); + } + } + + @Override + public void flush() { + stdoutHandler.flush(); + stderrHandler.flush(); + } + + @Override + public void close() throws SecurityException { + stdoutHandler.close(); + stderrHandler.close(); + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java index b2a88979..5d24c212 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java @@ -28,7 +28,7 @@ class CspFormatter extends Formatter { private final ThreadLocal dateFormatThreadLocal = new ThreadLocal() { @Override public SimpleDateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); } }; @@ -37,6 +37,7 @@ class CspFormatter extends Formatter { final DateFormat df = dateFormatThreadLocal.get(); StringBuilder builder = new StringBuilder(1000); builder.append(df.format(new Date(record.getMillis()))).append(" "); + builder.append(record.getLevel().getName()).append(" "); builder.append(formatMessage(record)); String throwable = ""; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java index ae71b1a4..9d8b990d 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java @@ -22,6 +22,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.alibaba.csp.sentinel.util.PidUtil; +import com.alibaba.csp.sentinel.util.StringUtil; /** * Default log base dir is ${user.home}/logs/csp/, we can use {@link #LOG_DIR} System property to override it. @@ -35,15 +36,22 @@ public class LogBase { public static final String LOG_CHARSET = "utf-8"; + // Output biz log(RecordLog,CommandCenterLog) to file + public static final String LOG_OUTPUT_TYPE_FILE = "file"; + // Output biz log(RecordLog,CommandCenterLog) to console + public static final String LOG_OUTPUT_TYPE_CONSOLE = "console"; + private static final String DIR_NAME = "logs" + File.separator + "csp"; private static final String USER_HOME = "user.home"; + // Output type of biz log(RecordLog,CommandCenterLog) + public static final String LOG_OUTPUT_TYPE = "csp.sentinel.log.output.type"; public static final String LOG_DIR = "csp.sentinel.log.dir"; public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid"; - private static boolean logNameUsePid = false; - + private static String logOutputType; private static String logBaseDir; + private static boolean logNameUsePid = false; static { try { @@ -55,6 +63,15 @@ public class LogBase { } private static void init() { + logOutputType = System.getProperty(LOG_OUTPUT_TYPE); + + // By default, output biz log(RecordLog,CommandCenterLog) to file + if (StringUtil.isBlank(logOutputType)) { + logOutputType = LOG_OUTPUT_TYPE_FILE; + } else if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) { + logOutputType = LOG_OUTPUT_TYPE_FILE; + } + // first use -D, then use user home. String logDir = System.getProperty(LOG_DIR); @@ -125,18 +142,37 @@ public class LogBase { protected static Handler makeLogger(String logName, Logger heliumRecordLog) { CspFormatter formatter = new CspFormatter(); - String fileName = LogBase.getLogBaseDir() + logName; - if (isLogNameUsePid()) { - fileName += ".pid" + PidUtil.getPid(); - } + Handler handler = null; - try { - handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); - handler.setFormatter(formatter); - handler.setEncoding(LOG_CHARSET); - } catch (IOException e) { - e.printStackTrace(); + + // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET + switch (logOutputType) { + case LOG_OUTPUT_TYPE_FILE: + String fileName = LogBase.getLogBaseDir() + logName; + if (isLogNameUsePid()) { + fileName += ".pid" + PidUtil.getPid(); + } + try { + handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); + handler.setFormatter(formatter); + handler.setEncoding(LOG_CHARSET); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case LOG_OUTPUT_TYPE_CONSOLE: + try { + handler = new ConsoleHandler(); + handler.setFormatter(formatter); + handler.setEncoding(LOG_CHARSET); + } catch (IOException e) { + e.printStackTrace(); + } + break; + default: + break; } + if (handler != null) { LoggerUtils.disableOtherHandlers(heliumRecordLog, handler); } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java new file mode 100644 index 00000000..57413603 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.log; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link ConsoleHandler}. + * + * @author cdfive + */ +public class ConsoleHandlerTest { + + @Test + public void testPublish() { + ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baosOut)); + + ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); + System.setErr(new PrintStream(baosErr)); + + CspFormatter cspFormatter = new CspFormatter(); + ConsoleHandler consoleHandler = new ConsoleHandler(); + + LogRecord logRecord; + + // Test INFO level, should log to stdout + logRecord = new LogRecord(Level.INFO, "test info message"); + consoleHandler.publish(logRecord); + assertEquals(cspFormatter.format(logRecord), baosOut.toString()); + assertEquals("", baosErr.toString()); + baosOut.reset(); + baosErr.reset(); + + // Test INFO level, should log to stderr + logRecord = new LogRecord(Level.WARNING, "test warning message"); + consoleHandler.publish(logRecord); + assertEquals(cspFormatter.format(logRecord), baosErr.toString()); + assertEquals("", baosOut.toString()); + baosOut.reset(); + baosErr.reset(); + + // Test FINE level, no log by default + // Default log level is INFO, to log FINE message to stdout, add following config in $JAVA_HOME/jre/lib/logging.properties + // java.util.logging.StreamHandler.level=FINE + logRecord = new LogRecord(Level.FINE, "test fine message"); + consoleHandler.publish(logRecord); + assertEquals("", baosOut.toString()); + assertEquals("", baosErr.toString()); + baosOut.reset(); + baosErr.reset(); + + // Test SEVERE level, should log to stderr + logRecord = new LogRecord(Level.SEVERE, "test severe message"); + consoleHandler.publish(logRecord); + assertEquals(cspFormatter.format(logRecord), baosErr.toString()); + assertEquals("", baosOut.toString()); + baosOut.reset(); + baosErr.reset(); + } +}