springboot+cxf+logback报文日志日志记录(已实现将不同接口写入不同日志)

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

于是我就找到了

	    <dependency>
	      <groupId>org.apache.cxf</groupId>
	      <artifactId>cxf-rt-features-logging</artifactId>
	      <version>3.2.5</version>
	    </dependency>		

然后查找资料,配置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<Message> {
        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<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
        Collection<PhaseInterceptor<? extends Message>> 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<String> 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<String> 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。

    <appender name="cxf" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${log.path}/cxf.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log.path}/cxf.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
        <logger name="org.apache.cxf.services.OSB_BP_MDM_HQ_PageInquiryCOASrv" level="INFO">
        <appender-ref ref="cxf"/>
    </logger> 

测试,已经可以将日志打印到指定的文件中了。

但是!!!我这里有40多个接口,就要去声明40多个appender,这样增加代码量的同时,让整个配置文件也变的无比臃肿!
所以,通过一下午的StackOverflow,我找到了这样的写法。
logback.xml

	<!-- cxf 日志设置 -->
	<appender name="cxf" class="ch.qos.logback.classic.sift.SiftingAppender">
		<discriminator class="com.account.framework.config.LoggerNameBasedDiscriminator" />
		<sift>
			<appender name="FILE-${loggerName}" class="ch.qos.logback.core.FileAppender">
				<file>${ws.path}/${loggerName}.log</file>
		        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		            <fileNamePattern>${ws.path}/${loggerName}%d{yyyy-MM-dd}.log</fileNamePattern>
		            <maxHistory>60</maxHistory>
		        </rollingPolicy>
		        <encoder>
		            <pattern>${log.pattern}</pattern>
		        </encoder>		        
			</appender>
		</sift>
    </appender>
    <!-- cxf 相关日志-->
    <logger name="org.apache.cxf.services.OSB_BP_MDM_HQ_PageInquiryCOASrv" level="INFO">
        <appender-ref ref="cxf"/>
    </logger>      

新建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<ILoggingEvent>{

	private static final String KEY = "loggerName";

	private boolean started;

	/**
	 * @description 
	 * 	 在给cxf的 SpringBus 注册了
	 *   LoggingInInterceptor/LoggingOutInterceptor之后
	 *   在logback中指定discriminator 
	 *   则可在此处对<file>xxx</file>使用参数
	 *   从而达到动态生成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则是你的服务名

    <logger name="org.apache.cxf.services.xxxx" level="INFO">
        <appender-ref ref="cxf"/>
    </logger>  

这样就实现了,不同接口报文日志可以到不同的文件中。

3.总结
随着开发经验的积累,越来越体会到会英语+看源码的重要性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值