8147569: Error messages from sjavac server does not always get relayed back to client
authoralundblad
Mon, 29 Feb 2016 13:24:01 +0100
changeset 36161 a025c0619f25
parent 36160 f42d362d0d17
child 36162 e5c4e9ab3cdd
8147569: Error messages from sjavac server does not always get relayed back to client Summary: Refactored how logging works in sjavac. Reviewed-by: jlahoda
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileProperties.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Log.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/SjavacClient.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/IdleResetSjavac.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/LinePrefixFilterWriter.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/PortFileMonitor.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/RequestHandler.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/Sjavac.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/SjavacServer.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/log/LazyInitFileLog.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/log/LoggingOutputStream.java
langtools/test/tools/sjavac/IdleShutdown.java
langtools/test/tools/sjavac/PooledExecution.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java	Mon Feb 29 13:24:01 2016 +0100
@@ -75,9 +75,7 @@
                              Map<String, PubApi> dependencyPublicApis,
                              int debugLevel,
                              boolean incremental,
-                             int numCores,
-                             Writer out,
-                             Writer err) {
+                             int numCores) {
         boolean rc = true;
         for (String pkgName : pkgSrcs.keySet()) {
             String pkgNameF = pkgName.replace('.',File.separatorChar);
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java	Mon Feb 29 13:24:01 2016 +0100
@@ -42,6 +42,8 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import com.sun.tools.sjavac.comp.CompilationService;
 import com.sun.tools.sjavac.options.Options;
@@ -89,9 +91,7 @@
                              final Map<String, PubApi> dependencyPubapis,
                              int debugLevel,
                              boolean incremental,
-                             int numCores,
-                             final Writer out,
-                             final Writer err) {
+                             int numCores) {
 
         Log.debug("Performing CompileJavaPackages transform...");
 
@@ -219,7 +219,9 @@
             }
 
             String chunkId = id + "-" + String.valueOf(i);
+            Log log = Log.get();
             compilationCalls.add(() -> {
+                Log.setLogForCurrentThread(log);
                 CompilationSubResult result = sjavac.compile("n/a",
                                                              chunkId,
                                                              args.prepJavacArgs(),
@@ -227,8 +229,8 @@
                                                              cc.srcs,
                                                              visibleSources);
                 synchronized (lock) {
-                    safeWrite(result.stdout, out);
-                    safeWrite(result.stderr, err);
+                    Util.getLines(result.stdout).forEach(Log::info);
+                    Util.getLines(result.stderr).forEach(Log::error);
                 }
                 return result;
             });
@@ -246,8 +248,10 @@
                 subResults.add(fut.get());
             } catch (ExecutionException ee) {
                 Log.error("Compilation failed: " + ee.getMessage());
-            } catch (InterruptedException ee) {
-                Log.error("Compilation interrupted: " + ee.getMessage());
+                Log.error(ee);
+            } catch (InterruptedException ie) {
+                Log.error("Compilation interrupted: " + ie.getMessage());
+                Log.error(ie);
                 Thread.currentThread().interrupt();
             }
         }
@@ -292,16 +296,6 @@
         return rc;
     }
 
-    private void safeWrite(String str, Writer w) {
-        if (str.length() > 0) {
-            try {
-                w.write(str);
-            } catch (IOException e) {
-                Log.error("Could not print compilation output.");
-            }
-        }
-    }
-
     /**
      * Split up the sources into compile chunks. If old package dependents information
      * is available, sort the order of the chunks into the most dependent first!
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileProperties.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileProperties.java	Mon Feb 29 13:24:01 2016 +0100
@@ -83,9 +83,7 @@
                              Map<String, PubApi> dependencyPublicApis,
                              int debugLevel,
                              boolean incremental,
-                             int numCores,
-                             Writer out,
-                             Writer err) {
+                             int numCores) {
         boolean rc = true;
         for (String pkgName : pkgSrcs.keySet()) {
             String pkgNameF = Util.toFileSystemPath(pkgName);
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java	Mon Feb 29 13:24:01 2016 +0100
@@ -70,9 +70,7 @@
                              Map<String, PubApi> dependencyPubapis,
                              int debugLevel,
                              boolean incremental,
-                             int numCores,
-                             Writer out,
-                             Writer err)
+                             int numCores)
     {
         boolean rc = true;
         String dest_filename;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java	Mon Feb 29 13:24:01 2016 +0100
@@ -123,16 +123,11 @@
     // Setup transform that always exist.
     private CompileJavaPackages compileJavaPackages = new CompileJavaPackages();
 
-    // Where to send stdout and stderr.
-    private Writer out, err;
-
     // Command line options.
     private Options options;
 
-    JavacState(Options op, boolean removeJavacState, Writer o, Writer e) {
+    JavacState(Options op, boolean removeJavacState) {
         options = op;
-        out = o;
-        err = e;
         numCores = options.getNumCores();
         theArgs = options.getStateArgsString();
         binDir = Util.pathToFile(options.getDestDir());
@@ -294,8 +289,8 @@
     /**
      * Load a javac_state file.
      */
