1、背景
雖然log4j很強大,可以將日志輸出到文件、DB、ES等。但是有時候確難免完全適合自己,此時我們就需要自定義Appender,使日志輸出到指定的位置上。
本文,將通過兩個例子說明自定義APPender,一個是將日志寫入文件中,另一個是將日志發送到遠程Thrift服務中。
本文代碼詳見:https://github.com/hawkingfoo/log-demo
2、自定義文件Appender
2.1 定義文件Appender
先上代碼:
-
-
public class FileAppender extends AbstractAppender {
-
private String fileName;
-
-
/* 構造函數 */
-
public FileAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, String fileName) {
-
super(name, filter, layout, ignoreExceptions);
-
this.fileName = fileName;
-
}
-
-
-
public void append(LogEvent event) {
-
final byte[] bytes = getLayout().toByteArray(event);
-
writerFile(bytes);
-
-
}
-
-
/* 接收配置文件中的參數 */
-
-
public static FileAppender createAppender(
-
-
-
-
-
if (name == null) {
-
LOGGER.error( "no name defined in conf.");
-
return null;
-
}
-
if (layout == null) {
-
layout = PatternLayout.createDefaultLayout();
-
}
-
// 創建文件
-
if (!createFile(fileName)) {
-
return null;
-
}
-
return new FileAppender(name, filter, layout, ignoreExceptions, fileName);
-
}
-
-
private static boolean createFile(String fileName) {
-
Path filePath = Paths. get(fileName);
-
try {
-
// 每次都重新寫文件,不追加
-
if (Files.exists(filePath)) {
-
Files.delete(filePath);
-
}
-
Files.createFile(filePath);
-
} catch (IOException e) {
-
LOGGER.error( "create file exception", e);
-
return false;
-
}
-
return true;
-
}
-
-
private void writerFile(byte[] log) {
-
try {
-
Files.write(Paths. get(fileName), log, StandardOpenOption.APPEND);
-
} catch (IOException e) {
-
LOGGER.error( "write file exception", e);
-
}
-
}
-
}
上面代碼有幾個需要注意的地方:
@Plugin..注解:這個注解,是為了在之后配置log4j2.xml時,指定的Appender Tag。- 構造函數:除了使用父類的以外,也可以增加一些自己的配置。
- 重寫
append()方法:這里面需要實現具體的邏輯,日志的去向。 createAppender()方法:主要是接收log4j2.xml中的配置項。
2.2 添加log4j2.xml配置
-
-
-
<configuration status="INFO" monitorInterval="30">
-
<appenders>
-
<!--這個輸出控制台的配置-->
-
<console name="Console" target="SYSTEM_OUT">
-
<!--輸出日志的格式-->
-
<PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/>
-
</console>
-
-
<!-- 這個就是自定義的Appender -->
-
<FileAppender name="File" fileName="log.log">
-
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
-
</FileAppender>
-
-
</appenders>
-
-
<loggers>
-
<!--過濾掉spring和mybatis的一些無用的DEBUG信息-->
-
<logger name="org.springframework" level="INFO"></logger>
-
<logger name="org.mybatis" level="INFO"></logger>
-
<root level="all">
-
<appender-ref ref="Console"/>
-
<appender-ref ref="File"/>
-
</root>
-
</loggers>
-
</configuration>
備注:
- 上面的log配置,一共配了2個輸出。一個是終端輸出,一個是采用自定義的FileAppender輸出到文件中。
<FileAppender>標簽要與自定義Appender中的類注解保持一致。
2.3 測試
-
public class TestLogFile {
-
private static final Logger logger = LogManager.getLogger(TestLogFile.class);
-
-
public static void main(String[] args) {
-
logger.info( "1");
-
logger.info( "2");
-
logger.info( "3");
-
}
-
}
日志輸出
日志輸出
可以看到,日志一共輸出了2份,一份到終端中,一份到log.log中(具體的文件路徑可在log4j2.xml中配置)。
3、自定義Thrift Appender
上一節,主要是日志的文件輸出。有時我們需要將日志發送給日志收集服務,常見的方法可以寫一個日志收集Agent,收集日志;或者將日志輸出方當成客戶端直接發送到遠程。
下文,通過自定義Appender的方式,將日志輸出到遠程的RPC服務中。
3.1 Thrift RPC服務
假設現在有一個Thrift RPC服務,實時接收日志消息。它的定義是下面的樣子:
-
namespace java thrift
-
-
service LogServer {
-
string getLogRes(1:string log);
-
}
服務很簡單,入參是log,返回值是String。
Thrift相關知識可以查看,Thrift RPC服務10分鍾上手。
3.2 定義ThriftAppender
-
-
public class ThriftAppender extends AbstractAppender {
-
-
private LogServer.Client client;
-
private TTransport transport;
-
-
/* 構造函數 */
-
public ThriftAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, String host) {
-
super(name, filter, layout, ignoreExceptions);
-
// 創建客戶端
-
createThriftClient(host);
-
}
-
-
-
public void append(LogEvent event) {
-
final byte[] bytes = getLayout().toByteArray(event);
-
try {
-
String response = client.getLogRes( new String(bytes));
-
System.out.println(response);
-
} catch (TException e) {
-
e.printStackTrace();
-
}
-
}
-
-
/* 接收配置文件中的參數 */
-
-
public static ThriftAppender createAppender(@PluginAttribute("name") String name,
-
@PluginAttribute("host") String host,
-
@PluginElement("Filter") final Filter filter,
-
@PluginElement("Layout") Layout<? extends Serializable> layout,
-
@PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
-
if (name == null) {
-
LOGGER.error( "no name defined in conf.");
-
return null;
-
}
-
if (layout == null) {
-
layout = PatternLayout.createDefaultLayout();
-
}
-
return new ThriftAppender(name, filter, layout, ignoreExceptions, host);
-
}
-
-
-
public void stop() {
-
if (transport != null) {
-
transport.close();
-
}
-
}
-
-
private void createThriftClient(String host) {
-
try {
-
transport = new TFramedTransport(new TSocket(host, 9000));
-
transport.open();
-
TProtocol protocol = new TBinaryProtocol(transport);
-
client = new LogServer.Client(protocol);
-
LOGGER.info( "create client success");
-
} catch (Exception e) {
-
LOGGER.error( "create file exception", e);
-
}
-
}
-
}
備注:
除了和文件Appender相同的外,這里需要注意兩個地方。一個是Thrift Client的創建,另一個Thrift發送log。
具體的發送邏輯,在append()方法中實現。
3.3 添加log4j2.xml配置
-
-
-
<configuration status="INFO" monitorInterval="30">
-
<appenders>
-
<!--這個輸出控制台的配置-->
-
<console name="Console" target="SYSTEM_OUT">
-
<!--輸出日志的格式-->
-
<PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/>
-
</console>
-
-
<!-- 這個就是自定義的Appender -->
-
<ThriftAppender name="Thrift" host="127.0.0.1">
-
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
-
</ThriftAppender>
-
</appenders>
-
-
<loggers>
-
<!--過濾掉spring和mybatis的一些無用的DEBUG信息-->
-
<logger name="org.springframework" level="INFO"></logger>
-
<logger name="org.mybatis" level="INFO"></logger>
-
<root level="all">
-
<appender-ref ref="Console"/>
-
<appender-ref ref="Thrift"/>
-
-
</root>
-
</loggers>
-
</configuration>
這里同樣是定義了兩個輸出路徑,一個是終端,一個是Thrift服務。
3.4 測試
-
public class TestThriftFile {
-
private static final Logger logger = LogManager.getLogger(TestThriftFile.class);
-
-
public static void main(String[] args) {
-
logger.info( "a");
-
logger.info( "b");
-
logger.info( "c");
-
}
-
}
Server端
Client端
可以看出,Server端成功接收到了log。
