分布式日志系统搭建-全局异常处理(2)

场景分析

         在上一篇章,讲述了最基本的ELK环境的搭建。这次来讲如何对日志进行处理,首先你需要明白的是elk的作用就是为了做日志的记录。当然es本身的作用是不止这些的,那么如何在JAVA代码中处理日志呢?

         通常,你可能会这样处理异常:

try{
			   ...
			 dosomething
}
catch (Exception e) 
{
          e.printStackTrace();
          log.info("出现的异常:", e);
          return "服务异常";
 } 

         将异常进行打印,随后写在日志文件当中。在我们的开发过程中,当你写的try/catch块会有很多的地方都在用,每个地方我们都需要这样log.info操作。
这样一来就变得十分的麻烦,所以我们可以使用全局异常处理

全局异常处理

         全局异常处理其实十分的简单,@RestControllerAdvice+@ExceptionHandler就可以实现全局异常处理

@ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度
@RestControllerAdvice:异常集中处理,更好的使业务逻辑与异常处理剥离开

接下来我们看看具体的代码
异常处理
         使用@RestControllerAdvice声明用来进行全局的异常处理,@ExceptionHandler捕获HdException这个异常,savelog()方法进行异常的存储,然后在发给前端ResponseResultVo。这里的HdException和ResponseResultVo都是我自己进行封装的。
         在一个系统当中,我们对于返回给前端的信息都是需要进行统一的,一来方便处理,二来优雅美观。
         为什么对异常也进行了自定义呢?

1.我们在工作的时候,项目是分模块或者分功能开发的,基本不会是一个人开发一整个项目,使用自定义异常类就统一了对外异常展示的方式。
2.有时候我们遇到某些校验或者问题的时候,需要直接结束掉当前的请求,这时便可以通过抛出自定义异常来结束。
3.自定义异常可以在我们的项目中某些特殊的业务逻辑时抛出异常。
4.使用自定义异常继承相关的异常来抛出处理后的异常信息,可以隐藏底层的异常。这样更安全,异常信息也会更加直观。自定义异常可以抛出我们自己想要抛出的异常,可以通过抛出的信息区分异常发生的位置,根据异常名我们就可以知道哪里有异常,根据异常提示信息对程序进行修改。比如空指针异常NullPointException,我们可以抛出信息为"XXX为空"的定位异常位置,而不用输出堆栈信息(默认异常抛出的信息)。

但是当我们在使用HdException和ResponseResultVo的时候,还有一个必不可缺的东西,那就是自定义的状态码。有了这三样,我们就能很方便的处理系统中业务相关的异常、返回的信息等。

接下来就是异常处理savelog()方法,在这里我为了演示的方便与直观,直接存在了数据库当中,然后logstash直接从数据库中进行读取,在存入es当中。后续会搭建elk+filebeat+中间件的形式走一遍完整的流程! 整个相关的代码我都会放在最下面。

在JAVA这边搭建好之后,我们需要对于logstash的配置进行下改变,之前我们是通过读取文件中的数据写入es当中,接下来,则需要从数据库中读取并存入es当中去。

Logstash配置

新建一个jdbc.conf在bin的目录下

