org.apache.hadoop.yarn.client.cli.LogsCLI.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.yarn.client.cli.LogsCLI.java

Source

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.apache.hadoop.yarn.client.cli;

import com.google.common.annotations.VisibleForTesting;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.client.urlconnection.HttpURLConnectionFactory;
import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import javax.ws.rs.core.MediaType;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Pair;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Evolving;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptReport;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.ApplicationReport;
import org.apache.hadoop.yarn.api.records.ContainerId;
import org.apache.hadoop.yarn.api.records.ContainerReport;
import org.apache.hadoop.yarn.api.records.ContainerState;
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
import org.apache.hadoop.yarn.client.api.YarnClient;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.logaggregation.ContainerLogFileInfo;
import org.apache.hadoop.yarn.logaggregation.ContainerLogsRequest;
import org.apache.hadoop.yarn.logaggregation.LogCLIHelpers;
import org.apache.hadoop.yarn.logaggregation.LogToolUtils;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.hadoop.yarn.webapp.util.YarnWebServiceUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

@Public
@Evolving
public class LogsCLI extends Configured implements Tool {

    private static final String CONTAINER_ID_OPTION = "containerId";
    private static final String APPLICATION_ID_OPTION = "applicationId";
    private static final String NODE_ADDRESS_OPTION = "nodeAddress";
    private static final String APP_OWNER_OPTION = "appOwner";
    private static final String AM_CONTAINER_OPTION = "am";
    private static final String PER_CONTAINER_LOG_FILES_OPTION = "log_files";
    private static final String PER_CONTAINER_LOG_FILES_REGEX_OPTION = "log_files_pattern";
    private static final String LIST_NODES_OPTION = "list_nodes";
    private static final String SHOW_APPLICATION_LOG_INFO = "show_application_log_info";
    private static final String SHOW_CONTAINER_LOG_INFO = "show_container_log_info";
    private static final String OUT_OPTION = "out";
    private static final String SIZE_OPTION = "size";
    private static final String CLIENT_MAX_RETRY_OPTION = "client_max_retries";
    private static final String CLIENT_RETRY_INTERVAL_OPTION = "client_retry_interval_ms";
    public static final String HELP_CMD = "help";
    private static final String SIZE_LIMIT_OPTION = "size_limit_mb";

    private PrintStream outStream = System.out;
    private YarnClient yarnClient = null;
    private Client webServiceClient = null;

    private static final int DEFAULT_MAX_RETRIES = 30;
    private static final long DEFAULT_RETRY_INTERVAL = 1000;

    private static final long LOG_SIZE_LIMIT_DEFAULT = 10240L;

    private long logSizeLeft = LOG_SIZE_LIMIT_DEFAULT * 1024 * 1024;
    private long specifedLogLimits = LOG_SIZE_LIMIT_DEFAULT;

    @Private
    @VisibleForTesting
    ClientConnectionRetry connectionRetry;

    @Override
    public int run(String[] args) throws Exception {
        try {
            yarnClient = createYarnClient();
            webServiceClient = new Client(new URLConnectionClientHandler(new HttpURLConnectionFactory() {
                @Override
                public HttpURLConnection getHttpURLConnection(URL url) throws IOException {
                    AuthenticatedURL.Token token = new AuthenticatedURL.Token();
                    HttpURLConnection conn = null;
                    try {
                        conn = new AuthenticatedURL().openConnection(url, token);
                    } catch (AuthenticationException e) {
                        throw new IOException(e);
                    }
                    return conn;
                }
            }));
            return runCommand(args);
        } finally {
            if (yarnClient != null) {
                yarnClient.close();
            }
        }
    }

