springboot logback多租户根据请求打印日志到不同文件
前提:不修改之前打印日志的代码(否则工作量太大),支持并发
服务改造为多租户服务,日志打印在一起不能区分是哪个租户请求的,在不改造项目之前打印日志代码的前提下,根据请求打印日志到不同文件。
一般项目中日志打印:
private static final Logger LOG = LoggerFactory.getLogger(xxx.class);
LOG.error("error");
LOG.info("info");
logback的主要对象
1.LoggerContext logback的核心对象,加载配置文件,存储loggerList……是log back的核心容器
2.Logger 这个Logger为logback的logger 它实现了slf4j的Logger对象,除了实现了所有的logger方法外,我们动态配置日志输出源,也需要里面的 addAppender(),detachAppender()方法
3.Appender 日志输出的最终实现, doAppend(E e)方法,最终实现了日志的输出,如果自定义appender 需要最终实现该接口。
注:以上对象并非logback全部核心对象,对于今天的日志动态输出,仅仅涉及到以上核心对象。
logback默认加载 classpath:logback.xml 配置,打印日志到文件中原理是通过 Appender 接口来实现不同的打印,logback原理请看:https://blog.csdn.net/qq_26462567/category_9307914.html
打印日志到文件中使用的 RollingFileAppender ,LOG.info("info");跟踪源码可以发现最后会调用当前Logger引用的Appender.doAppend 方法:
AppenderAttachableImpl:

所以我们重写doAppend
public class DynamicRollingFileAppenderextends RollingFileAppender {/*** 日志打印会调用此方法,进行复写,判断租户,根据租户打印到不同日志文件* @param eventObject*/public void doAppend(E eventObject) {String tenantType = RequestContext.getContext().getTenantType();if(StringUtils.isBlank(tenantType)){return;// throw new IllegalArgumentException("当前请求未找到租户类型");}// this.getName() 是在logback.xml中配置的 // 只打印当前租户的Append,RollingFileAppender追加器以租户类型标识开头的执行追加if(this.getName().startsWith(tenantType)){super.doAppend(eventObject);}}}
RequestContext 是当前线程文本类,存储当前线程中的一些变量以及租户信息,
TenantType是枚举类,定义租户信息,读者自行定义,这里不提供了。
public class RequestContext {private static final String TENANT_TYPE = "TENANT_TYPE";private Map values = new HashMap();private static final ThreadLocal LOCAL = new ThreadLocal() {@Overrideprotected RequestContext initialValue() {return new RequestContext();}};public static RequestContext getContext() {return LOCAL.get();}public static void clearContext() {LOCAL.remove();LOCAL.set(new RequestContext());}public Object get(String key) {return values.get(key);}public void remove(String key) {values.remove(key);}public RequestContext set(String key, Object value) {if (value == null) {values.remove(key);} else {values.put(key, value);}return this;}/*** 设置数据源* @param value* @return*/public RequestContext setDataSource(String value) {if (value == null) {values.remove(TENANT_TYPE);} else {values.put(TENANT_TYPE, value);}return this;}/*** 设置数据源* @param value* @return*/public RequestContext setTenantType(String value) {if (value == null) {values.remove(TENANT_TYPE);} else {values.put(TENANT_TYPE, value);}return this;}/*** Get current DataSource** @return data source name*/public String getTenantType() {return (String) values.get(TENANT_TYPE);}/*** Get current DataSource** @return data source name*/public String getRedisPre() {if(StringUtils.isBlank(this.getTenantType())){throw new IllegalArgumentException("当前请求未找到租户类型");}return TenantType.getRedisPre(this.getTenantType());}/*** Get current DataSource** @return data source name*/public String getDataSource() {return (String) values.get(TENANT_TYPE);}/*** Clear current DataSource** @return data source name*/public void clearDataSource() {remove(TENANT_TYPE);}}
利用 Filter 过滤器在请求进入后,根据请求域名来判断租户(每个租户的域名请求是不同的),并且把租户保存到 RequestContext 中,供当前后续代码获取。
public class SwitchTenantFilter implements Filter {/** 日志 */private static Logger logger = LoggerFactory.getLogger(SwitchTenantFilter.class);@Overridepublic void doFilter(ServletRequest req, ServletResponse resp,FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) resp;String host = request.getHeader("host");// 租户1访问if(host.contains("test1")){RequestContext.getContext().setDataSource("test1");}// 租户2访问if(host.contains("test2")){RequestContext.getContext().setDataSource("test2");}String dataSource = RequestContext.getContext().getDataSource();if(StringUtils.isBlank(dataSource)){logger.error("访问host:{},切换数据源失败!!!", host);filterChain.doFilter(request, response);return;}logger.info("访问host:{},切换数据源成功,数据源key:{}", host, RequestContext.getContext().getDataSource());filterChain.doFilter(request, response);// 清除 ThreadLocal 变量}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// TODO Auto-generated method stub}@Overridepublic void destroy() {// TODO Auto-generated method stub}}
在Configuration类中实例化这个Filter Bean,配置拦截
// ===SwitchTenantFilter 切换租户,在WebAccessLogFilter之前加载===@Beanpublic FilterRegistrationBean switchDataSourceFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();SwitchTenantFilter switchTenantFilter = new SwitchTenantFilter();registration.setFilter(switchTenantFilter);registration.addUrlPatterns("/*");registration.setName("switchDataSourceFilter");registration.setOrder(12);registration.setDispatcherTypes(DispatcherType.REQUEST);return registration;}
// ===SwitchTenantFilter===
最后配置logback.xml,将自定义的Appender配置进去,每个租户配置一个Appender,配置打印路径以及文件大小切换策略,把所有Appender在root中引用,打印会一层一层往上调用,最后会调用到root根logger,调用doAppend方法。
logback.xml:
%date{yyyy-MM-dd HH:mm:ss.SSS}\t%serialNo\t${APPNAME}\t%level\t%thread\t%logger{0}\t%logger{5}:%line\t%userName\t%clientIp:%clientPort\t%message%n ${test1PATH}test1.log ${test1PATH}test1-%d{yyyy-MM-dd}.%i.log 10MB 30 %date{yyyy-MM-dd HH:mm:ss.SSS}\t%serialNo\t%level\t%thread\t%logger{0}\t%logger{5}:%line\t%userName\t%clientIp:%clientPort\t%message%n ${test2PATH}test2.log ${test2PATH}test2-%d{yyyy-MM-dd}.%i.log 10MB 30 %date{yyyy-MM-dd HH:mm:ss.SSS}\t%serialNo\t%level\t%thread\t%logger{0}\t%logger{5}:%line\t%userName\t%clientIp:%clientPort\t%message%n ${test1PATH}test1-mysql.log ${test1PATH}test1-mysql-%d{yyyy-MM-dd}.%i.log 10MB 30 %date{yyyy-MM-dd HH:mm:ss.SSS}\t%serialNo\t%thread\t%userName\t%clientIp\t%message%n ${test2PATH}test2-mysql.log ${test2PATH}test2-mysql-%d{yyyy-MM-dd}.%i.log 10MB 30 %date{yyyy-MM-dd HH:mm:ss.SSS}\t%serialNo\t%thread\t%userName\t%clientIp\t%message%n
参考:
https://blog.csdn.net/qq_26462567/category_9307914.html
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