input{
    jdbc{
		# 数据库驱动包存放路径
        jdbc_driver_library => "..\config\mysql-connector-java-5.1.46.jar"
		# 数据库驱动器;
        jdbc_driver_class => "Java::com.mysql.jdbc.Driver"
		# 数据库连接方式
        jdbc_connection_string => "jdbc:mysql://localhost:3306/study"
		# 数据库用户名
        jdbc_user => "admin"
		# 数据库密码
        jdbc_password => "admin"
		# 数据库重连尝试次数
		connection_retry_attempts => "3"
		# 判断数据库连接是否可用,默认false不开启
		jdbc_validate_connection => "true"
		# 数据库连接可用校验超时时间,默认3600s
		jdbc_validation_timeout => "3600"
		# 开启分页查询(默认false不开启)
        jdbc_paging_enabled => "true"
		# 单次分页查询条数(默认100000,若字段较多且更新频率较高,建议调低此值)
        jdbc_page_size => "500"
		# statement为查询数据sql,如果sql较复杂,建议通过statement_filepath配置sql文件的存放路径
        # statement_filepath => "D:\logstash-5.6.9\config\jdbc.sql"
		# sql_last_value为内置的变量,存放上次查询结果中最后一条数据tracking_column的值,此处即为rowid
		statement => "select t.* from sys_log_error t where t.create_date > :sql_last_value"
		# 是否将字段名转换为小写,默认true(如果有数据序列化、反序列化需求,建议改为false);
		lowercase_column_names => false
		# 是否记录上次执行结果,true表示会将上次执行结果的tracking_column字段的值保存到last_run_metadata_path指定的文件中
		record_last_run => true
		# 需要记录查询结果某字段的值时,此字段为true,否则默认tracking_column为timestamp的值
		use_column_value => true
		# 查询结果某字段的数据类型,仅包括numeric和timestamp,默认为numeric
		tracking_column => "create_date"
		# 需要记录的字段,用于增量同步,需是数据库字段
		tracking_column => "rowid"
		# 记录上次执行结果数据的存放位置
		last_run_metadata_path => "D:\logstash-5.6.9\logs\last_id.txt"
		# 是否清除last_run_metadata_path的记录,需要增量同步时此字段必须为false
		clean_run => false
		# 同步频率(分 时 天 月 年),默认每分钟同步一次
        schedule => "* * * * *"
		# ES索引的type
        type => "test-log"
    }
	
	# 如果同步多个表,可以在复制一个jdbc{}块,修改相应的地方即可
}

output {
   elasticsearch {
     hosts => "http://localhost:9200"
     index => "logs"
     document_id => "%{id}"
   }
  stdout {}
}


注意需要你将数据库相关的jar包进行配置,其余相关字段可以看上面的注释,已经十分的详细了。

logstash -f jdbc.conf

启动logstash,如果遇到提示需要utf-8编码,另存为utf-8格式即可。

代码测试

在Controller层直接抛出一个异常

@RestController
@RequestMapping("test")
public class TestController {

    @RequestMapping("log")
    public void log(String name) {
        throw  new HdException(ResultVoStatus.Bad);
    }
}


效果查看

[图片]
在这里插入图片描述
大功告成!

下集预告

在这里插入图片描述

相关代码

自定义异常代码

package com.springboot.study.common.exception;


import com.springboot.study.common.response.ResultVoStatus;

public class HdException extends RuntimeException {

    ResultVoStatus resultVoStatus;
    Exception e;


    public HdException(ResultVoStatus resultVoStatus){
        super(resultVoStatus.getMsg());
        this.resultVoStatus=resultVoStatus;
    }

    public HdException(ResultVoStatus resultVoStatus, Exception e){
        super(resultVoStatus.getMsg());
        this.resultVoStatus=resultVoStatus;
        this.e=e;


    }

    public Exception getE() {
        return e;
    }

    public void setE(Exception e) {
        this.e = e;
    }

    public ResultVoStatus getResultVoStatus() {
        return resultVoStatus;
    }

    public void setResultVoStatus(ResultVoStatus resultVoStatus) {
        this.resultVoStatus = resultVoStatus;
    }

}

封装返回数据代码

package com.springboot.study.common.response;


import io.swagger.annotations.ApiModelProperty;

import java.util.HashMap;
import java.util.Map;

public class ResponseResultVo<T> {
    private static final long serialVersionUID = 1679552421651455773L;
    @ApiModelProperty(value = "是否成功")
    private String sucess;
    @ApiModelProperty(value = "状态码")
    private Integer status;
    @ApiModelProperty(value = "数据")
    private T data;
    @ApiModelProperty(value = "返回信息")
    private String msg;
    @ApiModelProperty("额外数据")
    private Map<String, Object> extra;


    public Map<String, Object> getExtra() {
        return extra;
    }

