springBoot篇2springBoot日志篇

Posted 革凡成圣211

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springBoot篇2springBoot日志篇相关的知识,希望对你有一定的参考价值。

目录

一、日志有什么作用

作用1:快速定位问题的所在之处(最主要)

作用2:记录用户的登录日志

作用3:记录系统的操作日志

作用4:记录方法的执行时间

二、日志怎样使用

  ①先得到日志对象(slf4j的Logger对象)  ​​​编辑

②根据日志等级来输出日志内容

 三、日志的级别(由低到高:6个)

有哪些级别:

     trace

     debug

     info

     warn

    error

    fatal

日志的级别规则:

日志的级别设置:

全局日志(作用于整个项目):

设置局部日志(指定某一个类)

日志的持久化(保存到磁盘)

持久化的方式1:设置保存路径 

持久化方式2:设置日志的保存名称

更加简易的添加日志的方式:添加lombok

第一步:需要安装插件(EditStarters)

第二步:添加注解@Slf4j

为什么@Slf4j可以自动生成这样的一个log对象呢?(lombok的实现原理)

lombok有哪些注解(一览)


一、日志有什么作用

       日志是程序的重要组成部分,如果程序报错了,不打开控制台查看日志,那么很难找到问题的所在地方。


作用1:快速定位问题的所在之处(最主要)

因此,日志有一个很重要的作用,那就是:方便快速定位问题的所在之处

当程序在线上运行的时候出现了bug,那就需要首先查看日志来锁定bug。


作用2:记录用户的登录日志

       假如当某一个用户一天之内的登录次数达到了几万次,那么就需要通过日志来记录这一个用户的非法登录行为,判定用户是正常登录还是恶意的破解


作用3:记录系统的操作日志

方便数据恢复定位操作人


作用4:记录方法的执行时间

记录到具体的方法执行的时间,方便为以后优化程序提供数据支持


二、日志怎样使用

  ①先得到日志对象(slf4j的Logger对象)  ​​


②根据日志等级来输出日志内容

public void sayHi()
         //各个级别的日志输出
         logger.info("I am info");
         logger.debug("I am debug");
         logger.warn("我是 warn");
         logger.error("我是 error");
    

 三、日志的级别(由低到高:6个)

