随着系统微服务化的开展,越来越多的服务被建立起来,而且服务都布署到公司私有云平台上。这个进程中,日志查看的问题逐渐暴露出来。
首先,分布式系统,程序部署在不同的机器上,查询日志需要登录到各台机器上,很不方便;其次,云服务使用 Docker 虚拟技术,当服务重新部署时,机器上的文件会全部抹除,因此日志文件也会因为服务重启或升级导致丢失。
所以,目前我们团队非常需要一个分布式日志的解决方案。查了一些资料,ELK 是一个比较靠谱的解决方案。但是通过收集机器上的日志文件的方式需要在机器上安装软件,不是很方便。因此最终选择使用日志框架提供的自定义 appender 功能,通过 kafka 上报日志的方式来实现。
然而,我们团队的项目中,各种日志框架可谓百花齐放。老项目使用 log4j,SpringBoot 项目默认是使用 logback,有些项目则采用 log4j2,虽然都有使用 slf4j 来避免直接使用具体日志实现,但是 slf4j 并没有提供 appender 的标准接口,而且这几种日志框架并存的现状难以改变。因此需要兼容这几种日志框架。
经过一番摸索终于搞定了。网上有很多的博文也介绍了怎么使用,但是首先,这些文章都没有提供完整的实例,看代码片段调试起来总会遇到这样那样的问题;其次,都没有说明在配置 appender 时如何配置自定义参数。所以,在解决了这些问题之后,我将日志上报解决方案中的自定义 appender 这部分总结并抽取出来,希望可以帮助到大家。
代码实例:https://github.com/dadiyang/appender
这里解释一下,各个自定义 appender 是如何实现的。
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
package com.github.dadiyang.appender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.AppenderBase;
/** * 自定义实现logback的输出源 * @author dadiyang * @since 2019/4/30 */
public class LogbackAppender extends AppenderBase<ILoggingEvent> {
private String appName;
@Override
protected void append(ILoggingEvent event) {
String level = event.getLevel().toString();
String loggerName = event.getLoggerName();
String msg = event.getFormattedMessage();
String threadName = event.getThreadName();
Throwable throwable = event.getThrowableProxy() != null ? ((ThrowableProxy) event.getThrowableProxy()).getThrowable() : null;
// todo 这里实现自定义的日志处理逻辑
}
/** * 定义 setter 方法,这样配置项会被注入到这个 appender 中 */
public void setAppName(String appName) {
this.appName = appName;
}
}
<configuration scan="true" debug="false">
<appender name="CustomAppender" class="com.github.dadiyang.appender.LogbackAppender">
<appName>test_logback_appender_appappName>
appender>
<root level="INFO">
<appender-ref ref="CustomAppender"/>
root>
configuration>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>2.6.2version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>1.7.21version>
dependency>
package com.github.dadiyang.appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.io.Serializable;
/** * 自定义实现log4j2的输出源 * @author dadiyang * @since 2019/4/30 */
@Plugin(name = "Log4j2Appender", category = "Core", elementType = "appender", printObject = true)
public final class Log4j2Appender extends AbstractAppender {
private String appName;
protected Log4j2Appender(String name, String appName, Filter filter, Layout<? extends Serializable> layout,
final boolean ignoreExceptions) {
super(name, filter, layout, ignoreExceptions);
this.appName = appName;
}
@Override
public void append(LogEvent event) {
// 此处自定义实现输出
String level = event.getLevel().toString();
String loggerName = event.getLoggerName();
String msg = event.getMessage().getFormattedMessage();
String threadName = event.getThreadName();
Throwable throwable = event.getThrown();
// todo 这里实现自定义的日志处理逻辑
}
/** * log4j2 使用 appender 插件工厂,因此传参可以直接通过 PluginAttribute 注解注入 */
@PluginFactory
public static Log4j2Appender createAppender(
@PluginAttribute("name") String name,
@PluginAttribute("appName") String appName,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginElement("Filter") final Filter filter) {
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new Log4j2Appender(name, appName, filter, layout, true);
}
}
<configuration status="WARN" packages="com.github.dadiyang.appender">
<appenders>
<Log4j2Appender name="CustomAppender" appName="test_logback_appender_app"/>
appenders>
<loggers>
<root level="INFO">
<appender-ref ref="CustomAppender"/>
root>
loggers>
configuration>
这里配置的 package 需要添加我们的 appender 所在的包,否则可能会出现
Error processing element XXX ([Appenders: null]): CLASS_NOT_FOUND
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.2version>
dependency>
package com.github.dadiyang.appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
/** * @author dadiyang * @since 2019/4/30 */
public class Log4jAppender extends AppenderSkeleton {
private String appName;
@Override
protected void append(LoggingEvent event) {
String level = event.getLevel().toString();
String loggerName = event.getLoggerName();
String msg = event.getRenderedMessage();
String threadName = event.getThreadName();
Throwable throwable = event.getThrowableInformation() != null ? event.getThrowableInformation().getThrowable() : null;
// todo 这里实现自定义的日志处理逻辑
}
/** * 定义 setter 方法,这样在配置文件中添加类似 log4j.appender.CustomAppender.appName=test_app_name 的配置项时,配置会被注入到这个 appender 中 */
public void setAppName(String appName) {
this.appName = appName;
}
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return false;
}
}
# 全局使用
log4j.rootLogger=INFO,CustomAppender
# 注册 Log4jAppender
log4j.appender.CustomAppender=com.github.dadiyang.appender.Log4jAppender
# 这里填写集群名称
log4j.appender.CustomAppender.appName=test_logback_appender_app
完整实例请查看:https://github.com/dadiyang/appender