欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

自定义log4j的appender写es日志

程序员文章站 2022-08-11 14:18:21
本篇和大家分享的是自定义log4j的appender,用es来记录日志并且通过kibana浏览es记录;就目前互联网或者一些中大型公司通常会用到第三方组合elk,其主要用写数据到es中,然后通过可视化工具kibana来做直观数据查看和统计;本篇内容节点如下: docker快速搭建es,es head ......

本篇和大家分享的是自定义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的集群名称,通过如下命令进入容器中改了配置文件(当然可直接通过命令启动时传递参数):

1 docker exec -it eae7731bb6a1 /bin/bash

然后进入到 /usr/share/elasticsearch/config 并打开elasticsearch.yml配置文件修改:

 1 #集群名称
 2 cluster.name: "shenniu_elasticsearch"
 3 #本节点名称
 4 node.name: master
 5 #是否master节点
 6 node.master: true
 7 #是否存储数据
 8 node.data: true
 9 #head插件设置
10 http.cors.enabled: true
11 http.cors.allow-origin: "*"
12 http.port: 9200
13 transport.tcp.port: 9300
14 #可以访问的ip
15 network.bind_host: 0.0.0.0

这里定义集群名为:shenniu_elasticsearch

如上启动了es后,我们为了直观的看到es中信息,这里用到了es header工具(当然不必须);只要docker启动其镜像后,我们能够在上面输入咋们的es地址,以此来检测es集群是否开启并浏览相关索引信息,es header默认端口9100:

自定义log4j的appender写es日志

通常搭配es的是kibana(可视化工具),用来查看es的数据和做一些统计(如数量统计,按列聚合统计等),这里通过docker run启动kibana镜像后,我们还需要让其关联上es才行,同样通过docker exec去修改里面配置信息,主要在里面配置es地址:

1 docker exec -it 67a0ef871ef7 /bin/bash
2 cd etc/
3 cd kibana/
4 vim kibana.yml

配置内容修改如:

1 server.host: '0.0.0.0'
2 elasticsearch.url: 'http://192.168.181.7:9200'  #es地址

如上操作完后,打开kibana地址  ,能够看到让咋们配置es索引查询规则的界面,如果es地址down掉或者配置不对,kibana会停留在red界面,让我们正确配置:

自定义log4j的appender写es日志

封装写es工具类

java往es中写数据,可以使用官网推荐的 org.elasticsearch.client 包(注意版本问题),我这里es是5.6版本对应的rest-high-leve-client最好也引入5.6版本的,如下pom信息:

 1         <dependency>
 2             <groupid>log4j</groupid>
 3             <artifactid>log4j</artifactid>
 4             <version>1.2.17</version>
 5         </dependency>
 6         <dependency>
 7             <groupid>org.elasticsearch.client</groupid>
 8             <artifactid>elasticsearch-rest-high-level-client</artifactid>
 9             <version>5.6.16</version>
10         </dependency>
11         <dependency>
12             <groupid>com.alibaba</groupid>
13             <artifactid>fastjson</artifactid>
14             <version>1.2.56</version>
15             <scope>compile</scope>
16         </dependency>

首先要明确用代码操作es(或其他第三方服务),往往都需ip(域名)+端口,这里我的配置信息:

1 #es连接串 ','分割
2 es.links=http://192.168.181.7:9200,http://localhost:9200
3 es.indexname=eslog_shenniu003

然后有如下封装代码:

 1 public class esresthighlevelclient {
 2 
 3     /**
 4      * new httphost("192.168.181.44", 9200, "http")
 5      */
 6     private httphost[] hosts;
 7     private string index;
 8     private string type;
 9     private string id;
10 
11     public esresthighlevelclient(string index, string type, string id, httphost[] hosts) {
12         this.hosts = hosts;
13         this.index = index;
14         this.type = type;
15         this.id = id;
16     }
17 
18     /**
19      * @param index
20      * @param type
21      * @param hosts
22      */
23     public esresthighlevelclient(string index, string type, string... hosts) {
24         this.hosts = iphelper.gethostarrbystr(hosts);
25         this.index = index;
26         this.type = type;
27     }
28 
29     public resthighlevelclient client() {
30         assert.requirenonempty(this.hosts, "无效的es连接");
31 
32         resthighlevelclient client = new resthighlevelclient(
33                 restclient.builder(this.hosts).build()
34         );
35         return client;
36     }
37 
38     public indexrequest indexrequest() {
39         return new indexrequest(this.index, this.type, this.id);
40     }
41 
42     public reststatus createindex(map<string, object> map) throws ioexception {
43         return client().
44                 index(this.indexrequest().source(map)).
45                 status();
46     }
47 }

