0. 问题来源
https://blog.csdn.net/a116475939/article/details/88526218
继上一篇博客之后,已经可以在发布cxf接口,并且记录日志报文到日志文件中。
但是所有接口的日志都在一个文件中,这样很不合理,不便于追溯问题查找日志。
所以今天则要实现这个功能。
1. 问题思考
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
SpringBus springBus = new SpringBus();
LoggingFeature logFeature = new LoggingFeature();
logFeature.setPrettyLogging(true);
logFeature.initialize(springBus);
springBus.getFeatures().add(logFeature);
return springBus;
}
在上个博客中,笔者使用LoggingFeature作为默认的日志记录工具。
然而,进入LoggingFeature之后,发现这个类已经弃用了。
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.interceptor.AbstractLoggingInterceptor;
import org.apache.cxf.interceptor.InterceptorProvider;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
@NoJSR250Annotations
@Deprecated
@Provider(value = Type.Feature)
*
* @deprecated use the logging module rt/features/logging instead
*/
public class LoggingFeature extends AbstractFeature {
private static final int DEFAULT_LIMIT = AbstractLoggingInterceptor.DEFAULT_LIMIT;
private static final LoggingInInterceptor IN = new LoggingInInterceptor(DEFAULT_LIMIT);
通过,这句话,了解到已经这块东西应该是废弃 并移植到另外的模块里了
use the logging module rt/features/logging instead
于是我就找到了
org.apache.cxf
cxf-rt-features-logging
3.2.5
然后查找资料,配置springBus,添加日志拦截器LoggingInInterceptor/LoggingOutInterceptor
/**
* @description 启用日志记录
* @author fg
* @date 2019年1月11日
*/
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
SpringBus springBus = new SpringBus();
LoggingInInterceptor ipt = new LoggingInInterceptor();
LoggingOutInterceptor opt = new LoggingOutInterceptor();
ipt.setPrettyLogging(true);
ipt.setLimit(LOG_LIMIT);
opt.setPrettyLogging(true);
opt.setLimit(LOG_LIMIT);
springBus.getInInterceptors().add(ipt);
springBus.getOutInterceptors().add(opt);
return springBus;
}
然而日志并未及时生效。
2. 深入分析
通过查看LoggingInInterceptor源码
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.ext.logging;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.ext.logging.event.DefaultLogEventMapper;
import org.apache.cxf.ext.logging.event.LogEvent;
import org.apache.cxf.ext.logging.event.LogEventSender;
import org.apache.cxf.ext.logging.event.PrintWriterEventSender;
import org.apache.cxf.ext.logging.slf4j.Slf4jVerboseEventSender;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.io.CachedWriter;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.phase.PhaseInterceptor;
/**
*
*/
@NoJSR250Annotations
public class LoggingInInterceptor extends AbstractLoggingInterceptor {
class LoggingInFaultInterceptor extends AbstractPhaseInterceptor {
LoggingInFaultInterceptor() {
super(Phase.RECEIVE);
}
@Override
public void handleMessage(Message message) throws Fault {
}
@Override
public void handleFault(Message message) throws Fault {
LoggingInInterceptor.this.handleMessage(message);
}
}
public LoggingInInterceptor() {
this(new Slf4jVerboseEventSender());
}
public LoggingInInterceptor(PrintWriter writer) {
this(new PrintWriterEventSender(writer));
}
public LoggingInInterceptor(LogEventSender sender) {
super(Phase.PRE_INVOKE, sender);
}
public Collection> getAdditionalInterceptors() {
Collection> ret = new ArrayList<>();
ret.add(new WireTapIn(getWireTapLimit(), threshold));
ret.add(new LoggingInFaultInterceptor());
return ret;
}
public void handleMessage(Message message) throws Fault {
if (isLoggingDisabledNow(message)) {
return;
}
createExchangeId(message);
final LogEvent event = new DefaultLogEventMapper().map(message);
if (shouldLogContent(event)) {
addContent(message, event);
} else {
event.setPayload(AbstractLoggingInterceptor.CONTENT_SUPPRESSED);
}
sender.send(event);
}
private void addContent(Message message, final LogEvent event) {
try {
CachedOutputStream cos = message.getContent(CachedOutputStream.class);
if (cos != null) {
handleOutputStream(event, message, cos);
} else {
CachedWriter writer = message.getContent(CachedWriter.class);
if (writer != null) {
handleWriter(event, writer);
}
}
} catch (IOException e) {
throw new Fault(e);
}
}
private void handleOutputStream(final LogEvent event, Message message, CachedOutputStream cos) throws IOException {
String encoding = (String) message.get(Message.ENCODING);
if (StringUtils.isEmpty(encoding)) {
encoding = StandardCharsets.UTF_8.name();
}
StringBuilder payload = new StringBuilder();
cos.writeCacheTo(payload, encoding, limit);
cos.close();
event.setPayload(payload.toString());
boolean isTruncated = cos.size() > limit && limit != -1;
event.setTruncated(isTruncated);
event.setFullContentFile(cos.getTempFile());
}
private void handleWriter(final LogEvent event, CachedWriter writer) throws IOException {
boolean isTruncated = writer.size() > limit && limit != -1;
StringBuilder payload = new StringBuilder();
writer.writeCacheTo(payload, limit);
event.setPayload(payload.toString());
event.setTruncated(isTruncated);
event.setFullContentFile(writer.getTempFile());
}
int getWireTapLimit() {
if (limit == -1) {
return -1;
} else if (limit == Integer.MAX_VALUE) {
return limit;
} else {
// add limit +1 as limit for the wiretab in order to read one byte more, so that truncated
// is correctly calculated in LogginInIntecepteor!
// See code line : boolean isTruncated = cos.size() > limit && limit != -1;
// cos is here the outputstream read by the wiretab which will return for cos.size() the
// limit in the truncated case!
return limit + 1;
}
}
}
可以自行追溯到Slf4jEventSender
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.ext.logging.slf4j;
import java.util.HashSet;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.cxf.ext.logging.event.LogEvent;
import org.apache.cxf.ext.logging.event.LogEventSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.MarkerFactory;
public class Slf4jEventSender implements LogEventSender {
@Override
public void send(LogEvent event) {
String cat = "org.apache.cxf.services." + event.getPortTypeName().getLocalPart() + "." + event.getType();
Logger log = LoggerFactory.getLogger(cat);
Set keys = new HashSet<>();
try {
put(keys, "Type", event.getType().toString());
put(keys, "Address", event.getAddress());
put(keys, "HttpMethod", event.getHttpMethod());
put(keys, "Content-Type", event.getContentType());
put(keys, "ResponseCode", event.getResponseCode());
put(keys, "ExchangeId", event.getExchangeId());
put(keys, "MessageId", event.getMessageId());
if (event.getServiceName() != null) {
put(keys, "ServiceName", localPart(event.getServiceName()));
put(keys, "PortName", localPart(event.getPortName()));
put(keys, "PortTypeName", localPart(event.getPortTypeName()));
}
if (event.getFullContentFile() != null) {
put(keys, "FullContentFile", event.getFullContentFile().getAbsolutePath());
}
put(keys, "Headers", event.getHeaders().toString());
log.info(MarkerFactory.getMarker(event.getServiceName() != null ? "SOAP" : "REST"),
getLogMessage(event));
} finally {
for (String key : keys) {
MDC.remove(key);
}
}
}
private String localPart(QName name) {
return name == null ? null : name.getLocalPart();
}
protected String getLogMessage(LogEvent event) {
return event.getPayload();
}
private void put(Set keys, String key, String value) {
if (value != null) {
MDC.put(key, value);
keys.add(key);
}
}
}
关键点在于
String cat = "org.apache.cxf.services." + event.getPortTypeName().getLocalPart() + "." + event.getType();
所以基本确定是没有对当前logger做处理,故打开logback.xml。
${log.path}/cxf.log
${log.path}/cxf.%d{yyyy-MM-dd}.log
60
${log.pattern}
但是!!!我这里有40多个接口,就要去声明40多个appender,这样增加代码量的同时,让整个配置文件也变的无比臃肿!
所以,通过一下午的StackOverflow,我找到了这样的写法。
logback.xml
${ws.path}/${loggerName}.log
${ws.path}/${loggerName}%d{yyyy-MM-dd}.log
60
${log.pattern}
新建LoggerNameBasedDiscriminator到项目中
package com.account.framework.config;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.sift.Discriminator;
/**
* @description
* 日志loggername管理器
* 实现动态配置logback日志文件名称参数
* @author fg
* @date 2019年3月14日
*/
public class LoggerNameBasedDiscriminator implements Discriminator{
private static final String KEY = "loggerName";
private boolean started;
/**
* @description
* 在给cxf的 SpringBus 注册了
* LoggingInInterceptor/LoggingOutInterceptor之后
* 在logback中指定discriminator
* 则可在此处对xxx 使用参数
* 从而达到动态生成fileName的功能
* @author fg
* @date 2019年3月14日
*/
@Override
public String getDiscriminatingValue(ILoggingEvent iLoggingEvent) {
String loggerName = iLoggingEvent.getLoggerName();
if(!loggerName.contains("cxf")) {
return iLoggingEvent.getLoggerName();
}
String[] arr = loggerName.split("\\.");
String lastName = arr[arr.length-2];
return lastName;
}
@Override
public String getKey() {
return KEY;
}
public void start() {
started = true;
}
public void stop() {
started = false;
}
public boolean isStarted() {
return started;
}
}
生成的文件则为
OSB_BP_MDM_HQ_PageInquiryCOASrv.log
如果需要对另外一个报文记录,则再添加,其中xxx则是你的服务名
这样就实现了,不同接口报文日志可以到不同的文件中。
3.总结
随着开发经验的积累,越来越体会到会英语+看源码的重要性。