Springboot对于Tomcat的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。
完整的源代码示例可以参照 源代码示例。
网友评论