这里还涉及到了一个iphelper辅助类,主要用来拆分多个ip信息参数,里面涉及到正则匹配方式:

 1 public class iphelper {
 2 
 3     private static final string strhosts = "(?<h>[^:]+)://(?<ip>[^:]+):(?<port>[^/|,]+)";
 4     private static final pattern hostpattern = pattern.compile(strhosts);
 5 
 6     public static optional<string> gethostip() {
 7         try {
 8             return optional.ofnullable(inetaddress.getlocalhost().gethostaddress());
 9         } catch (unknownhostexception e) {
10             e.printstacktrace();
11         }
12         return optional.empty();
13     }
14 
15     public static optional<string> gethostname() {
16         try {
17             return optional.ofnullable(inetaddress.getlocalhost().gethostname());
18         } catch (unknownhostexception e) {
19             e.printstacktrace();
20         }
21         return optional.empty();
22     }
23 
24     /**
25      * strhosts:"http://192.168.0.1:9200","http://192.168.0.1:9200","http://192.168.0.1:9200"
26      *
27      * @return
28      */
29     public static list<httphost> gethostsbystr(string... strhosts) {
30         list<httphost> hosts = new arraylist<>();
31         for (int i = 0; i < strhosts.length; i++) {
32             string[] hostarr = strhosts[i].split(",");
33             for (string strhost : hostarr) {
34                 matcher matcher = hostpattern.matcher(strhost);
35                 if (matcher.find()) {
36                     string http = matcher.group("h");
37                     string ip = matcher.group("ip");
38                     string port = matcher.group("port");
39 
40                     if (strings.isempty(http) || strings.isempty(ip) || strings.isempty(port)) {
41                         continue;
42                     }
43                     hosts.add(new httphost(ip, integer.valueof(port), http));
44                 }
45             }
46         }
47         return hosts;
48     }
49 
50     public static httphost[] gethostarrbystr(string... strhosts) {
51         list<httphost> list = gethostsbystr(strhosts);
52         return arrays.copyof(list.toarray(), list.size(), httphost[].class);
53     }
54 }

自定义log4j的appender

对于日志来说log4j是大众化的,有很多语言也在用这种方式来记录,使用它相当于一种共识;它提供了很好的扩展,很方便达到把日志记录到数据库,文本获取其他自定义代码方式中;定义一个esappend类,继承appenderskeleton类,代码上我们要做的仅仅重写如下方法即可:

自定义log4j的appender写es日志

本期咋们实现的步骤是:

  1. activateoptions方法获取自定义配置信息(es连接串,写es的日志索引名等)
  2. append方法获取并记录logger.xx()等级的日志
  3. executorservice线程池类操作多个线程执行execute提交日志到es

