SpringBoot AOP+Kafka+ELK 日誌系統

在這裏插入圖片描述
準備工作:
各種環境,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,選擇剛創建的索引,就可以看到索引內的信息了,可以選擇時間篩選,可以加過濾條件,以及…
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章