From 14f456d60d55cdaf413ef0669ff6897660b5cab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Wed, 27 May 2015 18:46:53 +0200 Subject: [PATCH] Logging Feature pretty-printing --- .../logging/event/PrettyLoggingFilter.java | 86 +++++++++----- .../ext/logging/TestPrettyLoggingFilter.java | 105 ++++++++++++++++++ 2 files changed, 165 insertions(+), 26 deletions(-) create mode 100644 rt/features/logging/src/test/java/org/apache/cxf/ext/logging/TestPrettyLoggingFilter.java diff --git a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/PrettyLoggingFilter.java b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/PrettyLoggingFilter.java index bb99ff2f750..dfa7f192e21 100644 --- a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/PrettyLoggingFilter.java +++ b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/PrettyLoggingFilter.java @@ -18,14 +18,12 @@ */ package org.apache.cxf.ext.logging.event; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.StringReader; import java.io.StringWriter; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; -import javax.xml.transform.stream.StreamSource; import org.apache.cxf.staxutils.PrettyPrintXMLStreamWriter; import org.apache.cxf.staxutils.StaxUtils; @@ -45,7 +43,7 @@ public PrettyLoggingFilter(LogEventSender next) { @Override public void send(LogEvent event) { if (shouldPrettyPrint(event)) { - event.setPayload(getPrettyMessage(event.getPayload(), event.getEncoding())); + event.setPayload(getPrettyMessage(event)); } next.send(event); } @@ -59,31 +57,67 @@ private boolean shouldPrettyPrint(LogEvent event) { && event.getPayload().length() > 0; } - public String getPrettyMessage(String message, String encoding) { - StringWriter swriter = new StringWriter(); + /** + * Pretty-print {@linkplain LogEvent} XML payload. + * + * @param event the log event containing an XML payload which is to be pretty-printed. + * @return pretty-printed XML or original payload in case of an unexpected exception. + */ + + private String getPrettyMessage(LogEvent event) { + /* + * Do not call close() on the {@linkplain XMLStreamWriter} before getting the underlying content, + * as some writers will close start tags and end scopes, see {@linkplain com.ctc.wstx.sw.BaseStreamWriter#close} + * and note test cases. + */ + + String payload = event.getPayload(); // potentially truncated XML string + + // roughly estimate pretty-printed message to twice the raw XML size + StringWriter swriter = new StringWriter(payload.length() * 2); + + XMLStreamWriter xwriter = new PrettyPrintXMLStreamWriter(StaxUtils.createXMLStreamWriter(swriter), 2); + XMLStreamReader xreader = StaxUtils.createXMLStreamReader(new StringReader(payload)); try { - // Using XMLStreamWriter instead of Transformer as it works with non well formed xml - // that can occur when we set a limit and cur the rest off - XMLStreamWriter xwriter = StaxUtils.createXMLStreamWriter(swriter); - xwriter = new PrettyPrintXMLStreamWriter(xwriter, 2); - InputStream in = new ByteArrayInputStream(message.getBytes(encoding)); + StaxUtils.copy(xreader, xwriter); + + xwriter.flush(); + + return swriter.toString(); + } catch (XMLStreamException xse) { + if (!event.isTruncated()) { + // log with debug level, assume additional warn or error logging elsewhere + + LOG.debug("Error while pretty printing cxf message, returning raw message.", xse); + + return payload; + } + + // Expected behavior for truncated payloads - keep what is already written. + // This might effectively result in additional truncation, + // as the truncated XML document might result in partially parsed XML nodes, + // for example an open start tag. As long as a truncated payload is not + // mistaken for a non-truncated payload, we're good. + try { - StaxUtils.copy(new StreamSource(in), xwriter); - } catch (XMLStreamException xse) { + xwriter.flush(); + } catch (XMLStreamException xse2) { //ignore - } finally { - try { - xwriter.flush(); - xwriter.close(); - } catch (XMLStreamException xse2) { - //ignore - } - in.close(); } - } catch (IOException e) { - LOG.debug("Error while pretty printing cxf message, returning what we got till now.", e); - } - return swriter.toString(); + return swriter.toString(); + } finally { + // free up resources + try { + xwriter.close(); + } catch (XMLStreamException xse2) { + //ignore + } + try { + xreader.close(); + } catch (XMLStreamException xse2) { + //ignore + } + } } public void setNext(LogEventSender next) { diff --git a/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/TestPrettyLoggingFilter.java b/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/TestPrettyLoggingFilter.java new file mode 100644 index 00000000000..905ce78fffa --- /dev/null +++ b/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/TestPrettyLoggingFilter.java @@ -0,0 +1,105 @@ +/** + * 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 org.apache.cxf.ext.logging.event.LogEvent; +import org.apache.cxf.ext.logging.event.LogEventSender; +import org.apache.cxf.ext.logging.event.PrettyLoggingFilter; +import org.easymock.EasyMockSupport; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * + * Test {@linkplain PrettyLoggingFilter}} with well-formed and non-well-formed XML payloads. + * + */ + +public class TestPrettyLoggingFilter extends EasyMockSupport { + + @Test + public void testWellformedXMLMessage() { + + String message = "text"; + String expected = "\n text\n\n"; + + filter(message, expected, false); + } + + @Test + public void testInvalidXMLMessageUnexpectedEndTag() { + + String message = "text"; + + filter(message, message, false); + } + + @Test + public void testInvalidXMLMessageMissingEndTag() { + + String message = "text"; + + filter(message, message, false); + } + + @Test + public void testInvalidXMLMessageGarbageStartTag() { + String message = "text"; + + filter(message, message, false); + } + + /** + * + * This test case demonstrates why you do not want to call close() on the {@linkplain XMLStreamWriter} + * before getting the underlying content. + * + */ + + @Test + public void testInvalidButTruncatedXMLMessageWithMissingEndTag() { + String message = "text"; + String expected = "\n text"; + + filter(message, expected, true); + } + + private void filter(String payload, String expected, boolean truncated) { + LogEvent logEvent = new LogEvent(); + logEvent.setPayload(payload); + logEvent.setContentType("text/xml"); + logEvent.setTruncated(truncated); + + LogEventSender logEventSender = createMock(LogEventSender.class); + PrettyLoggingFilter prettyFilter = new PrettyLoggingFilter(logEventSender); + prettyFilter.setPrettyLogging(true); + + // record + logEventSender.send(logEvent); + + replayAll(); + + // test + prettyFilter.send(logEvent); + verifyAll(); + + assertEquals(expected, logEvent.getPayload()); + } + +}