自定义log4j的appender写es日志
本篇和大家分享的是自定义log4j的appender,用es来记录日志并且通过kibana浏览es记录;就目前互联网或者一些中大型公司通常会用到第三方组合elk,其主要用写数据到es中,然后通过可视化工具kibana来做直观数据查看和统计;本篇内容节点如下:
- docker快速搭建es,es header,kibana 环境
- 封装写es工具类
- 自定义log4j的appender
- kibana基础使用
docker快速搭建es,kibana,es header 环境
对于爱研究第三方服务的程序员来说docker是很好的助手,能够快速搭建一套简易的使用环境;docker启动es镜像具体不多说了看这里docker快速搭建几个常用的第三方服务,值得注意的是这里我定义了es的集群名称,通过如下命令进入容器中改了配置文件(当然可直接通过命令启动时传递参数):
docker exec -it eae7731bb6a1 /bin/bash
然后进入到 /usr/share/elasticsearch/config 并打开elasticsearch.yml配置文件修改:
#集群名称
cluster.name: "shenniu_elasticsearch"
#本节点名称
node.name: master
#是否master节点
node.master: true
#是否存储数据
node.data: true
#head插件设置
http.cors.enabled: true
http.cors.allow-origin: "*"
http.port:
transport.tcp.port:
#可以访问的ip
network.bind_host: 0.0.0.0
这里定义集群名为:shenniu_elasticsearch
如上启动了es后,我们为了直观的看到es中信息,这里用到了es header工具(当然不必须);只要docker启动其镜像后,我们能够在上面输入咋们的es地址,以此来检测es集群是否开启并浏览相关索引信息,es header默认端口9100:

通常搭配es的是kibana(可视化工具),用来查看es的数据和做一些统计(如数量统计,按列聚合统计等),这里通过docker run启动kibana镜像后,我们还需要让其关联上es才行,同样通过docker exec去修改里面配置信息,主要在里面配置es地址:
docker exec -it 67a0ef871ef7 /bin/bash
cd etc/
cd kibana/
vim kibana.yml
配置内容修改如:
server.host: '0.0.0.0'
elasticsearch.url: 'http://192.168.181.7:9200' #es地址
如上操作完后,打开kibana地址 http://192.168.181.7:5601/app/kibana ,能够看到让咋们配置es索引查询规则的界面,如果es地址down掉或者配置不对,kibana会停留在red界面,让我们正确配置:

