前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springboot项目使用aop切面保存详细日志到ELK日志平台

Springboot项目使用aop切面保存详细日志到ELK日志平台

作者头像
天涯泪小武
发布2019-01-17 11:43:55
1.3K0
发布2019-01-17 11:43:55
举报
文章被收录于专栏:SpringCloud专栏SpringCloud专栏

上一篇讲过了将Springboot项目中logback日志插入到ELK日志平台,它只是个示例。这一篇来看一下实际使用中,我们应该怎样通过aop切面,拦截所有请求日志插入到ELK日志系统。同时,由于往往我们有很多个服务,都需要记录日志,为每个服务都搭建一个ELK并不现实,所以我们采用集中化管理日志,将所有日志都插到同一个ELK中。这样又会遇到另一个问题,就是ES中的Index如果只有一个,那么所有日志都混杂在一个Index里,为日后的检索带来不便,我们希望能根据不同的应用来拆分为不同的ES的Index。

下面看看怎么做。

配置logback

同样的,新建一个Springboot项目,勾选aop。

首先我们来配置logback,见logback-spring.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <!-- 本地日志文件 -->
    <appender name="MY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Prudent>true</Prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>

            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>

        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss} -%msg%n</Pattern>
        </layout>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Logstash日志 -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!-- 远程Logstash的IP:PORT -->
        <destination>IP:4560</destination>
        <!-- encoder必须要有,是logback和logstash的纽带 -->
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
            <!-- 用户自定义Json字符串,用于向logstash提交额外的自定义属性。可在logstash的input里接收该参数 -->
            <customFields>{"appName":"testelk"}</customFields>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="LOGSTASH"/>
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="MY_FILE"/>
    </root>


</configuration>

为了演示,我们将日志保存3份,一份在控制台打印,即appender-ref中的CONSOLE,再一份存文件,即MY_FILE,这两个在很早之前的文章Springboot使用logback详解中有介绍。

重点看一下LOGSTASH的配置,destination配置你的Logstash的ip和端口,请修改为自己服务器的ip地址,4560是Logstash默认的端口。通过LogstashTcpSocketAppender继承的类源码可以看到

注意里面有destinations的集合,用于接收xml配置的ip,说明可以配置多个。然后还有一个encoder属性,接收一个Encoder对象。见xml里配置的encoder

encoder可以接收一个customFields的属性,见xml配置,这是一个json字符串,用于给Logstash传递自定义的值,设置之后就会在kibana里默认给每条数据都带着这个属性。

这个appName就是在xml里配置的自定义属性。之前有讲过,要使用kibana管理ES,ES的index必须要有timestamp字段。如果是不通过Logstash插入ES的话,自定义的model必须要有FieldType.DATE字段,使用Logstash的话,就可以不加这个字段。原因就是见源码里的,里面有timezone,version等都是该encoder源码自动给我们补上的。

设置appName意义何在?下面请看。

配置Logstash

创建编辑一个Logstash的配置文件,不会的去看前两篇

代码语言:javascript
复制
input {
  # For detail config for log4j as input,
  # See: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-log4j.html
  tcp {
    mode => "server"
    host => "0.0.0.0"
    port => 4560
    codec => json_lines
  }
}
filter {
  #Only matched data are send to output.
}
output {
  # For detail config for elasticsearch as output,
  # See: https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html
  elasticsearch {
    index => "%{[appName]}"          #The operation on ES
    hosts  => "localhost:9200"   #ElasticSearch host, can be array.
    action  => "index"         #The index to write data to.
  }
}

input没什么说的,设置端口和codec即可,主要是output,我们看elasticsearch的index标签,这个代表将日志插入到ES的哪个index里,这里我们不写死,而是使用获取客户端传来的appName来指定index,Logstash通过动态指定ES的index,就可以完成不同的项目存储到不同的ES的index。这样既完成了日志统一管理,又区分开了不同的项目。 配置完毕,启动Logstash即可。

设置aop拦截

上面的Logstash配置好后,假定你已经启动好了ES,也配置好了kibana,那么基础工作就已经完成了,那么我们就来完成aop切面日志的收集了。

先来设置application.yml,配置文件log的路径。

代码语言:javascript
复制
logging:
  file: ./logback.log

创建aop切面类,拦截所有Controller。

