简介
日志能记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。尤其是项目线上问题,不允许远程调试的情况下,只能依赖日志定位问题,如果日志写的好,那就能快速找到问题所在。反之,日志没写好,反而会影响程序的运行性能和稳定性。日志的用途大致可以分为:
- 问题追踪:辅助排查和定位线上问题,优化程序运行性能
- 状态监控:通过日志分析,可以监控系统的运行状态
- 安全审计:审计主要体现在安全上,可以发现非授权的操作
框架选择
SLF4J——Simple Logging Facade For Java,它是一个针对于各类Java日志框架的统一Facade抽象。Java日志框架众多——常用的有java.util.logging, log4j, logback,commons-logging, Spring框架使用的是Jakarta Commons Logging API (JCL)。Logback是log4j框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J。所以尽管框架组合选择的方式很多,但选择SLF4J + Logback的组合,性能高,可扩展性强,很多开源项目都是此选项。SpringBoot默认情况下也是。所以我们使用默认的SLF4J + Logback组合就好
何时记录日志
- 系统初始化: 系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录INFO日志,打印出参数以及启动完成态服务表述
- 系统核心操作: 核心操作是正常系统正常运行的重要指标,建议关键点用INFO级别日志,其它考虑使用DEBUG级别,因为如果日志打印过多也会影响性能
- 与业务流程不符时: 此时即是我们的业务场景不可能发生的情况,但是发生了,这时可以记录成日志,主要还是看开发人员的容忍程度了
- 抛出异常时: 质量非常高的报错,根据业务的情况使用warn或者error级别,此项目需要在统一异常中添加日志记录
- 第三方服务调用: 微服务架构体系中有一个重要的点就是第三方永远不可信,对于第三方服务远程调用建议打印请求和响应的参数,方便在和各个终端定位问题,不会因为第三方服务日志的缺失变得手足无措
日志格式
- 日志时间:精确到毫秒,格式:yyyy-MM-dd HH:mm:ss.SSS
- 日志级别:TRACE < DEBUG < INFO < WARN < ERROR
- 线程ID:即PID
- 分隔符:— 标识实际日志的开始
- 线程名:方括号括起来(可能会截断控制台输出)
- Logger名:通常使用源代码的类名,例如:c.e.b.BackendTemplateApplication,c.e.b是上三层的头字母
- 日志内容
- 异常堆栈(不一定有)
注:上面的格式其实就是Spring Boot日志默认的格式
例如:
//SpringBoot日志默认格式
2020-07-22 16:53:51.532 INFO 14912 --- [ main] c.e.b.BackendTemplateApplication: Starting BackendTemplateApplication...
日志级别
平时主要是用以下几种,分别讲一下怎么选择使用
- TRACE :追踪程序推进,如果要写的话,trace会特别多,所以一般都不输出
- DEBUG:输出调试性质内容,主要用于开发、测试阶段,这种级别的日志也特别多,因为有很多调试信息,比如参数信息,调试细节信息,返回值信息等等,主要方便开发时分析错误使用,一般生产环境下不输出,一是影响性能,二是信息太多容易掩盖重要信息
- INFO:主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,例如:初始化系统配置、业务状态变化信息,建议在生产环境中输出,并且INFO输出的信息可看作是软件产品的一部分,所以需要谨慎对待输出,不可太多,也不可无用
- WARN:主要输出警告性质的内容,WARN代表可恢复的异常,此次失败不影响下次业务的执行
- ERROR:主要针对于一些不可预知的信息,诸如:错误、异常等,输出时应该尽可能的详细,比如,入参,错误提示,异常对象等,这样才方便分析错误
其它规范
-
日志输出需使用点位符输出,而不是用字符拼接
//如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,浪费了系统资源 logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
改成
logger.debug("Processing trade with id: [{}] and symbol : [{}] ", id, symbol);
-
不要记录太多无用信息,不利于定位错误,且容易占太多磁盘资源,甚至撑爆磁盘
-
尽量不要在日志中调用对象的获取方法,除非肯定该对象一定不为空,否则容易出NPE异常
-
本地开发环境,可使用日志输出到控制台上,测试、生产可再写入文件中
-
生产还可以考虑异步写入文件,这样不会因为日志的写入,而浪费性能,但AOP+异步只能是方法或类级别,方法内日志记录不到,看个人选择
-
一个对象中通常只使用一个Logger对象,Logger应该是static final的
private static final Logger log = LoggerFactory.getLogger(Main.class);
-
尽量不要把日志输出写在循环内,容易影响运行时间
步骤
-
在application-dev.properties配置文件下,增加以下配置,其实正常情况不用这么多配置,这里写了很多都是默认值,给出的原因是,方便以后改配置功能。这里需要注意的是,
logs//backend_template.log
这里配置的日志目录,是一个相对路径,如果在IDE中运行,则和backend_template/src
平级,打包成jar包后运行,则和jar包平级,可自行配置,配置后程序会自行创建该目录及文件# 日志 # 最小能打印出的日志级别,root级别即所有日志 logging.level.root=INFO # 日志文件保存目录 logging.file.name=logs//backend_template.log # 启动时是否清除日志 logging.file.clean-history-on-start=false # 日志的最大尺寸,超过后分割 logging.file.max-size=10MB # 日志总大小,0为不设限 logging.file.total-size-cap=0 # 限制日志保留天数,到期自动删除 logging.file.max-history=15 # 控制台日志输出格式 logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} # 文件日志输出格式 logging.pattern.file=%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} # appender的log level输出样式 logging.pattern.level=%5p # 滚动文件名称 logging.pattern.rolling-file-name=${LOG_FILE} + ".%d{yyyy-MM-dd}.%i.gz" # 时间格式 logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss:SSS
-
接下来就可以在类中写日志了,值得注意的是,在导入Logger类的包时,不要导错了,要导slf4j包下的Logger,而不是其它的日志包,要不然到时出错了还不方便改错,示例如下
// 导入slf4j包 import org.slf4j.Logger; import org.slf4j.LoggerFactory; // 类中使用Log private static final Logger log = LoggerFactory.getLogger(Main.class); log.info("The request path is: [{}]",path);
-
那们我们现在开始来分别在系统初始化、系统核心操作、与业务流程不符、抛出异常时的情况下,来打印日志,这里是因为我们没有第三方服务调用,所示没办法演示,但不是说不重要,反而第三服务相当重要,一定要记录日志,因为第三方服务永远都不可信,记录下来好判断错误
-
首先是系统初始化时的日志,在本模板中,用到了Redis和Swagger2,在这些配置参数里,初始化时最重要的莫过于Redis 的hostname和端口和是否启用Swagger当然你也可以打印其它,你觉得重要的信息,方法一致,将com.example.backend_template.config.RedisConfigurer.java改为如下即可
package com.example.backend_template.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import redis.clients.jedis.JedisPoolConfig; /** * @ClassName RedisConfigurer Redis的配置类 * @Description * @Author L * @Date Create by 2020/6/26 */ @Configuration @EnableCaching //开启缓存 public class RedisConfigurer extends CachingConfigurerSupport { private static final Logger log = LoggerFactory.getLogger(RedisConfigurer.class); @Bean @ConfigurationProperties(prefix = "spring.redis") public JedisPoolConfig getRedisConfig() { JedisPoolConfig config = new JedisPoolConfig(); return config; } @Bean @ConfigurationProperties(prefix = "spring.redis") public JedisConnectionFactory getConnectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); JedisPoolConfig config = getRedisConfig(); factory.setPoolConfig(config); log.info("The hostname of the redis connection is:{}, and the port is: {}",factory.getHostName(),factory.getPort()); return factory; } public RedisTemplate<?, ?> getRedisTemplate() { RedisTemplate<?, ?> template = new StringRedisTemplate(getConnectionFactory()); return template; } }
-
将com.example.backend_template.config.Swagger2Config.java类改为如下代码
package com.example.backend_template.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @ClassName Swagger2Config swagger的配置内容即是创建一个Docket实例 * @Description * @Author L * @Date Create by 2020/6/30 */ @Configuration @EnableSwagger2 //启用swagger2 public class Swagger2Config { //是否开启 swagger-ui 功能,默认为false @Value("${swagger.enable:false}") private Boolean enable; private static final Logger log = LoggerFactory.getLogger(Swagger2Config.class); @Bean public Docket createRestApi() { log.info("Whether to open the Swagger service: {}",enable); return new Docket(DocumentationType.SWAGGER_2) .enable(enable) .pathMapping("/") .apiInfo(apiInfo()) .select() //需要Swagger描述的接口包路径,如果不想某接口暴露,可在接口上加@ApiIgnore注解 .apis(RequestHandlerSelectors.basePackage("com.example.backend_template.controller")) .paths(PathSelectors.any()) .build(); } //配置在线文档的基本信息 private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("BackendTemplate项目") .description("使基于SpringBoot的后端开发变得简单") .version("1.0") .build(); } }
-
完成这两个操作后,再次启动我们的项目,不论控制台和日志文件里,就会有如下两行系统初始化数据了,这样就可以很方便从日志信息中,知道系统关键配置信息
2020-07-24 15:34:40,998 INFO 9864 --- [ main] c.e.b.config.RedisConfigurer : The hostname of the redis connection is:localhost, and the port is: 6379 2020-07-24 15:34:41,538 INFO 9864 --- [ main] c.e.b.config.Swagger2Config : Whether to open the Swagger service: true
-
接下来记录系统核心操作,其中controller层的操作即可认为是核心操作,可记录重要入参与返回值,根据业务的不同,可以选择是INFO级别还是DEBUG级别
修改com.example.backend_template.config.RedisController.java类为如下代码package com.example.backend_template.controller; import com.example.backend_template.config.Swagger2Config; import com.example.backend_template.service.RedisService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * @ClassName RedisController * @Description * @Author L * @Date Create by 2020/6/26 */ @Api(tags = "redis简单测试接口") @RestController @RequestMapping("redis") public class RedisController { @Resource private RedisService redisService; private static final Logger log = LoggerFactory.getLogger(RedisController.class); @ApiOperation("向redis中存储`name`值") @ApiImplicitParam(name = "name",value = "名称值",defaultValue = "L",required = true) @PostMapping("/setRedis") public Boolean setRedis(@RequestBody String name) { log.info("The name value stored in Redis is: {}",name); return redisService.set("name", name); } @ApiOperation("向redis中取`name`值") @GetMapping("/getRedis") public String getRedis() { String name = redisService.get("name"); log.info("The name value obtained from Redis is: {}",name); return name; } }
-
接下来启动项目,用postman发起post请求访问http://localhost:8080/redis/setRedis,其中Body值设为{“L”},并再次发起get请求访问 http://localhost:8080/redis/getRedis,两次操作都成功后,控制台和日志中就会出现如下两条记录
2020-07-24 16:16:57,268 INFO 19660 --- [nio-8080-exec-1] c.e.b.controller.RedisController : The name value stored in Redis is: {"L"} 2020-07-24 16:19:33,650 INFO 6396 --- [nio-8080-exec-2] c.e.b.controller.RedisController : The name value obtained from Redis is: {"L"}
-
与业务流程不符就不演示了,主要是和自已预想状态不一致时记录日志,这里重点说一下抛出异常时的日志记录,因为日志的一大重要功能就是,在线上无法调试,只能通过日志来调试,一个好的日志可以快速的找出错误,所以这里尤为重要,因为我们是统一异常,要GlobalExceptionHandler类,我们要把可能报的错都记录下来,这样方便调试,修改com.example.backend_template.exception.GlobalExceptionHandler.java类的内容为如下
package com.example.backend_template.exception; import com.example.backend_template.utils.ResultData; import com.example.backend_template.utils.ResultUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.ConversionNotSupportedException; import org.springframework.beans.TypeMismatchException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.validation.BindException; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; /** * @ClassName GlobalExceptionHandler 全局统一处理异常 * @Description * @Author L * @Date Create by 2020/7/7 */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 该请求控制器存在,但请求HTTP方法与该控制器提供不符 * * @param ex * @param request * @return */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(405, "Http Request Method Not Supported!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Http Request Method Not Supported",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.METHOD_NOT_ALLOWED); } /** * content-type 内容设置类型不支持 * * @param ex * @param request * @return */ @ExceptionHandler(HttpMediaTypeNotSupportedException.class) protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(415, "Http Media Type Not Supported!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Http Media Type Not Supported",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.UNSUPPORTED_MEDIA_TYPE); } /** * content-type 内容设置类型不能接受 * * @param ex * @param request * @return */ @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(406, "Http Media Type Not Acceptable!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Http Media Type Not Acceptable",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.NOT_ACCEPTABLE); } /** * 缺少路径参数 * * @param ex * @param request * @return */ @ExceptionHandler(MissingPathVariableException.class) protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(500, "Missing Path Variable!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Missing Path Variable",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.INTERNAL_SERVER_ERROR); } /** * 缺少请求参数 * * @param ex * @param request * @return */ @ExceptionHandler(MissingServletRequestParameterException.class) protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Missing Servlet Request Parameter!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Missing Servlet Request Parameter",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * Servlet请求绑定出错 * * @param ex * @param request * @return */ @ExceptionHandler(ServletRequestBindingException.class) protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Servlet Request Binding!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Servlet Request Binding",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * 转换不支持 * * @param ex * @param request * @return */ @ExceptionHandler(ConversionNotSupportedException.class) protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(500, "Conversion Not Supported!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Conversion Not Supported",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.INTERNAL_SERVER_ERROR); } /** * 参数类型匹配失败 * * @param ex * @param request * @return */ @ExceptionHandler(TypeMismatchException.class) protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Type Mismatch!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Type Mismatch",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * HTTP 信息请求不可读 * * @param ex * @param request * @return */ @ExceptionHandler(HttpMessageNotReadableException.class) protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Http Message Not Readable!", ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Http Message Not Readable",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * HTTP 信息请求不可写 * * @param ex * @param request * @return */ @ExceptionHandler(HttpMessageNotWritableException.class) protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(500, "Http Message Not Writable!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Http Message Not Writable",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.INTERNAL_SERVER_ERROR); } /** * 参数校验出错 * * @param ex * @param request * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Method Argument Not Valid!", ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Method Argument Not Valid",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * 丢失了一部分请求信息 * * @param ex * @param request * @return */ @ExceptionHandler(MissingServletRequestPartException.class) protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Missing Servlet Request Part!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Missing Servlet Request Part",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * 参数绑定出错 * * @param ex * @param request * @return */ @ExceptionHandler(BindException.class) protected ResponseEntity<Object> handleBindException(BindException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(400, "Bind Exception!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Bind Exception",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.BAD_REQUEST); } /** * 根据请求url找不到控制器,即404异常 * * @param ex * @param request * @return */ @ExceptionHandler(NoHandlerFoundException.class) protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(404, "No Handler Found!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","No Handler Found",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.NOT_FOUND); } /** * 异步请求超时 * * @param ex * @param request * @return */ @ExceptionHandler(AsyncRequestTimeoutException.class) protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(503, "Async Request Timeout!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Async Request Timeout",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.SERVICE_UNAVAILABLE); } /** * 其它未统一的异常,都由以下handle处理,生产可写网络异常 * * @param ex * @param request * @return */ @ExceptionHandler(Exception.class) protected ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(500, "Server Error!",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","Server Error",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.INTERNAL_SERVER_ERROR); } //以下是自定义异常 /** * 没有找到该用户 * * @param ex * @param request * @return */ @ExceptionHandler(UserNotFoundException.class) protected ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) { ResultData<Object> errorBody = ResultUtils.fail(404, "User Not Found",ex.getLocalizedMessage()); log.error("Throw [{}] exception: {}","User Not Found",ex.getLocalizedMessage()); return new ResponseEntity<>(errorBody, HttpStatus.NOT_FOUND); } }
-
如果系统抛出异常,信息就会打印在控制台,以及储存在日志文件中,格式如下
2020-07-24 17:03:58,449 ERROR 13600 --- [nio-8080-exec-1] c.e.b.exception.GlobalExceptionHandler : Throw [User Not Found] exception: This user was not found!
至此日志规范就完成了,如果是业务开发,只需要向上面几步一样,在相应的地方添加日志记录就行了
项目地址
项目介绍:从零搭建 Spring Boot 后端项目
代码地址:https://github.com/xiaoxiamo/backend-template