You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2014/02/19 21:10:17 UTC

svn commit: r1569896 - in /sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util: iso/AbstractInternationalString.java logging/Logging.java logging/MonolineFormatter.java

Author: desruisseaux
Date: Wed Feb 19 20:10:17 2014
New Revision: 1569896

URL: http://svn.apache.org/r1569896
Log:
Attempt to fix reported performance issues in logging:
- Use a single monitor instead of two.
- Cache MessageFormat and use the existing StringBuffer.

Modified:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java?rev=1569896&r1=1569895&r2=1569896&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java [UTF-8] Wed Feb 19 20:10:17 2014
@@ -61,7 +61,7 @@ public abstract class AbstractInternatio
     /**
      * Constructs an international string.
      */
-    public AbstractInternationalString() {
+    protected AbstractInternationalString() {
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java?rev=1569896&r1=1569895&r2=1569896&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java [UTF-8] Wed Feb 19 20:10:17 2014
@@ -313,14 +313,12 @@ public final class Logging extends Stati
          */
         if (logger==null || classe==null || method==null) {
             String paquet = (logger != null) ? logger.getName() : null;
-            final StackTraceElement[] elements = error.getStackTrace();
-            for (int i=0; i<elements.length; i++) {
+            for (final StackTraceElement element : error.getStackTrace()) {
                 /*
                  * Searches for the first stack trace element with a classname matching the
                  * expected one. We compare preferably against the name of the class given
                  * in argument, or against the logger name (taken as the package name) otherwise.
                  */
-                final StackTraceElement element = elements[i];
                 final String classname = element.getClassName();
                 if (classe != null) {
                     if (!classname.equals(classe)) {

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java?rev=1569896&r1=1569895&r2=1569896&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/logging/MonolineFormatter.java [UTF-8] Wed Feb 19 20:10:17 2014
@@ -20,6 +20,7 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.IOException;
 import java.text.FieldPosition;
+import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.TimeZone;
@@ -28,6 +29,7 @@ import java.util.Arrays;
 import java.util.TreeMap;
 import java.util.SortedMap;
 import java.util.Comparator;
+import java.util.ResourceBundle;
 import java.util.logging.*;
 import org.apache.sis.internal.system.OS;
 import org.apache.sis.internal.util.X364;
@@ -220,6 +222,18 @@ public class MonolineFormatter extends F
     private SimpleDateFormat timeFormat;
 
     /**
+     * The message format, or {@code null} if not yet created.
+     */
+    private transient MessageFormat messageFormat;
+
+    /**
+     * Value of the last call to {@link MessageFormat#applyPattern(String)}. Saved in order to avoid
+     * calling {@code applyPattern(String)} in the common case where the same message is logged many
+     * times with different arguments.
+     */
+    private transient String messagePattern;
+
+    /**
      * One of the following constants: {@link #NO_SOURCE}, {@link #LOGGER_SHORT},
      * {@link #LOGGER_LONG}, {@link #CLASS_SHORT} or {@link #CLASS_LONG}.
      */
@@ -228,6 +242,10 @@ public class MonolineFormatter extends F
     /**
      * Buffer for formatting messages. We will reuse this buffer in order to reduce memory allocations.
      * This is the buffer used internally by {@link #writer}.
+     *
+     * <p>This buffer is also arbitrarily chosen as our synchronization lock. The rational is that all
+     * operations on {@code StringBuffer} are synchronized anyway. So by reusing it for our lock, we
+     * will take only one monitor instead of two.</p>
      */
     private final StringBuffer buffer;
 
@@ -339,7 +357,11 @@ loop:   for (int i=0; ; i++) {
      *
      * @return The string to write on the left side of the first line of every log records, or {@code null} if none.
      */
-    public synchronized String getHeader() {
+    public String getHeader() {
+        final String header;
+        synchronized (buffer) {
+            header = this.header;
+        }
         // All other properties in MonolineFormatter are defined in such a way
         // that null means "none", so we do the same here for consistency.
         return header.isEmpty() ? null : header;
@@ -351,11 +373,13 @@ loop:   for (int i=0; ; i++) {
      * @param header The string to write on the left side of the first line of every log records,
      *        or {@code null} if none.
      */
-    public synchronized void setHeader(String header) {
+    public void setHeader(String header) {
         if (header == null) { // See comment in getHeader().
             header = "";
         }
-        this.header = header;
+        synchronized (buffer) {
+            this.header = header;
+        }
     }
 
     /**
@@ -367,8 +391,10 @@ loop:   for (int i=0; ; i++) {
      *
      * @return The time pattern, or {@code null} if elapsed time is not formatted.
      */
-    public synchronized String getTimeFormat() {
-        return (timeFormat != null) ? timeFormat.toPattern() : null;
+    public String getTimeFormat() {
+        synchronized (buffer) {
+            return (timeFormat != null) ? timeFormat.toPattern() : null;
+        }
     }
 
     /**
@@ -381,8 +407,10 @@ loop:   for (int i=0; ; i++) {
      * @param  pattern The time pattern, or {@code null} to disable time formatting.
      * @throws IllegalArgumentException If the given pattern is invalid.
      */
-    public synchronized void setTimeFormat(final String pattern) throws IllegalArgumentException {
-        timeFormat(pattern);
+    public void setTimeFormat(final String pattern) throws IllegalArgumentException {
+        synchronized (buffer) {
+            timeFormat(pattern);
+        }
     }
 
     /**
@@ -408,8 +436,10 @@ loop:   for (int i=0; ; i++) {
      *
      * @return The source format, or {@code null} if source is not formatted.
      */
-    public synchronized String getSourceFormat() {
-        return FORMAT_LABELS[sourceFormat];
+    public String getSourceFormat() {
+        synchronized (buffer) {
+            return FORMAT_LABELS[sourceFormat];
+        }
     }
 
     /**
@@ -430,8 +460,10 @@ loop:   for (int i=0; ; i++) {
      * @param  format The format for displaying the source, or {@code null} if the source shall not be formatted.
      * @throws IllegalArgumentException If the given argument is not one of the recognized format names.
      */
-    public synchronized void setSourceFormat(final String format) throws IllegalArgumentException {
-        sourceFormat(format);
+    public void setSourceFormat(final String format) throws IllegalArgumentException {
+        synchronized (buffer) {
+            sourceFormat(format);
+        }
     }
 
     /**
@@ -460,11 +492,13 @@ loop:   for (int i=0; ; i++) {
      * @param  level The level for which to get the color.
      * @return The color for the given level, or {@code null} if none.
      */
-    public synchronized String getLevelColor(final Level level) {
-        if (colors != null) {
-            final X364 code = colors.get(level);
-            if (code != null) {
-                return code.color;
+    public String getLevelColor(final Level level) {
+        synchronized (buffer) {
+            if (colors != null) {
+                final X364 code = colors.get(level);
+                if (code != null) {
+                    return code.color;
+                }
             }
         }
         return null;
@@ -483,20 +517,22 @@ loop:   for (int i=0; ; i++) {
      * @param  color The case-insensitive new color, or {@code null} if none.
      * @throws IllegalArgumentException If the given color is not one of the recognized values.
      */
-    public synchronized void setLevelColor(final Level level, final String color) throws IllegalArgumentException {
+    public void setLevelColor(final Level level, final String color) throws IllegalArgumentException {
         boolean changed = false;
-        if (color != null) {
-            final X364 code = X364.forColorName(color).background();
-            changed = (colors().put(level, code) != code);
-        } else if (colors != null) {
-            changed = (colors.remove(level) != null);
-            if (colors.isEmpty()) {
-                colors = null;
+        synchronized (buffer) {
+            if (color != null) {
+                final X364 code = X364.forColorName(color).background();
+                changed = (colors().put(level, code) != code);
+            } else if (colors != null) {
+                changed = (colors.remove(level) != null);
+                if (colors.isEmpty()) {
+                    colors = null;
+                }
+            }
+            if (changed) {
+                colorLevels = null;
+                colorSequences = null;
             }
-        }
-        if (changed) {
-            colorLevels = null;
-            colorSequences = null;
         }
     }
 
@@ -538,13 +574,15 @@ loop:   for (int i=0; ; i++) {
      *
      * @param enabled {@code true} for defining a default set of colors, or {@code false} for removing all colors.
      */
-    public synchronized void resetLevelColors(final boolean enabled) {
-        if (enabled) {
-            resetLevelColors();
-        } else {
-            colors = null;
-            colorLevels = null;
-            colorSequences = null;
+    public void resetLevelColors(final boolean enabled) {
+        synchronized (buffer) {
+            if (enabled) {
+                resetLevelColors();
+            } else {
+                colors = null;
+                colorLevels = null;
+                colorSequences = null;
+            }
         }
     }
 
@@ -577,120 +615,176 @@ loop:   for (int i=0; ; i++) {
      * @return A formatted log record.
      */
     @Override
-    public synchronized String format(final LogRecord record) {
+    public String format(final LogRecord record) {
         final Level level = record.getLevel();
-        final boolean colors  = (this.colors != null);
-        final boolean emphase = !faintSupported || (level.intValue() >= LEVEL_THRESHOLD.intValue());
         final StringBuffer buffer = this.buffer;
-        buffer.setLength(header.length());
-        /*
-         * Appends the time (e.g. "00:00:12.365"). The time pattern can be set either
-         * programmatically by a call to 'setTimeFormat(…)', or in logging.properties
-         * file with the "org.apache.sis.util.logging.MonolineFormatter.time" property.
-         */
-        if (timeFormat != null) {
-            Date time = new Date(Math.max(0, record.getMillis() - startMillis));
-            timeFormat.format(time, buffer, new FieldPosition(0));
-            buffer.append(' ');
-        }
-        /*
-         * Appends the level (e.g. "FINE"). We do not provide the option to turn level off for now.
-         * This level will be formatted with a colorized background if ANSI escape sequences are enabled.
-         */
-        int margin = buffer.length();
-        if (SHOW_LEVEL) {
-            if (colors) {
-                buffer.append(colorAt(level));
-            }
-            final int offset = buffer.length();
-            buffer.append(level.getLocalizedName())
-                  .append(CharSequences.spaces(levelWidth - (buffer.length() - offset)));
-            margin += buffer.length() - offset;
-            if (colors) {
-                buffer.append(X364.BACKGROUND_DEFAULT.sequence());
+        synchronized (buffer) {
+            final boolean colors  = (this.colors != null);
+            final boolean emphase = !faintSupported || (level.intValue() >= LEVEL_THRESHOLD.intValue());
+            buffer.setLength(header.length());
+            /*
+             * Appends the time (e.g. "00:00:12.365"). The time pattern can be set either
+             * programmatically by a call to 'setTimeFormat(…)', or in logging.properties
+             * file with the "org.apache.sis.util.logging.MonolineFormatter.time" property.
+             */
+            if (timeFormat != null) {
+                Date time = new Date(Math.max(0, record.getMillis() - startMillis));
+                timeFormat.format(time, buffer, new FieldPosition(0));
+                buffer.append(' ');
             }
-            buffer.append(' ');
-            margin++;
-        }
-        /*
-         * Appends the logger name or source class name, in long of short form.
-         * The name may be formatted in bold characters if ANSI escape sequences are enabled.
-         */
-        String source;
-        switch (sourceFormat) {
-            case LOGGER_SHORT: // Fall through
-            case LOGGER_LONG:  source = record.getLoggerName(); break;
-            case CLASS_SHORT:  // Fall through
-            case CLASS_LONG:   source = record.getSourceClassName(); break;
-            default:           source = null; break;
-        }
-        if (source != null) {
+            /*
+             * Appends the level (e.g. "FINE"). We do not provide the option to turn level off for now.
+             * This level will be formatted with a colorized background if ANSI escape sequences are enabled.
+             */
+            int margin = buffer.length();
+            if (SHOW_LEVEL) {
+                if (colors) {
+                    buffer.append(colorAt(level));
+                }
+                final int offset = buffer.length();
+                buffer.append(level.getLocalizedName())
+                      .append(CharSequences.spaces(levelWidth - (buffer.length() - offset)));
+                margin += buffer.length() - offset;
+                if (colors) {
+                    buffer.append(X364.BACKGROUND_DEFAULT.sequence());
+                }
+                buffer.append(' ');
+                margin++;
+            }
+            /*
+             * Appends the logger name or source class name, in long of short form.
+             * The name may be formatted in bold characters if ANSI escape sequences are enabled.
+             */
+            String source;
             switch (sourceFormat) {
                 case LOGGER_SHORT: // Fall through
-                case CLASS_SHORT: {
-                    // Works even if there is no '.' since we get -1 as index.
-                    source = source.substring(source.lastIndexOf('.') + 1);
-                    break;
+                case LOGGER_LONG:  source = record.getLoggerName(); break;
+                case CLASS_SHORT:  // Fall through
+                case CLASS_LONG:   source = record.getSourceClassName(); break;
+                default:           source = null; break;
+            }
+            if (source != null) {
+                switch (sourceFormat) {
+                    case LOGGER_SHORT: // Fall through
+                    case CLASS_SHORT: {
+                        // Works even if there is no '.' since we get -1 as index.
+                        source = source.substring(source.lastIndexOf('.') + 1);
+                        break;
+                    }
+                }
+                if (colors && emphase) {
+                    buffer.append(X364.BOLD.sequence());
+                }
+                buffer.append('[').append(source).append(']');
+                if (colors && emphase) {
+                    buffer.append(X364.NORMAL.sequence());
                 }
+                buffer.append(' ');
             }
-            if (colors && emphase) {
-                buffer.append(X364.BOLD.sequence());
+            /*
+             * Now prepare the LineAppender for the message. We set a line separator prefixed by some
+             * amount of spaces in order to align message body on the column after the level name.
+             */
+            String bodyLineSeparator = writer.getLineSeparator();
+            final String lineSeparator = System.lineSeparator();
+            if (bodyLineSeparator.length() != lineSeparator.length() + margin) {
+                bodyLineSeparator = lineSeparator + CharSequences.spaces(margin);
+                writer.setLineSeparator(bodyLineSeparator);
+            }
+            if (colors && !emphase) {
+                buffer.append(X364.FAINT.sequence());
+            }
+            final Throwable exception = record.getThrown();
+            String message = formatMessage(record);
+            int length = 0;
+            if (message != null) {
+                length = CharSequences.skipTrailingWhitespaces(message, 0, message.length());
             }
-            buffer.append('[').append(source).append(']');
-            if (colors && emphase) {
+            /*
+             * Up to this point, we wrote directly in the StringBuilder for performance reasons.
+             * Now for the message part, we need to use the LineAppender in order to replace EOL
+             * and tabulations.
+             */
+            try {
+                if (message != null) {
+                    writer.append(message, 0, length);
+                }
+                if (exception != null) {
+                    if (message != null) {
+                        writer.append("\nCaused by: "); // LineAppender will replace '\n' by the system EOL.
+                    }
+                    if (level.intValue() >= LEVEL_THRESHOLD.intValue()) {
+                        exception.printStackTrace(printer);
+                    } else {
+                        printAbridged(exception, writer, record.getLoggerName(),
+                                record.getSourceClassName(), record.getSourceMethodName());
+                    }
+                }
+                writer.flush();
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
+            buffer.setLength(CharSequences.skipTrailingWhitespaces(buffer, 0, buffer.length()));
+            if (colors && !emphase) {
                 buffer.append(X364.NORMAL.sequence());
             }
-            buffer.append(' ');
-        }
-        /*
-         * Now prepare the LineAppender for the message. We set a line separator prefixed by some
-         * amount of spaces in order to align message body on the column after the level name.
-         */
-        String bodyLineSeparator = writer.getLineSeparator();
-        final String lineSeparator = System.lineSeparator();
-        if (bodyLineSeparator.length() != lineSeparator.length() + margin) {
-            bodyLineSeparator = lineSeparator + CharSequences.spaces(margin);
-            writer.setLineSeparator(bodyLineSeparator);
-        }
-        if (colors && !emphase) {
-            buffer.append(X364.FAINT.sequence());
-        }
-        final Throwable exception = record.getThrown();
-        String message = formatMessage(record);
-        int length = 0;
-        if (message != null) {
-            length = CharSequences.skipTrailingWhitespaces(message, 0, message.length());
+            buffer.append(lineSeparator);
+            return buffer.toString();
         }
+    }
+
+    /**
+     * Returns the localized message from the given log record.
+     * First this method gets the {@linkplain LogRecord#getMessage() raw message} from the given record.
+     * Then there is choices:
+     *
+     * <ul>
+     *   <li>If the given record specifies a {@linkplain LogRecord#getResourceBundle() resource bundle},
+     *       then the message is used as a key for fetching the localized resources in the given bundle.</li>
+     *   <li>If the given record specifies one or more {@linkplain LogRecord#getParameters() parameters}
+     *       and if the message seems to use the {@link MessageFormat} syntax, then the message is formatted
+     *       by {@code MessageFormat}.</li>
+     * </ul>
+     *
+     * @param  record The log record from which to get a localized message.
+     * @return The localized message.
+     */
+    @Override
+    public String formatMessage(final LogRecord record) {
         /*
-         * Up to this point, we wrote directly in the StringBuilder for performance reasons.
-         * Now for the message part, we need to use the LineAppender in order to replace EOL
-         * and tabulations.
+         * Same work than java.util.logging.Formatter.formatMessage(LogRecord) except for the synchronization lock,
+         * the reuse of existing MessageFormat and StringBuffer instances, and not catching formatting exceptions
+         * (we want to know if our messages have a problem).
          */
-        try {
-            if (message != null) {
-                writer.append(message, 0, length);
-            }
-            if (exception != null) {
-                if (message != null) {
-                    writer.append("\nCaused by: "); // LineAppender will replace '\n' by the system EOL.
-                }
-                if (level.intValue() >= LEVEL_THRESHOLD.intValue()) {
-                    exception.printStackTrace(printer);
-                } else {
-                    printAbridged(exception, writer, record.getLoggerName(),
-                            record.getSourceClassName(), record.getSourceMethodName());
+        String message = record.getMessage();
+        ResourceBundle resources = record.getResourceBundle();
+        if (resources != null) {
+            message = resources.getString(message);
+        }
+        final Object parameters[] = record.getParameters();
+        if (parameters != null && parameters.length != 0) {
+            int i = message.indexOf('{');
+            if (i >= 0 && ++i < message.length()) {
+                final char c = message.charAt(i);
+                if (c >= '0' && c <= '9') {
+                    synchronized (buffer) {
+                        if (messageFormat == null) {
+                            messageFormat = new MessageFormat(message);
+                        } else if (!message.equals(messagePattern)) {
+                            messageFormat.applyPattern(message);
+                        }
+                        messagePattern = message;
+                        final int base = buffer.length();
+                        try {
+                            message = messageFormat.format(parameters, buffer, new FieldPosition(0)).substring(base);
+                        } finally {
+                            buffer.setLength(base);
+                        }
+                    }
                 }
             }
-            writer.flush();
-        } catch (IOException e) {
-            throw new AssertionError(e);
-        }
-        buffer.setLength(CharSequences.skipTrailingWhitespaces(buffer, 0, buffer.length()));
-        if (colors && !emphase) {
-            buffer.append(X364.NORMAL.sequence());
         }
-        buffer.append(lineSeparator);
-        return buffer.toString();
+        return message;
     }
 
     /**