aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/events/Reporter.java
blob: d8ea92929459986e173c760ae241bcc14b19c240 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// 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.base.Preconditions;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.util.io.OutErr;
import java.io.PrintStream;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 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
 * arbitrary thread (but right now, they will not be run concurrently).
 */
public final class Reporter implements ExtendedEventHandler, ExceptionListener {

  private final ConcurrentLinkedQueue<EventHandler> handlers = new ConcurrentLinkedQueue<>();
  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 void addHandler(EventHandler handler) {
    handlers.add(Preconditions.checkNotNull(handler));
  }

  /** Removes handler from this reporter. */
  public void removeHandler(EventHandler handler) {
     handlers.remove(handler);
  }

  /** This method is called by the build system to report an event. */
  @Override
  public void handle(Event e) {
    if (e.getKind() != EventKind.ERROR
        && e.getKind() != EventKind.DEBUG
        && 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;
    }
  }
}