com.google.devtools.build.lib.runtime.BugReport.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.runtime.BugReport.java

Source

// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.runtime;

import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
import com.google.devtools.build.lib.util.CustomExitCodePublisher;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.LoggingUtil;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.util.io.OutErr;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Utility methods for sending bug reports.
 *
 * <p> Note, code in this class must be extremely robust.  There's nothing
 * worse than a crash-handler that itself crashes!
 */
public abstract class BugReport {

    private BugReport() {
    }

    private static Logger LOG = Logger.getLogger(BugReport.class.getName());

    private static BlazeVersionInfo versionInfo = BlazeVersionInfo.instance();

    private static BlazeRuntime runtime = null;

    private static AtomicBoolean alreadyHandlingCrash = new AtomicBoolean(false);

    public static void setRuntime(BlazeRuntime newRuntime) {
        Preconditions.checkNotNull(newRuntime);
        Preconditions.checkState(runtime == null, "runtime already set: %s, %s", runtime, newRuntime);
        runtime = newRuntime;
    }

    /**
     * Logs the unhandled exception with a special prefix signifying that this was a crash.
     *
     * @param exception the unhandled exception to display.
     * @param args additional values to record in the message.
     * @param values Additional string values to clarify the exception.
     */
    public static void sendBugReport(Throwable exception, List<String> args, String... values) {
        if (!versionInfo.isReleasedBlaze()) {
            LOG.info("(Not a released binary; not logged.)");
            return;
        }

        logException(exception, filterClientEnv(args), values);
    }

    private static void logCrash(Throwable throwable, String... args) {
        BugReport.sendBugReport(throwable, Arrays.asList(args));
        BugReport.printBug(OutErr.SYSTEM_OUT_ERR, throwable);
        System.err.println(runtime.getProductName() + " crash in async thread:");
        throwable.printStackTrace();
    }

    /**
     * Print and send a bug report, and exit with the proper Blaze code. Does not exit if called a
     * second time. This method tries hard to catch any throwables thrown during its execution and
     * halts the runtime in that case.
     */
    public static void handleCrash(Throwable throwable, String... args) {
        int exitCode = getExitCodeForThrowable(throwable);
        try {
            if (alreadyHandlingCrash.compareAndSet(false, true)) {
                try {
                    logCrash(throwable, args);
                    if (runtime != null) {
                        runtime.notifyCommandComplete(exitCode);
                        // We don't call runtime#shutDown() here because all it does is shut down the modules,
                        // and who knows if they can be trusted. Instead, we call runtime#shutdownOnCrash()
                        // which attempts to cleanly shutdown those modules that might have something pending
                        // to do as a best-effort operation.
                        runtime.shutdownOnCrash();
                    }
                    CustomExitCodePublisher.maybeWriteExitStatusFile(exitCode);
                } finally {
                    // Avoid shutdown deadlock issues: If an application shutdown hook crashes, it will
                    // trigger our Blaze crash handler (this method). Calling System#exit() here, would
                    // therefore induce a deadlock. This call would block on the shutdown sequence completing,
                    // but the shutdown sequence would in turn be blocked on this thread finishing. Instead,
                    // exit fast via halt().
                    Runtime.getRuntime().halt(exitCode);
                }
            } else {
                logCrash(throwable, args);
            }
        } catch (Throwable t) {
            System.err.println("An crash occurred while " + runtime.getProductName()
                    + " was trying to handle a crash! Please file a bug against " + runtime.getProductName()
                    + " and include the information below.");

            System.err.println("Original uncaught exception:");
            throwable.printStackTrace(System.err);

            System.err.println("Exception encountered during BugReport#handleCrash:");
            t.printStackTrace(System.err);

            Runtime.getRuntime().halt(exitCode);
        }
    }

    /** Get exit code corresponding to throwable. */
    public static int getExitCodeForThrowable(Throwable throwable) {
        return (Throwables.getRootCause(throwable) instanceof OutOfMemoryError)
                ? ExitCode.OOM_ERROR.getNumericExitCode()
                : ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode();
    }

    private static void printThrowableTo(OutErr outErr, Throwable e) {
        PrintStream err = new PrintStream(outErr.getErrorStream());
        e.printStackTrace(err);
        err.flush();
        LOG.log(Level.SEVERE, runtime.getProductName() + " crashed", e);
    }

    /**
     * Print user-helpful information about the bug/crash to the output.
     *
     * @param outErr where to write the output
     * @param e the exception thrown
     */
    public static void printBug(OutErr outErr, Throwable e) {
        if (e instanceof OutOfMemoryError) {
            outErr.printErr(
                    e.getMessage() + "\n\n" + runtime.getProductName() + " ran out of memory and crashed.\n");
        } else {
            printThrowableTo(outErr, e);
        }
    }

    /**
     * Filters {@code args} by removing any item that starts with "--client_env",
     * then returns this as an immutable list.
     *
     * <p>The client's environment variables may contain sensitive data, so we filter it out.
     */
    private static List<String> filterClientEnv(Iterable<String> args) {
        if (args == null) {
            return null;
        }

        ImmutableList.Builder<String> filteredArgs = ImmutableList.builder();
        for (String arg : args) {
            if (arg != null && !arg.startsWith("--client_env")) {
                filteredArgs.add(arg);
            }
        }
        return filteredArgs.build();
    }

    // Log the exception.  Because this method is only called in a blaze release,
    // this will result in a report being sent to a remote logging service.
    private static void logException(Throwable exception, List<String> args, String... values) {
        // The preamble is used in the crash watcher, so don't change it
        // unless you know what you're doing.
        String preamble = runtime.getProductName()
                + (exception instanceof OutOfMemoryError ? " OOMError: " : " crashed with args: ");

        LoggingUtil.logToRemote(Level.SEVERE, preamble + Joiner.on(' ').join(args), exception, values);
    }
}