com.facebook.buck.doctor.DefaultDefectReporter.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.doctor.DefaultDefectReporter.java

Source

/*
 * 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.doctor;

import static com.facebook.buck.util.zip.ZipOutputStreams.HandleDuplicates.APPEND_TO_ZIP;

import com.facebook.buck.core.util.log.Logger;
import com.facebook.buck.doctor.config.DoctorConfig;
import com.facebook.buck.doctor.config.DoctorJsonResponse;
import com.facebook.buck.doctor.config.DoctorProtocolVersion;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.slb.ClientSideSlb;
import com.facebook.buck.slb.HttpResponse;
import com.facebook.buck.slb.HttpService;
import com.facebook.buck.slb.LoadBalancedService;
import com.facebook.buck.slb.RetryingHttpService;
import com.facebook.buck.slb.SlbBuckConfig;
import com.facebook.buck.util.json.ObjectMappers;
import com.facebook.buck.util.timing.Clock;
import com.facebook.buck.util.versioncontrol.VersionControlCommandFailedException;
import com.facebook.buck.util.versioncontrol.VersionControlSupplier;
import com.facebook.buck.util.zip.CustomZipEntry;
import com.facebook.buck.util.zip.CustomZipOutputStream;
import com.facebook.buck.util.zip.ZipOutputStreams;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.BufferedSink;

/** Takes care of actually writing out the report. */
public class DefaultDefectReporter implements DefectReporter {

    private static final Logger LOG = Logger.get(AbstractReport.class);

    private static final String REPORT_FILE_NAME = "report.json";
    private static final String DIFF_FILE_NAME = "changes.diff";
    private static final int HTTP_SUCCESS_CODE = 200;
    private static final String REQUEST_PROTOCOL_VERSION = "x-buck-protocol-version";

    private final ProjectFilesystem filesystem;
    private final DoctorConfig doctorConfig;
    private final BuckEventBus buckEventBus;
    private final Clock clock;

    public DefaultDefectReporter(ProjectFilesystem filesystem, DoctorConfig doctorConfig, BuckEventBus buckEventBus,
            Clock clock) {
        this.filesystem = filesystem;
        this.doctorConfig = doctorConfig;
        this.buckEventBus = buckEventBus;
        this.clock = clock;
    }

    private void addFilesToArchive(CustomZipOutputStream out, ImmutableSet<Path> paths) throws IOException {
        for (Path logFile : paths) {
            Path destPath = logFile;
            if (destPath.isAbsolute()) {
                // If it's an absolute path, make it relative instead
                destPath = destPath.subpath(0, logFile.getNameCount());
                Preconditions.checkArgument(!destPath.isAbsolute(), "Should be a relative path", destPath);
            }
            if (destPath.getFileName().toString().startsWith(".")) {
                // If the file is hidden(UNIX terms) save it as normal file.
                destPath = Optional.ofNullable(destPath.getParent()).orElse(Paths.get(""))
                        .resolve(destPath.getFileName().toString().replaceAll("^\\.*", ""));
            }

            out.putNextEntry(new CustomZipEntry(destPath));

            try (InputStream input = filesystem.newFileInputStream(logFile)) {
                ByteStreams.copy(input, out);
            }
            out.closeEntry();
        }
    }

    private void addNamedFilesToArchive(CustomZipOutputStream out,
            ImmutableMap<String, VersionControlSupplier<InputStream>> fileStreams)
            throws IOException, VersionControlCommandFailedException, InterruptedException {
        for (Map.Entry<String, VersionControlSupplier<InputStream>> fs : fileStreams.entrySet()) {
            try (InputStream input = fs.getValue().get()) {
                out.writeEntry(fs.getKey(), input);
            }
            out.closeEntry();
        }
    }

    @Override
    public DefectSubmitResult submitReport(DefectReport defectReport) throws IOException {
        DefectSubmitResult.Builder defectSubmitResult = DefectSubmitResult.builder();
        defectSubmitResult.setRequestProtocol(doctorConfig.getProtocolVersion());
        Optional<SlbBuckConfig> frontendConfig = doctorConfig.getFrontendConfig();

        if (frontendConfig.isPresent()) {
            Optional<ClientSideSlb> slb = frontendConfig.get().tryCreatingClientSideSlb(clock, buckEventBus);
            if (slb.isPresent()) {
                try {
                    return uploadReport(defectReport, defectSubmitResult, slb.get());
                } catch (IOException e) {
                    LOG.debug(e, "Failed uploading report to server.");
                    defectSubmitResult.setIsRequestSuccessful(false);
                    defectSubmitResult.setReportSubmitErrorMessage(e.getMessage());
                }
            }
        }

        filesystem.mkdirs(filesystem.getBuckPaths().getBuckOut());
        Path defectReportPath = filesystem.createTempFile(filesystem.getBuckPaths().getBuckOut(), "defect_report",
                ".zip");
        try (OutputStream outputStream = filesystem.newFileOutputStream(defectReportPath)) {
            writeReport(defectReport, outputStream);
        }

        return defectSubmitResult.setIsRequestSuccessful(Optional.empty())
                .setReportSubmitLocation(defectReportPath.toString()).build();
    }