-    public static JavacState load(Options options, Writer out, Writer err) {
-        JavacState db = new JavacState(options, false, out, err);
+    public static JavacState load(Options options) {
+        JavacState db = new JavacState(options, false);
         Module  lastModule = null;
         Package lastPackage = null;
         Source  lastSource = null;
@@ -367,22 +362,22 @@
             noFileFound = true;
         } catch (IOException e) {
             Log.info("Dropping old javac_state because of errors when reading it.");
-            db = new JavacState(options, true, out, err);
+            db = new JavacState(options, true);
             foundCorrectVerNr = true;
             newCommandLine = false;
             syntaxError = false;
     }
         if (foundCorrectVerNr == false && !noFileFound) {
             Log.info("Dropping old javac_state since it is of an old version.");
-            db = new JavacState(options, true, out, err);
+            db = new JavacState(options, true);
         } else
         if (newCommandLine == true && !noFileFound) {
             Log.info("Dropping old javac_state since a new command line is used!");
-            db = new JavacState(options, true, out, err);
+            db = new JavacState(options, true);
         } else
         if (syntaxError == true) {
             Log.info("Dropping old javac_state since it contains syntax errors.");
-            db = new JavacState(options, true, out, err);
+            db = new JavacState(options, true);
         }
         db.prev.calculateDependents();
         return db;
@@ -812,9 +807,7 @@
                                     dependencyPublicApis,
                                     0,
                                     isIncremental(),
-                                    numCores,
-                                    out,
-                                    err);
+                                    numCores);
             if (!r)
                 rc = false;
 
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Log.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Log.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2016, 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
@@ -26,11 +26,24 @@
 package com.sun.tools.sjavac;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.io.Writer;