封装写es工具类
java往es中写数据,可以使用官网推荐的 org.elasticsearch.client 包(注意版本问题),我这里es是5.6版本对应的rest-high-leve-client最好也引入5.6版本的,如下pom信息:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>5.6.</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.</version>
<scope>compile</scope>
</dependency>
首先要明确用代码操作es(或其他第三方服务),往往都需ip(域名)+端口,这里我的配置信息:
#es连接串 ','分割
es.links=http://192.168.181.7:9200,http://localhost:9200
es.indexName=eslog_shenniu003
然后有如下封装代码:
public class EsRestHighLevelClient {
/**
* new HttpHost("192.168.181.44", 9200, "http")
*/
private HttpHost[] hosts;
private String index;
private String type;
private String id;
public EsRestHighLevelClient(String index, String type, String id, HttpHost[] hosts) {
this.hosts = hosts;
this.index = index;
this.type = type;
this.id = id;
}
/**
* @param index
* @param type
* @param hosts
*/
public EsRestHighLevelClient(String index, String type, String... hosts) {
this.hosts = IpHelper.getHostArrByStr(hosts);
this.index = index;
this.type = type;
}
public RestHighLevelClient client() {
Assert.requireNonEmpty(this.hosts, "无效的es连接");
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(this.hosts).build()
);
return client;
}
public IndexRequest indexRequest() {
return new IndexRequest(this.index, this.type, this.id);
}
public RestStatus createIndex(Map<String, Object> map) throws IOException {
return client().
index(this.indexRequest().source(map)).
status();
}
}
这里还涉及到了一个IpHelper辅助类,主要用来拆分多个ip信息参数,里面涉及到正则匹配方式:
public class IpHelper {
private static final String strHosts = "(?<h>[^:]+)://(?<ip>[^:]+):(?<port>[^/|,]+)";
private static final Pattern hostPattern = Pattern.compile(strHosts);
public static Optional<String> getHostIp() {
try {
return Optional.ofNullable(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
return Optional.empty();
}
public static Optional<String> getHostName() {
try {
return Optional.ofNullable(InetAddress.getLocalHost().getHostName());
} catch (UnknownHostException e) {
e.printStackTrace();
}
return Optional.empty();
}
/**
* strHosts:"http://192.168.0.1:9200","http://192.168.0.1:9200","http://192.168.0.1:9200"
*
* @return
*/
public static List<HttpHost> getHostsByStr(String... strHosts) {
List<HttpHost> hosts = new ArrayList<>();
for (int i = ; i < strHosts.length; i++) {
String[] hostArr = strHosts[i].split(",");
for (String strHost : hostArr) {
Matcher matcher = hostPattern.matcher(strHost);
if (matcher.find()) {
String http = matcher.group("h");
String ip = matcher.group("ip");
String port = matcher.group("port");
if (Strings.isEmpty(http) || Strings.isEmpty(ip) || Strings.isEmpty(port)) {
continue;
}
hosts.add(new HttpHost(ip, Integer.valueOf(port), http));
}
}
}
return hosts;
}
public static HttpHost[] getHostArrByStr(String... strHosts) {
List<HttpHost> list = getHostsByStr(strHosts);
return Arrays.copyOf(list.toArray(), list.size(), HttpHost[].class);
}
}
自定义log4j的appender
对于日志来说log4j是大众化的,有很多语言也在用这种方式来记录,使用它相当于一种共识;它提供了很好的扩展,很方便达到把日志记录到数据库,文本获取其他自定义代码方式中;定义一个EsAppend类,继承AppenderSkeleton类,代码上我们要做的仅仅重写如下方法即可:

本期咋们实现的步骤是:
- activateOptions方法获取自定义配置信息(es连接串,写es的日志索引名等)
- append方法获取并记录logger.xx()等级的日志
- ExecutorService线程池类操作多个线程执行execute提交日志到es
具体实现代码如下,可按照上面步骤分析:
public class EsAppend extends AppenderSkeleton {
//es客户端
private static EsRestHighLevelClient esClient;
//es配置文件名
private String confName;
private ExecutorService executorService = Executors.newFixedThreadPool();
protected void append(LoggingEvent loggingEvent) {
if (this.isAsSevereAsThreshold(loggingEvent.getLevel())) {
executorService.execute(new EsAppendTask(loggingEvent, this.layout));
// new EsAppendTask(loggingEvent, this.layout).run();
}
}
public void close() {
this.closed = true;
}
public boolean requiresLayout() {
return false;
}
@Override
public void activateOptions() {
super.activateOptions();
try {
System.out.println("初始化 - EsAppend...");
if (this.getConfName() == null || this.getConfName().isEmpty()) {
this.setConfName("eslog.properties");
}
PropertiesHelper propertiesHelper = new PropertiesHelper(this.getConfName());
//es hosts
String strHosts = propertiesHelper.getProperty("es.links", "http://127.0.0.1:9200");
//es日志索引
String esLogIndex = propertiesHelper.getProperty("es.indexName", "eslog");
esClient = new EsRestHighLevelClient(esLogIndex, "docs", strHosts);
System.out.println("初始化完成 - EsAppend");
} catch (Exception ex) {
System.out.println("初始化失败- EsAppend");
ex.printStackTrace();
}
}
public String getConfName() {
return confName;
}
public void setConfName(String confName) {
this.confName = confName;
}
/**
* runable写es
*/
class EsAppendTask implements Runnable {
private HashMap<String, Object> map;
public EsAppendTask(LoggingEvent loggingEvent, Layout layout) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSSZ");
map = new HashMap<String, Object>() {
{
put("timeStamp",df.format(new Date()));
put("serverIp", IpHelper.getHostIp().get());
put("hostname", IpHelper.getHostName().get());
put("level", loggingEvent.getLevel().toString());
put("className", loggingEvent.getLocationInformation().getClassName());
put("methodName", loggingEvent.getLocationInformation().getMethodName());
put("data", loggingEvent.getMessage());
if (loggingEvent.getThrowableInformation() != null && !CollectionUtils.isEmpty(loggingEvent.getThrowableInformation().getThrowableStrRep())) {
put("exception", String.join(";", loggingEvent.getThrowableInformation().getThrowableStrRep()));
} else {
put("exception", "");
}
}
};
}
@Override
public void run() {
try {
EsAppend.esClient.createIndex(map);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上代码有一些自定义属性如confName,这个对应log4j.properties文件中自定义的confName属性,也就是说代码中confName和配置文件中的节点对应,可以直接get获取值;如下log4j配置信息:
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG,esAppend
# A1 is set to be a ConsoleAppender.
log4j.appender.esAppend=log.EsAppend
#自定义es配置文件
log4j.appender.esAppend.confName=eslog.properties # A1 uses PatternLayout.
#log4j.appender.esAppend.layout=org.apache.log4j.PatternLayout
#log4j.appender.esAppend.layout
上面PatternLayout配置是注释的,因为对于我写es来说没啥用处,不做格式化处理所以可以直接忽略;
- log4j.rootLogger:log4根节点配置,根节点配置debug其他子节点不重新定义的话使用继承模式;esAppend是随意定义append名称
- log4j.appender.esAppend:这里的esAppend对应rootLogger节点上随意定义的名称;log.EsAppend是只对应append的代码实现类
- log4j.appender.esAppend.confName:自定义es配置节点,代码中get获取即可(注意:activateOptions方法)
下面列出扩展append时需要注意的地方:
- 如果log4j.properties文件中有自定义属性,那么activateOptions方法是必须的,不然通过属性get是获取不了log4j.properties文件中自定义属性的值
- 因为是使用线程池来操作写es,所以顺序方面不能保证,因此最好插入时间列
- 对应用程序而言,es没法主动区分请求处理服务器是哪台,所以需要插入日志时最好带上服务器ip或者唯一标识
- 时间格式:yyyy-MM-dd'T'HH:mm:ss.SSSZ ,目前kibana搜索默认支持的时间格式
kibana基础使用
有了上面步骤后,我们来到测试环节,建一个测试接口,并且请求插入一些数据:
static Logger logger = Logger.getLogger(TestController.class);
@GetMapping("/hello/{nickname}")
public String getHello(@PathVariable String nickname) {
String str = String.format("你好,%s", nickname);
logger.debug(str);
logger.info(str);
logger.error(str);
return str;
}
当我们请求接口 http://localhost:4020/hello/神牛003 一次后,通过es header查看内容如下:

这种方式不怎么直观,可以通过kibana来查看,如下先配置kibana使用的索引:

最后通过Discover界面搜索相关日志信息:


自定义log4j的appender写es日志的更多相关文章
- Log4j/Log4j2自定义Appender来实现日志级别计数统计及监控
一.简述 本文主要讲如何基于Log4j2来实现自定义的Appender.一般用途是用于Log4j2自带的Appender不足以满足我们的需求,或者需要我们对日志进行拦截统计等操作时,需要我们自定义Ap ...
- 自定义log4j日志级别
转载自: http://blog.csdn.net/seven_cm/article/details/26849821 自定义log4j日志级别 参考了网上资料:http://www.360doc. ...
- log4j.xml配置,包含自定义log4j日志级别及输出日志到不同文件
一.配置 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configura ...
- How To Write In Sharepoint Log File 怎么对自定义的MOSS代码写日志
How To Write In Sharepoint Log File 怎么对自定义的MOSS代码写日志 Add Microsoft.Office.Server dll in your project ...
- Log4J是Apache组织的开源一个开源项目,通过Log4J,可以指定日志信息输出的目的地,如console、file等。Log4J采用日志级别机制,请按照输出级别由低到高的顺序写出日志输出级别。
Log4J是Apache组织的开源一个开源项目,通过Log4J,可以指定日志信息输出的目的地,如console.file等.Log4J采用日志级别机制,请按照输出级别由低到高的顺序写出日志输出级别. ...
- 自己写的日志框架--linkinLog4j--框架可配置+提性能
OK,上一篇博客我们已经实现了日志框架的基本的功能,但是还有一个最大的问题就是日志输出地不能重定向,然后一些输出也不可控.那现在我们来实现一个比较完整的日志框架. 设计思路如下: 1,定义一堆常量Li ...
- SpringBoot日志logback-spring.xml分环境log4j logback slf4j区别 springboot日志设置
转载. https://blog.csdn.net/qianyiyiding/article/details/76565810 springboot按照profile进行打印日志log4j logba ...
- Spring AOP 实现写事件日志功能
什么是AOP?AOP使用场景?AOP相关概念?Spring AOP组件?如何使用Spring AOP?等等这些问题请参考博文:Spring AOP 实现原理 下面重点介绍如何写事件日志功能,把日志保存 ...
- slf4j+log4j在Java中实现日志记录
小Alan今天来跟大家聊聊开发中既简单又常用但必不可少的一样东西,那是什么呢?那就是日志记录,日志输出,日志保存. 后面就统一用日志记录四个字来形容啦. 日志记录是项目的开发中必不可少的一个环节,特别 ...
随机推荐
- The Log-Structured Merge-Tree (LSM-Tree
https://www.cs.umb.edu/~poneil/lsmtree.pdf [Log-Structured Merge-Tree ][结构化日志归并树][要解决的问题]The Log-S ...
- 5.2 《锋利的jQuery》jQuery对表格的操作(选项卡/换肤)
表格隔行变色以及单选/复选 表格展开关闭 表格筛选 字体变大/缩小 选项卡 网页换肤 tip1: $("tr:odd")和$("tr:even")选择器索引是从 ...
- CSS那个背景图片的坐标怎么设置?怎么计算的?
background:url(images/hh.gif) no-repeat -10px 0;},作用是移动背景的位置. 背影图片的左上角相对当前元素左上角的坐标. 右为X轴正半轴, 下为Y轴正半轴 ...
- 通道(Channel)的原理获取
通道表示打开到 IO 设备(例如:文件.套接字)的连接.若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区.然后操作缓冲区,对数据进行处理.Channel 负责传输, ...
- 51nod 1189
题目 神犇题解 表示自己数论渣成狗...膜拜神犇. n!*(x+y)=x*y n!^2=(x-n!)*(y-n!) 那么求出n!^2的因数个数就可以了.
- netstat参数记录
可以使用man netstat查看TCP的各种状态信息描述 ESTABLISHED socket已经建立连接 CLOSED socket没有被使用,无连接 CL ...
- 《java编程思想》:异常丢失
finally子句的不恰当使用,会造成异常的丢失,此处列举两种典型的错误使用示例.编程中要避免这种情况 示例一: try{ throw new ExceptionA(); }finally{ thro ...
- C语言中的文件操作
按照字符的方式读取文件 按照行的方式读取文件 按照数据块的方式读取文件 按照格式化的方式读取文件 文件分类 记录文件:具有一定的结构记录组成,分为定长和不定长两种方式 流式文件:按照一个字符一个字符( ...
- linux shell date 时间运算以及时间差计算方法
最近一段时间,在处理Shell 脚本时候,遇到时间的处理问题. 时间的加减,以及时间差的计算. 获取当前时间戳 date +%s . 时间加减 这里处理方法,是将基础的时间转变为时间戳,然后,需要增加 ...
- Vue 中数据流组件
好久不见呀,这两年写了很多很多东西,也学到很多很多东西,没有时常分享是因为大多都是我独自思考.明年我想出去与更多的大神交流,再修筑自己构建的内容. 有时候我会想:我们遇到的问题,碰到的界限,是别人给的 ...