代码语言:javascript
复制
package com.tianyalei.testelk.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * Created by wuwf on 17/4/27.
 * 日志切面
 */
@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(getClass().getName());
    private ObjectError error;

    @Pointcut("execution(public * com.tianyalei.testelk.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("-------------------用户发起请求-----------------");
        // 记录下请求内容
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP_METHOD : " + request.getMethod());
        //如果是表单,参数值是普通键值对。如果是application/json,则request.getParameter是取不到的。
        logger.info("HTTP_HEAD Type : " + request.getHeader("Content-Type"));
        logger.info("IP : " + request.getRemoteAddr());
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

        if ("application/json".equals(request.getHeader("Content-Type"))) {
            //记录application/json时的传参,SpringMVC中使用@RequestBody接收的值
            logger.info(getRequestPayload(request));
        } else {
            //记录请求的键值对
            for (String key : request.getParameterMap().keySet()) {
                logger.info(key + "----" + request.getParameter(key));
            }
        }

    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        logger.info("方法的返回值 : " + ret);
        logger.info("------------------请求结束------------------");
    }

    //后置异常通知
    @AfterThrowing(throwing = "ex", pointcut = "webLog()")
    public void throwss(JoinPoint jp, Exception ex){
        logger.info("方法异常时执行.....");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
//        logger.info("方法最后执行.....");
    }


    private String getRequestPayload(HttpServletRequest req) {
        StringBuilder sb = new StringBuilder();
        try(BufferedReader reader = req.getReader()) {
            char[]buff = new char[1024];
            int len;
            while((len = reader.read(buff)) != -1) {
                sb.append(buff,0, len);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}

在这里获取用户的请求地址、参数和返回值。

Controller如下:

代码语言:javascript
复制
package com.tianyalei.testelk.controller;

import com.tianyalei.testelk.service.LoginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    private Logger logger = LoggerFactory.getLogger(getClass().getName());
    @Autowired
    LoginService loginService;

    @GetMapping("index")
    public Object index() {
        logger.info("进入了index方法");
        logger.info("开始执行业务逻辑");
        logger.info("index方法结束");

        return "success";
    }

    @PostMapping("login")
    public Object login() {
        logger.info("进入了login方法");

        loginService.login();

        logger.info("登录成功");

        return "success";
    }
}

Service如下

代码语言:javascript
复制
package com.tianyalei.testelk.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class LoginService {
    private Logger logger = LoggerFactory.getLogger(getClass().getName());

    public void login() {
        logger.info("进入Service方法,执行登录查询");
    }
}

OK了,如果你的spring-logback.xml里配置的ip和port是对的,那么日志就能进到ELK了。

启动,访问/login,并传参

可以看到控制台日志:

代码语言:javascript
复制
2017-08-08 17:11:52.603  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : -------------------用户发起请求-----------------
2017-08-08 17:11:52.604  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : URL : http://localhost:8080/login
2017-08-08 17:11:52.604  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : HTTP_METHOD : POST
2017-08-08 17:11:52.605  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : HTTP_HEAD Type : application/x-www-form-urlencoded
2017-08-08 17:11:52.605  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : IP : 0:0:0:0:0:0:0:1
2017-08-08 17:11:52.607  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : CLASS_METHOD : com.tianyalei.testelk.controller.IndexController.login
2017-08-08 17:11:52.607  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : key:name----value:wolf
2017-08-08 17:11:52.607  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : key:pass----value:123456
2017-08-08 17:11:52.610  INFO 89159 --- [nio-8080-exec-1] c.t.testelk.controller.IndexController   : 进入了login方法
2017-08-08 17:11:52.611  INFO 89159 --- [nio-8080-exec-1] c.t.testelk.service.LoginService         : 进入Service方法,执行登录查询
2017-08-08 17:11:52.611  INFO 89159 --- [nio-8080-exec-1] c.t.testelk.controller.IndexController   : 登录成功
2017-08-08 17:11:52.611  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : 方法的返回值 : success
2017-08-08 17:11:52.613  INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect      : ------------------请求结束------------------

查看ES数据:

我们可以看到index名为testelk的数据如图。说明自定义的index已经成功创建,值也插入成功了。

看看kibana,首先在kibana的setting里添加testelk的index。然后可以在左边的Available Fields勾选AppName和message。

可以看到记录的日志了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年08月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 配置logback
  • 配置Logstash
  • 设置aop拦截
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档