* 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>() { | |||
@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 = ""; | |||
@@ -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); | |||
} | |||
@@ -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(); | |||
} | |||
} |