/* * 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.rage; import static com.facebook.buck.log.MachineReadableLogConfig.PREFIX_EXIT_CODE; import static com.facebook.buck.log.MachineReadableLogConfig.PREFIX_INVOCATION_INFO; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildId; import com.facebook.buck.util.BuckConstant; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.ObjectMappers; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.io.BufferedReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import org.immutables.value.Value; /** Methods for finding and inspecting buck log files. */ public class BuildLogHelper { private final ProjectFilesystem projectFilesystem; private static final String INFO_FIELD_UNEXPANDED_CMD_ARGS = "unexpandedCommandArgs"; public BuildLogHelper(ProjectFilesystem projectFilesystem) { this.projectFilesystem = projectFilesystem; } public ImmutableList<BuildLogEntry> getBuildLogs() throws IOException { // Remove commands with unknown args or invocations of buck rage. // Sort the remaining logs based on time, reverse order. ImmutableList.Builder<BuildLogEntry> logEntries = ImmutableList.builder(); for (Path logFile : getAllBuckLogFiles()) { BuildLogEntry entry = newBuildLogEntry(logFile); if (entry.getCommandArgs().isPresent() && !entry.getCommandArgs().get().matches(".*\\b(rage|doctor|server|launch)\\b.*")) { logEntries.add(newBuildLogEntry(logFile)); } } return logEntries .build() .stream() .sorted(Comparator.comparing(BuildLogEntry::getLastModifiedTime).reversed()) .collect(MoreCollectors.toImmutableList()); } @SuppressWarnings("unchecked") private BuildLogEntry newBuildLogEntry(Path logFile) throws IOException { BuildLogEntry.Builder builder = BuildLogEntry.builder(); Path machineReadableLogFile = logFile.getParent().resolve(BuckConstant.BUCK_MACHINE_LOG_FILE_NAME); if (projectFilesystem.isFile(machineReadableLogFile)) { String invocationInfoLine = Files.lines(projectFilesystem.resolve(machineReadableLogFile)) .filter(line -> line.startsWith(PREFIX_INVOCATION_INFO)) .map(line -> line.substring(PREFIX_INVOCATION_INFO.length())) .findFirst() .get(); // TODO(mikath): Keep this for a while for compatibility for commandArgs, then replace with // a proper ObjectMapper deserialization of InvocationInfo. Map<String, Object> invocationInfo = ObjectMappers.readValue(invocationInfoLine, new TypeReference<Map<String, Object>>() {}); Optional<String> commandArgs = Optional.empty(); if (invocationInfo.containsKey(INFO_FIELD_UNEXPANDED_CMD_ARGS) && invocationInfo.get(INFO_FIELD_UNEXPANDED_CMD_ARGS) instanceof List) { commandArgs = Optional.of( String.join( " ", (List<String>) invocationInfo.get(INFO_FIELD_UNEXPANDED_CMD_ARGS))); } builder.setBuildId(new BuildId((String) invocationInfo.get(("buildId")))); builder.setCommandArgs(commandArgs); builder.setMachineReadableLogFile(machineReadableLogFile); builder.setExitCode(readExitCode(machineReadableLogFile)); } Path ruleKeyLoggerFile = logFile.getParent().resolve(BuckConstant.RULE_KEY_LOGGER_FILE_NAME); if (projectFilesystem.isFile(ruleKeyLoggerFile)) { builder.setRuleKeyLoggerLogFile(ruleKeyLoggerFile); } Optional<Path> traceFile = projectFilesystem .getFilesUnderPath(logFile.getParent()) .stream() .filter(input -> input.toString().endsWith(".trace")) .findFirst(); return builder .setRelativePath(logFile) .setSize(projectFilesystem.getFileSize(logFile)) .setLastModifiedTime(Date.from(projectFilesystem.getLastModifiedTime(logFile).toInstant())) .setTraceFile(traceFile) .build(); } private OptionalInt readExitCode(Path machineReadableLogFile) { try (BufferedReader reader = Files.newBufferedReader(projectFilesystem.resolve(machineReadableLogFile))) { Optional<String> line = reader .lines() .filter(s -> s.startsWith(PREFIX_EXIT_CODE)) .map(s -> s.substring(PREFIX_EXIT_CODE.length())) .findFirst(); if (line.isPresent()) { Map<String, Integer> exitCode = ObjectMappers.READER.readValue( ObjectMappers.createParser(line.get().getBytes(StandardCharsets.UTF_8)), new TypeReference<Map<String, Integer>>() {}); if (exitCode.containsKey("exitCode")) { return OptionalInt.of(exitCode.get("exitCode")); } } } catch (IOException e) { return OptionalInt.empty(); } return OptionalInt.empty(); } private Collection<Path> getAllBuckLogFiles() throws IOException { final List<Path> logfiles = new ArrayList<>(); projectFilesystem.walkRelativeFileTree( projectFilesystem.getBuckPaths().getLogDir(), new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().equals(BuckConstant.BUCK_LOG_FILE_NAME)) { logfiles.add(file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); return logfiles; } @Value.Immutable @BuckStyleImmutable abstract static class AbstractBuildLogEntry { public abstract Path getRelativePath(); public abstract Optional<BuildId> getBuildId(); public abstract Optional<String> getCommandArgs(); public abstract OptionalInt getExitCode(); public abstract Optional<Path> getRuleKeyLoggerLogFile(); public abstract Optional<Path> getMachineReadableLogFile(); public abstract Optional<Path> getTraceFile(); public abstract long getSize(); public abstract Date getLastModifiedTime(); @Value.Check void pathIsRelative() { Preconditions.checkState(!getRelativePath().isAbsolute()); if (getRuleKeyLoggerLogFile().isPresent()) { Preconditions.checkState(!getRuleKeyLoggerLogFile().get().isAbsolute()); } if (getTraceFile().isPresent()) { Preconditions.checkState(!getTraceFile().get().isAbsolute()); } } } }