blob: 403728628987c88eb4fa49ad2f0d9dc61129d8d8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.lib.uihandler;
import java.awt.Component;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.XMLFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.JMenuItem;
import org.openide.util.Lookup;
import static java.util.Calendar.*;
/**this class uses most of XML Formater code, but it adds cause of throwable
*
* @author Jindrich Sedek
*/
class LogFormatter extends XMLFormatter{
private static final int MAX_NUM_CAUSE = 20; // Do not recurse more than this number of causes during exception printing
private final Pattern javaHome;
private final Pattern userHome;
private final Pattern netbeansUserDir;
private final Pattern netbeansHome;
private final List<Pattern> installDirs;
private final Pattern filePrefix = Pattern.compile("file:", Pattern.LITERAL);
private final Pattern nbjclPrefix = Pattern.compile("nbjcl:", Pattern.LITERAL);
private final Pattern jarPrefix = Pattern.compile("jar:", Pattern.LITERAL);
private final Pattern hexPattern = Pattern.compile("@[0-9a-fA-F]*");
/** Creates a new instance of LogFormatter */
public LogFormatter() {
javaHome = convert(System.getProperty("java.home", ""));// NOI18N
userHome = convert(System.getProperty("user.home", ""));// NOI18N
netbeansUserDir = convert(System.getProperty("netbeans.user", ""));// NOI18N
netbeansHome = convert(System.getProperty("netbeans.home", ""));// NOI18N
String nbdirsStr = System.getProperty("netbeans.dirs");// NOI18N
if (nbdirsStr != null){
String [] fields = nbdirsStr.split(File.pathSeparator);
Pattern [] resultFields = new Pattern[fields.length];
for (int i = 0; i < fields.length; i++) {
resultFields[i] = convert(fields[i]);
}
installDirs = Arrays.asList(resultFields);
}else{
installDirs = Collections.emptyList();
}
}
private Pattern convert(String str){
try{
String name = new File(str).toURI().toURL().toString();
return Pattern.compile(name, Pattern.LITERAL);
}catch(MalformedURLException exc){
Logger.getLogger(LogFormatter.class.getName()).log(Level.INFO, "unaccessible file", exc);// NOI18N
}
return null;
}
private void a2(StringBuffer sb, int x) {
if (x < 10) {
sb.append('0');
}
sb.append(x);
}
private void escape(StringBuffer sb, String text) {
if (text == null) {
text = "<null>";// NOI18N
}
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '<') {
sb.append("&lt;");// NOI18N
} else if (ch == '>') {
sb.append("&gt;");// NOI18N
} else if (ch == '&') {
sb.append("&amp;");// NOI18N
} else if (ch < 0x20 && ch != '\t' && ch != '\r' && ch != '\n') { // #119820
sb.append('^').append((char) (ch + 0x40));
} else {
sb.append(ch);
}
}
}
private String doReplace(String where, Pattern pattern, String replacement){
return pattern.matcher(where).replaceAll(Matcher.quoteReplacement(replacement));
}
private void printFrame(StackTraceElement frame, StringBuffer sb){
if (frame == null){ // might be caused by OOM bugs see #145298
return;
}
sb.append(" <frame>\n");// NOI18N
sb.append(" <class>");// NOI18N
escape(sb, frame.getClassName());
sb.append("</class>\n");// NOI18N
sb.append(" <method>");// NOI18N
escape(sb, frame.getMethodName());
sb.append("</method>\n");// NOI18N
// Check for a line number.
if (frame.getLineNumber() >= 0) {
sb.append(" <line>");// NOI18N
sb.append(frame.getLineNumber());
sb.append("</line>\n");// NOI18N
}
sb.append(" <file>");// NOI18N
ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class);
Class clazz=null;
URL jarName=null;
String fileName=null;
try{
clazz = loader.loadClass(frame.getClassName());
}catch(Throwable exc){
Logger.getLogger(LogFormatter.class.getName()).log(Level.FINE, "Class loading error", exc);// NOI18N
}
if (clazz != null){
String[] fields = clazz.getName().split("\\.");// NOI18N
if (fields.length> 0){
jarName = clazz.getResource(fields[fields.length-1]+".class");// NOI18N
}
if (jarName!= null){
fileName = jarName.toString();
int index = fileName.indexOf("!");// NOI18N
if (index!= -1){
fileName = fileName.substring(0, index);
}
fileName = doReplace(fileName, jarPrefix, "");// NOI18N
if (javaHome != null){
fileName = doReplace(fileName, javaHome, "${java.home}");// NOI18N
}
if (netbeansHome != null){
fileName = doReplace(fileName, netbeansHome, "${netbeans.home}");// NOI18N
}
if (netbeansUserDir != null){
fileName = doReplace(fileName, netbeansUserDir, "${user.dir}");// NOI18N
}
for (Iterator<Pattern> it = installDirs.iterator(); it.hasNext();) {
fileName = doReplace(fileName, it.next(), "${netBeansDir}");// NOI18N
}
if (userHome != null){
fileName = doReplace(fileName, userHome, "${user.home}");// NOI18N
}
fileName = doReplace(fileName, filePrefix, "");// NOI18N
fileName = doReplace(fileName, nbjclPrefix, "");// NOI18N
escape(sb, fileName);
}
}
sb.append("</file>\n");// NOI18N
sb.append(" </frame>\n");// NOI18N
}
private void printCause(Throwable th, StringBuffer sb, StackTraceElement[] causedTrace){
printCause(th, sb, causedTrace, 0);
}
private void printCause(Throwable th, StringBuffer sb, StackTraceElement[] causedTrace, int depth){
sb.append(" <exception>\n");// NOI18N
sb.append(" <message>");// NOI18N
escape(sb, th.toString());
sb.append("</message>\n");// NOI18N
StackTraceElement[] trace = th.getStackTrace();
int m = trace.length-1;
int n = causedTrace.length-1;
while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) {
m--; n--;
}
int framesInCommon = trace.length - 1 - m;
for (int i=0; i <= m; i++) {
printFrame(trace[i], sb);
}
sb.append(" <more>");// NOI18N
sb.append(framesInCommon);
sb.append("</more>\n");// NOI18N
sb.append(" </exception>\n");// NOI18N
if (th.getCause() != null && depth < MAX_NUM_CAUSE){
printCause(th.getCause(), sb, trace, depth + 1);
}
}
// Report on the state of the throwable.
private void printThrown(Throwable th, StringBuffer sb){
sb.append(" <exception>\n");// NOI18N
sb.append(" <message>");// NOI18N
escape(sb, th.toString());
sb.append("</message>\n");// NOI18N
StackTraceElement trace[] = th.getStackTrace();
for (int i = 0; i < trace.length; i++) {
printFrame(trace[i], sb);
}
sb.append(" </exception>\n");// NOI18N
if (th.getCause() != null){
printCause(th.getCause(), sb, trace);
}
}
// Append the time and date in ISO 8601 format
private void appendISO8601(StringBuffer sb, long millis) {
Date date = new Date(millis);
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
sb.append(calendar.get(YEAR));
sb.append('-');
a2(sb, calendar.get(MONTH) + 1);
sb.append('-');
a2(sb, calendar.get(DAY_OF_MONTH));
sb.append('T');
a2(sb, calendar.get(HOUR_OF_DAY));
sb.append(':');
a2(sb, calendar.get(MINUTE));
sb.append(':');
a2(sb, calendar.get(SECOND));
}
/**
* Format the given message to XML.
* @param record the log record to be formatted.
* @return a formatted log record
*/
public @Override String format(LogRecord record) {
StringBuffer sb = new StringBuffer(1000);
sb.append("<record>\n");// NOI18N
sb.append(" <date>");// NOI18N
appendISO8601(sb, record.getMillis());
sb.append("</date>\n");// NOI18N
sb.append(" <millis>");// NOI18N
sb.append(record.getMillis());
sb.append("</millis>\n");// NOI18N
sb.append(" <sequence>");// NOI18N
sb.append(record.getSequenceNumber());
sb.append("</sequence>\n");// NOI18N
String name = record.getLoggerName();
if (name != null) {
sb.append(" <logger>");// NOI18N
escape(sb, name);
sb.append("</logger>\n");// NOI18N
}
sb.append(" <level>");// NOI18N
String level = Integer.toString(record.getLevel().intValue());
escape(sb, level);
sb.append("</level>\n");// NOI18N
if (record.getSourceClassName() != null) {
sb.append(" <class>");// NOI18N
escape(sb, record.getSourceClassName());
sb.append("</class>\n");// NOI18N
}
if (record.getSourceMethodName() != null) {
sb.append(" <method>");// NOI18N
escape(sb, record.getSourceMethodName());
sb.append("</method>\n");// NOI18N
}
sb.append(" <thread>");// NOI18N
sb.append(record.getThreadID());
sb.append("</thread>\n");// NOI18N
String message = record.getMessage();
if (message != null) {
sb.append(" <message>");// NOI18N
escape(sb, message);
sb.append("</message>\n");// NOI18N
}
// If the message is being localized, output the key, resource
// bundle name, and params.
ResourceBundle bundle = record.getResourceBundle();
try {
if (bundle != null && bundle.getString(message) != null) {
sb.append(" <key>");// NOI18N
escape(sb, message);
sb.append("</key>\n");// NOI18N
sb.append(" <catalog>");// NOI18N
escape(sb, record.getResourceBundleName());
sb.append("</catalog>\n");// NOI18N
}
} catch (Exception exc) {
// The message is not in the catalog. Drop through.
Logger.getLogger(LogFormatter.class.getName()).log(Level.FINE, "Catalog loading error", exc);// NOI18N
}
Object parameters[] = record.getParameters();
// Check to see if the parameter was not a messagetext format
// or was not null or empty
if ( parameters != null && parameters.length != 0
&& (message == null || message.indexOf("{") == -1) ) {
for (int i = 0; i < parameters.length; i++) {
sb.append(" <param>");// NOI18N
try {
escape(sb, paramToString(parameters[i]));
} catch (Exception ex) {
sb.append("???");// NOI18N
}
sb.append("</param>\n");// NOI18N
}
}
if (record.getThrown() != null) {
printThrown(record.getThrown(), sb);
}
sb.append("</record>\n");// NOI18N
return sb.toString();
}
private String paramToString(Object obj) {
if (obj == null) {
return "null"; // NOI18N
}
if (obj instanceof JMenuItem) {
JMenuItem ab = (JMenuItem)obj;
Action a = ab.getAction();
if (a == null) {
// fall thru to AbstractButton
} else {
return ab.getClass().getName() + '[' + paramToString(a) + ']';
}
}
if (obj instanceof AbstractButton) {
AbstractButton ab = (AbstractButton)obj;
return ab.getClass().getName() + '[' + ab.getText() + ']';
}
if (obj instanceof Action) {
Action a = (Action)obj;
if (
a.getClass().getName().endsWith("$DelegateAction") && // NOI18N
a.getClass().getName().startsWith("org.openide") // NOI18N
) {
return hexPattern.matcher(a.toString()).replaceAll("," + a.getValue(Action.NAME));
}
return a.getClass().getName() + '[' + a.getValue(Action.NAME) + ']';
}
if (obj instanceof Component) {
Component c = (Component)obj;
return c.getClass().getName() + '[' + c.getName() + ']'; // NOI18N
}
return obj.toString();
}
}