    private void writeReport(DefectReport defectReport, OutputStream outputStream) throws IOException {
        try (BufferedOutputStream baseOut = new BufferedOutputStream(outputStream);
                CustomZipOutputStream out = ZipOutputStreams.newOutputStream(baseOut, APPEND_TO_ZIP)) {

            try {
                if (defectReport.getSourceControlInfo().isPresent()
                        && defectReport.getSourceControlInfo().get().getDiff().isPresent()) {
                    addNamedFilesToArchive(out, ImmutableMap.of(DIFF_FILE_NAME,
                            defectReport.getSourceControlInfo().get().getDiff().get()));
                }
            } catch (VersionControlCommandFailedException | InterruptedException e) {
                // log the exceptions thrown from VersionControlSupplier<InputStream> when generating diff
                LOG.warn(e,
                        "Failed to gather diff from source control. Some diff information may be missing from the report");
            }
            addFilesToArchive(out, defectReport.getIncludedPaths());

            out.putNextEntry(new CustomZipEntry(REPORT_FILE_NAME));
            ObjectMappers.WRITER.writeValue(out, defectReport);
        }
    }

    private DefectSubmitResult uploadReport(DefectReport defectReport,
            DefectSubmitResult.Builder defectSubmitResult, ClientSideSlb slb) throws IOException {
        long timeout = doctorConfig.getReportTimeoutMs();
        OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.MILLISECONDS)
                .readTimeout(timeout, TimeUnit.MILLISECONDS).writeTimeout(timeout, TimeUnit.MILLISECONDS).build();
        HttpService httpService = new RetryingHttpService(buckEventBus,
                new LoadBalancedService(slb, httpClient, buckEventBus), "buck_defect_reporter_http_retries",
                doctorConfig.getReportMaxUploadRetries());

        try {
            Request.Builder requestBuilder = new Request.Builder();
            requestBuilder.addHeader(REQUEST_PROTOCOL_VERSION,
                    doctorConfig.getProtocolVersion().name().toLowerCase());
            for (Map.Entry<String, String> entry : doctorConfig.getEndpointExtraRequestHeaders().entrySet()) {
                requestBuilder.addHeader(entry.getKey(), entry.getValue());
            }
            requestBuilder.post(new RequestBody() {
                @Override
                public MediaType contentType() {
                    return MediaType.parse("application/x-www-form-urlencoded");
                }

                @Override
                public void writeTo(BufferedSink bufferedSink) throws IOException {
                    writeReport(defectReport, bufferedSink.outputStream());
                }
            });

            HttpResponse response = httpService.makeRequest(doctorConfig.getReportUploadPath(), requestBuilder);
            String responseBody;
            try (InputStream inputStream = response.getBody()) {
                responseBody = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
            }

            if (response.statusCode() == HTTP_SUCCESS_CODE) {
                defectSubmitResult.setIsRequestSuccessful(true);
                if (doctorConfig.getProtocolVersion().equals(DoctorProtocolVersion.SIMPLE)) {
                    return defectSubmitResult.setReportSubmitMessage(responseBody)
                            .setReportSubmitLocation(responseBody).build();
                } else {
                    // Decode Json response.
                    DoctorJsonResponse json = ObjectMappers.READER.readValue(
                            ObjectMappers.createParser(responseBody.getBytes(Charsets.UTF_8)),
                            DoctorJsonResponse.class);
                    return defectSubmitResult.setIsRequestSuccessful(json.getRequestSuccessful())
                            .setReportSubmitErrorMessage(json.getErrorMessage())
                            .setReportSubmitMessage(json.getMessage()).setReportSubmitLocation(json.getRageUrl())
                            .build();
                }
            } else {
                throw new IOException(String.format("Connection to %s returned code %d and message: %s",
                        response.requestUrl(), response.statusCode(), responseBody));
            }
        } catch (IOException e) {
            throw new IOException(String.format("Failed uploading report because [%s].", e.getMessage()));
        } finally {
            httpService.close();
        }
    }
}