src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java
changeset 49765 ee6f7a61f3a5
parent 48083 b1c1b4ef4be2
child 49944 4690a2871b44
child 56451 9585061fdb04
equal deleted inserted replaced
49707:f7fd051519ac 49765:ee6f7a61f3a5
       
     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 
       
    26 package jdk.internal.net.http.common;
       
    27 
       
    28 import java.io.PrintStream;
       
    29 import java.util.Objects;
       
    30 import java.util.ResourceBundle;
       
    31 import java.util.function.Supplier;
       
    32 
       
    33 /**
       
    34  * A {@code System.Logger} that forwards all messages to an underlying
       
    35  * {@code System.Logger}, after adding some decoration. The logger also has the
       
    36  * ability to additionally send the logged messages to System.err or System.out,
       
    37  * whether the underlying logger is activated or not. In addition instance of
       
    38  * {@code DebugLogger} support both {@link String#format(String, Object...)} and
       
    39  * {@link java.text.MessageFormat#format(String, Object...)} formatting.
       
    40  * String-like formatting is enabled by the presence of "%s" or "%d" in the
       
    41  * format string. MessageFormat-like formatting is enabled by the presence of
       
    42  * "{0" or "{1".
       
    43  *
       
    44  * <p> See {@link Utils#getDebugLogger(Supplier, boolean)} and
       
    45  * {@link Utils#getHpackLogger(Supplier, boolean)}.
       
    46  */
       
    47 final 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 WS_NAME  = "jdk.internal.httpclient.websocket.debug";
       
    51     final static String HPACK_NAME = "jdk.internal.httpclient.hpack.debug";
       
    52     final static System.Logger HTTP = System.getLogger(HTTP_NAME);
       
    53     final static System.Logger WS = System.getLogger(WS_NAME);
       
    54     final static System.Logger HPACK = System.getLogger(HPACK_NAME);
       
    55     final static long START_NANOS = System.nanoTime();
       
    56 
       
    57     private final Supplier<String> dbgTag;
       
    58     private final Level errLevel;
       
    59     private final Level outLevel;
       
    60     private final System.Logger logger;
       
    61     private final boolean debugOn;
       
    62     private final boolean traceOn;
       
    63 
       
    64     /**
       
    65      * Create a logger for debug traces.The logger should only be used
       
    66      * with levels whose severity is {@code <= DEBUG}.
       
    67      *
       
    68      * By default, this logger will forward all messages logged to the supplied
       
    69      * {@code logger}.
       
    70      * But in addition, if the message severity level is {@code >=} to
       
    71      * the provided {@code errLevel} it will print the messages on System.err,
       
    72      * and if the message severity level is {@code >=} to
       
    73      * the provided {@code outLevel} it will also print the messages on System.out.
       
    74      * <p>
       
    75      * The logger will add some decoration to the printed message, in the form of
       
    76      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
       
    77      *
       
    78      * @apiNote To obtain a logger that will always print things on stderr in
       
    79      *          addition to forwarding to the internal logger, use
       
    80      *          {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.ALL);}.
       
    81      *          To obtain a logger that will only forward to the internal logger,
       
    82      *          use {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.OFF);}.
       
    83      *
       
    84      * @param logger The internal logger to which messages will be forwarded.
       
    85      *               This should be either {@link #HPACK} or {@link #HTTP};
       
    86      *
       
    87      * @param dbgTag A lambda that returns a string that identifies the caller
       
    88      *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
       
    89      * @param outLevel The level above which messages will be also printed on
       
    90      *               System.out (in addition to being forwarded to the internal logger).
       
    91      * @param errLevel The level above which messages will be also printed on
       
    92      *               System.err (in addition to being forwarded to the internal logger).
       
    93      *
       
    94      * @return A logger for HTTP internal debug traces
       
    95      */
       
    96     private DebugLogger(System.Logger logger,
       
    97                 Supplier<String> dbgTag,
       
    98                 Level outLevel,
       
    99                 Level errLevel) {
       
   100         this.dbgTag = dbgTag;
       
   101         this.errLevel = errLevel;
       
   102         this.outLevel = outLevel;
       
   103         this.logger = Objects.requireNonNull(logger);
       
   104         // support only static configuration.
       
   105         this.debugOn = isEnabled(Level.DEBUG);
       
   106         this.traceOn = isEnabled(Level.TRACE);
       
   107     }
       
   108 
       
   109     @Override
       
   110     public String getName() {
       
   111         return logger.getName();
       
   112     }
       
   113 
       
   114     private boolean isEnabled(Level level) {
       
   115         if (level == Level.OFF) return false;
       
   116         int severity = level.getSeverity();
       
   117         return severity >= errLevel.getSeverity()
       
   118                 || severity >= outLevel.getSeverity()
       
   119                 || logger.isLoggable(level);
       
   120     }
       
   121 
       
   122     @Override
       
   123     public final boolean on() {
       
   124         return debugOn;
       
   125     }
       
   126 
       
   127     @Override
       
   128     public boolean isLoggable(Level level) {
       
   129         // fast path, we assume these guys never change.
       
   130         // support only static configuration.
       
   131         if (level == Level.DEBUG) return debugOn;
       
   132         if (level == Level.TRACE) return traceOn;
       
   133         return isEnabled(level);
       
   134     }
       
   135 
       
   136     @Override
       
   137     public void log(Level level, ResourceBundle unused,
       
   138                     String format, Object... params) {
       
   139         // fast path, we assume these guys never change.
       
   140         // support only static configuration.
       
   141         if (level == Level.DEBUG && !debugOn) return;
       
   142         if (level == Level.TRACE && !traceOn) return;
       
   143 
       
   144         int severity = level.getSeverity();
       
   145         if (errLevel != Level.OFF
       
   146                 && errLevel.getSeverity() <= severity) {
       
   147             print(System.err, level, format, params, null);
       
   148         }
       
   149         if (outLevel != Level.OFF
       
   150                 && outLevel.getSeverity() <= severity) {
       
   151             print(System.out, level, format, params, null);
       
   152         }
       
   153         if (logger.isLoggable(level)) {
       
   154             logger.log(level, unused,
       
   155                     getFormat(new StringBuilder(), format, params).toString(),
       
   156                     params);
       
   157         }
       
   158     }
       
   159 
       
   160     @Override
       
   161     public void log(Level level, ResourceBundle unused, String msg,
       
   162                     Throwable thrown) {
       
   163         // fast path, we assume these guys never change.
       
   164         if (level == Level.DEBUG && !debugOn) return;
       
   165         if (level == Level.TRACE && !traceOn) return;
       
   166 
       
   167         if (errLevel != Level.OFF
       
   168                 && errLevel.getSeverity() <= level.getSeverity()) {
       
   169             print(System.err, level, msg, null, thrown);
       
   170         }
       
   171         if (outLevel != Level.OFF
       
   172                 && outLevel.getSeverity() <= level.getSeverity()) {
       
   173             print(System.out, level, msg, null, thrown);
       
   174         }
       
   175         if (logger.isLoggable(level)) {
       
   176             logger.log(level, unused,
       
   177                     getFormat(new StringBuilder(), msg, null).toString(),
       
   178                     thrown);
       
   179         }
       
   180     }
       
   181 
       
   182     private void print(PrintStream out, Level level, String msg,
       
   183                        Object[] params, Throwable t) {
       
   184         StringBuilder sb = new StringBuilder();
       
   185         sb.append(level.name()).append(':').append(' ');
       
   186         sb = format(sb, msg, params);
       
   187         if (t != null) sb.append(' ').append(t.toString());
       
   188         out.println(sb.toString());
       
   189         if (t != null) {
       
   190             t.printStackTrace(out);
       
   191         }
       
   192     }
       
   193 
       
   194     private StringBuilder decorate(StringBuilder sb, String msg) {
       
   195         String tag = dbgTag == null ? null : dbgTag.get();
       
   196         String res = msg == null ? "" : msg;
       
   197         long elapsed = System.nanoTime() - START_NANOS;
       
   198         long millis = elapsed / 1000_000;
       
   199         long secs   = millis / 1000;
       
   200         sb.append('[').append(Thread.currentThread().getName()).append(']')
       
   201                 .append(' ').append('[');
       
   202         if (secs > 0) {
       
   203             sb.append(secs).append('s');
       
   204         }
       
   205         millis = millis % 1000;
       
   206         if (millis > 0) {
       
   207             if (secs > 0) sb.append(' ');
       
   208             sb.append(millis).append("ms");
       
   209         }
       
   210         sb.append(']').append(' ');
       
   211         if (tag != null) {
       
   212             sb.append(tag).append(' ');
       
   213         }
       
   214         sb.append(res);
       
   215         return sb;
       
   216     }
       
   217 
       
   218 
       
   219     private StringBuilder getFormat(StringBuilder sb, String format, Object[] params) {
       
   220         if (format == null || params == null || params.length == 0) {
       
   221             return decorate(sb, format);
       
   222         } else if (format.contains("{0}") || format.contains("{1}")) {
       
   223             return decorate(sb, format);
       
   224         } else if (format.contains("%s") || format.contains("%d")) {
       
   225             try {
       
   226                 return decorate(sb, String.format(format, params));
       
   227             } catch (Throwable t) {
       
   228                 return decorate(sb, format);
       
   229             }
       
   230         } else {
       
   231             return decorate(sb, format);
       
   232         }
       
   233     }
       
   234 
       
   235     private StringBuilder format(StringBuilder sb, String format, Object[] params) {
       
   236         if (format == null || params == null || params.length == 0) {
       
   237             return decorate(sb, format);
       
   238         } else if (format.contains("{0}") || format.contains("{1}")) {
       
   239             return decorate(sb, java.text.MessageFormat.format(format, params));
       
   240         } else if (format.contains("%s") || format.contains("%d")) {
       
   241             try {
       
   242                 return decorate(sb, String.format(format, params));
       
   243             } catch (Throwable t) {
       
   244                 return decorate(sb, format);
       
   245             }
       
   246         } else {
       
   247             return decorate(sb, format);
       
   248         }
       
   249     }
       
   250 
       
   251     public static DebugLogger createHttpLogger(Supplier<String> dbgTag,
       
   252                                                Level outLevel,
       
   253                                                Level errLevel) {
       
   254         return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
       
   255     }
       
   256 
       
   257     public static DebugLogger createWebSocketLogger(Supplier<String> dbgTag,
       
   258                                                     Level outLevel,
       
   259                                                     Level errLevel) {
       
   260         return new DebugLogger(WS, dbgTag, outLevel, errLevel);
       
   261     }
       
   262 
       
   263     public static DebugLogger createHpackLogger(Supplier<String> dbgTag,
       
   264                                                 Level outLevel,
       
   265                                                 Level errLevel) {
       
   266         return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
       
   267     }
       
   268 }