美文网首页
Springboot中对Tomcat的AccessLog进行过滤

Springboot中对Tomcat的AccessLog进行过滤

作者: 一曲畔上 | 来源:发表于2019-12-18 15:52 被阅读0次

Springboot对于Tomcat的accesslog支持条件输出:


条件输出accesslog

但是,我们查看源代码:


条件的使用
发现使用条件时,需要在request中添加一个参数值,根据该值的有无来决定是否启用日志。这样会导致业务方需要去修改接口!
如果我不想修改业务接口,又有类似需求,那么我们可以按照如下方式对tomcat进行定制:

1,定制FiltedAccessLogValve

import java.util.List;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.AccessLogValve;

/**
 * author:  Daniel.Cao
 * date:    2019年12月17日
 * time:    下午1:24:43
 *
 */
public class FiltedAccessLogValve extends AccessLogValve {
    private List<RequestEntry> filtedRequestEntries;
    
    @Override
    public void log(Request request, Response response, long time) {
        String uri = request.getDecodedRequestURI();
        String method = request.getMethod();
        
        // 不跳过,输出日志
        if (uri == null || uri.trim().equals("")) {
            super.log(request, response, time);
            
            return;
        }
        
        uri = uri.trim();
        if (filtedRequestEntries != null && !filtedRequestEntries.isEmpty()) {
            boolean needSkip = false;
            for (RequestEntry _entry : filtedRequestEntries) {
                String _uri = _entry.getUri();
                if (_uri == null || _uri.trim().equals("")) {
                    continue;
                }
                boolean uriChecked = false;
                Integer _type = _entry.getType();
                if (_type == null) {
                    _type = 0;
                }
                if (uri.equalsIgnoreCase(_entry.getUri())) {
                    uriChecked = true;
                } else {
                    if (_type.intValue() == 1 && uri.toLowerCase().startsWith(_uri.toLowerCase())) { // 前缀匹配
                        uriChecked = true;
                    } else if (_type.intValue() == 2 && uri.toLowerCase().endsWith(_uri.toLowerCase())) { // 后缀匹配
                        uriChecked = true;
                    } else if (_type.intValue() == 3 && uri.toLowerCase().matches(_uri.toLowerCase())) {
                        uriChecked = true;
                    }
                }
                if (uriChecked) {
                    String _method = _entry.getMethod();
                    // 跳过,不输出日志
                    if (_method == null || _method.trim().equals("") || _method.trim().equals("-")) {
                        needSkip = true;
                        break;
                    }
                    
                    _method = _method.trim();
                    // 跳过,不输出日志
                    if (method != null && method.trim().equalsIgnoreCase(_method)) {
                        needSkip = true;
                        break;
                    }
                }
            }
            // 跳过,不输出日志
            if (needSkip) {
                return;
            }
        }
        
        // 不跳过,输出日志
        super.log(request, response, time);
    }
    
    /**
     * 
     * @return filtedRequestEntries
     */
    public List<RequestEntry> getFiltedRequestEntries() {
        return filtedRequestEntries;
    }
    
    /**
     * 
     * @param filtedRequestEntries filtedRequestEntries
     */
    public void setFiltedRequestEntries(List<RequestEntry> filtedRequestEntries) {
        this.filtedRequestEntries = filtedRequestEntries;
    }
    
    public static class RequestEntry {
        private String uri; // 请求的uri
        private String method; //求情的method
        private Integer type; //uri匹配模式:0/null-完全匹配;1-前缀匹配;2-后缀匹配;3-正则表达式
        
        /**
         * 
         * @return uri
         */
        public String getUri() {
            return uri;
        }
        /**
         * 
         * @param uri uri
         */
        public void setUri(String uri) {
            this.uri = uri;
        }
        /**
         * 
         * @return method
         */
        public String getMethod() {
            return method;
        }
        /**
         * 
         * @param method method
         */
        public void setMethod(String method) {
            this.method = method;
        }
        /**
         * 
         * @return type
         */
        public Integer getType() {
            return type;
        }
        /**
         * 
         * @param type type
         */
        public void setType(Integer type) {
            this.type = type;
        }
    }
}

其实现原理就是根据配置的uri和method来确定是否要输出到accesslog。
2,定制FiltedTomcatWebServerFactoryCustomizer

import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.core.env.Environment;

/**
 * author:  Daniel.Cao
 * date:    2019年12月17日
 * time:    下午3:37:14
 *
 */
public class FiltedTomcatWebServerFactoryCustomizer extends TomcatWebServerFactoryCustomizer {
    private final ServerProperties serverProperties;
    private final FiltedAccessLogProperties filtedServerProperties;
    
