src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/DebugLogger.java
branchhttp-client-branch
changeset 56089 42208b2f224e
parent 56088 38fac6d0521d
child 56090 5c7fb702948a
equal deleted inserted replaced
56088:38fac6d0521d 56089:42208b2f224e
     1 /*
       
     2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.incubator.http.internal.common;
       
    26 
       
    27 import java.io.PrintStream;
       
    28 import java.util.Objects;
       
    29 import java.util.ResourceBundle;
       
    30 import java.util.function.Supplier;
       
    31 import java.lang.System.Logger;
       
    32 
       
    33 /**
       
    34  * A {@code System.Logger} that forwards all messages to an underlying
       
    35  * {@code System.Logger}, after adding some decoration.
       
    36  * The logger also has the ability to additionally send the logged messages
       
    37  * to System.err or System.out, whether the underlying logger is activated or not.
       
    38  * In addition instance of {@code DebugLogger} support both
       
    39  * {@link String#format(String, Object...)} and
       
    40  * {@link java.text.MessageFormat#format(String, Object...)} formatting.
       
    41  * String-like formatting is enabled by the presence of "%s" or "%d" in the format
       
    42  * string. MessageFormat-like formatting is enabled by the presence of "{0" or "{1".
       
    43  * <p>
       
    44  * See {@link Utils#getDebugLogger(Supplier, boolean)} and
       
    45  * {@link Utils#getHpackLogger(Supplier, boolean)}.
       
    46  */
       
    47 class DebugLogger implements Logger {
       
    48     // deliberately not in the same subtree than standard loggers.
       
    49     final static String HTTP_NAME  = "jdk.internal.httpclient.debug";
       
    50     final static String HPACK_NAME = "jdk.internal.httpclient.hpack.debug";
       
    51     final static Logger HTTP = System.getLogger(HTTP_NAME);
       
    52     final static Logger HPACK = System.getLogger(HPACK_NAME);
       
    53     final static long START_NANOS = System.nanoTime();
       
    54 
       
    55     private final Supplier<String> dbgTag;
       
    56     private final Level errLevel;
       
    57     private final Level outLevel;
       
    58     private final Logger logger;
       
    59     private final boolean debugOn;
       
    60     private final boolean traceOn;
       
    61 
       
    62     /**
       
    63      * Create a logger for debug traces.The logger should only be used
       
    64      * with levels whose severity is {@code <= DEBUG}.
       
    65      *
       
    66      * By default, this logger will forward all messages logged to the supplied
       
    67      * {@code logger}.
       
    68      * But in addition, if the message severity level is {@code >=} to
       
    69      * the provided {@code errLevel} it will print the messages on System.err,
       
    70      * and if the message severity level is {@code >=} to
       
    71      * the provided {@code outLevel} it will also print the messages on System.out.
       
    72      * <p>
       
    73      * The logger will add some decoration to the printed message, in the form of
       
    74      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
       
    75      *
       
    76      * @apiNote To obtain a logger that will always print things on stderr in
       
    77      *          addition to forwarding to the internal logger, use
       
    78      *          {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.ALL);}.
       
    79      *          To obtain a logger that will only forward to the internal logger,
       
    80      *          use {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.OFF);}.
       
    81      *
       
    82      * @param logger The internal logger to which messages will be forwarded.
       
    83      *               This should be either {@link #HPACK} or {@link #HTTP};
       
    84      *
       
    85      * @param dbgTag A lambda that returns a string that identifies the caller
       
    86      *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
       
    87      * @param outLevel The level above which messages will be also printed on
       
    88      *               System.out (in addition to being forwarded to the internal logger).
       
    89      * @param errLevel The level above which messages will be also printed on
       
    90      *               System.err (in addition to being forwarded to the internal logger).
       
    91      *
       
    92      * @return A logger for HTTP internal debug traces
       
    93      */
       
    94     private DebugLogger(Logger logger,
       
    95                 Supplier<String> dbgTag,
       
    96                 Level outLevel,
       
    97                 Level errLevel) {
       
    98         this.dbgTag = dbgTag;
       
    99         this.errLevel = errLevel;
       
   100         this.outLevel = outLevel;
       
   101         this.logger = Objects.requireNonNull(logger);
       
   102         // support only static configuration.
       
   103         this.debugOn = isEnabled(Level.DEBUG);
       
   104         this.traceOn = isEnabled(Level.TRACE);
       
   105     }
       
   106 
       
   107     @Override
       
   108     public String getName() {
       
   109         return logger.getName();
       
   110     }
       
   111 
       
   112     private boolean isEnabled(Level level) {
       
   113         if (level == Level.OFF) return false;
       
   114         int severity = level.getSeverity();
       
   115         return severity >= errLevel.getSeverity()
       
   116                 || severity >= outLevel.getSeverity()
       
   117                 || logger.isLoggable(level);
       
   118     }
       
   119 
       
   120     @Override
       
   121     public boolean isLoggable(Level level) {
       
   122         // fast path, we assume these guys never change.
       
   123         // support only static configuration.
       
   124         if (level == Level.DEBUG) return debugOn;
       
   125         if (level == Level.TRACE) return traceOn;
       
   126         return isEnabled(level);
       
   127     }
       
   128 
       
   129     @Override
       
   130     public void log(Level level, ResourceBundle unused,
       
   131                     String format, Object... params) {
       
   132         // fast path, we assume these guys never change.
       
   133         // support only static configuration.
       
   134         if (level == Level.DEBUG && !debugOn) return;
       
   135         if (level == Level.TRACE && !traceOn) return;
       
   136 
       
   137         int severity = level.getSeverity();
       
   138         if (errLevel != Level.OFF
       
   139                 && errLevel.getSeverity() <= severity) {
       
   140             print(System.err, level, format, params, null);
       
   141         }
       
   142         if (outLevel != Level.OFF
       
   143                 && outLevel.getSeverity() <= severity) {
       
   144             print(System.out, level, format, params, null);
       
   145         }
       
   146         if (logger.isLoggable(level)) {
       
   147             logger.log(level, unused,
       
   148                     getFormat(new StringBuilder(), format, params).toString(),
       
   149                     params);
       
   150         }
       
   151     }
       
   152 
       
   153     @Override
       
   154     public void log(Level level, ResourceBundle unused, String msg,
       
   155                     Throwable thrown) {
       
   156         // fast path, we assume these guys never change.
       
   157         if (level == Level.DEBUG && !debugOn) return;
       
   158         if (level == Level.TRACE && !traceOn) return;
       
   159 
       
   160         if (errLevel != Level.OFF
       
   161                 && errLevel.getSeverity() <= level.getSeverity()) {
       
   162             print(System.err, level, msg, null, thrown);
       
   163         }
       
   164         if (outLevel != Level.OFF
       
   165                 && outLevel.getSeverity() <= level.getSeverity()) {
       
   166             print(System.out, level, msg, null, thrown);
       
   167         }
       
   168         if (logger.isLoggable(level)) {
       
   169             logger.log(level, unused,
       
   170                     getFormat(new StringBuilder(), msg, null).toString(),
       
   171                     thrown);
       
   172         }
       
   173     }
       
   174 
       
   175     private void print(PrintStream out, Level level, String msg,
       
   176                        Object[] params, Throwable t) {
       
   177         StringBuilder sb = new StringBuilder();
       
   178         sb.append(level.name()).append(':').append(' ');
       
   179         sb = format(sb, msg, params);
       
   180         if (t != null) sb.append(' ').append(t.toString());
       
   181         out.println(sb.toString());
       
   182         if (t != null) {
       
   183             t.printStackTrace(out);
       
   184         }
       
   185     }
       
   186 
       
   187     private StringBuilder decorate(StringBuilder sb, String msg) {
       
   188         String tag = dbgTag == null ? null : dbgTag.get();
       
   189         String res = msg == null ? "" : msg;
       
   190         long elapsed = System.nanoTime() - START_NANOS;
       
   191         long millis = elapsed / 1000_000;
       
   192         long secs   = millis / 1000;
       
   193         sb.append('[').append(Thread.currentThread().getName()).append(']')
       
   194                 .append(' ').append('[');
       
   195         if (secs > 0) {
       
   196             sb.append(secs).append('s');
       
   197         }
       
   198         millis = millis % 1000;
       
   199         if (millis > 0) {
       
   200             if (secs > 0) sb.append(' ');
       
   201             sb.append(millis).append("ms");
       
   202         }
       
   203         sb.append(']').append(' ');
       
   204         if (tag != null) {
       
   205             sb.append(tag).append(' ');
       
   206         }
       
   207         sb.append(res);
       
   208         return sb;
       
   209     }
       
   210 
       
   211 
       
   212     private StringBuilder getFormat(StringBuilder sb, String format, Object[] params) {
       
   213         if (format == null || params == null || params.length == 0) {
       
   214             return decorate(sb, format);
       
   215         } else if (format.contains("{0}") || format.contains("{1}")) {
       
   216             return decorate(sb, format);
       
   217         } else if (format.contains("%s") || format.contains("%d")) {
       
   218             try {
       
   219                 return decorate(sb, String.format(format, params));
       
   220             } catch (Throwable t) {
       
   221                 return decorate(sb, format);
       
   222             }
       
   223         } else {
       
   224             return decorate(sb, format);
       
   225         }
       
   226     }
       
   227 
       
   228     private StringBuilder format(StringBuilder sb, String format, Object[] params) {
       
   229         if (format == null || params == null || params.length == 0) {
       
   230             return decorate(sb, format);
       
   231         } else if (format.contains("{0}") || format.contains("{1}")) {
       
   232             return decorate(sb, java.text.MessageFormat.format(format, params));
       
   233         } else if (format.contains("%s") || format.contains("%d")) {
       
   234             try {
       
   235                 return decorate(sb, String.format(format, params));
       
   236             } catch (Throwable t) {
       
   237                 return decorate(sb, format);
       
   238             }
       
   239         } else {
       
   240             return decorate(sb, format);
       
   241         }
       
   242     }
       
   243 
       
   244     public static DebugLogger createHttpLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
       
   245         return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
       
   246     }
       
   247 
       
   248     public static DebugLogger createHpackLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
       
   249         return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
       
   250     }
       
   251 }