Is it possible to log the request/response as XML using CXF, ideally to a separate file so I can monitor what an application is doing?
Add the following to your endpoints and clients:
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
This will log everything to the server log.
If you want to log them elsewhere, then look at the source code of the built-in CXF LoggingInInterceptor and LoggingOutInterceptor. You can follow the pattern they use to grab the messages on their way in/out and do with them what you like.
Add your own interceptors to the chain with something like this:
<jaxws:inInterceptors>
<ref bean="myLoggingInInterceptor" />
</jaxws:inInterceptors>
So, I tried a little more with this. To get the XML Request and Replies logged, and if you are using Log4J, you need to set the Log-level of CXF in the log4j.xml file like this (>= INFO):
<logger name="org.apache.cxf" >
<level value="INFO" />
</logger>
And the cxf.xml file should contains this:
<cxf:bus>
<cxf:features>
<cxf:logging/>
</cxf:features>
</cxf:bus>
Both files should be in the CLASSPATH.
To display the soap message add this to your code:
Client client = ClientProxy.getClient(service);
client.getInInterceptors().add(new LoggingInInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());
The request soap xml can be logged easily by an custom In interceptor. Say, we have an interceptor named "wsLoggingInInterceptor", So in the context file it will be like the following :
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
<bean id="logOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
<bean id="wsLoggingInInterceptor" class="org.jinouts.webservice.logging.WSLoggingInInterceptor"/>
<cxf:bus>
<cxf:inInterceptors>
<ref bean="loggingInInterceptor"/>
<ref bean="wsLoggingInInterceptor"/>
</cxf:inInterceptors>
<cxf:outInterceptors>
<ref bean="logOutInterceptor"/>
</cxf:outInterceptors>
</cxf:bus>
In the class we can get the request xml as follows:
public class WSLoggingInInterceptor extends AbstractSoapInterceptor
{
public WSLoggingInInterceptor ()
{
super(Phase.RECEIVE);
}
@Override
public void handleMessage ( SoapMessage message ) throws Fault
{
//get the remote address
HttpServletRequest httpRequest = (HttpServletRequest) message.get ( AbstractHTTPDestination.HTTP_REQUEST );
System.out.println ("Request From the address : " + httpRequest.getRemoteAddr ( ) );
try
{
// now get the request xml
InputStream is = message.getContent ( InputStream.class );
CachedOutputStream os = new CachedOutputStream ( );
IOUtils.copy ( is, os );
os.flush ( );
message.setContent ( InputStream.class, os.getInputStream ( ) );
is.close ( );
System.out.println ("The request is: " + IOUtils.toString ( os.getInputStream ( ) ));
os.close ( );
}
catch ( Exception ex )
{
ex.printStackTrace ( );
}
}
}
Look, here I have also log the address from where the request is coming. You can also get some more information from the "HttpServletRequest" object. you can have more from : http://cxf.apache.org/docs/interceptors.html
To log response xml you can have a look at this thread
CachedOutputStream
to get a re-readable inputStream
but why is the name of the class : (CachedOutputStream
), it doesn't even have input word in it ? is it supposed to perform other major function OR my understanding is flawed. Also why can't we use DelegatingInputStream
instead ? –
Silviasilviculture Add the following to your endpoints and clients:
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
This will log everything to the server log.
If you want to log them elsewhere, then look at the source code of the built-in CXF LoggingInInterceptor and LoggingOutInterceptor. You can follow the pattern they use to grab the messages on their way in/out and do with them what you like.
Add your own interceptors to the chain with something like this:
<jaxws:inInterceptors>
<ref bean="myLoggingInInterceptor" />
</jaxws:inInterceptors>
If you are using Spring with it´s Java-Configuration, there are 2 easy ways to activate Logging of SOAP-Messages with Apache CXF:
Directly on the SpringBus - that is usefull, if you want to log the Messages of all your CXF-Endpoints:
@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; }
Activate Logging seperately on every exposed CXF-Endpoint
@Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(springBus(), weatherService()); endpoint.publish(SERVICE_NAME_URL_PATH); endpoint.setWsdlLocation("Weather1.0.wsdl"); LoggingFeature logFeature = new LoggingFeature(); logFeature.setPrettyLogging(true); logFeature.initialize(springBus()); endpoint.getFeatures().add(logFeature); return endpoint; }
Remind the LoggingFeature.setPrettyLogging(true); Method to see pretty printed SOAP-Messages and LoggingFeature.initialize(springBus()); - without the latter, the magic doesn´t happen. For cleaner Code, you could also separate the LoggingFeature as separate Bean and inject it either in your SpringBus or Endpoint-Bean.
It's much more easier to add your own logger to the endpoint properties. In this case default logging interceptor will look for your logger in the endpoint properties and if it finds one it will use it otherwise it will create default. Here is my usage example:
<jaxws:endpoint
xmlns:client="http://service.info.client.diasoft.services.stream.integration.cib.sberbank.ru"
address="/diasoft/clientInfoWS"
serviceName="client:ClientWS"
implementor="#clientServiceImpl">
<jaxws:properties>
<entry key="MessageLogger" value-ref="logger"/>
</jaxws:properties>
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature"/>
</jaxws:features>
</jaxws:endpoint>
<bean id="logger" class="org.apache.cxf.common.logging.LogUtils" factory-method="getLogger">
<constructor-arg value="ru.sberbank.cib.integration.stream.services.diasoft.client.info.service.ClientWSImpl"/>
</bean>
In my case, I had to maintain a jaxb generated client. So I had found with the following situation in the applicationContext:
<jaxws:client id="aService"
serviceClass="org.xxx.ServiceClass"
address="${service.url}"
username="${service.username}"
password="${service.password}">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
</jaxws:client>
And I jus turned it into this:
<jaxws:client id="aService"
address="${service.url}"
username="${service.username}"
password="${service.password}">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
<jaxws:inInterceptors>
<bean class="com.blah.blah.SoapInInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="com.blah.blah.SoapOutInterceptor"/>
</jaxws:outInterceptors>
<jaxws:inFaultInterceptors>
<bean class="com.blah.blah.SoapFaultInterceptor"/>
</jaxws:inFaultInterceptors>
</jaxws:client>
My SoapInInterceptor class:
public class SoapInInterceptor extends AbstractSoapInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(SoapInInterceptor .class);
public SoapInInterceptor () {
super(Phase.RECEIVE);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
InputStream is = message.getContent(InputStream.class);
CachedOutputStream os = new CachedOutputStream();
IOUtils.copy(is, os);
os.flush();
message.setContent(InputStream.class, os.getInputStream());
is.close();
LOGGER.debug("RESPONSE: {}", IOUtils.toString(os.getInputStream()));
os.close();
} catch (Exception ex) {
LOGGER.error("Error trying to log response", ex);
}
}
}
My SoapOutInterceptor class:
public class SoapOutInterceptor extends AbstractSoapInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(SoapOutInterceptor .class);
public SoapOutInterceptor () {
super(Phase.PRE_STREAM);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
CacheAndWriteOutputStream cwos = new CacheAndWriteOutputStream(message.getContent(OutputStream.class));
message.setContent(OutputStream.class, cwos);
cwos.registerCallback(new LoggingOutCallBack());
}
class LoggingOutCallBack implements CachedOutputStreamCallback {
@Override
public void onClose(CachedOutputStream cos) {
try {
if (cos != null) {
LOGGER.debug("REQUEST: {}", IOUtils.toString(cos.getInputStream()));
}
} catch (Exception e) {
LOGGER.error("Error trying to log request", e);
}
}
@Override
public void onFlush(CachedOutputStream arg0) {}
}
}
And my SoapFaultInterceptor class (almost equals to SoapInInterceptor):
public class SoapFaultInterceptor extends AbstractSoapInterceptor{
private static final Logger LOGGER = LoggerFactory.getLogger(SoapFaultInterceptor.class);
public SoapFaultInterceptor() {
super(Phase.RECEIVE);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
InputStream is = message.getContent(InputStream.class);
CachedOutputStream os = new CachedOutputStream();
IOUtils.copy(is, os);
os.flush();
message.setContent(InputStream.class, os.getInputStream());
is.close();
LOGGER.debug("FAULT: {}", IOUtils.toString(os.getInputStream()));
os.close();
} catch (Exception ex) {
LOGGER.error("Error trying to log fault", ex);
}
}
}
With the latter defined in the application, I only had to configure my logback.xml file like this:
<!—logger-->
<logger name="com.blah.blah">
<level value="DEBUG"/>
<appender-ref ref="aDailyRollingFileAppender"/>
</logger>
<!-- appender -->
<appender name="aDailyRollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/jboss-as-7.2.0.Final/standalone/log/soap-payloads.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/jboss-as-7.2.0.Final/standalone/log/soap-payloads-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} [%file:%line] - %msg %n</pattern>
</encoder>
</appender>
To make all the payloads be in the same file.
If you want to separate it, create different loggers/appenders by specifiying the complete class name of each Interceptor.
For me, this solution fits perfectly for what I wanted.
Hope this helps.
Regards.
© 2022 - 2024 — McMap. All rights reserved.