    public FiltedTomcatWebServerFactoryCustomizer(Environment environment, 
            ServerProperties serverProperties, FiltedAccessLogProperties filtedServerProperties) {
        super(environment, serverProperties);
        
        this.serverProperties = serverProperties;
        this.filtedServerProperties = filtedServerProperties;
    }

    @Override
    public void customize(ConfigurableTomcatWebServerFactory factory) {
        boolean accesslogEnable = serverProperties.getTomcat().getAccesslog().isEnabled();
        // 阻止父节点进行AccessLog组装
        serverProperties.getTomcat().getAccesslog().setEnabled(false);
        super.customize(factory);
        
        // 恢复设置
        serverProperties.getTomcat().getAccesslog().setEnabled(accesslogEnable);
        
        // 配置AccessLog
        if (accesslogEnable) {
            customizeAccessLog(factory);
        }
    }
    
    /**
     * 
     * @param factory factory
     */
    private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
        FiltedAccessLogValve valve = new FiltedAccessLogValve();
        PropertyMapper map = PropertyMapper.get();
        ServerProperties.Tomcat.Accesslog accessLogConfig = tomcatProperties.getAccesslog();
        map.from(accessLogConfig.getConditionIf()).to(valve::setConditionIf);
        map.from(accessLogConfig.getConditionUnless()).to(valve::setConditionUnless);
        map.from(accessLogConfig.getPattern()).to(valve::setPattern);
        map.from(accessLogConfig.getDirectory()).to(valve::setDirectory);
        map.from(accessLogConfig.getPrefix()).to(valve::setPrefix);
        map.from(accessLogConfig.getSuffix()).to(valve::setSuffix);
        map.from(accessLogConfig.getEncoding()).whenHasText().to(valve::setEncoding);
        map.from(accessLogConfig.getLocale()).whenHasText().to(valve::setLocale);
        map.from(accessLogConfig.isCheckExists()).to(valve::setCheckExists);
        map.from(accessLogConfig.isRotate()).to(valve::setRotatable);
        map.from(accessLogConfig.isRenameOnRotate()).to(valve::setRenameOnRotate);
        map.from(accessLogConfig.getMaxDays()).to(valve::setMaxDays);
        map.from(accessLogConfig.getFileDateFormat()).to(valve::setFileDateFormat);
        map.from(accessLogConfig.isIpv6Canonical()).to(valve::setIpv6Canonical);
        map.from(accessLogConfig.isRequestAttributesEnabled()).to(valve::setRequestAttributesEnabled);
        map.from(accessLogConfig.isBuffered()).to(valve::setBuffered);
        
        // 添加不输出日志的请求
        map.from(this.filtedServerProperties.getFiltedRequestEntries()).to(valve::setFiltedRequestEntries);
        
        factory.addEngineValves(valve);
    }
}

其原理就是是用我们自己定义的FiltedAccessLogValve。
3,配置FiltedTomcatWebServerFactoryCustomizer到我们的项目中

    /**
     * +自定义带有access log过滤功能的tomcat容器
     * @param environment environment
     * @param serverProperties serverProperties
     * @param filtedAccessLogProperties filtedAccessLogProperties
     * @return FiltedTomcatWebServerFactoryCustomizer
     */
    @Primary
    @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
    @Bean
    public FiltedTomcatWebServerFactoryCustomizer filtedTomcatWebServerFactoryCustomizer(Environment environment,
                ServerProperties serverProperties, FiltedAccessLogProperties filtedAccessLogProperties) {
        return new FiltedTomcatWebServerFactoryCustomizer(environment, serverProperties, filtedAccessLogProperties);
    }

注意别忘记引入属性配置文件

@EnableConfigurationProperties({ ServerProperties.class, FiltedAccessLogProperties.class })

4,修改配置文件,阻止系统自定义tomcat的加载

spring: 
  autoconfigure: 
    exclude: org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration

5,添加需要过滤的uil

server: 
    accesslog: 
      enabled: true
      filted-request-entries: 
       - uri: /
         method: HEAD
         type: 0
       - uri: /a/
         method: GET
         type: 1

这样,项目发布后"HEAD /"和"GET /a/xxxxxxx"这样的请求的access log就不会再输出了。
注意:使用Springboot版本是2.2.2.RELEASE。
完整的源代码示例可以参照 源代码示例

相关文章

网友评论

      本文标题:Springboot中对Tomcat的AccessLog进行过滤

      本文链接:https://www.haomeiwen.com/subject/fqjunctx.html