log4j2自定義Appender(輸出到檔案/RPC服務中)
1、背景
雖然log4j很強大,可以將日誌輸出到檔案、DB、ES等。但是有時候確難免完全適合自己,此時我們就需要自定義Appender,使日誌輸出到指定的位置上。
本文,將通過兩個例子說明自定義APPender,一個是將日誌寫入檔案中,另一個是將日誌傳送到遠端Thrift服務中。
2、自定義檔案Appender
2.1 定義檔案Appender
先上程式碼:
@Plugin(name = "FileAppender", category = "Core", elementType = "appender", printObject = true)
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;
}
@Override
public void append(LogEvent event) {
final byte[] bytes = getLayout().toByteArray(event);
writerFile(bytes);
}
/* 接收配置檔案中的引數 */
@PluginFactory
public static FileAppender createAppender(@PluginAttribute("name") String name,
@PluginAttribute ("fileName") String fileName,
@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();
}
// 建立檔案
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配置
<?xml version="1.0" encoding="UTF-8"?>
<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
@Plugin(name = "ThriftAppender", category = "Core", elementType = "appender", printObject = true)
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);
}
@Override
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();
}
}
/* 接收配置檔案中的引數 */
@PluginFactory
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);
}
@Override
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配置
<?xml version="1.0" encoding="UTF-8"?>
<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端成功接收到了log。