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