本篇和大家分享的是自定义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:
通常搭配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地址 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信息:
复制代码
1
2 log4j
3 log4j
4 1.2.17
5
6
7 org.elasticsearch.client
8 elasticsearch-rest-high-level-client
9 5.6.16
10
11
12 com.alibaba
13 fastjson
14 1.2.56
15 compile
16
复制代码
首先要明确用代码操作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
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 = "(?[^:]+)://(?[^:]+):(?[^/|,]+)";
4 private static final Pattern hostPattern = Pattern.compile(strHosts);
5
6 public static Optional 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 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 getHostsByStr(String... strHosts) {
30 List 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 list = getHostsByStr(strHosts);
52 return Arrays.copyOf(list.toArray(), list.size(), HttpHost[].class);
53 }
54 }
复制代码
自定义log4j的appender
对于日志来说log4j是大众化的,有很多语言也在用这种方式来记录,使用它相当于一种共识;它提供了很好的扩展,很方便达到把日志记录到数据库,文本获取其他自定义代码方式中;定义一个EsAppend类,继承AppenderSkeleton类,代码上我们要做的仅仅重写如下方法即可:
本期咋们实现的步骤是:
activateOptions方法获取自定义配置信息(es连接串,写es的日志索引名等)
append方法获取并记录logger.xx()等级的日志
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 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() {
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来说没啥用处,不做格式化处理所以可以直接忽略;
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基础使用
有了上面步骤后,我们来到测试环节,建一个测试接口,并且请求插入一些数据:
复制代码
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 }
复制代码
当我们请求接口 http://localhost:4020/hello/神牛003 一次后,通过es header查看内容如下:
这种方式不怎么直观,可以通过kibana来查看,如下先配置kibana使用的索引:
最后通过Discover界面搜索相关日志信息:
git地址: https://github.com/shenniubuxing3 云栖社区博客:https://yq.aliyun.com/users/xjexlr3no5xj4https://www.cnblogs.com/wangrudong003/p/10886423.html