    private int runCommand(String[] args) throws Exception {
        Options opts = createCommandOpts();
        Options printOpts = createPrintOpts(opts);
        if (args.length < 1) {
            printHelpMessage(printOpts);
            return -1;
        }
        if (args[0].equals("-help")) {
            printHelpMessage(printOpts);
            return 0;
        }
        CommandLineParser parser = new GnuParser();
        String appIdStr = null;
        String containerIdStr = null;
        String nodeAddress = null;
        String appOwner = null;
        boolean getAMContainerLogs = false;
        boolean nodesList = false;
        boolean showApplicationLogInfo = false;
        boolean showContainerLogInfo = false;
        boolean useRegex = false;
        String[] logFiles = null;
        String[] logFilesRegex = null;
        List<String> amContainersList = new ArrayList<String>();
        String localDir = null;
        long bytes = Long.MAX_VALUE;
        boolean ignoreSizeLimit = false;
        int maxRetries = DEFAULT_MAX_RETRIES;
        long retryInterval = DEFAULT_RETRY_INTERVAL;
        try {
            CommandLine commandLine = parser.parse(opts, args, false);
            appIdStr = commandLine.getOptionValue(APPLICATION_ID_OPTION);
            containerIdStr = commandLine.getOptionValue(CONTAINER_ID_OPTION);
            nodeAddress = commandLine.getOptionValue(NODE_ADDRESS_OPTION);
            appOwner = commandLine.getOptionValue(APP_OWNER_OPTION);
            getAMContainerLogs = commandLine.hasOption(AM_CONTAINER_OPTION);
            nodesList = commandLine.hasOption(LIST_NODES_OPTION);
            localDir = commandLine.getOptionValue(OUT_OPTION);
            showApplicationLogInfo = commandLine.hasOption(SHOW_APPLICATION_LOG_INFO);
            showContainerLogInfo = commandLine.hasOption(SHOW_CONTAINER_LOG_INFO);
            if (getAMContainerLogs) {
                try {
                    amContainersList = parseAMContainer(commandLine, printOpts);
                } catch (NumberFormatException ex) {
                    System.err.println(ex.getMessage());
                    return -1;
                }
            }
            if (commandLine.hasOption(PER_CONTAINER_LOG_FILES_OPTION)) {
                logFiles = commandLine.getOptionValues(PER_CONTAINER_LOG_FILES_OPTION);
            }
            if (commandLine.hasOption(PER_CONTAINER_LOG_FILES_REGEX_OPTION)) {
                logFilesRegex = commandLine.getOptionValues(PER_CONTAINER_LOG_FILES_REGEX_OPTION);
                useRegex = true;
            }
            if (commandLine.hasOption(SIZE_OPTION)) {
                bytes = Long.parseLong(commandLine.getOptionValue(SIZE_OPTION));
            }
            if (commandLine.hasOption(CLIENT_MAX_RETRY_OPTION)) {
                maxRetries = Integer.parseInt(commandLine.getOptionValue(CLIENT_MAX_RETRY_OPTION));
            }
            if (commandLine.hasOption(CLIENT_RETRY_INTERVAL_OPTION)) {
                retryInterval = Long.parseLong(commandLine.getOptionValue(CLIENT_RETRY_INTERVAL_OPTION));
            }
            if (commandLine.hasOption(SIZE_LIMIT_OPTION)) {
                specifedLogLimits = Long.parseLong(commandLine.getOptionValue(SIZE_LIMIT_OPTION));
                logSizeLeft = specifedLogLimits * 1024 * 1024;
            }
            if (logSizeLeft < 0L) {
                ignoreSizeLimit = true;
            }
        } catch (ParseException e) {
            System.err.println("options parsing failed: " + e.getMessage());
            printHelpMessage(printOpts);
            return -1;
        }

        if (appIdStr == null && containerIdStr == null) {
            System.err.println(
                    "Both applicationId and containerId are missing, " + " one of them must be specified.");
            printHelpMessage(printOpts);
            return -1;
        }

        ApplicationId appId = null;
        if (appIdStr != null) {
            try {
                appId = ApplicationId.fromString(appIdStr);
            } catch (Exception e) {
                System.err.println("Invalid ApplicationId specified");
                return -1;
            }
        }

        if (containerIdStr != null) {
            try {
                ContainerId containerId = ContainerId.fromString(containerIdStr);
                if (appId == null) {
                    appId = containerId.getApplicationAttemptId().getApplicationId();
                } else if (!containerId.getApplicationAttemptId().getApplicationId().equals(appId)) {
                    System.err.println("The Application:" + appId + " does not have the container:" + containerId);
                    return -1;
                }
            } catch (Exception e) {
                System.err.println("Invalid ContainerId specified");
                return -1;
            }
        }

        if (showApplicationLogInfo && showContainerLogInfo) {
            System.err.println("Invalid options. Can only accept one of "
                    + "show_application_log_info/show_container_log_info.");
            return -1;
        }

        if (logFiles != null && logFiles.length > 0 && logFilesRegex != null && logFilesRegex.length > 0) {
            System.err.println("Invalid options. Can only accept one of " + "log_files/log_files_pattern.");
            return -1;
        }
        if (localDir != null) {
            File file = new File(localDir);
            if (file.exists() && file.isFile()) {
                System.err.println("Invalid value for -out option. " + "Please provide a directory.");
                return -1;
            }
        }

        // Set up Retry WebService Client
        connectionRetry = new ClientConnectionRetry(maxRetries, retryInterval);
        ClientJerseyRetryFilter retryFilter = new ClientJerseyRetryFilter();
        webServiceClient.addFilter(retryFilter);

        LogCLIHelpers logCliHelper = new LogCLIHelpers();
        logCliHelper.setConf(getConf());

        YarnApplicationState appState = YarnApplicationState.NEW;
        ApplicationReport appReport = null;
        try {
            appReport = getApplicationReport(appId);
            appState = appReport.getYarnApplicationState();
            if (appState == YarnApplicationState.NEW || appState == YarnApplicationState.NEW_SAVING
                    || appState == YarnApplicationState.SUBMITTED) {
                System.err.println("Logs are not available right now.");
                return -1;
            }
        } catch (IOException | YarnException e) {
            // If we can not get appReport from either RM or ATS
            // We will assume that this app has already finished.
            appState = YarnApplicationState.FINISHED;
            System.err.println(
                    "Unable to get ApplicationState." + " Attempting to fetch logs directly from the filesystem.");
        }

        if (appOwner == null || appOwner.isEmpty()) {
            appOwner = guessAppOwner(appReport, appId);
            if (appOwner == null) {
                System.err.println("Can not find the appOwner. " + "Please specify the correct appOwner");
                System.err.println("Could not locate application logs for " + appId);
                return -1;
            }
        }

        Set<String> logs = new HashSet<String>();
        if (fetchAllLogFiles(logFiles, logFilesRegex)) {
            logs.add("ALL");
        } else if (logFiles != null && logFiles.length > 0) {
            logs.addAll(Arrays.asList(logFiles));
        } else if (logFilesRegex != null && logFilesRegex.length > 0) {
            logs.addAll(Arrays.asList(logFilesRegex));
        }

        ContainerLogsRequest request = new ContainerLogsRequest(appId, isApplicationFinished(appState), appOwner,
                nodeAddress, null, containerIdStr, localDir, logs, bytes, null);

        if (showContainerLogInfo) {
            return showContainerLogInfo(request, logCliHelper);
        }

        if (nodesList) {
            return showNodeLists(request, logCliHelper);
        }

        if (showApplicationLogInfo) {
            return showApplicationLogInfo(request, logCliHelper);
        }
        // To get am logs
        if (getAMContainerLogs) {
            return fetchAMContainerLogs(request, amContainersList, logCliHelper, useRegex, ignoreSizeLimit);
        }

        int resultCode = 0;
        if (containerIdStr != null) {
            return fetchContainerLogs(request, logCliHelper, useRegex, ignoreSizeLimit);
        } else {
            if (nodeAddress == null) {
                resultCode = fetchApplicationLogs(request, logCliHelper, useRegex, ignoreSizeLimit);
            } else {
                System.err.println("Should at least provide ContainerId!");
                printHelpMessage(printOpts);
                resultCode = -1;
            }
        }
        return resultCode;
    }

    private ApplicationReport getApplicationReport(ApplicationId appId) throws IOException, YarnException {
        return yarnClient.getApplicationReport(appId);
    }

    @VisibleForTesting
    protected YarnClient createYarnClient() {
        YarnClient client = YarnClient.createYarnClient();
        client.init(getConf());
        client.start();
        return client;
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = new YarnConfiguration();
        LogsCLI logDumper = new LogsCLI();
        logDumper.setConf(conf);
        int exitCode = logDumper.run(args);
        System.exit(exitCode);
    }

    private void printHelpMessage(Options options) {
        outStream.println("Retrieve logs for YARN applications.");
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("yarn logs -applicationId <application ID> [OPTIONS]", new Options());
        formatter.setSyntaxPrefix("");
        formatter.printHelp("general options are:", options);
    }

    protected List<JSONObject> getAMContainerInfoForRMWebService(Configuration conf, String appId)
            throws ClientHandlerException, UniformInterfaceException, JSONException {
        String webAppAddress = WebAppUtils.getRMWebAppURLWithScheme(conf);

        WebResource webResource = webServiceClient.resource(webAppAddress);

        ClientResponse response = webResource.path("ws").path("v1").path("cluster").path("apps").path(appId)
                .path("appattempts").accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
        JSONObject json = response.getEntity(JSONObject.class).getJSONObject("appAttempts");
        JSONArray requests = json.getJSONArray("appAttempt");
        List<JSONObject> amContainersList = new ArrayList<JSONObject>();
        for (int i = 0; i < requests.length(); i++) {
            amContainersList.add(requests.getJSONObject(i));
        }
        return amContainersList;
    }

    private List<JSONObject> getAMContainerInfoForAHSWebService(Configuration conf, String appId)
            throws ClientHandlerException, UniformInterfaceException, JSONException {
        String webAppAddress = WebAppUtils.getHttpSchemePrefix(conf)
                + WebAppUtils.getAHSWebAppURLWithoutScheme(conf);
        WebResource webResource = webServiceClient.resource(webAppAddress);

        ClientResponse response = webResource.path("ws").path("v1").path("applicationhistory").path("apps")
                .path(appId).path("appattempts").accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
        JSONObject json = response.getEntity(JSONObject.class);
        JSONArray requests = json.getJSONArray("appAttempt");
        List<JSONObject> amContainersList = new ArrayList<JSONObject>();
        for (int i = 0; i < requests.length(); i++) {
            amContainersList.add(requests.getJSONObject(i));
        }
        Collections.reverse(amContainersList);
        return amContainersList;
    }

