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}