001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Mananagement System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.util;
029
030import java.util.ArrayList;
031import java.util.Iterator;
032import java.util.List;
033
034import org.apache.log4j.Layout;
035import org.apache.log4j.PatternLayout;
036import org.apache.log4j.spi.LoggingEvent;
037import org.apache.log4j.spi.ThrowableInformation;
038
039/**
040 * Extends the default pattern layout of log4j by adding functionality for filtering the
041 * stack traces output.<p>
042 *
043 * CAUTION: Do not use classes which instantiate a logger in this class!!!<p>
044 *
045 * Usage (log4j.properties):<br/>
046 * log4j.appender.OC.layout=org.opencms.util.CmsPatternLayout<br/>
047 * log4j.appender.OC.layout.ConversionPattern=%d{DATE} %5p [%30.30C:%4L] %m%n<br/>
048 * log4j.appender.OC.layout.Filter=org.apache.tomcat,org.apache.catalina,org.apache.coyote<br/>
049 * log4j.appender.OC.layout.Exclude=org.opencms.workplace.list.A_CmsListDialog<br/>
050 * log4j.appender.OC.layout.MaxLength=5<p>
051 *
052 * @since 7.0.5
053 */
054public class CmsPatternLayout extends PatternLayout {
055
056    /** List of class names which prevents displaying the stack trace. */
057    private List<String> m_excludes;
058
059    /** List of class names which should be filtered. */
060    private List<String> m_filters;
061
062    /** Maximum length of the filtered stack trace. */
063    private int m_maxLength;
064
065    /**
066     * Default constructor.<p>
067     */
068    public CmsPatternLayout() {
069
070        this(DEFAULT_CONVERSION_PATTERN);
071    }
072
073    /**
074     * Constructs a PatternLayout using the supplied conversion pattern.
075     *
076     * @param pattern the pattern to use for the layout
077     */
078    CmsPatternLayout(String pattern) {
079
080        super(pattern);
081        m_filters = new ArrayList<String>();
082        m_excludes = new ArrayList<String>();
083        m_maxLength = Integer.MAX_VALUE;
084    }
085
086    /**
087     * @see org.apache.log4j.PatternLayout#format(org.apache.log4j.spi.LoggingEvent)
088     */
089    @Override
090    public String format(LoggingEvent event) {
091
092        String result = super.format(event);
093
094        ThrowableInformation ti = event.getThrowableInformation();
095        if (ti != null) {
096            // buffer for the complete filtered trace
097            StringBuffer trace = new StringBuffer();
098            // buffer for the minimum trace if an exclusion matches
099            StringBuffer minTrace = new StringBuffer();
100
101            boolean exclFound = false;
102            int count = 0;
103            int filtered = 0;
104            int truncated = 0;
105
106            String[] elements = ti.getThrowableStrRep();
107            for (int i = 0; i < elements.length; i++) {
108                String elem = elements[i];
109
110                // if entry not start with "at" -> put in minimum trace
111                if (!elem.trim().startsWith("at ") && !elem.trim().startsWith("...")) {
112                    minTrace.append(elem).append(Layout.LINE_SEP);
113                }
114
115                // if cause trace starts reset counter (subtrace)
116                if (elem.trim().startsWith("Caused")) {
117                    if (!exclFound && ((truncated > 0) || (filtered > 0))) {
118                        trace.append(createSummary(truncated, filtered));
119                    }
120                    count = 0;
121                    filtered = 0;
122                    truncated = 0;
123                }
124
125                // filter the entry
126                if (!matches(elem, m_filters) && !exclFound) {
127                    if (count < m_maxLength) {
128                        trace.append(elem).append(Layout.LINE_SEP);
129
130                        count++;
131                    } else {
132                        truncated++;
133                    }
134                } else {
135                    filtered++;
136                }
137
138                // check for exclusion
139                if (!exclFound && matches(elem, m_excludes)) {
140                    exclFound = true;
141                }
142            }
143
144            if (exclFound) {
145                result += minTrace.toString();
146            } else {
147                if ((truncated > 0) || (filtered > 0)) {
148                    trace.append(createSummary(truncated, filtered));
149                }
150                result += trace.toString();
151            }
152        }
153
154        return result;
155    }
156
157    /**
158     * @see org.apache.log4j.PatternLayout#ignoresThrowable()
159     */
160    @Override
161    public boolean ignoresThrowable() {
162
163        return false;
164    }
165
166    /**
167     * Sets an exclusion for preventing the stack trace output.<p>
168     *
169     * @param exclude the names of a classes (comma separated) which should prevent the stack trace output
170     */
171    public void setExclude(String exclude) {
172
173        String[] entries = exclude.split(",");
174        for (int i = 0; i < entries.length; i++) {
175            String entry = entries[i].trim();
176
177            if (!entry.startsWith("at ")) {
178                entry = "at " + entry;
179            }
180
181            m_excludes.add(entry);
182        }
183    }
184
185    /**
186     * Sets a filter for the stack trace output.<p>
187     *
188     * @param filter the names of a classes (comma separated) which should be filtered in the stack trace output
189     */
190    public void setFilter(String filter) {
191
192        String[] entries = filter.split(",");
193        for (int i = 0; i < entries.length; i++) {
194            String entry = entries[i].trim();
195
196            if (!entry.startsWith("at ")) {
197                entry = "at " + entry;
198            }
199
200            m_filters.add(entry);
201        }
202    }
203
204    /**
205     * Sets the maximum length of the stack trace.<p>
206     *
207     * @param len the maximum length (lines) of the stack trace
208     */
209    public void setMaxLength(String len) {
210
211        try {
212            m_maxLength = Integer.parseInt(len);
213        } catch (NumberFormatException ex) {
214            m_maxLength = Integer.MAX_VALUE;
215        }
216    }
217
218    /**
219     * Creates a string with the count of filtered and truncated elements.<p>
220     *
221     * @param truncated the number of truncated elements
222     * @param filtered the number of filtered elements
223     *
224     * @return a string with the count of filtered and truncated elements
225     */
226    private String createSummary(int truncated, int filtered) {
227
228        StringBuffer result = new StringBuffer(128);
229
230        result.append("\t... ");
231        result.append(filtered + truncated);
232        result.append(" more (");
233        result.append(filtered);
234        result.append(" filtered; ");
235        result.append(truncated);
236        result.append(" truncated)");
237        result.append(Layout.LINE_SEP);
238
239        return result.toString();
240    }
241
242    /**
243     * Checks if the element in the stack trace is filtered.<p>
244     *
245     * @param element the element in the stack trace to check
246     * @param list the list to check against
247     *
248     * @return true if filtered otherwise false
249     */
250    private boolean matches(String element, List<String> list) {
251
252        boolean result = false;
253
254        Iterator<String> iter = list.iterator();
255        while (iter.hasNext()) {
256            String rule = iter.next();
257
258            if (element.trim().startsWith(rule)) {
259                return true;
260            }
261        }
262
263        return result;
264    }
265}