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
This commit is contained in:
parent
7997cf65fc
commit
8661d9abc1
|
|
@ -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);
|
// Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET
|
||||||
handler.setFormatter(formatter);
|
switch (logOutputType) {
|
||||||
handler.setEncoding(LOG_CHARSET);
|
case LOG_OUTPUT_TYPE_FILE:
|
||||||
} catch (IOException e) {
|
String fileName = LogBase.getLogBaseDir() + logName;
|
||||||
e.printStackTrace();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue