001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.util; 021 022import java.util.Arrays; 023import java.util.Map; 024import java.util.Set; 025import java.util.logging.Formatter; 026import java.util.logging.LogRecord; 027 028import org.slf4j.MDC; 029 030/** 031 * Implementation of {@link java.util.logging.Formatter} that generates xml in the log4j format. 032 * <p> 033 * The generated xml corresponds 100% with what is generated by 034 * log4j's <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/XMLLayout.html">XMLLayout</a> 035 * <p> 036 * The MDC properties will only be correct when <code>format</code> is called from the same thread 037 * that generated the LogRecord. 038 * <p> 039 * The file and line attributes in the locationInfo element will always be "?" 040 * since java.util.logging.LogRecord does not provide that info. 041 * <p> 042 * The implementation is heavily based on org.apache.log4j.xml.XMLLayout 043 * </p> 044 * 045 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 046 */ 047public class Log4jXmlFormatter extends Formatter { 048 049 private final int DEFAULT_SIZE = 256; 050 051 private final int UPPER_LIMIT = 2048; 052 053 private StringBuffer buf = new StringBuffer(DEFAULT_SIZE); 054 055 private boolean locationInfo = false; 056 057 private boolean properties = false; 058 059 /** 060 * The <b>LocationInfo</b> option takes a boolean value. By default, 061 * it is set to false which means there will be no location 062 * information output by this layout. If the the option is set to 063 * true, then the file name and line number of the statement at the 064 * origin of the log statement will be output. 065 * 066 * @param flag whether locationInfo should be output by this layout 067 */ 068 public void setLocationInfo(boolean flag) { 069 locationInfo = flag; 070 } 071 072 /** 073 * @return the current value of the <b>LocationInfo</b> option. 074 */ 075 public boolean getLocationInfo() { 076 return locationInfo; 077 } 078 079 /** 080 * Sets whether MDC key-value pairs should be output, default false. 081 * 082 * @param flag new value. 083 */ 084 public void setProperties(final boolean flag) { 085 properties = flag; 086 } 087 088 /** 089 * Gets whether MDC key-value pairs should be output. 090 * 091 * @return true if MDC key-value pairs are output. 092 */ 093 public boolean getProperties() { 094 return properties; 095 } 096 097 @Override 098 public String format(final LogRecord record) { 099 // Reset working buffer. If the buffer is too large, then we need a new 100 // one in order to avoid the penalty of creating a large array. 101 if (buf.capacity() > UPPER_LIMIT) { 102 buf = new StringBuffer(DEFAULT_SIZE); 103 } else { 104 buf.setLength(0); 105 } 106 buf.append("<log4j:event logger=\""); 107 buf.append(Transform.escapeTags(record.getLoggerName())); 108 buf.append("\" timestamp=\""); 109 buf.append(record.getMillis()); 110 buf.append("\" level=\""); 111 112 buf.append(Transform.escapeTags(record.getLevel().getName())); 113 buf.append("\" thread=\""); 114 buf.append(String.valueOf(record.getThreadID())); 115 buf.append("\">\r\n"); 116 117 buf.append("<log4j:message><![CDATA["); 118 // Append the rendered message. Also make sure to escape any 119 // existing CDATA sections. 120 Transform.appendEscapingCDATA(buf, record.getMessage()); 121 buf.append("]]></log4j:message>\r\n"); 122 123 if (record.getThrown() != null) { 124 String[] s = Transform.getThrowableStrRep(record.getThrown()); 125 if (s != null) { 126 buf.append("<log4j:throwable><![CDATA["); 127 for (String value : s) { 128 Transform.appendEscapingCDATA(buf, value); 129 buf.append("\r\n"); 130 } 131 buf.append("]]></log4j:throwable>\r\n"); 132 } 133 } 134 135 if (locationInfo) { 136 buf.append("<log4j:locationInfo class=\""); 137 buf.append(Transform.escapeTags(record.getSourceClassName())); 138 buf.append("\" method=\""); 139 buf.append(Transform.escapeTags(record.getSourceMethodName())); 140 buf.append("\" file=\"?\" line=\"?\"/>\r\n"); 141 } 142 143 if (properties) { 144 Map<String,String> contextMap = MDC.getCopyOfContextMap(); 145 146 if (contextMap != null) { 147 Set<String> keySet = contextMap.keySet(); 148 149 if ((keySet != null) && (keySet.size() > 0)) { 150 buf.append("<log4j:properties>\r\n"); 151 Object[] keys = keySet.toArray(); 152 Arrays.sort(keys); 153 154 for (Object key1 : keys) { 155 String key = (key1 == null ? "" : key1.toString()); 156 Object val = contextMap.get(key); 157 158 if (val != null) { 159 buf.append("<log4j:data name=\""); 160 buf.append(Transform.escapeTags(key)); 161 buf.append("\" value=\""); 162 buf.append(Transform.escapeTags(String.valueOf(val))); 163 buf.append("\"/>\r\n"); 164 } 165 } 166 167 buf.append("</log4j:properties>\r\n"); 168 } 169 } 170 171 } 172 buf.append("</log4j:event>\r\n\r\n"); 173 174 return buf.toString(); 175 } 176 177}