/*
* 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 java.util.logging;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.ResourceBundle;
/**
* Formatter to convert a {@link LogRecord} into an XML string. The DTD
* specified in Appendix A to the Java Logging APIs specification is used.
* {@code XMLFormatter} uses the output handler's encoding if it is specified,
* otherwise the default platform encoding is used instead. UTF-8 is the
* recommended encoding.
*/
public class XMLFormatter extends Formatter {
private static final String indent = " ";
/**
* Constructs a new {@code XMLFormatter}.
*/
public XMLFormatter() {
}
/**
* Converts a {@code LogRecord} into an XML string.
*
* @param r
* the log record to be formatted.
* @return the log record formatted as an XML string.
*/
@Override
public String format(LogRecord r) {
// call a method of LogRecord to ensure not null
long time = r.getMillis();
// format to date
String date = MessageFormat.format("{0, date} {0, time}", new Date(time));
String nl = System.lineSeparator();
StringBuilder sb = new StringBuilder();
sb.append("<record>").append(nl);
append(sb, 1, "date", date);
append(sb, 1, "millis", time);
append(sb, 1, "sequence", r.getSequenceNumber());
if (r.getLoggerName() != null) {
escapeAndAppend(sb, 1, "logger", r.getLoggerName());
}
append(sb, 1, "level", r.getLevel().getName());
if (r.getSourceClassName() != null) {
append(sb, 1, "class", r.getSourceClassName());
}
if (r.getSourceMethodName() != null) {
escapeAndAppend(sb, 1, "method", r.getSourceMethodName());
}
append(sb, 1, "thread", r.getThreadID());
formatMessages(r, sb);
Object[] params = r.getParameters();
if (params != null) {
for (Object element : params) {
escapeAndAppend(sb, 1, "param", element);
}
}
formatThrowable(r, sb);
sb.append("</record>").append(nl);
return sb.toString();
}
private void formatMessages(LogRecord r, StringBuilder sb) {
// get localized message if has, but don't call Formatter.formatMessage
// to parse pattern string
ResourceBundle rb = r.getResourceBundle();
String pattern = r.getMessage();
if (rb != null && pattern != null) {
String message;
try {
message = rb.getString(pattern);
} catch (Exception e) {
message = null;
}
if (message == null) {
message = pattern;
escapeAndAppend(sb, 1, "message", message);
} else {
escapeAndAppend(sb, 1, "message", message);
escapeAndAppend(sb, 1, "key", pattern);
escapeAndAppend(sb, 1, "catalog", r.getResourceBundleName());
}
} else if (pattern != null) {
escapeAndAppend(sb, 1, "message", pattern);
} else {
sb.append(indent).append("<message/>");
}
}
private void formatThrowable(LogRecord r, StringBuilder sb) {
Throwable t;
if ((t = r.getThrown()) != null) {
String nl = System.lineSeparator();
sb.append(indent).append("<exception>").append(nl);
escapeAndAppend(sb, 2, "message", t.toString());
// format throwable's stack trace
StackTraceElement[] elements = t.getStackTrace();
for (StackTraceElement e : elements) {
sb.append(indent).append(indent).append("<frame>").append(nl);
append(sb, 3, "class", e.getClassName());
escapeAndAppend(sb, 3, "method", e.getMethodName());
append(sb, 3, "line", e.getLineNumber());
sb.append(indent).append(indent).append("</frame>").append(nl);
}
sb.append(indent).append("</exception>").append(nl);
}
}
private static void append(StringBuilder sb, int indentCount, String tag, Object value) {
for (int i = 0; i < indentCount; ++i) {
sb.append(indent);
}
sb.append("<").append(tag).append(">");
sb.append(value);
sb.append("</").append(tag).append(">");
sb.append(System.lineSeparator());
}
private static void escapeAndAppend(StringBuilder sb, int indentCount, String tag, Object value) {
if (value == null) {
append(sb, indentCount, tag, value);
} else {
for (int i = 0; i < indentCount; ++i) {
sb.append(indent);
}
sb.append("<").append(tag).append(">");
try {
escapeXml(sb, value.toString());
} catch (IOException e) {
throw new AssertionError();
}
sb.append("</").append(tag).append(">");
sb.append(System.lineSeparator());
}
}
private static void escapeXml(Appendable valueBuilder, String value) throws IOException {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
case '\"':
valueBuilder.append(""");
break;
case '>':
valueBuilder.append(">");
break;
case '<':
valueBuilder.append("<");
break;
case '&':
valueBuilder.append("&");
break;
case '\'':
valueBuilder.append("'");
break;
default:
valueBuilder.append(c);
}
}
}
/**
* Returns the header string for a set of log records formatted as XML
* strings, using the output handler's encoding if it is defined, otherwise
* using the default platform encoding.
*
* @param h
* the output handler, may be {@code null}.
* @return the header string for log records formatted as XML strings.
*/
@Override
public String getHead(Handler h) {
String encoding = null;
if (h != null) {
encoding = h.getEncoding();
}
if (encoding == null) {
encoding = System.getProperty("file.encoding");
}
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"").append(encoding).append("\" standalone=\"no\"?>");
sb.append(System.lineSeparator());
sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">");
sb.append(System.lineSeparator());
sb.append("<log>");
return sb.toString();
}
/**
* Returns the tail string for a set of log records formatted as XML
* strings.
*
* @param h
* the output handler, may be {@code null}.
* @return the tail string for log records formatted as XML strings.
*/
@Override
public String getTail(Handler h) {
return "</log>";
}
}