/* * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.ref.Reference; import java.nio.charset.Charset; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.net.http.common.Utils; import static java.nio.charset.StandardCharsets.UTF_8; /* * @test * @summary Verify the behaviour of the debug logger. * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.httpclient.test.lib.common.HttpServerAdapters jdk.test.lib.net.SimpleSSLContext * DebugLoggerTest * @run main/othervm DebugLoggerTest * @run main/othervm -Djdk.internal.httpclient.debug=errr DebugLoggerTest * @run main/othervm -Djdk.internal.httpclient.debug=err DebugLoggerTest ERR * @run main/othervm -Djdk.internal.httpclient.debug=out DebugLoggerTest OUT * @run main/othervm -Djdk.internal.httpclient.debug=log DebugLoggerTest LOG * @run main/othervm -Djdk.internal.httpclient.debug=true DebugLoggerTest ERR LOG * @run main/othervm -Djdk.internal.httpclient.debug=err,OUT DebugLoggerTest ERR OUT * @run main/othervm -Djdk.internal.httpclient.debug=err,out,log DebugLoggerTest ERR OUT LOG * @run main/othervm -Djdk.internal.httpclient.debug=true,log DebugLoggerTest ERR LOG * @run main/othervm -Djdk.internal.httpclient.debug=true,out DebugLoggerTest ERR OUT LOG * @run main/othervm -Djdk.internal.httpclient.debug=err,OUT,foo DebugLoggerTest ERR OUT */ public class DebugLoggerTest { static final PrintStream stdErr = System.err; static final PrintStream stdOut = System.out; static final String LOGGER_NAME = "jdk.internal.httpclient.debug"; static final String MESSAGE = "May the luck of the Irish be with you!"; static final String MESSAGE2 = "May the wind be at your back!"; static final String MESSAGE3 = "May the sun shine warm upon your face!"; static RecordingPrintStream err = null; static RecordingPrintStream out = null; /** * A {@code RecordingPrintStream} is a {@link PrintStream} that makes * it possible to record part of the data stream in memory while still * forwarding everything to a delegated {@link OutputStream}. * @apiNote * For instance, a {@code RecordingPrintStream} might be used as an * interceptor to record anything printed on {@code System.err} * at specific times. Recording can be started and stopped * at any time, and multiple times. For instance, a typical * usage might be: *
static final PrintStream stderr = System.err; * static final RecordingPrintString recorder = * new RecordingPrintStream(stderr, true, UTF_8); * static { * System.setErr(recorder); * } * * ... * // .... * recorder.startRecording(); * try { * // do something * String str1 = recorder.drainRecordedData(); * // do something else * String str2 = recorder.drainRecordedData(); * // .... * } finally { * recorder.stopRecording(); * } * // .... * ... **
Though the methods are thread safe, {@link #startRecording()}
* {@link #stopRecording()} and {@link #drainRecordedData()} must
* not be called concurrently by different threads without external
* orchestration, as calling these methods mutate the state of
* the recorder in a way that can be globally observed by all
* threads.
*/
public static final class RecordingPrintStream extends PrintStream {
private final Charset charset;
private final ByteArrayOutputStream recordedData;
private volatile boolean recording;
/**
* Creates a new {@code RecordingPrintStream} instance that wraps
* the provided {@code OutputStream}.
* @implSpec Calls {@link PrintStream#PrintStream(
* OutputStream, boolean, Charset)}.
* @param out An {@code OutputStream} instance to which all bytes will
* be forwarded.
* @param autoFlush Whether {@linkplain PrintStream#PrintStream(
* OutputStream, boolean, Charset) autoFlush} is on.
* @param charset A {@linkplain Charset} used to transform text to
* bytes and bytes to string.
*/
public RecordingPrintStream(OutputStream out, boolean autoFlush, Charset charset) {
super(out, autoFlush, charset);
this.charset = charset;
recordedData = new ByteArrayOutputStream();
}
/**
* Flushes the stream and starts recording.
* If recording is already started, this method has no effect beyond
* {@linkplain PrintStream#flush() flushing} the stream.
*/
public void startRecording() {
flush(); // make sure everything that was printed before is flushed
synchronized (recordedData) {
recording = true;
}
}
/**
* Flushes the stream and stops recording.
* If recording is already stopped, this method has no effect beyond
* {@linkplain PrintStream#flush() flushing} the stream.
*/
public void stopRecording() {
flush(); // make sure everything that was printed before is flushed
synchronized (recordedData) {
recording = false;
}
}
/**
* Flushes the stream, drains the recorded data, convert it
* to a string, and returns it. This has the effect of
* flushing any recorded data from memory: the next call
* to {@code drainRecordedData()} will not return any data
* previously returned.
* This method can be called regardless of whether recording
* is started or stopped.
*/
public String drainRecordedData() {
flush(); // make sure everything that was printed before is flushed
synchronized (recordedData) {
String data = recordedData.toString(charset);
recordedData.reset();
return data;
}
}
@Override
public void write(int b) {
super.write(b);
if (recording) {
synchronized (recordedData) {
if (recording) recordedData.write(b);
}
}
}
@Override
public void write(byte[] buf, int off, int len) {
super.write(buf, off, len);
if (recording) {
synchronized (recordedData) {
if (recording) recordedData.write(buf, off, len);
}
}
}
}
static class TestHandler extends Handler {
final CopyOnWriteArrayList logs = new CopyOnWriteArrayList();
TestHandler() {
setLevel(Level.ALL);
}
@Override
public void publish(LogRecord record) {
logs.add(record);
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
}
enum Destination {OUT, ERR, LOG}
static Set