准备工作:
各种环境,JDK8,Kafka,Logstash,Elasticsearch,Kibana等等。
参考文章:
ELK + kafka 日志方案
快速搭建ELK日志分析系统
spring boot aop elk kafka 搭建 日志收集系统
Logstash常用配置和日志解析
pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>elk-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springLog</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.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>
<spring.boot.starter.log4j.version>1.3.8.RELEASE</spring.boot.starter.log4j.version>
<fastjson.version>1.1.26</fastjson.version>
<org.codehaus.jackson.version>1.9.13</org.codehaus.jackson.version>
<commonslang.version>2.6</commonslang.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--对json格式的支持 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${org.codehaus.jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commonslang.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
KafkaProducerConfig
这里配置Kafka的连接信息,最最最最最重要的是配置了ip及端口号
import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
@Configuration
@EnableKafka
public class KafkaProducerConfig {
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.**.**:9092");
// 如果请求失败,生产者会自动重试,我们指定是0次,如果启用重试,则会有重复消息的可能性
props.put(ProducerConfig.RETRIES_CONFIG, 0);
/**
* Server完成 producer request 前需要确认的数量。 acks=0时,producer不会等待确认,直接添加到socket等待发送;
* acks=1时,等待leader写到local log就行; acks=all或acks=-1时,等待isr中所有副本确认 (注意:确认都是 broker
* 接收到消息放入内存就直接返回确认,不是需要等待数据写入磁盘后才返回确认,这也是kafka快的原因)
*/
// props.put("acks", "all");
/**
* Producer可以将发往同一个Partition的数据做成一个Produce
* Request发送请求,即Batch批处理,以减少请求次数,该值即为每次批处理的大小。
* 另外每个Request请求包含多个Batch,每个Batch对应一个Partition,且一个Request发送的目的Broker均为这些partition的leader副本。
* 若将该值设为0,则不会进行批处理
*/
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 4096);//
/**
* 默认缓冲可立即发送,即遍缓冲空间还没有满,但是,如果你想减少请求的数量,可以设置linger.ms大于0。
* 这将指示生产者发送请求之前等待一段时间,希望更多的消息填补到未满的批中。这类似于TCP的算法,例如上面的代码段,
* 可能100条消息在一个请求发送,因为我们设置了linger(逗留)时间为1毫秒,然后,如果我们没有填满缓冲区,
* 这个设置将增加1毫秒的延迟请求以等待更多的消息。 需要注意的是,在高负载下,相近的时间一般也会组成批,即使是
* linger.ms=0。在不处于高负载的情况下,如果设置比0大,以少量的延迟代价换取更少的,更有效的请求。
*/
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
/**
* 控制生产者可用的缓存总量,如果消息发送速度比其传输到服务器的快,将会耗尽这个缓存空间。
* 当缓存空间耗尽,其他发送调用将被阻塞,阻塞时间的阈值通过max.block.ms设定, 之后它将抛出一个TimeoutException。
*/
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 40960);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return props;
}
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<String, String>(producerFactory());
}
}
@interface
声明一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLog {
}
intercepter
对带有注解的方法进行处理
处理参数,发起信息
最重要的 kafkaTemplate.send(“test-log”,message.toJSONString())
将数据发送给Kafka,test-log是topic名
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSONObject;
@Aspect
@Component
public class LogInterceptor implements Ordered{
@Autowired
private KafkaTemplate kafkaTemplate;
@Around("@annotation(systemLog)")
public Object Log(ProceedingJoinPoint joinPoint,SystemLog systemLog){
Object result = null;
try {
if (joinPoint == null) {
return null;
}
JSONObject message = new JSONObject();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//获取方法参数,这种方式会导致后期解析字段时有一些麻烦,抛弃
//Enumeration<String> eParams = request.getParameterNames();
//Map map = new HashMap();
//while (eParams.hasMoreElements()) {
// String key = eParams.nextElement();
// String value = request.getParameter(key);
// map.put(key, value);
//}
////获取header参数
//Enumeration<?> headerNames = request.getHeaderNames();
//Map headMap = new HashMap();
//while (headerNames.hasMoreElements()) {
// String key = (String) headerNames.nextElement();
// String value = request.getHeader(key);
// //时间戳单位统一
// if ("timestamp".equals(key) && StringUtils.isNotBlank(value) && value.length() > 10) {
// value = value.substring(0, 10);
// }
// headMap.put(key, value);
//}
//message.put("headParams",headMap);
//message.put("userName", "");//可以改为用户名
Date now = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//可以方便地修改日期格式
message.put("time", dateFormat.format(now));//时间
message.put("ip", getIpAddr(request));
message.put("requestURL", request.getRequestURL().toString());
message.put("params",request.getQueryString());//参数
result =joinPoint.proceed();
message.put("return", result);//返回结果
//message.put("requestMethod", joinPoint.getSignature().getName());//方法
//message.put("class", joinPoint.getTarget().getClass().getName());
kafkaTemplate.send("test-log",message.toJSONString());
System.out.println("message:" + message.toJSONString());
} catch (Throwable throwable) {
throwable.getMessage();
}
return result;
}
//如果不需要ip地址,这段可以省略
public String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0
|| "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0
|| "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0
|| "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
//或者这样也行,对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
//return ipAddress!=null&&!"".equals(ipAddress)?ipAddress.split(",")[0]:null;
return ipAddress;
}
@Override
public int getOrder() {
return 0;
}
}
至此,AOP部分结束~
代码结构如图:
写个conroller试试效果:
import com.example.elkdemo.elk.SystemLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@SystemLog
@GetMapping("tet")
public String tet() {
System.out.println("执行了tet");
return "==========tet==========";
}
@SystemLog
@GetMapping("tst")
public String tst() {
System.out.println("执行了tst");
return "----------tst----------";
}
}
然后!数据其实已经发送到Kafka了
验证:
在kafka文件夹下:
bin/kafka-topics.sh --zookeeper 127.0.0.1:2181 --topic test-log --describe
接下来就是用Logstash把数据从Kafka put 到Elasticsearch
在logstash下的config文件夹内新建一个.conf文件
内容:
根据需要自己改
#不完整!!!
input {
kafka {
bootstrap_servers => "127.0.0.1:9092"
topics => ["test-log"]
}
}
output {
elasticsearch {
hosts => "127.0.0.1:9200"
action => "index"
index => "test-log--%{+YYYY.MM.dd}"
codec => "json"
}
}
今天整合Kibana,发现如果这样直接导入elasticsearch
数据就是——
一条message:
要想在Kibana上做一些分析处理,
还是要把它拆分成字段呐
所以,需要在conf文件内加上解析JSON的filter
完整的配置文件如下:
input {
kafka {
bootstrap_servers => "127.0.0.1:9092"
topics => ["test-log"]
}
}
filter{
json{
source => "message"
}
}
output {
elasticsearch {
hosts => "127.0.0.1:9200"
action => "index"
index => "test-log--%{+YYYY.MM.dd}"
codec => "json"
}
}
这样,以后发送到elasticsearch的数据就会变成:
在logstash的bin目录下执行:
记得修改文件名
./logstash -f ../config/logstash-test.conf --config.reload.automatic
成功的话,数据就到elasticsearch内了,验证:
http://localhost:9200/_cat/indices?v
草草结束,我对elk的认识还比较浅薄,后期补上
要被后续操作kibana…虐哭辽 好想放弃
嗷嗷嗷!!!!
elasticsearch与Kibana之间的联系我自己没有写
操作Kibana:
上图只是验证我的日志已经在了。
以下:
1.选择“索引模式”,点击“创建索引模式”,选择“标准索引模式”
2.要匹配这两个,输入“test-log–*”,下一步,时间字段默认是@timestamp,也可以选我不想使用时间筛选,然后点击“创建索引模式”,索引创建完成。
3.到 Discover,选择刚创建的索引,就可以看到索引内的信息了,可以选择时间筛选,可以加过滤条件,以及…