+import java.util.Locale;
 
 /**
  * Utility class only for sjavac logging.
- * The log level can be set using for example --log=DEBUG on the sjavac command line.
+ *
+ * Logging in sjavac has special requirements when running in server/client
+ * mode. Most of the log messages is generated server-side, but the server
+ * is typically spawned by the client in the background, so the user usually
+ * does not see the server stdout/stderr. For this reason log messages needs
+ * to relayed back to the client that performed the request that generated the
+ * log message. To support this use case this class maintains a per-thread log
+ * instance so that each connected client can have its own instance that
+ * relays messages back to the requesting client.
+ *
+ * On the client-side (or when running sjavac without server-mode) there will
+ * typically just be one Log instance.
  *
  *  <p><b>This is NOT part of any supported API.
  *  If you write code that depends on this, you do so at your own risk.
@@ -38,61 +51,94 @@
  *  deletion without notice.</b>
  */
 public class Log {
-    private static PrintWriter out, err;
+
+    public enum Level {
+        ERROR,
+        WARN,
+        INFO,
+        DEBUG,
+        TRACE;
+    }
+
+    private static Log stdOutErr = new Log(new PrintWriter(System.out), new PrintWriter(System.err));
+    private static ThreadLocal<Log> loggers = new ThreadLocal<>();
+
+    protected PrintWriter err; // Used for error and warning messages
+    protected PrintWriter out; // Used for other messages
+    protected Level level = Level.INFO;
 
-    public final static int WARN = 1;
-    public final static int INFO = 2;
-    public final static int DEBUG = 3;
-    public final static int TRACE = 4;
-    private static int level = WARN;
+    public Log(Writer out, Writer err) {
+        this.out = out == null ? null : new PrintWriter(out, true);
+        this.err = err == null ? null : new PrintWriter(err, true);
+    }
+
+    public static void setLogForCurrentThread(Log log) {
+        loggers.set(log);
+    }
+
+    public static void setLogLevel(String l) {
+        setLogLevel(Level.valueOf(l.toUpperCase(Locale.US)));
+    }
+
+    public static void setLogLevel(Level l) {
+        get().level = l;
+    }
 
     static public void trace(String msg) {
-        if (level >= TRACE) {
-            out.println(msg);
-        }
+        log(Level.TRACE, msg);
     }
 
     static public void debug(String msg) {
-        if (level >= DEBUG) {
-            out.println(msg);
-        }
+        log(Level.DEBUG, msg);
     }
 
     static public void info(String msg) {
-        if (level >= INFO) {
-            out.println(msg);
-        }
+        log(Level.INFO, msg);
     }
 
     static public void warn(String msg) {
-        err.println(msg);
+        log(Level.WARN, msg);
     }
 
     static public void error(String msg) {
-        err.println(msg);
+        log(Level.ERROR, msg);
     }
 
-    static public void initializeLog(Writer o, Writer e) {
-        out = new PrintWriter(o);
-        err = new PrintWriter(e);
+    static public void error(Throwable t) {
+        log(Level.ERROR, t);
     }
 
-    static public void setLogLevel(String l) {
-        switch (l) {
-            case "warn": level = WARN; break;
-            case "info": level = INFO; break;
-            case "debug": level = DEBUG; break;
-            case "trace": level = TRACE; break;
-            default:
-                throw new IllegalArgumentException("No such log level \"" + l + "\"");
-        }
+    static public void log(Level l, String msg) {
+        get().printLogMsg(l, msg);
     }
 
-    static public boolean isTracing() {
-        return level >= TRACE;
+    public static void debug(Throwable t) {
+        log(Level.DEBUG, t);
+    }
+
+    public static void log(Level l, Throwable t) {
+        StringWriter sw = new StringWriter();
+        t.printStackTrace(new PrintWriter(sw, true));
+        log(l, sw.toString());
     }
 
     static public boolean isDebugging() {
-        return level >= DEBUG;
+        return get().isLevelLogged(Level.DEBUG);
+    }
+
+    protected boolean isLevelLogged(Level l) {
+        return l.ordinal() <= level.ordinal();
+    }
+
+    public static Log get() {
+        Log log = loggers.get();
+        return log != null ? log : stdOutErr;
+    }
+
+    protected void printLogMsg(Level msgLevel, String msg) {
+        if (isLevelLogged(msgLevel)) {
+            PrintWriter pw = msgLevel.ordinal() <= Level.WARN.ordinal() ? err : out;
+            pw.println(msg);
+        }
     }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java	Mon Feb 29 13:24:01 2016 +0100
@@ -95,9 +95,7 @@
                       Map<String, PubApi>     dependencyApis,
                       int debugLevel,
                       boolean incremental,
-                      int numCores,
-                      Writer out,
-                      Writer err);
+                      int numCores);
 
     void setExtra(String e);
     void setExtra(Options args);
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java	Mon Feb 29 13:24:01 2016 +0100
@@ -36,7 +36,9 @@
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.function.Function;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Utilities.
@@ -236,4 +238,10 @@
         int dotIndex = fileNameStr.indexOf('.');
         return dotIndex == -1 ? "" : fileNameStr.substring(dotIndex);
     }
+
+    public static Stream<String> getLines(String str) {
+        return str.isEmpty()
+                ? Stream.empty()
+                : Stream.of(str.split(Pattern.quote(System.lineSeparator())));
+    }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java	Mon Feb 29 13:24:01 2016 +0100
@@ -51,7 +51,7 @@
 
     public static int run(String[] args, Writer out, Writer err) {
 
-        Log.initializeLog(out, err);
+        Log.setLogForCurrentThread(new Log(out, err));
 
         Options options;
         try {
@@ -61,6 +61,8 @@
             return -1;
         }
 
+        Log.setLogLevel(options.getLogLevel());
+
         Log.debug("==========================================================");
         Log.debug("Launching sjavac client with the following parameters:");
         Log.debug("    " + options.getStateArgsString());
@@ -81,7 +83,7 @@
             sjavac = new SjavacImpl();
         }
 
-        int rc = sjavac.compile(args, out, err);
+        int rc = sjavac.compile(args);
 
         // If sjavac is running in the foreground we should shut it down at this point
         if (!useServer)
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/SjavacClient.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/SjavacClient.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -32,6 +32,7 @@
 import java.io.OutputStreamWriter;
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.io.Reader;
 import java.io.Writer;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -40,6 +41,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Scanner;
+import java.util.stream.Stream;
 
 import com.sun.tools.sjavac.Log;
 import com.sun.tools.sjavac.Util;
@@ -50,6 +52,8 @@
 import com.sun.tools.sjavac.server.Sjavac;
 import com.sun.tools.sjavac.server.SjavacServer;
 
+import static java.util.stream.Collectors.joining;
+
 /**
  * Sjavac implementation that delegates requests to a SjavacServer.
  *
@@ -64,8 +68,6 @@
     // JavaCompiler instance for several compiles using the same id.
     private final String id;
     private final PortFile portFile;
-    private final String logfile;
-    private final String stdouterrfile;
 
     // Default keepalive for server is 120 seconds.
     // I.e. it will accept 120 seconds of inactivity before quitting.
@@ -102,8 +104,6 @@
             Log.error("Port file inaccessable: " + e);
             throw e;
         }
-        logfile = Util.extractStringOption("logfile", serverConf, portfileName + ".javaclog");
-        stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfileName + ".stdouterr");
         sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac");
         int poolsize = Util.extractIntOption("poolsize", serverConf);
         keepalive = Util.extractIntOption("keepalive", serverConf, 120);
@@ -121,7 +121,7 @@
     }
 
     @Override
-    public int compile(String[] args, Writer stdout, Writer stderr) {
+    public int compile(String[] args) {
         int result = -1;
         try (Socket socket = tryConnect()) {
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
@@ -136,32 +136,33 @@
             // Read server response line by line
             String line;
             while (null != (line = in.readLine())) {
+                if (!line.contains(":")) {
+                    throw new AssertionError("Could not parse protocol line: >>\"" + line + "\"<<");
+                }
                 String[] typeAndContent = line.split(":", 2);
                 String type = typeAndContent[0];
                 String content = typeAndContent[1];
-                switch (type) {
-                case SjavacServer.LINE_TYPE_STDOUT:
-                    stdout.write(content);
-                    stdout.write('\n');
-                    break;
-                case SjavacServer.LINE_TYPE_STDERR:
-                    stderr.write(content);
-                    stderr.write('\n');
-                    break;
-                case SjavacServer.LINE_TYPE_RC:
+
+                try {
+                    Log.log(Log.Level.valueOf(type), "[server] " + content);
+                    continue;
+                } catch (IllegalArgumentException e) {
+                    // Parsing of 'type' as log level failed.
+                }
+
+                if (type.equals(SjavacServer.LINE_TYPE_RC)) {
                     result = Integer.parseInt(content);
-                    break;
                 }
             }
         } catch (IOException ioe) {
-            Log.error("[CLIENT] Exception caught: " + ioe);
+            Log.error("IOException caught during compilation: " + ioe.getMessage());
+            Log.debug(ioe);
             result = CompilationSubResult.ERROR_FATAL;
-            ioe.printStackTrace(new PrintWriter(stderr));
         } catch (InterruptedException ie) {
             Thread.currentThread().interrupt(); // Restore interrupt
-            Log.error("[CLIENT] compile interrupted.");
+            Log.error("Compilation interrupted.");
+            Log.debug(ie);
             result = CompilationSubResult.ERROR_FATAL;
-            ie.printStackTrace(new PrintWriter(stderr));
         }
         return result;
     }
@@ -215,11 +216,8 @@
         // Fork a new server and wait for it to start
         SjavacClient.fork(sjavacForkCmd,
                           portFile,
-                          logfile,
                           poolsize,
-                          keepalive,
-                          System.err,
-                          stdouterrfile);
+                          keepalive);
     }
 
     @Override
@@ -230,51 +228,53 @@
     /*
      * Fork a server process process and wait for server to come around
      */
-    public static void fork(String sjavacCmd,
-                            PortFile portFile,
-                            String logfile,
-                            int poolsize,
-                            int keepalive,
-                            final PrintStream err,
-                            String stdouterrfile)
-                                    throws IOException, InterruptedException {
+    public static void fork(String sjavacCmd, PortFile portFile, int poolsize, int keepalive)
+            throws IOException, InterruptedException {
         List<String> cmd = new ArrayList<>();
         cmd.addAll(Arrays.asList(OptionHelper.unescapeCmdArg(sjavacCmd).split(" ")));
         cmd.add("--startserver:"
               + "portfile=" + portFile.getFilename()
-              + ",logfile=" + logfile
-              + ",stdouterrfile=" + stdouterrfile
               + ",poolsize=" + poolsize
               + ",keepalive="+ keepalive);
 
-        Process p = null;
+        Process serverProcess;
         Log.info("Starting server. Command: " + String.join(" ", cmd));
         try {
-            // If the cmd for some reason can't be executed (file not found, or
-            // is not executable) this will throw an IOException with a decent
-            // error message.
-            p = new ProcessBuilder(cmd)
-                        .redirectErrorStream(true)
-                        .redirectOutput(new File(stdouterrfile))
-                        .start();
+            // If the cmd for some reason can't be executed (file is not found,
+            // or is not executable for instance) this will throw an
+            // IOException and p == null.
+            serverProcess = new ProcessBuilder(cmd)
+                    .redirectErrorStream(true)
+                    .start();
+        } catch (IOException ex) {
+            // Message is typically something like:
+            // Cannot run program "xyz": error=2, No such file or directory
+            Log.error("Failed to create server process: " + ex.getMessage());
+            Log.debug(ex);
+            throw new IOException(ex);
+        }
 
+        // serverProcess != null at this point.
+        try {
             // Throws an IOException if no valid values materialize
             portFile.waitForValidValues();
-
         } catch (IOException ex) {
-            // Log and rethrow exception
-            Log.error("Faild to launch server.");
-            Log.error("    Message: " + ex.getMessage());
-            String rc = p == null || p.isAlive() ? "n/a" : "" + p.exitValue();
-            Log.error("    Server process exit code: " + rc);
-            Log.error("Server log:");
-            Log.error("------- Server log start -------");
-            try (Scanner s = new Scanner(new File(stdouterrfile))) {
-                while (s.hasNextLine())
-                    Log.error(s.nextLine());
+            // Process was started, but server failed to initialize. This could
+            // for instance be due to the JVM not finding the server class,
+            // or the server running in to some exception early on.
+            Log.error("Sjavac server failed to initialize: " + ex.getMessage());
+            Log.error("Process output:");
+            Reader serverStdoutStderr = new InputStreamReader(serverProcess.getInputStream());
+            try (BufferedReader br = new BufferedReader(serverStdoutStderr)) {
+                br.lines().forEach(Log::error);
             }
-            Log.error("------- Server log end ---------");
-            throw ex;
+            Log.error("<End of process output>");
+            try {
+                Log.error("Process exit code: " + serverProcess.exitValue());
+            } catch (IllegalThreadStateException e) {
+                // Server is presumably still running.
+            }
+            throw new IOException("Server failed to initialize: " + ex.getMessage(), ex);
         }
     }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -25,15 +25,14 @@
 
 package com.sun.tools.sjavac.comp;
 
-import java.io.Writer;
+import com.sun.tools.sjavac.Log;
+import com.sun.tools.sjavac.server.Sjavac;
+
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-import com.sun.tools.sjavac.Log;
-import com.sun.tools.sjavac.server.Sjavac;
-
 /**
  * An sjavac implementation that limits the number of concurrent calls by
  * wrapping invocations in Callables and delegating them to a FixedThreadPool.
@@ -55,10 +54,12 @@
     }
 
     @Override
-    public int compile(String[] args, Writer out, Writer err) {
+    public int compile(String[] args) {
+        Log log = Log.get();
         try {
             return pool.submit(() -> {
-                return delegate.compile(args, out, err);
+                Log.setLogForCurrentThread(log);
+                return delegate.compile(args);
             }).get();
         } catch (Exception e) {
             e.printStackTrace();
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java	Mon Feb 29 13:24:01 2016 +0100
@@ -27,6 +27,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.io.Writer;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -68,7 +69,7 @@
 public class SjavacImpl implements Sjavac {
 
     @Override
-    public int compile(String[] args, Writer out, Writer err) {
+    public int compile(String[] args) {
         Options options;
         try {
             options = Options.parseArgs(args);
@@ -77,8 +78,6 @@
             return RC_FATAL;
         }
 
-        Log.setLogLevel(options.getLogLevel());
-
         if (!validateOptions(options))
             return RC_FATAL;
 
@@ -100,18 +99,21 @@
         if (stateDir == null) {
             // Prepare context. Direct logging to our byte array stream.
             Context context = new Context();
-            PrintWriter writer = new PrintWriter(err);
-            com.sun.tools.javac.util.Log.preRegister(context, writer);
+            StringWriter strWriter = new StringWriter();
+            PrintWriter printWriter = new PrintWriter(strWriter);
+            com.sun.tools.javac.util.Log.preRegister(context, printWriter);
             JavacFileManager.preRegister(context);
 
             // Prepare arguments
             String[] passThroughArgs = Stream.of(args)
                                              .filter(arg -> !arg.startsWith(Option.SERVER.arg))
                                              .toArray(String[]::new);
+            // Compile
+            Main.Result result = new Main("javac", printWriter).compile(passThroughArgs, context);
 
-            // Compile
-            com.sun.tools.javac.main.Main compiler = new com.sun.tools.javac.main.Main("javac", writer);
-            Main.Result result = compiler.compile(passThroughArgs, context);
+            // Process compiler output (which is always errors)
+            printWriter.flush();
+            Util.getLines(strWriter.toString()).forEach(Log::error);
 
             // Clean up
             JavaFileManager fileManager = context.get(JavaFileManager.class);
@@ -126,7 +128,7 @@
 
         } else {
             // Load the prev build state database.
-            JavacState javac_state = JavacState.load(options, out, err);
+            JavacState javac_state = JavacState.load(options);
 
             // Setup the suffix rules from the command line.
             Map<String, Transformer> suffixRules = new HashMap<>();
@@ -288,10 +290,12 @@
 
                 return rc[0] ? RC_OK : RC_FATAL;
             } catch (ProblemException e) {
+                // For instance make file list mismatch.
                 Log.error(e.getMessage());
+                Log.debug(e);
                 return RC_FATAL;
             } catch (Exception e) {
-                e.printStackTrace(new PrintWriter(err));
+                Log.error(e);
                 return RC_FATAL;
             }
         }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/IdleResetSjavac.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/IdleResetSjavac.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -25,6 +25,10 @@
 
 package com.sun.tools.sjavac.server;
 
+import com.sun.tools.sjavac.Log;
+
+import java.io.FileWriter;
+import java.io.IOException;
 import java.io.Writer;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -53,8 +57,8 @@
     private TimerTask idlenessTimerTask;
 
     public IdleResetSjavac(Sjavac delegate,
-                            Terminable toShutdown,
-                            long idleTimeout) {
+                           Terminable toShutdown,
+                           long idleTimeout) {
         this.delegate = delegate;
         this.toShutdown = toShutdown;
         this.idleTimeout = idleTimeout;
@@ -62,10 +66,10 @@
     }
 
     @Override
-    public int compile(String[] args, Writer out, Writer err) {
+    public int compile(String[] args) {
         startCall();
         try {
-            return delegate.compile(args, out, err);
+            return delegate.compile(args);
         } finally {
             endCall();
         }
@@ -95,6 +99,7 @@
             throw new IllegalStateException("Idle timeout already scheduled");
         idlenessTimerTask = new TimerTask() {
             public void run() {
+                Log.setLogForCurrentThread(ServerMain.getErrorLog());
                 toShutdown.shutdown("Server has been idle for " + (idleTimeout / 1000) + " seconds.");
             }
         };
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/LinePrefixFilterWriter.java	Mon Feb 29 11:54:06 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.sun.tools.sjavac.server;
-
-import java.io.FilterWriter;
-import java.io.IOException;
-import java.io.Writer;
-
-/**
- * Inserts {@literal prefix} in front of each line written.
- *
- * A line is considered to be terminated by any one of a line feed, a carriage
- * return, or a carriage return followed immediately by a line feed.
- */
-public class LinePrefixFilterWriter extends FilterWriter {
-
-    private final String prefix;
-    private boolean atBeginningOfLine = true;
-    private char lastChar = '\0';
-
-    protected LinePrefixFilterWriter(Writer out, String prefix) {
-        super(out);
-        this.prefix = prefix;
-    }
-
-    @Override
-    public void write(String str, int off, int len) throws IOException {
-        for (int i = 0; i < len; i++) {
-            write(str.charAt(off + i));
-        }
-    }
-
-    @Override
-    public void write(char[] cbuf, int off, int len) throws IOException {
-        for (int i = 0; i < len; i++) {
-            write(cbuf[off + i]);
-        }
-    }
-
-    @Override
-    public void write(int c) throws IOException {
-        if (lastChar == '\r' && c == '\n') {
-            // Second character of CR+LF sequence.
-            // Do nothing. We already started a new line on last character.
-        } else {
-            if (atBeginningOfLine) {
-                super.write(prefix, 0, prefix.length());
-            }
-            super.write(c);
-            atBeginningOfLine = c == '\r' || c == '\n';
-        }
-        lastChar = (char) c;
-    }
-}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/PortFileMonitor.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/PortFileMonitor.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -25,6 +25,8 @@
 
 package com.sun.tools.sjavac.server;
 
+import com.sun.tools.sjavac.Log;
+
 import java.io.IOException;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -56,8 +58,11 @@
     }
 
     public void start() {
+        Log log = Log.get();
         TimerTask shutdownCheck = new TimerTask() {
             public void run() {
+                Log.setLogForCurrentThread(log);
+                Log.debug("Checking port file status...");
                 try {
                     if (!portFile.exists()) {
                         // Time to quit because the portfile was deleted by another
@@ -74,12 +79,11 @@
                         server.shutdown("Quitting because portfile is now owned by another javac server!");
                     }
                 } catch (IOException e) {
-                    e.printStackTrace(server.theLog);
-                    server.flushLog();
+                    Log.error("IOException caught in PortFileMonitor.");
+                    Log.debug(e);
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
-                    e.printStackTrace(server.theLog);
-                    server.flushLog();
+                    Log.error(e);
                 }
             }
         };
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/RequestHandler.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/RequestHandler.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -25,19 +25,16 @@
 
 package com.sun.tools.sjavac.server;
 
-import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC;
-import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDERR;
-import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDOUT;
+import com.sun.tools.sjavac.Log;
+import com.sun.tools.sjavac.Util;
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
 import java.net.Socket;
+import java.nio.file.Path;
 
-import com.sun.tools.sjavac.AutoFlushWriter;
-import com.sun.tools.sjavac.Log;
+import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC;
 
 
 /**
@@ -56,7 +53,7 @@
  *  This code and its internal interfaces are subject to change or
  *  deletion without notice.</b>
  */
-public class RequestHandler implements Runnable {
+public class RequestHandler extends Thread {
 
     private final Socket socket;
     private final Sjavac sjavac;
@@ -68,9 +65,30 @@
 
     @Override
     public void run() {
+
         try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
              PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
 
+            // Set up logging for this thread. Stream back logging messages to
+            // client on the format format "level:msg".
+            Log.setLogForCurrentThread(new Log(out, out) {
+                @Override
+                protected boolean isLevelLogged(Level l) {
+                    // Make sure it is up to the client to decide whether or
+                    // not this message should be displayed.
+                    return true;
+                }
+
+                @Override
+                protected void printLogMsg(Level msgLevel, String msg) {
+                    // Follow sjavac server/client protocol: Send one line
+                    // at a time and prefix with message with "level:".
+                    Util.getLines(msg)
+                        .map(line -> msgLevel + ":" + line)
+                        .forEach(line -> super.printLogMsg(msgLevel, line));
+                }
+            });
+
             // Read argument array
             int n = Integer.parseInt(in.readLine());
             String[] args = new String[n];
@@ -78,23 +96,32 @@
                 args[i] = in.readLine();
             }
 
+            // If there has been any internal errors, notify client
+            checkInternalErrorLog();
+
             // Perform compilation
-            Writer stdout = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDOUT + ":");
-            Writer stderr = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDERR + ":");
-            int rc = sjavac.compile(args, stdout, stderr);
-            stdout.flush();
-            stderr.flush();
+            int rc = sjavac.compile(args);
 
             // Send return code back to client
             out.println(LINE_TYPE_RC + ":" + rc);
 
+            // Check for internal errors again.
+            checkInternalErrorLog();
         } catch (Exception ex) {
             // Not much to be done at this point. The client side request
             // code will most likely throw an IOException and the
             // compilation will fail.
-            StringWriter sw = new StringWriter();
-            ex.printStackTrace(new PrintWriter(sw));
-            Log.error(sw.toString());
+            Log.error(ex);
+        } finally {
+            Log.setLogForCurrentThread(null);
+        }
+    }
+
+    private void checkInternalErrorLog() {
+        Path errorLog = ServerMain.getErrorLog().getLogDestination();
+        if (errorLog != null) {
+            Log.error("Server has encountered an internal error. See " + errorLog.toAbsolutePath()
+                    + " for details.");
         }
     }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -25,10 +25,20 @@
 
 package com.sun.tools.sjavac.server;
 
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.FilterWriter;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.lang.Thread.UncaughtExceptionHandler;
 
 import com.sun.tools.sjavac.Log;
+import com.sun.tools.sjavac.Log.Level;
+import com.sun.tools.sjavac.server.log.LazyInitFileLog;
+import com.sun.tools.sjavac.server.log.LoggingOutputStream;
+
+import static com.sun.tools.sjavac.Log.Level.ERROR;
+import static com.sun.tools.sjavac.Log.Level.INFO;
 
 /**
  *  <p><b>This is NOT part of any supported API.
@@ -37,20 +47,40 @@
  *  deletion without notice.</b>
  */
 public class ServerMain {
+
+    // For logging server internal (non request specific) errors.
+    private static LazyInitFileLog errorLog;
+
     public static int run(String[] args) {
 
-        Log.initializeLog(new OutputStreamWriter(System.out),
-                          new OutputStreamWriter(System.err));
+        // Under normal operation, all logging messages generated server-side
+        // are due to compilation requests. These logging messages should
+        // be relayed back to the requesting client rather than written to the
+        // server log. The only messages that should be written to the server
+        // log (in production mode) should be errors,
+        Log.setLogForCurrentThread(errorLog = new LazyInitFileLog("server.log"));
+        Log.setLogLevel(ERROR); // should be set to ERROR.
+
+        // Make sure no exceptions go under the radar
+        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
+            Log.setLogForCurrentThread(errorLog);
+            Log.error(e);
+        });
+
+        // Inevitably someone will try to print messages using System.{out,err}.
+        // Make sure this output also ends up in the log.
+        System.setOut(new PrintStream(new LoggingOutputStream(System.out, INFO, "[stdout] ")));
+        System.setErr(new PrintStream(new LoggingOutputStream(System.err, ERROR, "[stderr] ")));
 
         // Any options other than --startserver?
         if (args.length > 1) {
-            System.err.println("When spawning a background server, only a single --startserver argument is allowed.");
+            Log.error("When spawning a background server, only a single --startserver argument is allowed.");
             return 1;
         }
 
         int exitCode;
         try {
-            SjavacServer server = new SjavacServer(args[0], System.err);
+            SjavacServer server = new SjavacServer(args[0]);
             exitCode = server.startServer();
         } catch (IOException | InterruptedException ex) {
             ex.printStackTrace();
@@ -59,4 +89,8 @@
 
         return exitCode;
     }
+
+    public static LazyInitFileLog getErrorLog() {
+        return errorLog;
+    }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/Sjavac.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/Sjavac.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -42,6 +42,6 @@
     final static int RC_FATAL = -1;
     final static int RC_OK = 0;
 
-    int compile(String[] args, Writer stdout, Writer stderr);
+    int compile(String[] args);
     void shutdown();
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/SjavacServer.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/SjavacServer.java	Mon Feb 29 13:24:01 2016 +0100
@@ -26,6 +26,7 @@
 package com.sun.tools.sjavac.server;
 
 import java.io.FileNotFoundException;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
@@ -39,6 +40,7 @@
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.sun.tools.sjavac.Log;
 import com.sun.tools.sjavac.Util;
 import com.sun.tools.sjavac.client.PortFileInaccessibleException;
 import com.sun.tools.sjavac.comp.PooledSjavac;
@@ -54,17 +56,12 @@
  */
 public class SjavacServer implements Terminable {
 
-    // Used in protocol to tell the content of each line
+    // Prefix of line containing return code.
     public final static String LINE_TYPE_RC = "RC";
-    public final static String LINE_TYPE_STDOUT = "STDOUT";
-    public final static String LINE_TYPE_STDERR = "STDERR";
 
     final private String portfilename;
-    final private String logfile;
-    final private String stdouterrfile;
     final private int poolsize;
     final private int keepalive;
-    final private PrintStream err;
 
     // The secret cookie shared between server and client through the port file.
     // Used to prevent clients from believing that they are communicating with
@@ -75,9 +72,6 @@
     // Accumulated build time, not counting idle time, used for logging purposes
     private long totalBuildTime;
 
-    // The javac server specific log file.
-    PrintWriter theLog;
-
     // The sjavac implementation to delegate requests to
     Sjavac sjavac;
 
@@ -92,40 +86,29 @@
     // For the client, all port files fetched, one per started javac server.
     // Though usually only one javac server is started by a client.
     private static Map<String, PortFile> allPortFiles;
-    private static Map<String, Long> maxServerMemory;
 
-    public SjavacServer(String settings, PrintStream err) throws FileNotFoundException {
+    public SjavacServer(String settings) throws FileNotFoundException {
         this(Util.extractStringOption("portfile", settings),
-             Util.extractStringOption("logfile", settings),
-             Util.extractStringOption("stdouterrfile", settings),
              Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
-             Util.extractIntOption("keepalive", settings, 120),
-             err);
+             Util.extractIntOption("keepalive", settings, 120));
     }
 
     public SjavacServer(String portfilename,
-                        String logfile,
-                        String stdouterrfile,
                         int poolsize,
-                        int keepalive,
-                        PrintStream err)
+                        int keepalive)
                                 throws FileNotFoundException {
         this.portfilename = portfilename;
-        this.logfile = logfile;
-        this.stdouterrfile = stdouterrfile;
         this.poolsize = poolsize;
         this.keepalive = keepalive;
-        this.err = err;
-
-        myCookie = new Random().nextLong();
-        theLog = new PrintWriter(logfile);
+        this.myCookie = new Random().nextLong();
     }
 
 
     /**
      * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
      */
-    public static synchronized PortFile getPortFile(String filename) throws PortFileInaccessibleException {
+    public static synchronized PortFile getPortFile(String filename)
+            throws PortFileInaccessibleException {
         if (allPortFiles == null) {
             allPortFiles = new HashMap<>();
         }
@@ -170,26 +153,6 @@
     }
 
     /**
-     * Log this message.
-     */
-    public void log(String msg) {
-        if (theLog != null) {
-            theLog.println(msg);
-        } else {
-            System.err.println(msg);
-        }
-    }
-
-    /**
-     * Make sure the log is flushed.
-     */
-    public void flushLog() {
-        if (theLog != null) {
-            theLog.flush();
-        }
-    }
-
-    /**
      * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
      * is sent as the settings parameter. Returns 0 on success, -1 on failure.
      */
@@ -203,7 +166,7 @@
             portFile.lock();
             portFile.getValues();
             if (portFile.containsPortInfo()) {
-                err.println("Javac server not started because portfile exists!");
+                Log.info("Javac server not started because portfile exists!");
                 portFile.unlock();
                 return -1;
             }
@@ -230,23 +193,23 @@
         portFileMonitor = new PortFileMonitor(portFile, this);
         portFileMonitor.start();
 
-        log("Sjavac server started. Accepting connections...");
-        log("    port: " + getPort());
-        log("    time: " + new java.util.Date());
-        log("    poolsize: " + poolsize);
-        flushLog();
+        Log.info("Sjavac server started. Accepting connections...");
+        Log.info("    port: " + getPort());
+        Log.info("    time: " + new java.util.Date());
+        Log.info("    poolsize: " + poolsize);
+
 
         keepAcceptingRequests.set(true);
         do {
             try {
                 Socket socket = serverSocket.accept();
-                new Thread(new RequestHandler(socket, sjavac)).start();
+                new RequestHandler(socket, sjavac).start();
             } catch (SocketException se) {
                 // Caused by serverSocket.close() and indicates shutdown
             }
         } while (keepAcceptingRequests.get());
 
-        log("Shutting down.");
+        Log.info("Shutting down.");
 
         // No more connections accepted. If any client managed to connect after
         // the accept() was interrupted but before the server socket is closed
@@ -254,8 +217,7 @@
         // IOException on the client side.
 
         long realTime = System.currentTimeMillis() - serverStart;
-        log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
-        flushLog();
+        Log.info("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
 
         // Shut down
         sjavac.shutdown();
@@ -270,8 +232,7 @@
             return;
         }
 
-        log("Quitting: " + quitMsg);
-        flushLog();
+        Log.info("Quitting: " + quitMsg);
 
         portFileMonitor.shutdown(); // No longer any need to monitor port file
 
@@ -280,12 +241,12 @@
         try {
             portFile.delete();
         } catch (IOException | InterruptedException e) {
-            e.printStackTrace(theLog);
+            Log.error(e);
         }
         try {
             serverSocket.close();
         } catch (IOException e) {
-            e.printStackTrace(theLog);
+            Log.error(e);
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/log/LazyInitFileLog.java	Mon Feb 29 13:24:01 2016 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.sun.tools.sjavac.server.log;
+
+import com.sun.tools.sjavac.Log;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class LazyInitFileLog extends Log {
+
+    String baseFilename;
+    Path destination = null;
+
+    public LazyInitFileLog(String baseFilename) {
+        super(null, null);
+        this.baseFilename = baseFilename;
+    }
+
+    protected void printLogMsg(Level msgLevel, String msg) {
+        try {
+            // Lazily initialize out/err
+            if (out == null && isLevelLogged(msgLevel)) {
+                destination = getAvailableDestination();
+                out = err = new PrintWriter(new FileWriter(destination.toFile()), true);
+            }
+            // Proceed to log the message
+            super.printLogMsg(msgLevel, msg);
+        } catch (IOException e) {
+            // This could be bad. We might have run into an error and we can't
+            // log it. Resort to printing on stdout.
+            System.out.println("IO error occurred: " + e.getMessage());
+            System.out.println("Original message: [" + msgLevel + "] " + msg);
+        }
+    }
+
+    /**
+     * @return The first available path of baseFilename, baseFilename.1,
+     * basefilename.2, ...
+     */
+    private Path getAvailableDestination() {
+        Path p = Paths.get(baseFilename);
+        int i = 1;
+        while (Files.exists(p)) {
+            p = Paths.get(baseFilename + "." + i++);
+        }
+        return p;
+    }
+
+    public Path getLogDestination() {
+        return destination;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/log/LoggingOutputStream.java	Mon Feb 29 13:24:01 2016 +0100
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.sun.tools.sjavac.server.log;
+
+import com.sun.tools.sjavac.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class LoggingOutputStream extends FilterOutputStream {
+
+    private static final byte[] LINE_SEP = System.lineSeparator().getBytes();
+
+    private final Log.Level level;
+    private final String linePrefix;
+    private EolTrackingByteArrayOutputStream buf = new EolTrackingByteArrayOutputStream();
+
+    public LoggingOutputStream(OutputStream out, Log.Level level, String linePrefix) {
+        super(out);
+        this.level = level;
+        this.linePrefix = linePrefix;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        super.write(b);
+        buf.write(b);
+        if (buf.isLineComplete()) {
+            String line = new String(buf.toByteArray(), 0, buf.size() - LINE_SEP.length);
+            Log.log(level, linePrefix + line);
+            buf = new EolTrackingByteArrayOutputStream();
+        }
+    }
+
+    private static class EolTrackingByteArrayOutputStream extends ByteArrayOutputStream {
+        private static final byte[] EOL = System.lineSeparator().getBytes();
+        private boolean isLineComplete() {
+            if (count < EOL.length) {
+                return false;
+            }
+            for (int i = 0; i < EOL.length; i++) {
+                if (buf[count - EOL.length + i] != EOL[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+}
--- a/langtools/test/tools/sjavac/IdleShutdown.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/test/tools/sjavac/IdleShutdown.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -65,11 +65,11 @@
         // Use Sjavac object and wait less than TIMEOUT_MS in between calls
         Thread.sleep(TIMEOUT_MS - 1000);
         log("Compiling");
-        service.compile(new String[0], null, null);
+        service.compile(new String[0]);
 
         Thread.sleep(TIMEOUT_MS - 1000);
         log("Compiling");
-        service.compile(new String[0], null, null);
+        service.compile(new String[0]);
 
         if (timeoutTimestamp.get() != -1)
             throw new AssertionError("Premature timeout detected.");
@@ -103,7 +103,7 @@
         public void shutdown() {
         }
         @Override
-        public int compile(String[] args, Writer out, Writer err) {
+        public int compile(String[] args) {
             // Attempt to trigger idle timeout during a call by sleeping
             try {
                 Thread.sleep(TIMEOUT_MS + 1000);
--- a/langtools/test/tools/sjavac/PooledExecution.java	Mon Feb 29 11:54:06 2016 +0100
+++ b/langtools/test/tools/sjavac/PooledExecution.java	Mon Feb 29 13:24:01 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, 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
@@ -30,10 +30,12 @@
  * @build Wrapper
  * @run main Wrapper PooledExecution
  */
+import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import com.sun.tools.sjavac.Log;
 import com.sun.tools.sjavac.comp.PooledSjavac;
 import com.sun.tools.sjavac.server.Sjavac;
 
@@ -67,7 +69,7 @@
             for (int i = 0; i < NUM_REQUESTS; i++) {
                 tasks[i] = new Thread() {
                     public void run() {
-                        service.compile(new String[0], null, null);
+                        service.compile(new String[0]);
                         tasksFinished.incrementAndGet();
                     }
                 };
@@ -109,7 +111,7 @@
             AtomicInteger activeRequests = new AtomicInteger(0);
 
             @Override
-            public int compile(String[] args, Writer out, Writer err) {
+            public int compile(String[] args) {
                 leftToStart.countDown();
                 int numActiveRequests = activeRequests.incrementAndGet();
                 System.out.printf("Left to start: %2d / Currently active: %2d%n",