在我们的很多 Java 应用开发中,我们有通常很多的日志信息。这些日志信息对我们的开发非常有用。它可以帮我们对我们的应用的运行情况进行分析,特别是在应用运行出现错的时候。在传统的开发中,我们可能会使用一些编辑器来查看这些日志。这对于一个这样的应用是可行的,但是假设我们有多个正在运行的应用程序,并且所有这些应用程序都生成日志。 如果必须手动分析日志,则需要遍历所有日志文件。 这些可能会变成数百个。在今天的教程中,我们将介绍如何使用 Elastic Stack 来查询分析我们的日志。
我们的整个系统如上图所示:
- 我们有一个 Spring boot 的应用,它会生成日志信息
- 使用 Logstash 把日志信息导入到 Elasticsearch 中
- Elasticsearch 被用于存储及分析日志信息
- Kibana 将被使用为可视化及查询日志工具
安装
Elasticsearch
我们可参考我之前的文章“如何在Linux,MacOS及Windows上进行安装Elasticsearch”来安装我们的Elasticsearch。我们可以不需要修改任何的配置文件,并在本机上运行。
Kibana
我们可以参考我之前的文章“如何在Linux,MacOS及Windows上安装Elastic栈中的Kibana”来进行我们的安装。并在本机上运行。如果 Elasticsearch 及 Kibana 都运行正常的话,那么我们可以看到 Kibana 的界面:
Logstash
我们可以参考我之前的文章“如何安装Elastic栈中的Logstash”来安装 Logstash。我们先不运行Logstash。
这样我们就基本安装好了所需要的 Elastic Stack 组件。接下来,我们将创建一个简单的 Spring boot 应用。
创建 Spring boot 应用
我们使用 Eclipse 来创建一个 Spring boot 的应用:
点击 Finish 按钮。这样就完成了一个最基本的 Maven 应用的框架。我们接着修改项目中的 pom.xml 文件:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.liuxg</groupId>
<artifactId>boot-elastic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-elastic</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在上面我们加入了项目所必须的一些 dependence。
接下来,我们创建一个 SpringBootApplication 的 class。
点击 Finish 按钮。这样就创建了我们的 ElasticSpringBootApplication class。我们进一步修改这个 class:
ElasticSpringBootApplication.java
ackage com.liuxg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ElasticSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(ElasticSpringBootApplication.class, args);
}
}
接下来定义控制器以公开REST API。 我们将利用这些调用将内容写入日志文件。我们创建另外一个叫做 ElasticController 的 class。我们可以仿照上面的方法来创建这个 class:
ElasticController.java
package com.liuxg;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
class ElasticController {
private static final Logger LOG = Logger.getLogger(ElasticController.class.getName());
@Autowired
RestTemplate restTemplete;
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@RequestMapping(value = "/elk")
public String helloWorld() {
String response = "Welcome to JAVA " + new Date();
LOG.log(Level.INFO, response);
return response;
}
@RequestMapping(value = "/exception")
public String exception() {
String response = "";
try {
throw new Exception("Opps Exception raised....");
} catch (Exception e) {
e.printStackTrace();
LOG.error(e);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stackTrace = sw.toString();
LOG.error("Exception - " + stackTrace);
response = stackTrace;
}
return response;
}
}
在上面 我们定义了一个叫做 /elk 和 /exception 两个 REST 接口。它们分别生产相应的 Log 到文件里去。我们接下来定义我们的l og 文件地址:
点击 Finish 按钮。我们接着把文件的内容写为:
logging.file=/Users/liuxg/tmp/spring-boot-elastic.log
注意:这个 log 文件的路径依赖于你自己想要的路径不同而不同。上面是在我自己电脑上的路径。
保存上面的 application.propertiese 文件。我们接下来运行这个应用:
我们在 Eclipse 的 console 里可以看到如下的信息:
也就是我们的微服务运行于 localhost:8080 端口:
我们测试 REST AP 的两个接口:
我们接下来查看在我们上面配置的 log 文件里的内容:
$ pwd
/Users/liuxg/tmp
liuxg:tmp liuxg$ vi spring-boot-elastic.log
在上面,我们可以看到已经生产了许多的 log 了。其中 exception 所生成的日志里面含有 "at" 的字样。在我们上传这个日志信息的时候,我们需要把这个日志当做一个整体作为一个文档进行上传,而不是每一行含有 at 的语句分别当做一个文档上传。
配置 Logstash
上面我已经生产了相应的日志文件了。接下来,我们将配置 Logstash。如果你想了解更多关于 Logstash 方面的知识,请参阅我之前的文章“Logstash:Data转换,分析,提取,丰富及核心操作” 及文章“如何安装Elastic栈中的Logstash”。
我们在 Logstash 的安装目录下,创建如下的 配置文件:
logstash.conf
input {
file {
type => "java"
path => "/Users/liuxg/tmp/spring-boot-elastic.log"
codec => multiline {
pattern => "^%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME}.*"
negate => "true"
what => "previous"
}
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
filter {
#If log line contains tab character followed by 'at' then we will tag that entry as stacktrace
if [message] =~ "\tat" {
grok {
match => ["message", "^(\tat)"]
add_tag => ["stacktrace"]
}
}
}
output {
stdout {
codec => rubydebug
}
# Sending properly parsed log events to elasticsearch
elasticsearch {
hosts => ["localhost:9200"]
}
}
在上面我们通过:
codec => multiline {
pattern => "^%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME}.*"
negate => "true"
what => "previous"
}
把多行的含有 at 的 stack trace 的日志变为一个文档,而不是多个文档。关于这个 multiline 的介绍可以参阅我之前的文章“Beats:使用Filebeat传送多行日志”。虽然那个是针对 Filebeat 的,但是基本原理是完全一样的。
在 filter 的部分:
filter {
#If log line contains tab character followed by 'at' then we will tag that entry as stacktrace
if [message] =~ "\tat" {
grok {
match => ["message", "^(\tat)"]
add_tag => ["stacktrace"]
}
}
}
如果有一行的日志含有一个 tab (\t) 及 at 字符为首,那么这个信息将被标记为 stacktrace。这个便于我们以后在 Kibana 中进行搜索。
最后,在 output 的部分,我们需要填入相应的 elasticsearch 的地址。
我们可以使用如下的命令来测试我们的 logstash.conf 是否正确:
bin/logstash --config.test_and_exit -f logstash.conf
上面显示是正确的。
我们可以接着使用如下的命令来启动 logstash:
sudo ./bin/logstash -f logstash.conf
这个时候,我们可以在运行 Logstash 的屏幕上看到很多被处理的 log 信息。
我们打开 Kibana,并运行如下的命令:
GET _cat/indices
我们可以看到有一个叫做 logstash 为开头的日志文件出现了。我们可以通过如下的命令来检查它的日志的内容:
GET logstash/_search
我们也可以通过 Discover 来对我们的日志进行搜索。为此,我们需要创建一个index pattern:
点击 Create index pattern:
点击 Next step:
点击 Create index pattern。然后,我们点击 Discover:
在这里我们可以看到我们在之前生成的一些日志:
我们也可以继续在浏览器中使用接口 /elk 及 /exception 来生产相应的 log 并在 Kibana 中进行查看:
如果大家对这个测试应用感兴趣,请在地址下载:https://github.com/liu-xiao-guo/spring-boot-elastic-logs
尽管目前的这种方式非常不错,但是细心的开发者可能会发现这种架构很难规模化,如果是针对几十个甚至上百个 log 文件。这是因为 Logstash 对资源的消耗非常大,而目前的这个架构每个 log 文件都需要一个 Logstash 的实例来处理。 在接下的文章 “Elastic:运用 Elastic Stack 分析 Spring boot 微服务日志 (二)” 我们将讲述如果配合 Filebeat 来针对多个 log 文件来进行处理。