具体实现代码如下,可按照上面步骤分析:

 1 public class esappend extends appenderskeleton {
 2 
 3     //es客户端
 4     private static esresthighlevelclient esclient;
 5     //es配置文件名
 6     private string confname;
 7 
 8     private executorservice executorservice = executors.newfixedthreadpool(10);
 9 
10     protected void append(loggingevent loggingevent) {
11         if (this.isassevereasthreshold(loggingevent.getlevel())) {
12             executorservice.execute(new esappendtask(loggingevent, this.layout));
13 //            new esappendtask(loggingevent, this.layout).run();
14         }
15     }
16 
17     public void close() {
18         this.closed = true;
19     }
20 
21     public boolean requireslayout() {
22         return false;
23     }
24 
25     @override
26     public void activateoptions() {
27         super.activateoptions();
28         try {
29             system.out.println("初始化 - esappend...");
30 
31             if (this.getconfname() == null || this.getconfname().isempty()) {
32                 this.setconfname("eslog.properties");
33             }
34             propertieshelper propertieshelper = new propertieshelper(this.getconfname());
35             //es hosts
36             string strhosts = propertieshelper.getproperty("es.links", "http://127.0.0.1:9200");
37             //es日志索引
38             string eslogindex = propertieshelper.getproperty("es.indexname", "eslog");
39             esclient = new esresthighlevelclient(eslogindex, "docs", strhosts);
40 
41             system.out.println("初始化完成 - esappend");
42         } catch (exception ex) {
43             system.out.println("初始化失败- esappend");
44             ex.printstacktrace();
45         }
46     }
47 
48     public string getconfname() {
49         return confname;
50     }
51 
52     public void setconfname(string confname) {
53         this.confname = confname;
54     }
55 
56     /**
57      * runable写es
58      */
59     class esappendtask implements runnable {
60         private hashmap<string, object> map;
61 
62         public esappendtask(loggingevent loggingevent, layout layout) {
63             simpledateformat df = new simpledateformat("yyyy-mm-dd\'t\'hh:mm:ss.sssz");
64             map = new hashmap<string, object>() {
65                 {
66                     put("timestamp",df.format(new date()));
67                     put("serverip", iphelper.gethostip().get());
68                     put("hostname", iphelper.gethostname().get());
69                     put("level", loggingevent.getlevel().tostring());
70 
71                     put("classname", loggingevent.getlocationinformation().getclassname());
72                     put("methodname", loggingevent.getlocationinformation().getmethodname());
73                     put("data", loggingevent.getmessage());
74 
75                     if (loggingevent.getthrowableinformation() != null && !collectionutils.isempty(loggingevent.getthrowableinformation().getthrowablestrrep())) {
76                         put("exception", string.join(";", loggingevent.getthrowableinformation().getthrowablestrrep()));
77                     } else {
78                         put("exception", "");
79                     }
80                 }
81             };
82         }
83 
84         @override
85         public void run() {
86             try {
87                 esappend.esclient.createindex(map);
88             } catch (ioexception e) {
89                 e.printstacktrace();
90             }
91         }
92     }
93 }

如上代码有一些自定义属性如confname,这个对应log4j.properties文件中自定义的confname属性,也就是说代码中confname和配置文件中的节点对应,可以直接get获取值;如下log4j配置信息:

 1 # set root logger level to debug and its only appender to a1.
 2 log4j.rootlogger=debug,esappend
 3 # a1 is set to be a consoleappender.
 4 log4j.appender.esappend=log.esappend
 5 #自定义es配置文件
 6 log4j.appender.esappend.confname=eslog.properties
 7 
 8 # a1 uses patternlayout.
 9 #log4j.appender.esappend.layout=org.apache.log4j.patternlayout
10 #log4j.appender.esappend.layout

上面patternlayout配置是注释的,因为对于我写es来说没啥用处,不做格式化处理所以可以直接忽略;

  1. log4j.rootlogger:log4根节点配置,根节点配置debug其他子节点不重新定义的话使用继承模式;esappend是随意定义append名称
  2. log4j.appender.esappend:这里的esappend对应rootlogger节点上随意定义的名称;log.esappend是只对应append的代码实现类
  3. log4j.appender.esappend.confname:自定义es配置节点,代码中get获取即可(注意:activateoptions方法)

下面列出扩展append时需要注意的地方:

  1. 如果log4j.properties文件中有自定义属性,那么activateoptions方法是必须的,不然通过属性get是获取不了log4j.properties文件中自定义属性的值
  2. 因为是使用线程池来操作写es,所以顺序方面不能保证,因此最好插入时间列
  3. 对应用程序而言,es没法主动区分请求处理服务器是哪台,所以需要插入日志时最好带上服务器ip或者唯一标识
  4. 时间格式:yyyy-mm-dd't'hh:mm:ss.sssz ,目前kibana搜索默认支持的时间格式

kibana基础使用

有了上面步骤后,我们来到测试环节,建一个测试接口,并且请求插入一些数据:

 1     static logger logger = logger.getlogger(testcontroller.class);
 2 
 3     @getmapping("/hello/{nickname}")
 4     public string gethello(@pathvariable string nickname) {
 5         string str = string.format("你好,%s", nickname);
 6         logger.debug(str);
 7         logger.info(str);
 8         logger.error(str);
 9         return str;
10     }

当我们请求接口 神牛003 一次后,通过es header查看内容如下:

自定义log4j的appender写es日志

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

自定义log4j的appender写es日志

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

自定义log4j的appender写es日志

自定义log4j的appender写es日志