/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2016 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2007-2016 Joern Huxhorn
*
* Licensed 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 de.huxhorn.lilith.data.logging.xml;
import de.huxhorn.lilith.data.eventsource.LoggerContext;
import de.huxhorn.lilith.data.logging.ExtendedStackTraceElement;
import de.huxhorn.lilith.data.logging.LoggingEvent;
import de.huxhorn.lilith.data.logging.Marker;
import de.huxhorn.lilith.data.logging.Message;
import de.huxhorn.lilith.data.logging.ThreadInfo;
import de.huxhorn.lilith.data.logging.ThrowableInfo;
import de.huxhorn.sulky.stax.DateTimeFormatter;
import de.huxhorn.sulky.stax.GenericStreamWriter;
import de.huxhorn.sulky.stax.StaxUtilities;
import de.huxhorn.sulky.stax.WhiteSpaceHandling;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
public class LoggingEventWriter
implements GenericStreamWriter<LoggingEvent>, LoggingEventSchemaConstants
{
private String preferredPrefix;
private String prefix;
private boolean sortingMaps;
private DateTimeFormatter dateTimeFormatter;
private boolean writingSchemaLocation;
private TimeStampType timeStampType;
public enum TimeStampType
{
ONLY_TIMESTAMP,
ONLY_MILLIS,
BOTH
}
public LoggingEventWriter()
{
dateTimeFormatter = new DateTimeFormatter();
timeStampType = TimeStampType.BOTH;
}
public TimeStampType getTimeStampType()
{
return timeStampType;
}
public void setTimeStampType(TimeStampType timeStampType)
{
this.timeStampType = timeStampType;
}
public boolean isSortingMaps()
{
return sortingMaps;
}
public void setSortingMaps(boolean sortingMaps)
{
this.sortingMaps = sortingMaps;
}
public boolean isWritingSchemaLocation()
{
return writingSchemaLocation;
}
public void setWritingSchemaLocation(boolean writingSchemaLocation)
{
this.writingSchemaLocation = writingSchemaLocation;
}
public String getPreferredPrefix()
{
return preferredPrefix;
}
public void setPreferredPrefix(String prefix)
{
this.preferredPrefix = prefix;
}
public void write(XMLStreamWriter writer, LoggingEvent event, boolean isRoot)
throws XMLStreamException
{
if(isRoot)
{
writer.writeStartDocument(StandardCharsets.UTF_8.toString(), "1.0");
}
StaxUtilities.NamespaceInfo ni = StaxUtilities
.setNamespace(writer, preferredPrefix, NAMESPACE_URI, DEFAULT_NAMESPACE_PREFIX);
prefix = ni.getPrefix();
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, LOGGING_EVENT_NODE);
if(ni.isCreated())
{
StaxUtilities.writeNamespace(writer, prefix, NAMESPACE_URI);
}
if(isRoot && writingSchemaLocation)
{
ni = StaxUtilities
.setNamespace(writer, StaxUtilities.XML_SCHEMA_INSTANCE_PREFIX, StaxUtilities.XML_SCHEMA_INSTANCE_NAMESPACE_URI, StaxUtilities.XML_SCHEMA_INSTANCE_PREFIX);
if(ni.isCreated())
{
StaxUtilities.writeNamespace(writer, ni.getPrefix(), StaxUtilities.XML_SCHEMA_INSTANCE_NAMESPACE_URI);
}
StaxUtilities.writeAttribute(writer,
true,
ni.getPrefix(),
StaxUtilities.XML_SCHEMA_INSTANCE_NAMESPACE_URI,
StaxUtilities.XML_SCHEMA_INSTANCE_SCHEMA_LOCATION_ATTRIBUTE,
NAMESPACE_URI + " " + NAMESPACE_LOCATION);
}
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, LOGGER_ATTRIBUTE, event.getLogger());
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, LEVEL_ATTRIBUTE, "" + event.getLevel());
Long sequence = event.getSequenceNumber();
if(sequence != null)
{
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, SEQUENCE_ATTRIBUTE, sequence.toString());
}
ThreadInfo threadInfo = event.getThreadInfo();
if(threadInfo != null)
{
Long id = threadInfo.getId();
String name = threadInfo.getName();
Long groupId = threadInfo.getGroupId();
String groupName = threadInfo.getGroupName();
Integer priority = threadInfo.getPriority();
if(name != null)
{
StaxUtilities
.writeAttributeIfNotNull(writer, false, prefix, NAMESPACE_URI, THREAD_NAME_ATTRIBUTE, name);
}
if(id != null)
{
StaxUtilities
.writeAttributeIfNotNull(writer, false, prefix, NAMESPACE_URI, THREAD_ID_ATTRIBUTE, "" + id);
}
if(groupName != null)
{
StaxUtilities
.writeAttributeIfNotNull(writer, false, prefix, NAMESPACE_URI, THREAD_GROUP_NAME_ATTRIBUTE, groupName);
}
if(groupId != null)
{
StaxUtilities
.writeAttributeIfNotNull(writer, false, prefix, NAMESPACE_URI, THREAD_GROUP_ID_ATTRIBUTE, "" + groupId);
}
if(priority != null)
{
StaxUtilities
.writeAttributeIfNotNull(writer, false, prefix, NAMESPACE_URI, THREAD_PRIORITY_ATTRIBUTE, "" + priority);
}
}
Long timeStamp = event.getTimeStamp();
if(timeStamp != null)
{
if(timeStampType == TimeStampType.ONLY_TIMESTAMP || timeStampType == TimeStampType.BOTH)
{
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, TIMESTAMP_ATTRIBUTE, dateTimeFormatter.format(new Date(timeStamp)));
}
if(timeStampType == TimeStampType.ONLY_MILLIS || timeStampType == TimeStampType.BOTH)
{
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, TIMESTAMP_MILLIS_ATTRIBUTE, "" + timeStamp);
}
}
Message message = event.getMessage();
if(message != null)
{
String messagePattern = message.getMessagePattern();
if(messagePattern != null)
{
StaxUtilities.writeSimpleTextNode(writer, prefix, NAMESPACE_URI, MESSAGE_NODE, messagePattern);
}
writeArguments(writer, message.getArguments());
}
writeThrowable(writer, event);
writeStringMap(writer, event.getMdc(), MDC_NODE);
writeNdc(writer, event);
writeMarker(writer, event);
writeCallStack(writer, event);
writeLoggerContext(writer, event);
writer.writeEndElement();
if(isRoot)
{
writer.writeEndDocument();
}
}
private void writeLoggerContext(XMLStreamWriter writer, LoggingEvent event)
throws XMLStreamException
{
LoggerContext context = event.getLoggerContext();
if(context == null)
{
return;
}
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, LOGGER_CONTEXT_NODE);
String name = context.getName();
if(name != null)
{
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, LOGGER_CONTEXT_NAME_ATTRIBUTE, name);
}
Long timeStamp = context.getBirthTime();
if(timeStamp != null)
{
if(timeStampType == TimeStampType.ONLY_TIMESTAMP || timeStampType == TimeStampType.BOTH)
{
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, LOGGER_CONTEXT_BIRTH_TIME_ATTRIBUTE, dateTimeFormatter.format(new Date(timeStamp)));
}
if(timeStampType == TimeStampType.ONLY_MILLIS || timeStampType == TimeStampType.BOTH)
{
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, LOGGER_CONTEXT_BIRTH_TIME_MILLIS_ATTRIBUTE, "" + timeStamp);
}
}
writeStringMap(writer, context.getProperties(), LOGGER_CONTEXT_PROPERTIES_NODE);
writer.writeEndElement();
}
private void writeCallStack(XMLStreamWriter writer, LoggingEvent event)
throws XMLStreamException
{
writeStackTraceNode(writer, event.getCallStack(), CALLSTACK_NODE);
}
private void writeMarker(XMLStreamWriter writer, LoggingEvent event)
throws XMLStreamException
{
Marker marker = event.getMarker();
if(marker != null)
{
List<String> handledMarkers = new ArrayList<>();
writeMarkerNode(writer, marker, handledMarkers);
}
}
private void writeMarkerNode(XMLStreamWriter writer, Marker marker, List<String> handledMarkers)
throws XMLStreamException
{
String markerName = marker.getName();
if(handledMarkers.contains(markerName))
{
StaxUtilities.writeEmptyElement(writer, prefix, NAMESPACE_URI, MARKER_REFERENCE_NODE);
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, MARKER_REFERENCE_ATTRIBUTE, markerName);
}
else
{
handledMarkers.add(markerName);
boolean hasChildren = marker.hasReferences();
if(hasChildren)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, MARKER_NODE);
}
else
{
StaxUtilities.writeEmptyElement(writer, prefix, NAMESPACE_URI, MARKER_NODE);
}
StaxUtilities.writeAttribute(writer, false, prefix, NAMESPACE_URI, MARKER_NAME_ATTRIBUTE, markerName);
if(hasChildren)
{
Map<String, Marker> children = marker.getReferences();
for(Map.Entry<String, Marker> current : children.entrySet())
{
writeMarkerNode(writer, current.getValue(), handledMarkers);
}
writer.writeEndElement();
}
}
}
private void writeStringMap(XMLStreamWriter writer, Map<String, String> map, String nodeName)
throws XMLStreamException
{
if(map != null)
{
if(sortingMaps)
{
map = new TreeMap<>(map);
}
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, nodeName);
for(Map.Entry<String, String> entry : map.entrySet())
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, STRING_MAP_ENTRY_NODE);
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, STRING_MAP_ENTRY_KEY_ATTRIBUTE, entry.getKey(), WhiteSpaceHandling.PRESERVE_NORMALIZE_NEWLINE);
StaxUtilities.writeText(writer, entry.getValue());
writer.writeEndElement();
}
writer.writeEndElement();
}
}
private void writeNdc(XMLStreamWriter writer, LoggingEvent event)
throws XMLStreamException
{
Message[] ndc = event.getNdc();
if(ndc != null)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, NDC_NODE);
for(Message entry : ndc)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, NDC_ENTRY_NODE);
StaxUtilities
.writeSimpleTextNode(writer, prefix, NAMESPACE_URI, MESSAGE_NODE, entry.getMessagePattern());
writeArguments(writer, entry.getArguments());
writer.writeEndElement();
}
writer.writeEndElement();
}
}
private void writeArguments(XMLStreamWriter writer, String[] arguments)
throws XMLStreamException
{
if(arguments != null)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, ARGUMENTS_NODE);
for(String current : arguments)
{
if(current == null)
{
StaxUtilities.writeEmptyElement(writer, prefix, NAMESPACE_URI, NULL_ARGUMENT_NODE);
}
else
{
StaxUtilities.writeSimpleTextNode(writer, prefix, NAMESPACE_URI, ARGUMENT_NODE, current);
}
}
writer.writeEndElement();
}
}
private void writeThrowable(XMLStreamWriter writer, LoggingEvent event)
throws XMLStreamException
{
ThrowableInfo throwable = event.getThrowable();
writeThrowableNode(writer, throwable, THROWABLE_NODE);
}
private void writeThrowableNode(XMLStreamWriter writer, ThrowableInfo throwable, String nodeName)
throws XMLStreamException
{
if(throwable != null)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, nodeName);
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, THROWABLE_CLASS_NAME_ATTRIBUTE, throwable.getName());
int omitted = throwable.getOmittedElements();
if(omitted != 0)
{
StaxUtilities
.writeAttribute(writer, false, prefix, NAMESPACE_URI, OMITTED_ELEMENTS_ATTRIBUTE, "" + throwable
.getOmittedElements());
}
// TODO: can message be null?
StaxUtilities
.writeSimpleTextNode(writer, prefix, NAMESPACE_URI, THROWABLE_MESSAGE_NODE, throwable.getMessage());
writeStackTraceNode(writer, throwable.getStackTrace(), STACK_TRACE_NODE);
ThrowableInfo[] suppressed = throwable.getSuppressed();
if(suppressed != null)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, SUPPRESSED_NODE);
for(ThrowableInfo current : suppressed)
{
writeThrowableNode(writer, current, THROWABLE_NODE);
}
writer.writeEndElement();
}
writeThrowableNode(writer, throwable.getCause(), CAUSE_NODE);
writer.writeEndElement();
}
}
private void writeStackTraceNode(XMLStreamWriter writer, ExtendedStackTraceElement[] ste, String nodeName)
throws XMLStreamException
{
if(ste != null)
{
StaxUtilities.writeStartElement(writer, prefix, NAMESPACE_URI, nodeName);
StackTraceElementWriter steWriter = new StackTraceElementWriter();
steWriter.setPreferredPrefix(prefix);
for(ExtendedStackTraceElement elem : ste)
{
steWriter.write(writer, elem, false);
}
writer.writeEndElement();
}
}
}