* 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 CspFormattermaster
@@ -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: | |||||
* <pre> | |||||
* -Dcsp.sentinel.log.output.type=console | |||||
* </pre> | |||||
* | |||||
* @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(); | |||||
} | |||||
} |
@@ -28,7 +28,7 @@ class CspFormatter extends Formatter { | |||||
private final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { | private final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { | ||||
@Override | @Override | ||||
public SimpleDateFormat initialValue() { | 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(); | final DateFormat df = dateFormatThreadLocal.get(); | ||||
StringBuilder builder = new StringBuilder(1000); | StringBuilder builder = new StringBuilder(1000); | ||||
builder.append(df.format(new Date(record.getMillis()))).append(" "); | builder.append(df.format(new Date(record.getMillis()))).append(" "); | ||||
builder.append(record.getLevel().getName()).append(" "); | |||||
builder.append(formatMessage(record)); | builder.append(formatMessage(record)); | ||||
String throwable = ""; | String throwable = ""; | ||||
@@ -22,6 +22,7 @@ import java.util.logging.Level; | |||||
import java.util.logging.Logger; | import java.util.logging.Logger; | ||||
import com.alibaba.csp.sentinel.util.PidUtil; | 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. | * 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"; | 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 DIR_NAME = "logs" + File.separator + "csp"; | ||||
private static final String USER_HOME = "user.home"; | 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_DIR = "csp.sentinel.log.dir"; | ||||
public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid"; | 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 String logBaseDir; | ||||
private static boolean logNameUsePid = false; | |||||
static { | static { | ||||
try { | try { | ||||
@@ -55,6 +63,15 @@ public class LogBase { | |||||
} | } | ||||
private static void init() { | 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. | // first use -D, then use user home. | ||||
String logDir = System.getProperty(LOG_DIR); | String logDir = System.getProperty(LOG_DIR); | ||||
@@ -125,18 +142,37 @@ public class LogBase { | |||||
protected static Handler makeLogger(String logName, Logger heliumRecordLog) { | protected static Handler makeLogger(String logName, Logger heliumRecordLog) { | ||||
CspFormatter formatter = new CspFormatter(); | CspFormatter formatter = new CspFormatter(); | ||||
String fileName = LogBase.getLogBaseDir() + logName; | |||||
if (isLogNameUsePid()) { | |||||
fileName += ".pid" + PidUtil.getPid(); | |||||
} | |||||
Handler handler = null; | 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) { | if (handler != null) { | ||||
LoggerUtils.disableOtherHandlers(heliumRecordLog, handler); | LoggerUtils.disableOtherHandlers(heliumRecordLog, handler); | ||||
} | } | ||||
@@ -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(); | |||||
} | |||||
} |