// 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.events; import com.google.common.eventbus.EventBus; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.io.OutErr; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; /** * The reporter is the primary means of reporting events such as errors, warnings, progress * information and diagnostic information to the user. It is not intended as a logging mechanism for * developer-only messages; use a Logger for that. * * <p>The reporter instance is consumed by the build system, and passes events to {@link * EventHandler} instances. These handlers are registered via {@link #addHandler(EventHandler)}. The * reporter's main use is in the blaze runtime and its lifetime is the lifetime of the blaze server. * * <p>Thread-safe: calls to {@code #report} may be made on any thread. Handlers may be run in an * arbitary thread (but right now, they will not be run concurrently). */ public final class Reporter implements ExtendedEventHandler, ExceptionListener { private final List<EventHandler> handlers = new ArrayList<>(); private EventBus eventBus; /** An OutErr that sends all of its output to this Reporter. * Each write will (when flushed) get mapped to an EventKind.STDOUT or EventKind.STDERR event. */ private final OutErr outErrToReporter = outErrForReporter(this); private volatile OutputFilter outputFilter = OutputFilter.OUTPUT_EVERYTHING; private EventHandler ansiAllowingHandler; private EventHandler ansiStrippingHandler; private boolean ansiAllowingHandlerRegistered; public Reporter(EventBus eventBus) { this.eventBus = eventBus; } public static OutErr outErrForReporter(EventHandler rep) { return OutErr.create( // We don't use BufferedOutputStream here, because in general the Blaze // code base assumes that the output streams are not buffered. new ReporterStream(rep, EventKind.STDOUT), new ReporterStream(rep, EventKind.STDERR)); } /** * A copy constructor, to make it convenient to replicate a reporter * config for temporary configuration changes. */ public Reporter(Reporter template) { handlers.addAll(template.handlers); this.eventBus = template.eventBus; } /** Constructor which configures a reporter with the specified handlers. */ public Reporter(EventBus eventBus, EventHandler... handlers) { this.eventBus = eventBus; for (EventHandler handler: handlers) { addHandler(handler); } } /** * Returns an OutErr that sends all of its output to this Reporter. * Each write to the OutErr will cause an EventKind.STDOUT or EventKind.STDERR event. */ public OutErr getOutErr() { return outErrToReporter; } /** * Adds a handler to this reporter. */ public synchronized void addHandler(EventHandler handler) { Preconditions.checkNotNull(handler); handlers.add(handler); } /** * Removes handler from this reporter. */ public synchronized void removeHandler(EventHandler handler) { handlers.remove(handler); } /** * This method is called by the build system to report an event. */ @Override public synchronized void handle(Event e) { if (e.getKind() != EventKind.ERROR && e.getTag() != null && !showOutput(e.getTag())) { return; } for (EventHandler handler : handlers) { handler.handle(e); } } @Override public void post(ExtendedEventHandler.Postable obj) { if (eventBus != null) { eventBus.post(obj); } } public void clearEventBus() { eventBus = null; } /** * Reports the start of a particular task. * Is a wrapper around report() with event kind START. * Should always be matched by a corresponding call to finishTask() * with the same message, except that the leading percentage * progress indicator (if any) in the message may differ. */ public void startTask(Location location, String message) { handle(Event.of(EventKind.START, location, message)); } /** * Reports the end of a particular task. * Is a wrapper around report() with event kind FINISH. * Should always be matched by a corresponding call to startTask() * with the same message, except that the leading percentage * progress indicator (if any) in the message may differ. */ public void finishTask(Location location, String message) { handle(Event.of(EventKind.FINISH, location, message)); } @Override public void error(Location location, String message, Throwable error) { handle(Event.error(location, message)); error.printStackTrace(new PrintStream(getOutErr().getErrorStream())); } /** * Returns true iff the given tag matches the output filter. */ public boolean showOutput(String tag) { return outputFilter.showOutput(tag); } public void setOutputFilter(OutputFilter outputFilter) { this.outputFilter = outputFilter; } /** * Registers an ANSI-control-code-allowing EventHandler with an ANSI-stripping EventHandler * that is already registered with the reporter. The ANSI-stripping handler can then be replaced * with the ANSI-allowing handler by calling {@code #switchToAnsiAllowingHandler} which * calls {@code removeHandler} for the ANSI-stripping handler and then {@code addHandler} for the * ANSI-allowing handler. */ public synchronized void registerAnsiAllowingHandler( EventHandler ansiStrippingHandler, EventHandler ansiAllowingHandler) { this.ansiAllowingHandler = ansiAllowingHandler; this.ansiStrippingHandler = ansiStrippingHandler; ansiAllowingHandlerRegistered = true; } /** * Restores the ANSI-allowing EventHandler registered using * {@link #registerAnsiAllowingHandler}. */ public synchronized void switchToAnsiAllowingHandler() { if (ansiAllowingHandlerRegistered) { removeHandler(ansiStrippingHandler); addHandler(ansiAllowingHandler); ansiStrippingHandler = null; ansiAllowingHandler = null; ansiAllowingHandlerRegistered = false; } } }