aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/worker/ErrorMessage.java
blob: 46d32985cb45bd7b0c2f14d1aec7cd541fa1fbf4 (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
// Copyright 2017 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.worker;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;

/** A well-formatted error message that is easy to read and easy to create. */
@AutoValue
abstract class ErrorMessage {
  abstract String message();

  @Override
  public String toString() {
    return message();
  }

  public static Builder builder() {
    return new Builder();
  }

  public static final class Builder {
    private String message = "Unknown error";
    private Path logFile;
    private String logText = "";
    private int logSizeLimit = Integer.MAX_VALUE;
    private Exception exception;

    private Builder() {}

    /** Sets the main text of this error message. */
    public Builder message(String message) {
      Preconditions.checkNotNull(message);
      this.message = message.isEmpty() ? "Unknown error" : message.trim();
      return this;
    }

    /** Sets the log file that should be printed as part of the error message. */
    public Builder logFile(Path logFile) {
      Preconditions.checkNotNull(logFile);
      try {
        this.logFile = logFile;
        return logText(FileSystemUtils.readContent(logFile, UTF_8));
      } catch (IOException e) {
        logSizeLimit(Integer.MAX_VALUE);
        return logText(
            "ERROR: IOException while trying to read log file:\n"
                + Throwables.getStackTraceAsString(e));
      }
    }

    /**
     * Sets additional text, which is to be presented as a log file in the error message.
     *
     * <p>If the log originally comes from a file, it is recommended to use {@link #logFile}
     * instead, because then the path to the log file can be printed together with the message.
     */
    public Builder logText(String logText) {
      Preconditions.checkNotNull(logText);
      // Set the log text to "(empty)" when the passed in string is empty, otherwise error messages
      // like "Something failed. Check below log for details:" would be pretty confusing for users.
      this.logText = logText.isEmpty() ? "(empty)" : logText.trim();
      return this;
    }

    /**
     * If the log file or text of this error message is longer than the character limit set via this
     * method, it will be truncated so that only the last X characters of the log are printed.
     */
    public Builder logSizeLimit(int logSizeLimit) {
      Preconditions.checkArgument(logSizeLimit > 0, "logSizeLimit must be positive");
      this.logSizeLimit = logSizeLimit;
      return this;
    }

    /** Lets the error message contain the details of an exception. */
    public Builder exception(Exception e) {
      this.exception = e;
      return this;
    }

    /** Builds and returns the formatted error message. */
    public ErrorMessage build() {
      StringBuilder sb = new StringBuilder(message);

      if (exception != null) {
        sb.append("\n\n---8<---8<--- Exception details ---8<---8<---\n");
        sb.append(Throwables.getStackTraceAsString(exception).trim());
        sb.append("\n---8<---8<--- End of exception details ---8<---8<---");
      }

      if (!logText.isEmpty()) {
        sb.append("\n\n---8<---8<--- Start of log");
        if (logText.length() > logSizeLimit) {
          sb.append(" snippet");
        }
        if (logFile != null) {
          sb.append(", file at ");
          sb.append(logFile.getPathString());
        }
        sb.append(" ---8<---8<---\n");

        // If the length of the log is longer than the limit, print only the last part.
        if (logText.length() > logSizeLimit) {
          sb.append("[... truncated ...]\n");
          sb.append(logText, logText.length() - logSizeLimit, logText.length());
          sb.append("\n---8<---8<--- End of log snippet, ");
          sb.append(logText.length() - logSizeLimit);
          sb.append(" chars omitted ---8<---8<---");
        } else {
          sb.append(logText);
          sb.append("\n---8<---8<--- End of log ---8<---8<---");
        }
      }

      return new AutoValue_ErrorMessage(sb.toString());
    }
  }
}