/*
* Copyright 2016-present Facebook, Inc.
*
* 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.facebook.buck.log;
import static com.facebook.buck.util.MoreThrowables.getInitialCause;
import static com.facebook.buck.util.MoreThrowables.getThrowableOrigin;
import com.facebook.buck.build_type.BuildType;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.util.network.hostname.HostnameFetching;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.logging.LogRecord;
import org.immutables.value.Value;
@Value.Immutable
@BuckStyleImmutable
abstract class AbstractErrorLogRecord {
private static final ThreadIdToCommandIdMapper MAPPER =
GlobalStateManager.singleton().getThreadIdToCommandIdMapper();
private static final CommandIdToIsDaemonMapper IS_DAEMON_MAPPER =
GlobalStateManager.singleton().getCommandIdToIsDaemonMapper();
private static final CommandIdToIsSuperConsoleEnabledMapper IS_SUPERCONSOLE_ENABLED_MAPPER =
GlobalStateManager.singleton().getCommandIdToIsSuperConsoleEnabledMapper();
private static final Logger LOG = Logger.get(AbstractErrorLogRecord.class);
public abstract LogRecord getRecord();
public abstract ImmutableList<String> getLogs();
@Value.Derived
public ImmutableMap<String, String> getTraits() {
String logger = getRecord().getLoggerName();
String hostname = "unknown";
try {
hostname = HostnameFetching.getHostname();
} catch (IOException e) {
LOG.debug(e, "Unable to fetch hostname");
}
ImmutableMap<String, String> traits =
ImmutableMap.<String, String>builder()
.put("severity", getRecord().getLevel().toString())
.put("logger", logger != null ? logger : "unknown")
.put("buckGitCommit", System.getProperty("buck.git_commit", "unknown"))
.put("javaVersion", System.getProperty("java.version", "unknown"))
.put("os", System.getProperty("os.name", "unknown"))
.put("osVersion", System.getProperty("os.version", "unknown"))
.put("user", System.getProperty("user.name", "unknown"))
.put("buckBinaryBuildType", BuildType.CURRENT_BUILD_TYPE.get().toString())
.put("hostname", hostname)
.build();
return traits;
}
@Value.Derived
public String getMessage() {
Optional<String> initialErr = Optional.empty();
Optional<String> initialErrorMsg = Optional.empty();
Optional<String> errorMsg = Optional.empty();
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
initialErr = Optional.ofNullable(getInitialCause(throwable).getClass().getName());
if (throwable.getMessage() != null) {
initialErrorMsg = Optional.ofNullable(getInitialCause(throwable).getLocalizedMessage());
}
}
errorMsg = Optional.ofNullable(getRecord().getMessage());
StringBuilder sb = new StringBuilder();
for (Optional<String> field : ImmutableList.of(initialErr, initialErrorMsg, errorMsg)) {
sb.append(field.orElse(""));
if (field.isPresent()) {
sb.append(": ");
}
}
sb.append(getRecord().getLoggerName());
return sb.toString();
}
/**
* Computes a category key based on relevant LogRecord information. If an exception is present,
* categorizes on the class + method that threw it. If no exception is found, categorizes on the
* logger name and the beginning of the message.
*/
@Value.Default
public String getCategory() {
String logger = "";
if (getRecord().getLoggerName() != null) {
logger = getRecord().getLoggerName();
}
StringBuilder sb = new StringBuilder(logger).append(":");
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
sb.append(extractClassMethod(getThrowableOrigin(getInitialCause(throwable))));
} else {
sb.append(truncateMessage(getRecord().getMessage()));
}
return sb.toString();
}
@Value.Derived
public long getTime() {
return TimeUnit.MILLISECONDS.toSeconds(getRecord().getMillis());
}
@Value.Derived
public Optional<String> getLogger() {
return Optional.ofNullable(getRecord().getLoggerName());
}
@Value.Derived
public Optional<String> getBuildUuid() {
String buildUuid = MAPPER.threadIdToCommandId(getRecord().getThreadID());
return Optional.ofNullable(buildUuid);
}
@Value.Derived
public Optional<Boolean> getIsSuperConsoleEnabled() {
String buildUuid = MAPPER.threadIdToCommandId(getRecord().getThreadID());
if (buildUuid == null) {
return Optional.empty();
}
return Optional.ofNullable(
IS_SUPERCONSOLE_ENABLED_MAPPER.commandIdToIsSuperConsoleEnabled(buildUuid));
}
@Value.Derived
public Optional<Boolean> getIsDaemon() {
String buildUuid = MAPPER.threadIdToCommandId(getRecord().getThreadID());
if (buildUuid == null) {
return Optional.empty();
}
return Optional.ofNullable(IS_DAEMON_MAPPER.commandIdToIsRunningAsDaemon(buildUuid));
}
@Value.Derived
public Optional<StackTraceElement[]> getStack() {
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
return Optional.ofNullable(throwable.getStackTrace());
}
return Optional.empty();
}
@Value.Derived
public Optional<String> getErrorMessage() {
Throwable throwable = getRecord().getThrown();
if (throwable != null && throwable.getMessage() != null) {
return Optional.ofNullable(throwable.getMessage());
}
return Optional.empty();
}
@Value.Derived
public Optional<String> getInitialError() {
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
return Optional.ofNullable(getInitialCause(throwable).getClass().getName());
}
return Optional.empty();
}
@Value.Derived
public Optional<String> getInitialErrorMsg() {
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
return Optional.ofNullable(getInitialCause(throwable).getLocalizedMessage());
}
return Optional.empty();
}
@Value.Derived
public Optional<String> getOrigin() {
Throwable throwable = getRecord().getThrown();
if (throwable != null) {
return Optional.ofNullable(getThrowableOrigin(throwable));
}
return Optional.empty();
}
/**
* We expect uploaded log records to contain a stack trace, but if they don't the logged message
* is important. To address the issue that these records often contain parametrized values, only
* first word (1 & 2 if first has 2 or less chars) of message is taken into account.
*/
private String truncateMessage(String name) {
String[] words = name.split("\\s+");
if (words.length > 1 && words[0].length() < 3) {
return words[0] + " " + words[1];
}
return words[0];
}
/**
* Extracts minimum valuable information set from lines in the following format:
* package.classname.method(filename:line_number)
*/
private String extractClassMethod(String name) {
if (name != null) {
return name.split("\\(", 1)[0];
}
return "";
}
}