    private boolean fetchAllLogFiles(String[] logFiles, String[] logFilesRegex) {

        // If no value is specified for the PER_CONTAINER_LOG_FILES_OPTION option
        // and PER_CONTAINER_LOG_FILES_REGEX_OPTION
        // we will assume all logs.
        if ((logFiles == null || logFiles.length == 0) && (logFilesRegex == null || logFilesRegex.length == 0)) {
            return true;
        }

        if (logFiles != null && logFiles.length > 0) {
            List<String> logs = Arrays.asList(logFiles);
            if (logs.contains("ALL") || logs.contains("*")) {
                return true;
            }
        }

        if (logFilesRegex != null && logFilesRegex.length > 0) {
            List<String> logsRegex = Arrays.asList(logFilesRegex);
            if (logsRegex.contains(".*")) {
                return true;
            }
        }

        return false;
    }

    private List<Pair<ContainerLogFileInfo, String>> getContainerLogFiles(Configuration conf, String containerIdStr,
            String nodeHttpAddress) throws IOException {
        List<Pair<ContainerLogFileInfo, String>> logFileInfos = new ArrayList<>();
        try {
            WebResource webResource = webServiceClient
                    .resource(WebAppUtils.getHttpSchemePrefix(conf) + nodeHttpAddress);
            ClientResponse response = webResource.path("ws").path("v1").path("node").path("containers")
                    .path(containerIdStr).path("logs").accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
            if (response.getStatusInfo().getStatusCode() == ClientResponse.Status.OK.getStatusCode()) {
                try {
                    JSONArray array = new JSONArray();
                    JSONObject json = response.getEntity(JSONObject.class);
                    if (!json.has("containerLogsInfo")) {
                        return logFileInfos;
                    }
                    Object logsInfoObj = json.get("containerLogsInfo");
                    if (logsInfoObj instanceof JSONObject) {
                        array.put((JSONObject) logsInfoObj);
                    } else if (logsInfoObj instanceof JSONArray) {
                        JSONArray logsArray = (JSONArray) logsInfoObj;
                        for (int i = 0; i < logsArray.length(); i++) {
                            array.put(logsArray.getJSONObject(i));
                        }
                    }
                    for (int i = 0; i < array.length(); i++) {
                        JSONObject log = array.getJSONObject(i);
                        String aggregateType = log.has("logAggregationType") ? log.getString("logAggregationType")
                                : "N/A";
                        if (!log.has("containerLogInfo")) {
                            continue;
                        }
                        Object ob = log.get("containerLogInfo");
                        if (ob instanceof JSONArray) {
                            JSONArray obArray = (JSONArray) ob;
                            for (int j = 0; j < obArray.length(); j++) {
                                logFileInfos.add(new Pair<ContainerLogFileInfo, String>(
                                        generatePerContainerLogFileInfoFromJSON(obArray.getJSONObject(j)),
                                        aggregateType));
                            }
                        } else if (ob instanceof JSONObject) {
                            logFileInfos.add(new Pair<ContainerLogFileInfo, String>(
                                    generatePerContainerLogFileInfoFromJSON((JSONObject) ob), aggregateType));
                        }
                    }
                } catch (Exception e) {
                    System.err.println("Unable to parse json from webservice. Error:");
                    System.err.println(e.getMessage());
                    throw new IOException(e);
                }
            }

        } catch (ClientHandlerException | UniformInterfaceException ex) {
            System.err.println("Unable to fetch log files list");
            throw new IOException(ex);
        }
        return logFileInfos;
    }

    private ContainerLogFileInfo generatePerContainerLogFileInfoFromJSON(JSONObject meta) throws JSONException {
        String fileName = meta.has("fileName") ? meta.getString("fileName") : "N/A";
        String fileSize = meta.has("fileSize") ? meta.getString("fileSize") : "N/A";
        String lastModificationTime = meta.has("lastModifiedTime") ? meta.getString("lastModifiedTime") : "N/A";
        return new ContainerLogFileInfo(fileName, fileSize, lastModificationTime);
    }

    @Private
    @VisibleForTesting
    public int printContainerLogsFromRunningApplication(Configuration conf, ContainerLogsRequest request,
            LogCLIHelpers logCliHelper, boolean useRegex, boolean ignoreSizeLimit) throws IOException {
        String containerIdStr = request.getContainerId().toString();
        String localDir = request.getOutputLocalDir();
        String nodeId = request.getNodeId();
        PrintStream out = LogToolUtils.createPrintStream(localDir, nodeId, containerIdStr);
        try {
            boolean foundAnyLogs = false;
            byte[] buffer = new byte[65536];
            for (String logFile : request.getLogTypes()) {
                InputStream is = null;
                try {
                    ClientResponse response = getResponeFromNMWebService(conf, webServiceClient, request, logFile);
                    if (response != null && response.getStatusInfo().getStatusCode() == ClientResponse.Status.OK
                            .getStatusCode()) {
                        is = response.getEntityInputStream();
                        int len = 0;
                        while ((len = is.read(buffer)) != -1) {
                            out.write(buffer, 0, len);
                        }
                        out.println();
                    } else {
                        out.println("Can not get any logs for the log file: " + logFile);
                        String msg = "Response from the NodeManager:" + nodeId + " WebService is "
                                + ((response == null) ? "null"
                                        : "not successful," + " HTTP error code: " + response.getStatus()
                                                + ", Server response:\n" + response.getEntity(String.class));
                        out.println(msg);
                    }
                    out.flush();
                    foundAnyLogs = true;
                } catch (ClientHandlerException | UniformInterfaceException ex) {
                    System.err.println("Can not find the log file:" + logFile + " for the container:"
                            + containerIdStr + " in NodeManager:" + nodeId);
                } finally {
                    IOUtils.closeQuietly(is);
                }
            }

            if (foundAnyLogs) {
                return 0;
            } else {
                return -1;
            }
        } finally {
            logCliHelper.closePrintStream(out);
        }
    }

    @Private
    @VisibleForTesting
    public ContainerReport getContainerReport(String containerIdStr) throws YarnException, IOException {
        return yarnClient.getContainerReport(ContainerId.fromString(containerIdStr));
    }

    private boolean isApplicationFinished(YarnApplicationState appState) {
        return appState == YarnApplicationState.FINISHED || appState == YarnApplicationState.FAILED
                || appState == YarnApplicationState.KILLED;
    }

