假设Spring 框架要打印日志,应该选择中其中哪一个组件?
发现哪个都不能选,只能基于应用实际使用的日志组件来。不然日志打印就会多份。
怎么找到应用实际使用的日志组件, Apache Commons Loging 解决了这个问题
Commons Loging 本身只提供日志接口,具体实现在运行时动态寻找对应组件,比如:log4j、jdk14looger 等。但这种动态绑定的方式当系统特别宠大的时候会出现绑定失败的问题。现在比较流行的slf4j 基于静态绑定的方式解决了这个问题;
slf4j 本身也只提供日志接口,与commons loging 不同的是其采用在classPath 加入以下jar包来表示具体采用哪种实现 :
• slfj-log4j12.jar (表示指定 log4j)
• slf4j-jdk14.jar(表示指定jdk Looging)
• slf4j-jcl.jar(表示指定jcl)
• log4j-slf4j-impl(表示指定log4j2)
• logback-classic(表示指定logback)
log4j的配置
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-log4j12
1.7.22
log4j
log4j
1.2.17
package com.maltose.studyproject.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author: sgw
* @Date 2019/11/10 16:06
* @Description: Slf4j日志
**/
public class HelloSlf4j {
private static Logger logger
= LoggerFactory.getLogger(HelloSlf4j.class);
public static void main(String[] args) {
logger.error("abc");
}
}
正常情况下,就会打印下边的日志
可是由于spring boot默认采用的是logback打印日志,所以如果是spring boot项目的话,会报如下的错
即找到两个日志框架(log4j与logback),所以报错了;
spring boot在spring-boot-starter-logging里包含了logback框架
org.springframework.boot
spring-boot-starter-logging
而spring-boot-starter包含了spring-boot-starter-logging,所以只要使用spring boot
就默认使用了logback作为日志框架了
org.springframework.boot
spring-boot-starter
那么,想要使用自己的log4j,而不使用spring boot默认的logback,需要使用exclusions节点将spring-boot-starter-logging这个依赖排除掉
org.springframework.boot
spring-boot-starter
spring-boot-starter-logging
org.springframework.boot
记住,要加上log4j.properties文件,否则报错
此时,日志打印使用的就是log4j了:
之前老项目使用的jcl日志框架,现在想要换成slf4j作为日志框架,如果从头开始替换,工作量会非常大,此时,只需引入一个jar即可:jcl-over-slf4j.jar
验证上边的jcl-over-slf4j.jar
先使用老的jcl打印日志,添加jcl依赖:
commons-logging
commons-logging-api
1.1
commons-logging
commons-logging
1.1
log4j
log4j
1.2.17
测试代码:
package com.maltose.studyproject.controller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @Author: sgw
* @Date 2019/11/10 17:16
* @Description: JCL日志
**/
public class HelloJcl {
public static void main(String[] args) {
Log logger = LogFactory.getLog(HelloJcl.class);
logger.error("error message jcl");
}
}
结果:
jcl升级日志框架到slf4j:
在上边的基础上,再引入下边的包
org.slf4j
slf4j-api
1.7.25
org.apache.logging.log4j
log4j-core
2.3
org.apache.logging.log4j
log4j-slf4j-impl
2.3
org.apache.logging.log4j
log4j-api
2.3
com.lmax
disruptor
3.3.2
org.slf4j
jcl-over-slf4j
1.7.25
添加log4j2.xml:
还是运行刚才的代码:
修改log4j2.xml,看日志有没有改变(加上了%d)
日志变了:
说明现在走的就是slf4j下的log4j2的日志框架了;
定义输出源—— 输出到哪里去,用什么方式输出,Appenders节点下有三种配置方式
方式一:Console
target=“SYSTEM_OUT”:即System.out.print();
target=“SYSTEM_ERR”:即System.err.print();
PatternLayout :模版匹配文件
方式二:File
fileName:日志保存的目录
append:要不要追加日志 false——不追加
再次执行方法:
方式三:RollingFile 滚动保存
系统异常设计的出发点:
如何做到以上3点?
分类一、 内部异常
分类二、业务异常
统一拦截的目的是确保出去的异常是可控的, 调用方能够明白的异常信息。
这里出口是指系统对外统一响应逻辑,一般我们可分三类场景
checkedException 与uncheckedException 声明原则
总结异常设计与处理原则
异常的定义技巧
新增商品的方法,需要接收两个参数:
price:价格
name:名称
然后对数据进行校验:
价格不可以为空(我们要做的就是:价格为空时,做统一异常处理)
新增时,自动生成ID,然后随商品对象一起返回;
实体类:
package com.leyou.item.pojo;
/**
* @Author: sgw
* @Date 2019/12/15 10:51
* @Description: TODO
**/
@Data
public class Item {
private Integer id;
private String name;
private Long price;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
}
service:
package com.leyou.item.service;
import com.leyou.item.pojo.Item;
import org.springframework.stereotype.Service;
import java.util.Random;
/**
* @Author: sgw
* @Date 2019/12/15 10:53
* @Description: TODO
**/
@Service
public class ItemService {
public Item saveItem(Item item){
int id=new Random().nextInt(100);
item.setId(id);
return item;
}
}
全局异常处理类:
package com.leyou.common.utils.com.leyou.common.advice;
import com.leyou.common.utils.com.leyou.common.advice.com.leyou.common.exception.LyException;
import com.leyou.common.vo.ExceptionResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* @Author: sgw
* @Date 2019/12/14 22:20
* @Description: 拦截所有Controller里的异常
**/
@ControllerAdvice
public class ControllerHanderException {
@ExceptionHandler(LyException.class)
public ResponseEntity handleException(LyException e) {
return ResponseEntity.status(e.getExceptionEnum().getCode()).body(new ExceptionResult(e.getExceptionEnum()));
}
}
service服务引用common(不是微服务的话,则忽略此处):
com.leyou.common
ly-common
1.0.0-SNAPSHOT
注意,通用异常处理器想生效,靠的是注解@ControllerAdvice,且这个异常处理类写在了common服务里,service引用common;所以common里的异常处理类所在的包名,必须是service扫描的包名完全一致(或者是service扫描包的子包),即下边两个包名必须一致:
枚举:
package com.leyou.common.utils.enums;
/**
* @Author: sgw
* @Date 2019/12/15 11:26
* @Description: 异常枚举
**/
public enum ExceptionEnum {
//枚举只能放在做最前边(相当于创建了一个对象,枚举类只能自己创建对象,其他类是不可以创建枚举类的对象的)
PRICE_CANNOT_BE_NULL(400,"价格不能为空"),
NMAE_CANNOT_BE_NULL(401,"名称不能为空"),
ID_CANNOT_BE_NULL(402,"ID不能为空");
private int code;
private String msg;
ExceptionEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
ExceptionEnum() {
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
自定义异常:
package com.leyou.common.utils.com.leyou.common.advice.com.leyou.common.exception;
import com.leyou.common.utils.enums.ExceptionEnum;
/**
* @Author: sgw
* @Date 2019/12/15 11:22
* @Description: 自定义异常
**/
public class LyException extends RuntimeException{
private ExceptionEnum exceptionEnum;
public LyException() {
}
public LyException(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
}
public ExceptionEnum getExceptionEnum() {
return exceptionEnum;
}
public void setExceptionEnum(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
}
}
将异常封装为自定义格式:
package com.leyou.common.vo;
import com.leyou.common.utils.enums.ExceptionEnum;
/**
* @Author: sgw
* @Date 2019/12/15 12:04
* @Description: 封装响应结果
**/
public class ExceptionResult {
private int status;
private String message;
private Long timestamp;
public ExceptionResult(ExceptionEnum em) {
this.status = em.getCode();
this.message=em.getMsg();
this.timestamp=System.currentTimeMillis();
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
}
controller抛异常的时候,抛出自定义异常:
package com.leyou.item.web;
import com.leyou.common.utils.com.leyou.common.advice.com.leyou.common.exception.LyException;
import com.leyou.common.utils.enums.ExceptionEnum;
import com.leyou.item.pojo.Item;
import com.leyou.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: sgw
* @Date 2019/12/15 10:57
* @Description: TODO
**/
@RestController("/item")
public class ItemController {
@Autowired
private ItemService itemService;
@PostMapping
public ResponseEntity- saveItem(Item item) {
if (item.getPrice() == null) {
throw new LyException(ExceptionEnum.PRICE_CANNOT_BE_NULL);
}
item = itemService.saveItem(item);
return ResponseEntity.status(HttpStatus.CREATED).body(item);
}
}