有哪些级别:

     trace

      少许的日志,这一个是最低的级别。


     debug

        这个级别用于"调试"的级别


     info

       这个级别是普通的日志信息。(如果不指定日志的级别,那么默认就是这个


     warn

       警告日志:有一些地方存在警告的风险的,就需要使用到这个warn级别的日志


    error

       错误级别的日志,如果发生了一些错误,那么就需要使用这个级别来记录。


    fatal

       发生了比较致命的情况,需要由系统来进行输出,不可以由用户手动进行debug输出。


日志的级别规则:

 规则1:越往上收到的消息就越少,出现的频率就越低

 规则2:当设置了一个级别之后,只有比这个级别及其更高的日志级别才可以输出,否则不可以输出。

 ​​​​​​​​


日志的级别设置:

全局日志(作用于整个项目):

需要在application.properties这一个配置文件当中进行日志等级的设置。

可以设置为:trace、debug、info、warning、error、fatal

 

然后启动一下springBoot项目:


设置局部日志(指定某一个类)

需要使用:logging.level.类的路径

需要注意的是:当局部日志全局日志冲突的时候,一般情况下以局部日志的为标准

局部日志的优先级>全局日志的级别


日志的持久化(保存到磁盘)

持久化的方式1:设置保存路径 

配置文件中需要设置日志的保存路径,当设置了保存的路径之后,那么日志就会自动持久化。

注意事项:

配置文件当中不可以使用\\D这样的"\\"的方式来进行路径的指明。

要改成:"/"的方式

      这样子,只要项目一启动,那么就会在E:/demo4/log这个位置多出来一个文件夹log,然后会在这个log文件夹下面多一个配置文件出来。

如果真的想改成"\\"的形式来进行保存,那么就需要使用到转义:

   


持久化方式2:设置日志的保存名称

配置文件当中设置日志的保存名称,那么日志也会自动进行保存。

然后就可以在对应的路径下面看到这一个日志了:


更加简易的添加日志的方式:添加lombok

第一步:需要安装插件(EditStarters)


第二步:添加注解@Slf4j

代码实现:

/**
 * @author 25043
 */
@Controller
@ResponseBody
@RequestMapping("/user")

@Slf4j
public class UserController 

    @RequestMapping("/sayHi2")
    public void sayHi2()
        log.info("I am info");
        log.error("I am debug");
        log.warn("我是warn");
        log.error("我是error");
    


为什么@Slf4j可以自动生成这样的一个log对象呢?(lombok的实现原理)

编译时期,@Slf4j会为当前类当中设置一个静态从常量属性,这一个属性就是log

通过查看UserController编译之后的.class文件,可以看到这个属性。

UserController的字节码(.class文件) 


lombok有哪些注解(一览)

@Data注解: 

 

 这些注解的原理,就是在编译时期添加对应的方法

SpringBoot实战 之 接口日志篇

在本篇文章中不会详细介绍日志如何配置、如果切换另外一种日志工具之类的内容,只用于记录作者本人在工作过程中对日志的几种处理方式。

1. Debug 日志管理

在开发的过程中,总会遇到各种莫名其妙的问题,而这些问题的定位一般会使用到两种方式,第一种是通过手工 Debug 代码,第二种则是直接查看日志输出。Debug 代码这种方式只能在 IDE 下使用,一旦程序移交部署,就只能通过日志来跟踪定位了。

在测试环境下,我们无法使用 Debug 代码来定位问题,所以这时候需要记录所有请求的参数及对应的响应报文。而在 数据交互篇 中,我们将请求及响应的格式都定义成了Json,而且传输的数据还是存放在请求体里面。而请求体对应在 HttpServletRequest 里面又只是一个输入流,这样的话,就无法在过滤器或者拦截器里面去做日志记录了,而必须要等待输入流转换成请求模型后(响应对象转换成输出流前)做数据日志输出。

有目标那就好办了,只需要找到转换发生的地方就可以植入我们的日志了。通过源码的阅读,终于在 AbstractMessageConverterMethodArgumentResolver 个类中发现了我们的期望的那个地方,对于请求模型的转换,实现代码如下:

@SuppressWarnings("unchecked")
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
        Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    MediaType contentType;
    boolean noContentType = false;
    try {
        contentType = inputMessage.getHeaders().getContentType();
    }
    catch (InvalidMediaTypeException ex) {
        throw new HttpMediaTypeNotSupportedException(ex.getMessage());
    }
    if (contentType == null) {
        noContentType = true;
        contentType = MediaType.APPLICATION_OCTET_STREAM;
    }

    Class<?> contextClass = (parameter != null ? parameter.getContainingClass() : null);
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
    if (targetClass == null) {
        ResolvableType resolvableType = (parameter != null ?
                ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
        targetClass = (Class<T>) resolvableType.resolve();
    }

    HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
    Object body = NO_VALUE;

    try {
        inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

        for (HttpMessageConverter<?> converter : this.messageConverters) {
            Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
            if (converter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                if (genericConverter.canRead(targetType, contextClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }
                    if (inputMessage.getBody() != null) {
                        inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                        body = genericConverter.read(targetType, contextClass, inputMessage);
                        body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                    }
                    break;
                }
            }
            else if (targetClass != null) {
                if (converter.canRead(targetClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }
                    if (inputMessage.getBody() != null) {
                        inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                        body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
                        body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }
    }
    catch (IOException ex) {
        throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
    }

    if (body == NO_VALUE) {
        if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                (noContentType && inputMessage.getBody() == null)) {
            return null;
        }
        throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
    }

    return body;
}

  

上面的代码中有一处非常重要的地方,那就在在数据转换前后都存在 Advice 相关的方法调用,显然,只需要在 Advice 里面完成日志记录就可以了,下面开始实现自定义 Advice。

首先,请求体日志切面 LogRequestBodyAdvice 实现如下:

@ControllerAdvice
public class LogRequestBodyAdvice implements RequestBodyAdvice {

    private Logger logger = LoggerFactory.getLogger(LogRequestBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
                                  MethodParameter parameter, Type targetType,
                                  Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
                                           MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                                MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        Method method = parameter.getMethod();
        String classMappingUri = getClassMappingUri(method.getDeclaringClass());
        String methodMappingUri = getMethodMappingUri(method);
        if (!methodMappingUri.startsWith("/")) {
            methodMappingUri = "/" + methodMappingUri;
        }
        logger.debug("uri={} | requestBody={}", classMappingUri + methodMappingUri, JSON.toJSONString(body));
        return body;
    }

    private String getMethodMappingUri(Method method) {
        RequestMapping methodDeclaredAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
        return methodDeclaredAnnotation == null ? "" : getMaxLength(methodDeclaredAnnotation.value());
    }

    private String getClassMappingUri(Class<?> declaringClass) {
        RequestMapping classDeclaredAnnotation = declaringClass.getDeclaredAnnotation(RequestMapping.class);
        return classDeclaredAnnotation == null ? "" : getMaxLength(classDeclaredAnnotation.value());
    }

    private String getMaxLength(String[] strings) {
        String methodMappingUri = "";
        for (String string : strings) {
            if (string.length() > methodMappingUri.length()) {
                methodMappingUri = string;
            }
        }
        return methodMappingUri;
    }
}

  

得到日志记录如下:

2017-05-02 22:48:15.435 DEBUG 888 --- [nio-8080-exec-1] c.q.funda.advice.LogRequestBodyAdvice    : uri=/sys/user/login | 
requestBody={"password":"123","username":"123"}

对应的,响应体日志切面 LogResponseBodyAdvice 实现如下:

@ControllerAdvice
public class LogResponseBodyAdvice implements ResponseBodyAdvice {

    private Logger logger = LoggerFactory.getLogger(LogResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        logger.debug("uri={} | responseBody={}", request.getURI().getPath(), JSON.toJSONString(body));
        return body;
    }
}

  

得到日志记录如下:

2017-05-02 22:48:15.520 DEBUG 888 --- [nio-8080-exec-1] c.q.funda.advice.LogResponseBodyAdvice   : uri=/sys/user/login | 
responseBody={"code":10101,"msg":"手机号格式不合法"}

  

2. 异常日志管理

Debug 日志只适用于开发及测试阶段,一般应用部署生产,鉴于日志里面的敏感信息过多,往往只会在程序出现异常时输出明细的日志信息,在 ExceptionHandler 标注的方法里面输入异常日志无疑是最好的,但摆在面前的一个问题是,如何将 @RequestBody 绑定的 Model 传递给异常处理方法?我想到的是通过 ThreadLocal 这个线程本地变量来存储每一次请求的 Model,这样就可以贯穿整个请求处理流程,下面使用 ThreadLocal 来协助完成异常日志的记录。

在绑定时,将绑定 Model 有存放到 ThreadLocal:

 

@RestController
@RequestMapping("/sys/user")
public class UserController {

    public static final ThreadLocal<Object> MODEL_HOLDER = new ThreadLocal<>();

    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        MODEL_HOLDER.set(webDataBinder.getTarget());
    }

}

  

异常处理时,从 ThreadLocal 中取出变量,并做相应的日志输出:

@ControllerAdvice
@ResponseBody
public class ExceptionHandlerAdvice {

    private Logger logger = LoggerFactory.getLogger(ExceptionHandlerAdvice.class);

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e, HttpServletRequest request) {
        logger.error("uri={} | requestBody={}", request.getRequestURI(),
                JSON.toJSONString(UserController.MODEL_HOLDER.get()));
        return new Result(ResultCode.WEAK_NET_WORK);
    }

}

  

当异常产生时,输出日志如下:

2017-05-03 21:46:07.177 ERROR 633 --- [nio-8080-exec-1] c.q.funda.advice.ExceptionHandlerAdvice  : uri=/sys/user/login | 
requestBody={"password":"123","username":"13632672222"}

  

注意:当 Mapping 方法中带有多个参数时,需要将 @RequestBody 绑定的变量当作方法的最后一个参数,否则 ThreadLocal 中的值将会被其它值所替换。如果需要输出 Mapping 方法中所有参数,可以在 ThreadLocal 里面存放一个 Map 集合。

项目的 github 地址:https://github.com/qchery/funda


原文地址:http://blog.csdn.net/chinrui/article/details/71056847

以上是关于springBoot篇2springBoot日志篇的主要内容,如果未能解决你的问题,请参考以下文章

springBoot日志篇

JAVAEE——SpringBoot日志篇:日志框架SLF4j日志配置日志使用切换日志框架

SpringBoot日记——日志框架篇

SpringBoot实战 之 接口日志篇

SpringBoot | 第二十三章:日志管理之整合篇

Springboot联结万物学习笔记--Springboot微服务基础搭建篇-- SpringBoot中日志的使用