    private int printAMContainerLogs(Configuration conf, ContainerLogsRequest request, List<String> amContainers,
            LogCLIHelpers logCliHelper, boolean useRegex, boolean ignoreSizeLimit) throws Exception {
        List<JSONObject> amContainersList = null;
        List<ContainerLogsRequest> requests = new ArrayList<ContainerLogsRequest>();
        boolean getAMContainerLists = false;
        String appId = request.getAppId().toString();
        StringBuilder errorMessage = new StringBuilder();
        // We will call RM webservice to get all AppAttempts information.
        // If we get nothing, we will try to call AHS webservice to get AppAttempts
        // which includes nodeAddress for the AM Containers.
        try {
            amContainersList = getAMContainerInfoForRMWebService(conf, appId);
            if (amContainersList != null && !amContainersList.isEmpty()) {
                getAMContainerLists = true;
                for (JSONObject amContainer : amContainersList) {
                    ContainerLogsRequest amRequest = new ContainerLogsRequest(request);
                    amRequest.setContainerId(amContainer.getString("containerId"));
                    String httpAddress = amContainer.getString("nodeHttpAddress");
                    if (httpAddress != null && !httpAddress.isEmpty()) {
                        amRequest.setNodeHttpAddress(httpAddress);
                    }
                    amRequest.setNodeId(amContainer.getString("nodeId"));
                    requests.add(amRequest);
                }
            }
        } catch (Exception ex) {
            errorMessage.append(ex.getMessage() + "\n");
            if (request.isAppFinished()) {
                if (!conf.getBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED,
                        YarnConfiguration.DEFAULT_TIMELINE_SERVICE_ENABLED)) {
                    errorMessage.append("Please enable the timeline service "
                            + "and make sure the timeline server is running.");
                } else {
                    try {
                        amContainersList = getAMContainerInfoForAHSWebService(conf, appId);
                        if (amContainersList != null && !amContainersList.isEmpty()) {
                            getAMContainerLists = true;
                            for (JSONObject amContainer : amContainersList) {
                                ContainerLogsRequest amRequest = new ContainerLogsRequest(request);
                                amRequest.setContainerId(amContainer.getString("amContainerId"));
                                requests.add(amRequest);
                            }
                        }
                    } catch (Exception e) {
                        errorMessage.append(e.getMessage());
                    }
                }
            }
        }

        if (!getAMContainerLists) {
            System.err.println("Unable to get AM container informations " + "for the application:" + appId);
            System.err.println(errorMessage);
            System.err.println("Can not get AMContainers logs for " + "the application:" + appId
                    + " with the appOwner:" + request.getAppOwner());
            return -1;
        }