    public ResponseResultVo<T> setExtra(Map<String, Object> extra) {
        this.extra = extra;
        return this;
    }


    public String getSucess() {
        return sucess;
    }

    public ResponseResultVo<T> setSucess(String sucess) {
        this.sucess = sucess;
        return this;
    }

    public Integer getStatus() {
        return status;
    }

    public ResponseResultVo<T> setStatus(Integer status) {
        this.status = status;
        return this;
    }

    public T getData() {
        return data;
    }

    public ResponseResultVo<T> setData(T data) {
        this.data = data;
        return this;
    }

    public String getMsg() {
        return msg;
    }

    public ResponseResultVo<T> setMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public ResponseResultVo() {
    }

    public static <T> ResponseResultVo<T> ok(ResultVoStatus resultVoStatus) {
        return new ResponseResultVo().setMsg(resultVoStatus.getMsg())
                .setStatus(resultVoStatus.getStatus())
                .setSucess("success");
    }


    public static <T> ResponseResultVo<T> sucess(T data) {
        return new ResponseResultVo()
                .setMsg(ResultVoStatus.OK.getMsg())
                .setData(data)
                .setStatus(ResultVoStatus.OK.getStatus())
                .setSucess("true");
    }

    public static <T> ResponseResultVo<T> sucess() {
        return new ResponseResultVo()
                .setMsg(ResultVoStatus.OK.getMsg())
                .setData("你的请求非常完美,我说的!")
                .setStatus(ResultVoStatus.OK.getStatus())
                .setSucess("true");
    }

    public static ResponseResultVo faild(ResultVoStatus resultVoStatus) {
        return new ResponseResultVo()
                .setSucess("false")
                .setStatus(resultVoStatus.getStatus())
                .setMsg(resultVoStatus.getMsg());
    }

    public static ResponseResultVo faild(Integer status, String msg) {
        return new ResponseResultVo()
                .setMsg(msg)
                .setStatus(status)
                .setSucess("false");
    }

    public ResponseResultVo<T> addExtraIfTrue(boolean bool, String key, Object value) {
        if (bool) {
            addExtra(key, value);
        }
        return this;
    }

    public ResponseResultVo<T> addExtra(String key, Object value) {
        extra = extra == null ? new HashMap<>(16) : extra;
        extra.put(key, value);
        return this;
    }
}

自定义状态码

package com.springboot.study.common.response;

public enum ResultVoStatus {

    //枚举字段以逗号隔开,结尾是分号,必须写在前面
    OK(200,"请求成功"),
    Bad(400,"请求失败"),
    Faild(400,"exportTPdf的异常"),
    wordTopdf(400,"word生成pdf异常"),
    FileOK(200,"文件生成成功!"),
    Success(200,"保存成功"),
    Error(400,"服务异常,插入失败!"),
    ErrorUp(400,"OSS项目上传成功,更新数据失败"),
    FileNotFound(400,"文件未生成!") ,
    ErrorException(500,"内部服务异常"),
    test(500,"test");

    ;



    private  Integer status;
    private  String msg;

