001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.log4j.layout; 018 019import java.io.PrintWriter; 020import java.io.StringWriter; 021import java.nio.charset.StandardCharsets; 022import java.util.List; 023import java.util.Objects; 024 025import org.apache.logging.log4j.core.Layout; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.config.Node; 028import org.apache.logging.log4j.core.config.plugins.Plugin; 029import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 030import org.apache.logging.log4j.core.config.plugins.PluginFactory; 031import org.apache.logging.log4j.core.layout.AbstractStringLayout; 032import org.apache.logging.log4j.core.layout.ByteBufferDestination; 033import org.apache.logging.log4j.core.util.Transform; 034import org.apache.logging.log4j.util.ReadOnlyStringMap; 035import org.apache.logging.log4j.util.Strings; 036 037/** 038 * Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations. 039 * 040 * Originally developed by Ceki Gülcü, Mathias Bogaert. 041 */ 042@Plugin(name = "Log4j1XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 043public final class Log4j1XmlLayout extends AbstractStringLayout { 044 045 private final boolean locationInfo; 046 private final boolean properties; 047 048 @PluginFactory 049 public static Log4j1XmlLayout createLayout( 050 // @formatter:off 051 @PluginAttribute(value = "locationInfo") final boolean locationInfo, 052 @PluginAttribute(value = "properties") final boolean properties 053 // @formatter:on 054 ) { 055 return new Log4j1XmlLayout(locationInfo, properties); 056 } 057 058 private Log4j1XmlLayout(final boolean locationInfo, final boolean properties) { 059 super(StandardCharsets.UTF_8); 060 this.locationInfo = locationInfo; 061 this.properties = properties; 062 } 063 064 public boolean isLocationInfo() { 065 return locationInfo; 066 } 067 068 public boolean isProperties() { 069 return properties; 070 } 071 072 @Override 073 public void encode(final LogEvent event, final ByteBufferDestination destination) { 074 final StringBuilder text = getStringBuilder(); 075 formatTo(event, text); 076 getStringBuilderEncoder().encode(text, destination); 077 } 078 079 @Override 080 public String toSerializable(final LogEvent event) { 081 final StringBuilder text = getStringBuilder(); 082 formatTo(event, text); 083 return text.toString(); 084 } 085 086 private void formatTo(final LogEvent event, final StringBuilder buf) { 087 // We yield to the \r\n heresy. 088 089 buf.append("<log4j:event logger=\""); 090 buf.append(Transform.escapeHtmlTags(event.getLoggerName())); 091 buf.append("\" timestamp=\""); 092 buf.append(event.getTimeMillis()); 093 buf.append("\" level=\""); 094 buf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); 095 buf.append("\" thread=\""); 096 buf.append(Transform.escapeHtmlTags(event.getThreadName())); 097 buf.append("\">\r\n"); 098 099 buf.append("<log4j:message><![CDATA["); 100 // Append the rendered message. Also make sure to escape any existing CDATA sections. 101 Transform.appendEscapingCData(buf, event.getMessage().getFormattedMessage()); 102 buf.append("]]></log4j:message>\r\n"); 103 104 final List<String> ndc = event.getContextStack().asList(); 105 if (!ndc.isEmpty()) { 106 buf.append("<log4j:NDC><![CDATA["); 107 Transform.appendEscapingCData(buf, Strings.join(ndc, ' ')); 108 buf.append("]]></log4j:NDC>\r\n"); 109 } 110 111 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 112 final Throwable thrown = event.getThrown(); 113 if (thrown != null) { 114 buf.append("<log4j:throwable><![CDATA["); 115 final StringWriter w = new StringWriter(); 116 thrown.printStackTrace(new PrintWriter(w)); 117 Transform.appendEscapingCData(buf, w.toString()); 118 buf.append("]]></log4j:throwable>\r\n"); 119 } 120 121 if (locationInfo) { 122 final StackTraceElement source = event.getSource(); 123 if (source != null) { 124 buf.append("<log4j:locationInfo class=\""); 125 buf.append(Transform.escapeHtmlTags(source.getClassName())); 126 buf.append("\" method=\""); 127 buf.append(Transform.escapeHtmlTags(source.getMethodName())); 128 buf.append("\" file=\""); 129 buf.append(Transform.escapeHtmlTags(source.getFileName())); 130 buf.append("\" line=\""); 131 buf.append(source.getLineNumber()); 132 buf.append("\"/>\r\n"); 133 } 134 } 135 136 if (properties) { 137 final ReadOnlyStringMap contextMap = event.getContextData(); 138 if (!contextMap.isEmpty()) { 139 buf.append("<log4j:properties>\r\n"); 140 contextMap.forEach((key, val) -> { 141 if (val != null) { 142 buf.append("<log4j:data name=\""); 143 buf.append(Transform.escapeHtmlTags(key)); 144 buf.append("\" value=\""); 145 buf.append(Transform.escapeHtmlTags(Objects.toString(val, null))); 146 buf.append("\"/>\r\n"); 147 } 148 }); 149 buf.append("</log4j:properties>\r\n"); 150 } 151 } 152 153 buf.append("</log4j:event>\r\n\r\n"); 154 } 155 156}