        List<ContainerLogsRequest> candidates = new ArrayList<>();
        if (amContainers.contains("ALL")) {
            candidates.addAll(requests);
            outStream.println();
            outStream.println("Specified ALL for -am option. " + "Printed logs for all am containers.");
        } else {
            for (String amContainer : amContainers) {
                int amContainerId = Integer.parseInt(amContainer.trim());
                if (amContainerId == -1) {
                    candidates.add(requests.get(requests.size() - 1));
                } else {
                    if (amContainerId <= requests.size()) {
                        candidates.add(requests.get(amContainerId - 1));
                    } else {
                        System.err.println(String.format(
                                "ERROR: Specified AM containerId"
                                        + " (%s) exceeds the number of AM containers (%s).",
                                amContainerId, requests.size()));
                        return -1;
                    }
                }
            }
        }
        Map<String, ContainerLogsRequest> newOptions = new HashMap<>();
        if (request.isAppFinished()) {
            newOptions = getMatchedLogTypesForFinishedApp(candidates, logCliHelper, useRegex, ignoreSizeLimit);
        } else {
            newOptions = getMatchedLogTypesForRunningApp(candidates, useRegex, ignoreSizeLimit);
        }
        for (Entry<String, ContainerLogsRequest> amRequest : newOptions.entrySet()) {
            outputAMContainerLogs(amRequest.getValue(), conf, logCliHelper, useRegex, ignoreSizeLimit);
        }
        return 0;
    }

    private void outputAMContainerLogs(ContainerLogsRequest request, Configuration conf, LogCLIHelpers logCliHelper,
            boolean useRegex, boolean ignoreSizeLimit) throws Exception {
        String nodeHttpAddress = request.getNodeHttpAddress();
        String containerId = request.getContainerId();
        String nodeId = request.getNodeId();

        if (request.isAppFinished()) {
            if (containerId != null && !containerId.isEmpty()) {
                if (nodeId != null && !nodeId.isEmpty()) {
                    logCliHelper.dumpAContainerLogsForLogType(request);
                } else {
                    logCliHelper.dumpAContainerLogsForLogTypeWithoutNodeId(request);
                }
            }
        } else {
            if (nodeHttpAddress != null && containerId != null && !nodeHttpAddress.isEmpty()
                    && !containerId.isEmpty()) {
                ContainerState containerState = getContainerReport(containerId).getContainerState();
                request.setContainerState(containerState);
                printContainerLogsFromRunningApplication(conf, request, logCliHelper, useRegex, ignoreSizeLimit);
            }
        }
    }

    private int showContainerLogInfo(ContainerLogsRequest request, LogCLIHelpers logCliHelper)
            throws IOException, YarnException, ClientHandlerException, UniformInterfaceException, JSONException {
        if (!request.isAppFinished()) {
            return printContainerInfoFromRunningApplication(request, logCliHelper);
        } else {
            return logCliHelper.printAContainerLogMetadata(request, System.out, System.err);
        }
    }

    private int showNodeLists(ContainerLogsRequest request, LogCLIHelpers logCliHelper) throws IOException {
        if (!request.isAppFinished()) {
            System.err.println("The -list_nodes command can be only used with " + "finished applications");
            return -1;
        } else {
            logCliHelper.printNodesList(request, System.out, System.err);
            return 0;
        }
    }

    private int showApplicationLogInfo(ContainerLogsRequest request, LogCLIHelpers logCliHelper)
            throws IOException, YarnException {
        String appState = "Application State: " + (request.isAppFinished() ? "Completed." : "Running.");
        if (!request.isAppFinished()) {
            List<ContainerReport> reports = getContainerReportsFromRunningApplication(request);
            List<ContainerReport> filterReports = filterContainersInfo(request, reports);
            if (filterReports.isEmpty()) {
                System.err.println("Can not find any containers for the application:" + request.getAppId() + ".");
                return -1;
            }
            outStream.println(appState);
            for (ContainerReport report : filterReports) {
                outStream.println(String.format(LogCLIHelpers.CONTAINER_ON_NODE_PATTERN, report.getContainerId(),
                        report.getAssignedNode()));
            }
            return 0;
        } else {
            outStream.println(appState);
            logCliHelper.printContainersList(request, System.out, System.err);
            return 0;
        }
    }

    /**
     * Create Command Options.
     * @return the command options
     */
    private Options createCommandOpts() {
        Options opts = new Options();
        opts.addOption(HELP_CMD, false, "Displays help for all commands.");
        Option appIdOpt = new Option(APPLICATION_ID_OPTION, true, "ApplicationId (required)");
        opts.addOption(appIdOpt);
        opts.addOption(CONTAINER_ID_OPTION, true,
                "ContainerId. " + "By default, it will print all available logs."
                        + " Work with -log_files to get only specific logs. If specified, the"
                        + " applicationId can be omitted");
        opts.addOption(NODE_ADDRESS_OPTION, true, "NodeAddress in the format " + "nodename:port");
        opts.addOption(APP_OWNER_OPTION, true, "AppOwner (assumed to be current user if not specified)");
        Option amOption = new Option(AM_CONTAINER_OPTION, true,
                "Prints the AM Container logs for this application. "
                        + "Specify comma-separated value to get logs for related AM "
                        + "Container. For example, If we specify -am 1,2, we will get "
                        + "the logs for the first AM Container as well as the second "
                        + "AM Container. To get logs for all AM Containers, use -am ALL. "
                        + "To get logs for the latest AM Container, use -am -1. "
                        + "By default, it will print all available logs. Work with -log_files "
                        + "to get only specific logs.");
        amOption.setValueSeparator(',');
        amOption.setArgs(Option.UNLIMITED_VALUES);
        amOption.setArgName("AM Containers");
        opts.addOption(amOption);
        Option logFileOpt = new Option(PER_CONTAINER_LOG_FILES_OPTION, true,
                "Specify comma-separated value " + "to get exact matched log files. Use \"ALL\" or \"*\" to "
                        + "fetch all the log files for the container.");
        logFileOpt.setValueSeparator(',');
        logFileOpt.setArgs(Option.UNLIMITED_VALUES);
        logFileOpt.setArgName("Log File Name");
        opts.addOption(logFileOpt);
        Option logFileRegexOpt = new Option(PER_CONTAINER_LOG_FILES_REGEX_OPTION, true,
                "Specify comma-separated value " + "to get matched log files by using java regex. Use \".*\" to "
                        + "fetch all the log files for the container.");
        logFileRegexOpt.setValueSeparator(',');
        logFileRegexOpt.setArgs(Option.UNLIMITED_VALUES);
        logFileRegexOpt.setArgName("Log File Pattern");
        opts.addOption(logFileRegexOpt);
        opts.addOption(SHOW_CONTAINER_LOG_INFO, false,
                "Show the container log metadata, " + "including log-file names, the size of the log files. "
                        + "You can combine this with --containerId to get log metadata for "
                        + "the specific container, or with --nodeAddress to get log metadata "
                        + "for all the containers on the specific NodeManager.");
        opts.addOption(SHOW_APPLICATION_LOG_INFO, false,
                "Show the " + "containerIds which belong to the specific Application. "
                        + "You can combine this with --nodeAddress to get containerIds "
                        + "for all the containers on the specific NodeManager.");
        opts.addOption(LIST_NODES_OPTION, false, "Show the list of nodes that successfully aggregated logs. "
                + "This option can only be used with finished applications.");
        opts.addOption(OUT_OPTION, true, "Local directory for storing individual "
                + "container logs. The container logs will be stored based on the " + "node the container ran on.");
        opts.addOption(SIZE_OPTION, true,
                "Prints the log file's first 'n' bytes "
                        + "or the last 'n' bytes. Use negative values as bytes to read from "
                        + "the end and positive values as bytes to read from the beginning.");
        opts.addOption(CLIENT_MAX_RETRY_OPTION, true,
                "Set max retry number for a" + " retry client to get the container logs for the running "
                        + "applications. Use a negative value to make retry forever. "
                        + "The default value is 30.");
        opts.addOption(CLIENT_RETRY_INTERVAL_OPTION, true,
                "Work with --client_max_retries to create a retry client. " + "The default value is 1000.");
        opts.addOption(SIZE_LIMIT_OPTION, true,
                "Use this option to limit " + "the size of the total logs which could be fetched. "
                        + "By default, we only allow to fetch at most " + LOG_SIZE_LIMIT_DEFAULT
                        + " MB logs. If the total log size is "
                        + "larger than the specified number, the CLI would fail. "
                        + "The user could specify -1 to ignore the size limit " + "and fetch all logs.");
        opts.getOption(APPLICATION_ID_OPTION).setArgName("Application ID");
        opts.getOption(CONTAINER_ID_OPTION).setArgName("Container ID");
        opts.getOption(NODE_ADDRESS_OPTION).setArgName("Node Address");
        opts.getOption(APP_OWNER_OPTION).setArgName("Application Owner");
        opts.getOption(AM_CONTAINER_OPTION).setArgName("AM Containers");
        opts.getOption(OUT_OPTION).setArgName("Local Directory");
        opts.getOption(SIZE_OPTION).setArgName("size");
        opts.getOption(CLIENT_MAX_RETRY_OPTION).setArgName("Max Retries");
        opts.getOption(CLIENT_RETRY_INTERVAL_OPTION).setArgName("Retry Interval");
        opts.getOption(SIZE_LIMIT_OPTION).setArgName("Size Limit");
        return opts;
    }

    /**
     * Create Print options for helper message.
     * @param commandOpts the options
     * @return the print options
     */
    private Options createPrintOpts(Options commandOpts) {
        Options printOpts = new Options();
        printOpts.addOption(commandOpts.getOption(HELP_CMD));
        printOpts.addOption(commandOpts.getOption(CONTAINER_ID_OPTION));
        printOpts.addOption(commandOpts.getOption(NODE_ADDRESS_OPTION));
        printOpts.addOption(commandOpts.getOption(APP_OWNER_OPTION));
        printOpts.addOption(commandOpts.getOption(AM_CONTAINER_OPTION));
        printOpts.addOption(commandOpts.getOption(PER_CONTAINER_LOG_FILES_OPTION));
        printOpts.addOption(commandOpts.getOption(LIST_NODES_OPTION));
        printOpts.addOption(commandOpts.getOption(SHOW_APPLICATION_LOG_INFO));
        printOpts.addOption(commandOpts.getOption(SHOW_CONTAINER_LOG_INFO));
        printOpts.addOption(commandOpts.getOption(OUT_OPTION));
        printOpts.addOption(commandOpts.getOption(SIZE_OPTION));
        printOpts.addOption(commandOpts.getOption(PER_CONTAINER_LOG_FILES_REGEX_OPTION));
        printOpts.addOption(commandOpts.getOption(CLIENT_MAX_RETRY_OPTION));
        printOpts.addOption(commandOpts.getOption(CLIENT_RETRY_INTERVAL_OPTION));
        printOpts.addOption(commandOpts.getOption(SIZE_LIMIT_OPTION));
        return printOpts;
    }

    private List<String> parseAMContainer(CommandLine commandLine, Options printOpts) throws NumberFormatException {
        List<String> amContainersList = new ArrayList<String>();
        String[] amContainers = commandLine.getOptionValues(AM_CONTAINER_OPTION);
        for (String am : amContainers) {
            boolean errorInput = false;
            if (!am.trim().equalsIgnoreCase("ALL")) {
                try {
                    int id = Integer.parseInt(am.trim());
                    if (id != -1 && id <= 0) {
                        errorInput = true;
                    }
                } catch (NumberFormatException ex) {
                    errorInput = true;
                }
                if (errorInput) {
                    String errMessage = "Invalid input for option -am. Valid inputs are 'ALL', -1 "
                            + "and any other integer which is larger than 0.";
                    printHelpMessage(printOpts);
                    throw new NumberFormatException(errMessage);
                }
                amContainersList.add(am.trim());
            } else {
                amContainersList.add("ALL");
                break;
            }
        }
        return amContainersList;
    }

    private int fetchAMContainerLogs(ContainerLogsRequest request, List<String> amContainersList,
            LogCLIHelpers logCliHelper, boolean useRegex, boolean ignoreSizeLimit) throws Exception {
        return printAMContainerLogs(getConf(), request, amContainersList, logCliHelper, useRegex, ignoreSizeLimit);
    }

    private int fetchContainerLogs(ContainerLogsRequest request, LogCLIHelpers logCliHelper, boolean useRegex,
            boolean ignoreSizeLimit)
            throws IOException, ClientHandlerException, UniformInterfaceException, JSONException {
        String appIdStr = request.getAppId().toString();
        String containerIdStr = request.getContainerId();
        String nodeAddress = request.getNodeId();
        String appOwner = request.getAppOwner();
        boolean isAppFinished = request.isAppFinished();
        // if the application is in the final state,
        // we could directly get logs from HDFS.
        if (isAppFinished) {
            // if user specified "ALL" as the logFiles param, pass empty list
            // to logCliHelper so that it fetches all the logs
            ContainerLogsRequest newOptions = getMatchedLogOptions(request, logCliHelper, useRegex,
                    ignoreSizeLimit);
            if (newOptions == null) {
                System.err.println("Can not find any log file matching the pattern: " + request.getLogTypes()
                        + " for the container: " + request.getContainerId() + " within the application: "
                        + request.getAppId());
                return -1;
            }
            if (nodeAddress != null && !nodeAddress.isEmpty()) {
                return logCliHelper.dumpAContainerLogsForLogType(newOptions);
            } else {
                return logCliHelper.dumpAContainerLogsForLogTypeWithoutNodeId(newOptions);
            }
        }
        String nodeHttpAddress = null;
        String nodeId = null;
        try {
            // If the nodeAddress is not provided, we will try to get
            // the ContainerReport. In the containerReport, we could get
            // nodeAddress and nodeHttpAddress
            ContainerReport report = getContainerReport(containerIdStr);
            nodeHttpAddress = report.getNodeHttpAddress();
            if (nodeHttpAddress != null && !nodeHttpAddress.isEmpty()) {
                nodeHttpAddress = nodeHttpAddress.replaceFirst(WebAppUtils.getHttpSchemePrefix(getConf()), "");
                request.setNodeHttpAddress(nodeHttpAddress);
            }
            nodeId = report.getAssignedNode().toString();
            request.setNodeId(nodeId);
            request.setContainerState(report.getContainerState());
        } catch (IOException | YarnException ex) {
            nodeHttpAddress = getNodeHttpAddressFromRMWebString(request);
            if (nodeHttpAddress != null && !nodeHttpAddress.isEmpty()) {
                request.setNodeHttpAddress(nodeHttpAddress);
            } else {
                // for the case, we have already uploaded partial logs in HDFS
                int result = -1;
                ContainerLogsRequest newOptions = getMatchedLogOptions(request, logCliHelper, useRegex,
                        ignoreSizeLimit);
                if (newOptions == null) {
                    System.err.println("Can not find any log file matching the pattern: " + request.getLogTypes()
                            + " for the container: " + request.getContainerId() + " within the application: "
                            + request.getAppId());
                } else {
                    if (nodeAddress != null && !nodeAddress.isEmpty()) {
                        result = logCliHelper.dumpAContainerLogsForLogType(newOptions);
                    } else {
                        result = logCliHelper.dumpAContainerLogsForLogTypeWithoutNodeId(newOptions);
                    }
                }
                if (result == -1) {
                    System.err.println("Unable to get logs for this container:" + containerIdStr
                            + " for the application:" + appIdStr + " with the appOwner: " + appOwner);
                    System.err.println("The application: " + appIdStr
                            + " is still running, and we can not get Container report " + "for the container: "
                            + containerIdStr + ". Please try later " + "or after the application finishes.");
                }
                return result;
            }
        }
        // If the application is not in the final state,
        // we will provide the NodeHttpAddress and get the container logs
        // by calling NodeManager webservice.
        ContainerLogsRequest newRequest = getMatchedOptionForRunningApp(request, useRegex, ignoreSizeLimit);
        if (newRequest == null) {
            return -1;
        }
        return printContainerLogsFromRunningApplication(getConf(), request, logCliHelper, useRegex,
                ignoreSizeLimit);
    }

    private int fetchApplicationLogs(ContainerLogsRequest options, LogCLIHelpers logCliHelper, boolean useRegex,
            boolean ignoreSizeLimit) throws IOException, YarnException {
        // If the application has finished, we would fetch the logs
        // from HDFS.
        // If the application is still running, we would get the full
        // list of the containers first, then fetch the logs for each
        // container from NM.
        int resultCode = -1;
        if (options.isAppFinished()) {
            ContainerLogsRequest newOptions = getMatchedLogOptions(options, logCliHelper, useRegex,
                    ignoreSizeLimit);
            if (newOptions == null) {
                System.err.println("Can not find any log file matching the pattern: " + options.getLogTypes()
                        + " for the application: " + options.getAppId());
            } else {
                resultCode = logCliHelper.dumpAllContainersLogs(newOptions);
            }
        } else {
            List<ContainerLogsRequest> containerLogRequests = getContainersLogRequestForRunningApplication(options);

            // get all matched container log types and check the total log size.
            Map<String, ContainerLogsRequest> matchedLogTypes = getMatchedLogTypesForRunningApp(
                    containerLogRequests, useRegex, ignoreSizeLimit);

            for (Entry<String, ContainerLogsRequest> container : matchedLogTypes.entrySet()) {
                int result = printContainerLogsFromRunningApplication(getConf(), container.getValue(), logCliHelper,
                        useRegex, ignoreSizeLimit);
                if (result == 0) {
                    resultCode = 0;
                }
            }
        }
        if (resultCode == -1) {
            System.err.println("Can not find the logs for the application: " + options.getAppId()
                    + " with the appOwner: " + options.getAppOwner());
        }
        return resultCode;
    }

    private String guessAppOwner(ApplicationReport appReport, ApplicationId appId) throws IOException {
        String appOwner = null;
        if (appReport != null) {
            //always use the app owner from the app report if possible
            appOwner = appReport.getUser();
        } else {
            appOwner = UserGroupInformation.getCurrentUser().getShortUserName();
            appOwner = LogCLIHelpers.getOwnerForAppIdOrNull(appId, appOwner, getConf());
        }
        return appOwner;
    }

    private ContainerLogsRequest getMatchedLogOptions(ContainerLogsRequest request, LogCLIHelpers logCliHelper,
            boolean useRegex, boolean ignoreSizeLimit) throws IOException {
        ContainerLogsRequest newOptions = new ContainerLogsRequest(request);
        Set<ContainerLogFileInfo> files = logCliHelper.listContainerLogs(request);
        Set<String> matchedFiles = getMatchedLogFiles(request, files, useRegex, ignoreSizeLimit);
        if (matchedFiles.isEmpty()) {
            return null;
        } else {
            newOptions.setLogTypes(matchedFiles);
            return newOptions;
        }
    }

    private Set<String> getMatchedLogFiles(ContainerLogsRequest options, Collection<ContainerLogFileInfo> candidate,
            boolean useRegex, boolean ignoreSizeLimit) throws IOException {
        Set<String> matchedFiles = new HashSet<String>();
        Set<String> filePattern = options.getLogTypes();
        long size = options.getBytes();
        boolean getAll = options.getLogTypes().contains("ALL");
        Iterator<ContainerLogFileInfo> iterator = candidate.iterator();
        while (iterator.hasNext()) {
            boolean matchedFile = false;
            ContainerLogFileInfo logInfo = iterator.next();
            if (getAll) {
                matchedFile = true;
            } else if (useRegex) {
                if (isFileMatching(logInfo.getFileName(), filePattern)) {
                    matchedFile = true;
                }
            } else {
                if (filePattern.contains(logInfo.getFileName())) {
                    matchedFile = true;
                }
            }
            if (matchedFile) {
                matchedFiles.add(logInfo.getFileName());
                if (!ignoreSizeLimit) {
                    decrLogSizeLimit(Math.min(Long.parseLong(logInfo.getFileSize()), size));
                    if (getLogSizeLimitLeft() < 0) {
                        throw new RuntimeException("The total log size is too large." + "The log size limit is "
                                + specifedLogLimits + "MB. "
                                + "Please specify a proper value --size option or if you "
                                + "really want to fetch all, please " + "specify -1 for --size_limit_mb option.");
                    }
                }
            }
        }
        return matchedFiles;
    }

    private boolean isFileMatching(String fileType, Set<String> logTypes) {
        for (String logType : logTypes) {
            Pattern filterPattern = Pattern.compile(logType);
            boolean match = filterPattern.matcher(fileType).find();
            if (match) {
                return true;
            }
        }
        return false;
    }

    private List<ContainerLogsRequest> getContainersLogRequestForRunningApplication(ContainerLogsRequest options)
            throws YarnException, IOException {
        List<ContainerLogsRequest> newOptionsList = new ArrayList<ContainerLogsRequest>();
        List<ContainerReport> reports = getContainerReportsFromRunningApplication(options);
        for (ContainerReport container : reports) {
            ContainerLogsRequest newOptions = new ContainerLogsRequest(options);
            newOptions.setContainerId(container.getContainerId().toString());
            newOptions.setNodeId(container.getAssignedNode().toString());
            String httpAddress = container.getNodeHttpAddress();
            if (httpAddress != null && !httpAddress.isEmpty()) {
                newOptions.setNodeHttpAddress(
                        httpAddress.replaceFirst(WebAppUtils.getHttpSchemePrefix(getConf()), ""));
            }
            newOptions.setContainerState(container.getContainerState());
            newOptionsList.add(newOptions);
        }
        return newOptionsList;
    }

    private List<ContainerReport> getContainerReportsFromRunningApplication(ContainerLogsRequest options)
            throws YarnException, IOException {
        List<ContainerReport> reports = new ArrayList<ContainerReport>();
        List<ApplicationAttemptReport> attempts = yarnClient.getApplicationAttempts(options.getAppId());
        Map<ContainerId, ContainerReport> containerMap = new TreeMap<ContainerId, ContainerReport>();
        for (ApplicationAttemptReport attempt : attempts) {
            List<ContainerReport> containers = yarnClient.getContainers(attempt.getApplicationAttemptId());
            for (ContainerReport container : containers) {
                if (!containerMap.containsKey(container.getContainerId())) {
                    containerMap.put(container.getContainerId(), container);
                }
            }
        }
        reports.addAll(containerMap.values());
        return reports;
    }

    // filter the containerReports based on the nodeId and ContainerId
    private List<ContainerReport> filterContainersInfo(ContainerLogsRequest options,
            List<ContainerReport> containers) {
        List<ContainerReport> filterReports = new ArrayList<ContainerReport>(containers);
        String nodeId = options.getNodeId();
        boolean filterBasedOnNodeId = (nodeId != null && !nodeId.isEmpty());
        String containerId = options.getContainerId();
        boolean filterBasedOnContainerId = (containerId != null && !containerId.isEmpty());

        if (filterBasedOnNodeId || filterBasedOnContainerId) {
            // filter the reports based on the containerId and.or nodeId
            for (ContainerReport report : containers) {
                if (filterBasedOnContainerId) {
                    if (!report.getContainerId().toString().equalsIgnoreCase(containerId)) {
                        filterReports.remove(report);
                    }
                }

                if (filterBasedOnNodeId) {
                    if (!report.getAssignedNode().toString().equalsIgnoreCase(nodeId)) {
                        filterReports.remove(report);
                    }
                }
            }
        }
        return filterReports;
    }

    private int printContainerInfoFromRunningApplication(ContainerLogsRequest options, LogCLIHelpers logCliHelper)
            throws YarnException, IOException, ClientHandlerException, UniformInterfaceException, JSONException {
        String containerIdStr = options.getContainerId();
        String nodeIdStr = options.getNodeId();
        List<ContainerReport> reports = getContainerReportsFromRunningApplication(options);
        List<ContainerReport> filteredReports = filterContainersInfo(options, reports);
        if (filteredReports.isEmpty()) {
            // if we specify the containerId as well as NodeAddress
            String nodeHttpAddress = null;
            if (options.getContainerId() != null && !options.getContainerId().isEmpty()) {
                nodeHttpAddress = getNodeHttpAddressFromRMWebString(options);
            }
            if (nodeHttpAddress != null) {
                outputContainerLogMeta(options.getContainerId(), options.getNodeId(), nodeHttpAddress);
                return 0;
            } else {
                int result = logCliHelper.printAContainerLogMetadata(options, System.out, System.err);
                if (result == -1) {
                    StringBuilder sb = new StringBuilder();
                    if (containerIdStr != null && !containerIdStr.isEmpty()) {
                        sb.append("Trying to get container with ContainerId: " + containerIdStr + "\n");
                    }
                    if (nodeIdStr != null && !nodeIdStr.isEmpty()) {
                        sb.append("Trying to get container from NodeManager: " + nodeIdStr + "\n");
                    }
                    sb.append("Can not find any matched containers for the application: " + options.getAppId());
                    System.err.println(sb.toString());
                }
                return result;
            }
        }
        for (ContainerReport report : filteredReports) {
            String nodeId = report.getAssignedNode().toString();
            String nodeHttpAddress = report.getNodeHttpAddress()
                    .replaceFirst(WebAppUtils.getHttpSchemePrefix(getConf()), "");
            String containerId = report.getContainerId().toString();
            outputContainerLogMeta(containerId, nodeId, nodeHttpAddress);
        }
        return 0;
    }

    private void outputContainerLogMeta(String containerId, String nodeId, String nodeHttpAddress)
            throws IOException {
        String containerString = String.format(LogCLIHelpers.CONTAINER_ON_NODE_PATTERN, containerId, nodeId);
        outStream.println(containerString);
        outStream.println(StringUtils.repeat("=", containerString.length()));
        outStream.printf(LogCLIHelpers.PER_LOG_FILE_INFO_PATTERN, "LogFile", "LogLength", "LastModificationTime",
                "LogAggregationType");
        outStream.println(StringUtils.repeat("=", containerString.length() * 2));
        List<Pair<ContainerLogFileInfo, String>> infos = getContainerLogFiles(getConf(), containerId,
                nodeHttpAddress);
        for (Pair<ContainerLogFileInfo, String> info : infos) {
            outStream.printf(LogCLIHelpers.PER_LOG_FILE_INFO_PATTERN, info.getKey().getFileName(),
                    info.getKey().getFileSize(), info.getKey().getLastModifiedTime(), info.getValue());
        }
    }

    @VisibleForTesting
    public Set<String> getMatchedContainerLogFiles(ContainerLogsRequest request, boolean useRegex,
            boolean ignoreSizeLimit) throws IOException {
        // fetch all the log files for the container
        // filter the log files based on the given -log_files pattern
        List<Pair<ContainerLogFileInfo, String>> allLogFileInfos = getContainerLogFiles(getConf(),
                request.getContainerId(), request.getNodeHttpAddress());
        List<ContainerLogFileInfo> fileNames = new ArrayList<ContainerLogFileInfo>();
        for (Pair<ContainerLogFileInfo, String> fileInfo : allLogFileInfos) {
            fileNames.add(fileInfo.getKey());
        }
        return getMatchedLogFiles(request, fileNames, useRegex, ignoreSizeLimit);
    }

    @VisibleForTesting
    public ClientResponse getResponeFromNMWebService(Configuration conf, Client webServiceClient,
            ContainerLogsRequest request, String logFile) {
        WebResource webResource = webServiceClient
                .resource(WebAppUtils.getHttpSchemePrefix(conf) + request.getNodeHttpAddress());
        return webResource.path("ws").path("v1").path("node").path("containers").path(request.getContainerId())
                .path("logs").path(logFile).queryParam("size", Long.toString(request.getBytes()))
                .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
    }

    @VisibleForTesting
    public String getNodeHttpAddressFromRMWebString(ContainerLogsRequest request)
            throws ClientHandlerException, UniformInterfaceException, JSONException {
        if (request.getNodeId() == null || request.getNodeId().isEmpty()) {
            return null;
        }
        JSONObject nodeInfo = YarnWebServiceUtils.getNodeInfoFromRMWebService(getConf(), request.getNodeId())
                .getJSONObject("node");
        return nodeInfo.has("nodeHTTPAddress") ? nodeInfo.getString("nodeHTTPAddress") : null;
    }

    // Class to handle retry
    static class ClientConnectionRetry {

        // maxRetries < 0 means keep trying
        @Private
        @VisibleForTesting
        public int maxRetries;

        @Private
        @VisibleForTesting
        public long retryInterval;

        // Indicates if retries happened last time. Only tests should read it.
        // In unit tests, retryOn() calls should _not_ be concurrent.
        private boolean retried = false;

        @Private
        @VisibleForTesting
        boolean getRetired() {
            return retried;
        }

        // Constructor with default retry settings
        public ClientConnectionRetry(int inputMaxRetries, long inputRetryInterval) {
            this.maxRetries = inputMaxRetries;
            this.retryInterval = inputRetryInterval;
        }

        public Object retryOn(ClientRetryOp op) throws RuntimeException, IOException {
            int leftRetries = maxRetries;
            retried = false;

            // keep trying
            while (true) {
                try {
                    // try perform the op, if fail, keep retrying
                    return op.run();
                } catch (IOException | RuntimeException e) {
                    // break if there's no retries left
                    if (leftRetries == 0) {
                        break;
                    }
                    if (op.shouldRetryOn(e)) {
                        logException(e, leftRetries);
                    } else {
                        throw e;
                    }
                }
                if (leftRetries > 0) {
                    leftRetries--;
                }
                retried = true;
                try {
                    // sleep for the given time interval
                    Thread.sleep(retryInterval);
                } catch (InterruptedException ie) {
                    System.out.println("Client retry sleep interrupted! ");
                }
            }
            throw new RuntimeException("Connection retries limit exceeded.");
        };

        private void logException(Exception e, int leftRetries) {
            if (leftRetries > 0) {
                System.out.println("Exception caught by ClientConnectionRetry," + " will try " + leftRetries
                        + " more time(s).\nMessage: " + e.getMessage());
            } else {
                // note that maxRetries may be -1 at the very beginning
                System.out.println("ConnectionException caught by ClientConnectionRetry,"
                        + " will keep retrying.\nMessage: " + e.getMessage());
            }
        }
    }

    private class ClientJerseyRetryFilter extends ClientFilter {
        @Override
        public ClientResponse handle(final ClientRequest cr) throws ClientHandlerException {
            // Set up the retry operation
            ClientRetryOp jerseyRetryOp = new ClientRetryOp() {
                @Override
                public Object run() {
                    // Try pass the request, if fail, keep retrying
                    return getNext().handle(cr);
                }

                @Override
                public boolean shouldRetryOn(Exception e) {
                    // Only retry on connection exceptions
                    return (e instanceof ClientHandlerException) && (e.getCause() instanceof ConnectException
                            || e.getCause() instanceof SocketTimeoutException
                            || e.getCause() instanceof SocketException);
                }
            };
            try {
                return (ClientResponse) connectionRetry.retryOn(jerseyRetryOp);
            } catch (IOException e) {
                throw new ClientHandlerException("Jersey retry failed!\nMessage: " + e.getMessage());
            }
        }
    }

    // Abstract class for an operation that should be retried by client
    private static abstract class ClientRetryOp {
        // The operation that should be retried
        public abstract Object run() throws IOException;

        // The method to indicate if we should retry given the incoming exception
        public abstract boolean shouldRetryOn(Exception e);
    }

    private long getLogSizeLimitLeft() {
        return this.logSizeLeft;
    }

    private void decrLogSizeLimit(long used) {
        this.logSizeLeft -= used;
    }

    @Private
    @VisibleForTesting
    public ContainerLogsRequest getMatchedOptionForRunningApp(ContainerLogsRequest container, boolean useRegex,
            boolean ignoreSizeLimit) throws IOException {
        String containerIdStr = container.getContainerId().toString();
        String nodeHttpAddress = container.getNodeHttpAddress();
        if (nodeHttpAddress == null || nodeHttpAddress.isEmpty()) {
            System.err.println("Can not get the logs for the container: " + containerIdStr);
            System.err.println(
                    "The node http address is required to get container " + "logs for the Running application.");
            return null;
        }

        Set<String> matchedFiles = getMatchedContainerLogFiles(container, useRegex, ignoreSizeLimit);
        if (matchedFiles.isEmpty()) {
            System.err.println("Can not find any log file matching the pattern: " + container.getLogTypes()
                    + " for the container: " + containerIdStr + " within the application: " + container.getAppId());
            return null;
        }
        container.setLogTypes(matchedFiles);
        return container;
    }

    @Private
    @VisibleForTesting
    public Map<String, ContainerLogsRequest> getMatchedLogTypesForRunningApp(
            List<ContainerLogsRequest> containerLogRequests, boolean useRegex, boolean ignoreSizeLimit) {
        Map<String, ContainerLogsRequest> containerMatchedLog = new HashMap<>();
        for (ContainerLogsRequest container : containerLogRequests) {
            try {
                ContainerLogsRequest request = getMatchedOptionForRunningApp(container, useRegex, ignoreSizeLimit);
                if (request == null) {
                    continue;
                }
                containerMatchedLog.put(container.getContainerId(), request);
            } catch (IOException ex) {
                System.err.println(ex);
                continue;
            }
        }
        return containerMatchedLog;
    }

    private Map<String, ContainerLogsRequest> getMatchedLogTypesForFinishedApp(
            List<ContainerLogsRequest> containerLogRequests, LogCLIHelpers logCliHelper, boolean useRegex,
            boolean ignoreSizeLimit) {
        Map<String, ContainerLogsRequest> containerMatchedLog = new HashMap<>();
        for (ContainerLogsRequest container : containerLogRequests) {
            try {
                ContainerLogsRequest request = getMatchedLogOptions(container, logCliHelper, useRegex,
                        ignoreSizeLimit);
                if (request == null) {
                    System.err.println("Can not find any log file matching the pattern: " + container.getLogTypes()
                            + " for the container: " + container.getContainerId() + " within the application: "
                            + container.getAppId());
                    continue;
                }
                containerMatchedLog.put(container.getContainerId(), request);
            } catch (IOException ex) {
                System.err.println(ex);
                continue;
            }
        }
        return containerMatchedLog;
    }
}