    private ResultVoStatus(Integer status, String msg){
        this.status=status;
        this.msg=msg;

    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

全局异常处理类

@RestControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    SysLogErrorMapper sysLogErrorMapper;

    @ExceptionHandler(HdException.class)
    public ResponseResultVo handleException(HttpServletRequest request,
                                            HttpServletResponse response, HdException e) {

        savelog(request, e);
        return ResponseResultVo.faild(ResultVoStatus.ErrorException)
                .addExtra("stackTrace", e.getStackTrace())
                .addExtra("exceptionMessage", e.getClass().getName() + ": " + e.getMessage());

    }
     private void savelog(HttpServletRequest request,
                         HdException e) {
        SysLogError sysLogError = new SysLogError();
        //获取IP
        String IP = IpUtil.getIpAddrByRequest(request);
        //获取请求路径
        String requestURI = request.getRequestURI();

        //获取请求参数
        Map<String, String[]> maps = request.getParameterMap();
        String cs = null;
        for (Map.Entry<String, String[]> entry : maps.entrySet()) {
            cs = entry.getKey() + ":" + Arrays.toString(entry.getValue()) + ";";
        }
        Exception f = null;
        if (e.getE() != null) {
            f = e.getE();
        } else {
            f = e;
        }
        StackTraceElement stackTraceElement = f.getStackTrace()[0];
        String errorMsg = "文件名:" + stackTraceElement.getFileName() +
                "\r\n类名:" + stackTraceElement.getClassName() +
                "\r\n方法名:" + stackTraceElement.getMethodName() +
                "\r\n抛出异常行号:" + stackTraceElement.getLineNumber() +
                "\r\n产生的异常为:" + e;

        //获取请求类型
        String type = request.getMethod();
        //头信息
        String header = request.getHeader(HttpHeaders.USER_AGENT);
        sysLogError.setCreateDate(new Date());
        sysLogError.setIp(IP);
        sysLogError.setRequestMethod(type);
        sysLogError.setRequestParams(cs);
        sysLogError.setRequestUri(requestURI);
        sysLogError.setUserAgent(header);
        sysLogError.setErrorInfo(errorMsg);
        sysLogErrorMapper.insertSelective(sysLogError);
    }
    }

获取IP的工具类


public class IpUtil {  
    
    /** 
     * @param 获取远程IP 
     * @return IP Address 
     */  
    public static String getIpAddrByRequest(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getRemoteAddr();  
        }  
        return ip;  
    }  
        
    /** 
     * @return 获取本机IP 
     * @throws SocketException 
     */  
    public static String getRealIp() throws SocketException {
        String localip = null;// 本地IP,如果没有配置外网IP则返回它  
        String netip = null;// 外网IP  
    
        Enumeration<NetworkInterface> netInterfaces =
            NetworkInterface.getNetworkInterfaces();  
        InetAddress ip = null;
        boolean finded = false;// 是否找到外网IP  
        while (netInterfaces.hasMoreElements() && !finded) {  
            NetworkInterface ni = netInterfaces.nextElement();  
            Enumeration<InetAddress> address = ni.getInetAddresses();  
            while (address.hasMoreElements()) {  
                ip = address.nextElement();  
                if (!ip.isSiteLocalAddress()   
                        && !ip.isLoopbackAddress()   
                        && ip.getHostAddress().indexOf(":") == -1) {// 外网IP  
                    netip = ip.getHostAddress();  
                    finded = true;  
                    break;  
                } else if (ip.isSiteLocalAddress()   
                        && !ip.isLoopbackAddress()   
                        && ip.getHostAddress().indexOf(":") == -1) {// 内网IP  
                    localip = ip.getHostAddress();  
                }  
            }  
        }  
        
        if (netip != null && !"".equals(netip)) {  
            return netip;  
        } else {  
            return localip;  
        }  
    }  
        
}  

建立表的SQL

/*
Navicat MySQL Data Transfer

Source Server         : local
Source Server Version : 50726
Source Host           : localhost:3306
Source Database       : study

Target Server Type    : MYSQL
Target Server Version : 50726
File Encoding         : 65001

Date: 2019-12-24 16:37:43
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for sys_log_error
-- ----------------------------
DROP TABLE IF EXISTS `sys_log_error`;
CREATE TABLE `sys_log_error` (
  `id` int(20) NOT NULL COMMENT 'id',
  `request_uri` varchar(200) DEFAULT NULL COMMENT '请求URI',
  `request_method` varchar(20) DEFAULT NULL COMMENT '请求方式',
  `request_params` text COMMENT '请求参数',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `ip` varchar(32) DEFAULT NULL COMMENT '操作IP',
  `error_info` text COMMENT '异常信息',
  `creator` bigint(20) DEFAULT NULL COMMENT '创建者',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_create_date` (`create_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='异常日志';


版权声明:本文为qq_36676682原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。