1. Background
While log4j is powerful, it can output logs to files, DB, ES, etc. But sometimes it is inevitable that it is completely suitable for us. At this time, we need to customize the Appender to output the log to the specified location.
In this article, two examples will be used to illustrate custom APPender, one is to write logs to a file, and the other is to send logs to a remote Thrift service.
See the code of this article for details: https://github.com/hawkingfoo/log-demo
2. Custom file Appender
2.1 Definition file Appender
Code first:
@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);
}
}
}
There are a few things to note in the above code:
@Plugin..
Annotation: This annotation is for thelog4j2.xml
specified Appender Tag when configuring later.- Constructor: In addition to using the parent class, you can also add some of your own configuration.
- Overriding
append()
method: It is necessary to implement specific logic and log whereabouts. createAppender()
Method: Mainly to receive the configuration items in log4j2.xml.
2.2 Add log4j2.xml configuration
<?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>
Remark:
- The above log configuration has a total of 2 outputs. One is terminal output, and the other is output to a file using a custom FileAppender.
<FileAppender>
The label should be consistent with the class annotation in the custom Appender.
2.3 Testing
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");
}
}
log output
log output
It can be seen that a total of 2 logs are output, one to the terminal and one to the log.log
middle (the specific file path can be configured in log4j2.xml).
3. Custom Thrift Appender
In the previous section, it is mainly the file output of the log. Sometimes we need to send logs to the log collection service. Common methods can be to write a log collection agent to collect logs; or use the log output party as a client to send it directly to the remote.
Below, the log is output to the remote RPC service by customizing the Appender.
3.1 Thrift RPC service
Suppose there is now a Thrift RPC service that receives log messages in real time. Its definition is as follows:
namespace java thrift
service LogServer {
string getLogRes(1:string log);
}
The service is very simple, the input parameter is log, and the return value is String.
Thrift related knowledge can be viewed, and the Thrift RPC service can be started in 10 minutes .
3.2 Define 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);
}
}
}
Note:
In addition to the same as the file Appender, there are two places to pay attention to here. One is the creation of Thrift Client, and the other Thrift sends log.
The specific sending logic append()
is implemented in the method.
3.3 Add log4j2.xml configuration
<?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>
Two output paths are also defined here, one is the terminal and the other is the Thrift service.
3.4 Testing
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 side
Client
It can be seen that the server side successfully received the log.
Author: Tang Yingruofan Source: Jianshu