001/**
002* Licensed to the Apache Software Foundation (ASF) under one
003* or more contributor license agreements.  See the NOTICE file
004* distributed with this work for additional information
005* regarding copyright ownership.  The ASF licenses this file
006* to you under the Apache License, Version 2.0 (the
007* "License"); you may not use this file except in compliance
008* with the License.  You may obtain a copy of the License at
009*
010*     http://www.apache.org/licenses/LICENSE-2.0
011*
012* Unless required by applicable law or agreed to in writing, software
013* distributed under the License is distributed on an "AS IS" BASIS,
014* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015* See the License for the specific language governing permissions and
016* limitations under the License.
017*/
018
019package org.apache.hadoop.yarn.client.cli;
020
021import java.io.IOException;
022import java.io.PrintStream;
023import java.io.StringReader;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.List;
029import java.util.Set;
030import java.util.regex.Pattern;
031import javax.ws.rs.core.MediaType;
032import javax.xml.parsers.DocumentBuilder;
033import javax.xml.parsers.DocumentBuilderFactory;
034
035import org.apache.commons.cli.CommandLine;
036import org.apache.commons.cli.CommandLineParser;
037import org.apache.commons.cli.GnuParser;
038import org.apache.commons.cli.HelpFormatter;
039import org.apache.commons.cli.Option;
040import org.apache.commons.cli.Options;
041import org.apache.commons.cli.ParseException;
042import org.apache.commons.lang.StringUtils;
043import org.apache.hadoop.classification.InterfaceAudience.Private;
044import org.apache.hadoop.classification.InterfaceAudience.Public;
045import org.apache.hadoop.classification.InterfaceStability.Evolving;
046import org.apache.hadoop.conf.Configuration;
047import org.apache.hadoop.conf.Configured;
048import org.apache.hadoop.security.UserGroupInformation;
049import org.apache.hadoop.util.Tool;
050import org.apache.hadoop.yarn.api.records.ApplicationAttemptReport;
051import org.apache.hadoop.yarn.api.records.ApplicationId;
052import org.apache.hadoop.yarn.api.records.ApplicationReport;
053import org.apache.hadoop.yarn.api.records.ContainerId;
054import org.apache.hadoop.yarn.api.records.ContainerReport;
055import org.apache.hadoop.yarn.api.records.YarnApplicationState;
056import org.apache.hadoop.yarn.client.api.YarnClient;
057import org.apache.hadoop.yarn.conf.YarnConfiguration;
058import org.apache.hadoop.yarn.exceptions.YarnException;
059import org.apache.hadoop.yarn.logaggregation.LogCLIHelpers;
060import org.apache.hadoop.yarn.logaggregation.ContainerLogsRequest;
061import org.apache.hadoop.yarn.util.ConverterUtils;
062import org.apache.hadoop.yarn.util.Times;
063import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
064import org.codehaus.jettison.json.JSONArray;
065import org.codehaus.jettison.json.JSONException;
066import org.codehaus.jettison.json.JSONObject;
067
068import com.google.common.annotations.VisibleForTesting;
069import com.sun.jersey.api.client.Client;
070import com.sun.jersey.api.client.ClientHandlerException;
071import com.sun.jersey.api.client.ClientResponse;
072import com.sun.jersey.api.client.UniformInterfaceException;
073import com.sun.jersey.api.client.WebResource;
074import org.w3c.dom.Document;
075import org.w3c.dom.NodeList;
076import org.xml.sax.InputSource;
077
078@Public
079@Evolving
080public class LogsCLI extends Configured implements Tool {
081
082  private static final String CONTAINER_ID_OPTION = "containerId";
083  private static final String APPLICATION_ID_OPTION = "applicationId";
084  private static final String NODE_ADDRESS_OPTION = "nodeAddress";
085  private static final String APP_OWNER_OPTION = "appOwner";
086  private static final String AM_CONTAINER_OPTION = "am";
087  private static final String CONTAINER_LOG_FILES = "logFiles";
088  private static final String SHOW_META_INFO = "show_meta_info";
089  private static final String LIST_NODES_OPTION = "list_nodes";
090  private static final String OUT_OPTION = "out";
091  private static final String SIZE_OPTION = "size";
092  public static final String HELP_CMD = "help";
093
094  @Override
095  public int run(String[] args) throws Exception {
096    Options opts = createCommandOpts();
097    Options printOpts = createPrintOpts(opts);
098    if (args.length < 1) {
099      printHelpMessage(printOpts);
100      return -1;
101    }
102    if (args[0].equals("-help")) {
103      printHelpMessage(printOpts);
104      return 0;
105    }
106    CommandLineParser parser = new GnuParser();
107    String appIdStr = null;
108    String containerIdStr = null;
109    String nodeAddress = null;
110    String appOwner = null;
111    boolean getAMContainerLogs = false;
112    boolean showMetaInfo = false;
113    boolean nodesList = false;
114    String[] logFiles = null;
115    List<String> amContainersList = new ArrayList<String>();
116    String localDir = null;
117    long bytes = Long.MAX_VALUE;
118    try {
119      CommandLine commandLine = parser.parse(opts, args, true);
120      appIdStr = commandLine.getOptionValue(APPLICATION_ID_OPTION);
121      containerIdStr = commandLine.getOptionValue(CONTAINER_ID_OPTION);
122      nodeAddress = commandLine.getOptionValue(NODE_ADDRESS_OPTION);
123      appOwner = commandLine.getOptionValue(APP_OWNER_OPTION);
124      getAMContainerLogs = commandLine.hasOption(AM_CONTAINER_OPTION);
125      showMetaInfo = commandLine.hasOption(SHOW_META_INFO);
126      nodesList = commandLine.hasOption(LIST_NODES_OPTION);
127      localDir = commandLine.getOptionValue(OUT_OPTION);
128      if (getAMContainerLogs) {
129        try {
130          amContainersList = parseAMContainer(commandLine, printOpts);
131        } catch (NumberFormatException ex) {
132          System.err.println(ex.getMessage());
133          return -1;
134        }
135      }
136      if (commandLine.hasOption(CONTAINER_LOG_FILES)) {
137        logFiles = commandLine.getOptionValues(CONTAINER_LOG_FILES);
138      }
139      if (commandLine.hasOption(SIZE_OPTION)) {
140        bytes = Long.parseLong(commandLine.getOptionValue(SIZE_OPTION));
141      }
142    } catch (ParseException e) {
143      System.err.println("options parsing failed: " + e.getMessage());
144      printHelpMessage(printOpts);
145      return -1;
146    }
147
148    if (appIdStr == null) {
149      System.err.println("ApplicationId cannot be null!");
150      printHelpMessage(printOpts);
151      return -1;
152    }
153
154    ApplicationId appId = null;
155    try {
156      appId = ApplicationId.fromString(appIdStr);
157    } catch (Exception e) {
158      System.err.println("Invalid ApplicationId specified");
159      return -1;
160    }
161
162    LogCLIHelpers logCliHelper = new LogCLIHelpers();
163    logCliHelper.setConf(getConf());
164
165    YarnApplicationState appState = YarnApplicationState.NEW;
166    ApplicationReport appReport = null;
167    try {
168      appReport = getApplicationReport(appId);
169      appState = appReport.getYarnApplicationState();
170      if (appState == YarnApplicationState.NEW
171          || appState == YarnApplicationState.NEW_SAVING
172          || appState == YarnApplicationState.SUBMITTED) {
173        System.err.println("Logs are not avaiable right now.");
174        return -1;
175      }
176    } catch (IOException | YarnException e) {
177      // If we can not get appReport from either RM or ATS
178      // We will assume that this app has already finished.
179      appState = YarnApplicationState.FINISHED;
180      System.err.println("Unable to get ApplicationState."
181          + " Attempting to fetch logs directly from the filesystem.");
182    }
183
184    if (appOwner == null || appOwner.isEmpty()) {
185      appOwner = guessAppOwner(appReport, appId);
186      if (appOwner == null) {
187        System.err.println("Can not find the appOwner. "
188            + "Please specify the correct appOwner");
189        System.err.println("Could not locate application logs for " + appId);
190        return -1;
191      }
192    }
193
194    List<String> logs = new ArrayList<String>();
195    if (fetchAllLogFiles(logFiles)) {
196      logs.add(".*");
197    } else if (logFiles != null && logFiles.length > 0) {
198      logs = Arrays.asList(logFiles);
199    }
200
201    ContainerLogsRequest request = new ContainerLogsRequest(appId,
202        isApplicationFinished(appState), appOwner, nodeAddress, null,
203        containerIdStr, localDir, logs, bytes);
204
205    if (showMetaInfo) {
206      return showMetaInfo(request, logCliHelper);
207    }
208
209    if (nodesList) {
210      return showNodeLists(request, logCliHelper);
211    }
212
213    // To get am logs
214    if (getAMContainerLogs) {
215      return fetchAMContainerLogs(request, amContainersList,
216          logCliHelper);
217    }
218
219    int resultCode = 0;
220    if (containerIdStr != null) {
221      ContainerId containerId = ContainerId.fromString(containerIdStr);
222      if (!containerId.getApplicationAttemptId().getApplicationId()
223          .equals(appId)) {
224        System.err.println("The Application:" + appId
225            + " does not have the container:" + containerId);
226        return -1;
227      }
228      return fetchContainerLogs(request, logCliHelper);
229    } else {
230      if (nodeAddress == null) {
231        resultCode = fetchApplicationLogs(request, logCliHelper);
232      } else {
233        System.err.println("Should at least provide ContainerId!");
234        printHelpMessage(printOpts);
235        resultCode = -1;
236      }
237    }
238    return resultCode;
239  }
240
241  private ApplicationReport getApplicationReport(ApplicationId appId)
242      throws IOException, YarnException {
243    YarnClient yarnClient = createYarnClient();
244
245    try {
246      return yarnClient.getApplicationReport(appId);
247    } finally {
248      yarnClient.close();
249    }
250  }
251  
252  @VisibleForTesting
253  protected YarnClient createYarnClient() {
254    YarnClient yarnClient = YarnClient.createYarnClient();
255    yarnClient.init(getConf());
256    yarnClient.start();
257    return yarnClient;
258  }
259
260  public static void main(String[] args) throws Exception {
261    Configuration conf = new YarnConfiguration();
262    LogsCLI logDumper = new LogsCLI();
263    logDumper.setConf(conf);
264    int exitCode = logDumper.run(args);
265    System.exit(exitCode);
266  }
267
268  private void printHelpMessage(Options options) {
269    System.out.println("Retrieve logs for completed YARN applications.");
270    HelpFormatter formatter = new HelpFormatter();
271    formatter.printHelp("yarn logs -applicationId <application ID> [OPTIONS]",
272        new Options());
273    formatter.setSyntaxPrefix("");
274    formatter.printHelp("general options are:", options);
275  }
276
277  protected List<JSONObject> getAMContainerInfoForRMWebService(
278      Configuration conf, String appId) throws ClientHandlerException,
279      UniformInterfaceException, JSONException {
280    Client webServiceClient = Client.create();
281    String webAppAddress = WebAppUtils.getHttpSchemePrefix(conf) +
282        WebAppUtils.getWebAppBindURL(conf, YarnConfiguration.RM_BIND_HOST,
283        WebAppUtils.getRMWebAppURLWithoutScheme(conf));
284    WebResource webResource = webServiceClient.resource(webAppAddress);
285
286    ClientResponse response =
287        webResource.path("ws").path("v1").path("cluster").path("apps")
288          .path(appId).path("appattempts").accept(MediaType.APPLICATION_JSON)
289          .get(ClientResponse.class);
290    JSONObject json =
291        response.getEntity(JSONObject.class).getJSONObject("appAttempts");
292    JSONArray requests = json.getJSONArray("appAttempt");
293    List<JSONObject> amContainersList = new ArrayList<JSONObject>();
294    for (int i = 0; i < requests.length(); i++) {
295      amContainersList.add(requests.getJSONObject(i));
296    }
297    return amContainersList;
298  }
299
300  private List<JSONObject> getAMContainerInfoForAHSWebService(
301      Configuration conf, String appId) throws ClientHandlerException,
302      UniformInterfaceException, JSONException {
303    Client webServiceClient = Client.create();
304    String webAppAddress =
305        WebAppUtils.getHttpSchemePrefix(conf)
306            + WebAppUtils.getAHSWebAppURLWithoutScheme(conf);
307    WebResource webResource = webServiceClient.resource(webAppAddress);
308
309    ClientResponse response =
310        webResource.path("ws").path("v1").path("applicationhistory")
311          .path("apps").path(appId).path("appattempts")
312          .accept(MediaType.APPLICATION_JSON)
313          .get(ClientResponse.class);
314    JSONObject json = response.getEntity(JSONObject.class);
315    JSONArray requests = json.getJSONArray("appAttempt");
316    List<JSONObject> amContainersList = new ArrayList<JSONObject>();
317    for (int i = 0; i < requests.length(); i++) {
318      amContainersList.add(requests.getJSONObject(i));
319    }
320    Collections.reverse(amContainersList);
321    return amContainersList;
322  }
323
324  private boolean fetchAllLogFiles(String[] logFiles) {
325    if(logFiles != null) {
326      List<String> logs = Arrays.asList(logFiles);
327      if(logs.contains("ALL") || logs.contains(".*")) {
328        return true;
329      }
330    }
331    return false;
332  }
333
334  private List<String> getContainerLogFiles(Configuration conf,
335      String containerIdStr, String nodeHttpAddress) throws IOException {
336    List<String> logFiles = new ArrayList<>();
337    Client webServiceClient = Client.create();
338    try {
339      WebResource webResource = webServiceClient
340          .resource(WebAppUtils.getHttpSchemePrefix(conf) + nodeHttpAddress);
341      ClientResponse response =
342          webResource.path("ws").path("v1").path("node").path("containers")
343              .path(containerIdStr).accept(MediaType.APPLICATION_XML)
344              .get(ClientResponse.class);
345      if (response.getStatusInfo().getStatusCode() ==
346          ClientResponse.Status.OK.getStatusCode()) {
347        try {
348          String xml = response.getEntity(String.class);
349          DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
350          DocumentBuilder db = dbf.newDocumentBuilder();
351          InputSource is = new InputSource();
352          is.setCharacterStream(new StringReader(xml));
353          Document dom = db.parse(is);
354          NodeList elements = dom.getElementsByTagName("containerLogFiles");
355          for (int i = 0; i < elements.getLength(); i++) {
356            logFiles.add(elements.item(i).getTextContent());
357          }
358        } catch (Exception e) {
359          System.err.println("Unable to parse xml from webservice. Error:");
360          System.err.println(e.getMessage());
361          throw new IOException(e);
362        }
363      }
364
365    } catch (ClientHandlerException | UniformInterfaceException ex) {
366      System.err.println("Unable to fetch log files list");
367      throw new IOException(ex);
368    }
369    return logFiles;
370  }
371
372  @Private
373  @VisibleForTesting
374  public void printContainerLogsFromRunningApplication(Configuration conf,
375      ContainerLogsRequest request, LogCLIHelpers logCliHelper)
376      throws IOException {
377    String containerIdStr = request.getContainerId().toString();
378    String localDir = request.getOutputLocalDir();
379    String nodeHttpAddress = request.getNodeHttpAddress();
380    String nodeId = request.getNodeId();
381    PrintStream out = logCliHelper.createPrintStream(localDir, nodeId,
382        containerIdStr);
383    try {
384      // fetch all the log files for the container
385      // filter the log files based on the given --logFiles pattern
386      List<String> allLogs=
387          getContainerLogFiles(getConf(), containerIdStr, nodeHttpAddress);
388      List<String> matchedFiles = getMatchedLogFiles(
389          request, allLogs, true);
390      if (matchedFiles.isEmpty()) {
391        return;
392      }
393      ContainerLogsRequest newOptions = new ContainerLogsRequest(request);
394      newOptions.setLogTypes(matchedFiles);
395
396      Client webServiceClient = Client.create();
397      String containerString = "\n\nContainer: " + containerIdStr + " on "
398          + nodeId;
399      out.println(containerString);
400      out.println(StringUtils.repeat("=", containerString.length()));
401
402      for (String logFile : newOptions.getLogTypes()) {
403        out.println("LogType:" + logFile);
404        out.println("Log Upload Time:"
405            + Times.format(System.currentTimeMillis()));
406        out.println("Log Contents:");
407        try {
408          WebResource webResource =
409              webServiceClient.resource(WebAppUtils.getHttpSchemePrefix(conf)
410                  + nodeHttpAddress);
411          ClientResponse response =
412              webResource.path("ws").path("v1").path("node")
413                .path("containerlogs").path(containerIdStr).path(logFile)
414                .queryParam("size", Long.toString(request.getBytes()))
415                .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
416          out.println(response.getEntity(String.class));
417          out.println("End of LogType:" + logFile + ". This log file belongs"
418              + " to a running container (" + containerIdStr + ") and so may"
419              + " not be complete.");
420          out.flush();
421        } catch (ClientHandlerException | UniformInterfaceException ex) {
422          System.err.println("Can not find the log file:" + logFile
423              + " for the container:" + containerIdStr + " in NodeManager:"
424              + nodeId);
425        }
426      }
427      // for the case, we have already uploaded partial logs in HDFS
428      logCliHelper.dumpAContainersLogsForALogType(newOptions, false);
429    } finally {
430      logCliHelper.closePrintStream(out);
431    }
432  }
433
434  private int printContainerLogsForFinishedApplication(
435      ContainerLogsRequest request, LogCLIHelpers logCliHelper)
436      throws IOException {
437    ContainerLogsRequest newOptions = getMatchedLogOptions(
438        request, logCliHelper);
439    if (newOptions == null) {
440      return -1;
441    }
442    return logCliHelper.dumpAContainersLogsForALogType(newOptions);
443  }
444
445  private int printContainerLogsForFinishedApplicationWithoutNodeId(
446      ContainerLogsRequest request, LogCLIHelpers logCliHelper)
447      throws IOException {
448    ContainerLogsRequest newOptions = getMatchedLogOptions(
449        request, logCliHelper);
450    if (newOptions == null) {
451      return -1;
452    }
453    return logCliHelper.dumpAContainersLogsForALogTypeWithoutNodeId(
454        newOptions);
455  }
456
457  @Private
458  @VisibleForTesting
459  public ContainerReport getContainerReport(String containerIdStr)
460      throws YarnException, IOException {
461    YarnClient yarnClient = createYarnClient();
462    try {
463      return yarnClient.getContainerReport(
464          ContainerId.fromString(containerIdStr));
465    } finally {
466      yarnClient.close();
467    }
468  }
469
470  private boolean isApplicationFinished(YarnApplicationState appState) {
471    return appState == YarnApplicationState.FINISHED
472        || appState == YarnApplicationState.FAILED
473        || appState == YarnApplicationState.KILLED; 
474  }
475
476  private int printAMContainerLogs(Configuration conf,
477      ContainerLogsRequest request, List<String> amContainers,
478      LogCLIHelpers logCliHelper) throws Exception {
479    List<JSONObject> amContainersList = null;
480    List<ContainerLogsRequest> requests =
481        new ArrayList<ContainerLogsRequest>();
482    boolean getAMContainerLists = false;
483    String appId = request.getAppId().toString();
484    String errorMessage = "";
485    try {
486      amContainersList = getAMContainerInfoForRMWebService(conf, appId);
487      if (amContainersList != null && !amContainersList.isEmpty()) {
488        getAMContainerLists = true;
489        for (JSONObject amContainer : amContainersList) {
490          ContainerLogsRequest amRequest = new ContainerLogsRequest(request);
491          amRequest.setContainerId(amContainer.getString("containerId"));
492          amRequest.setNodeHttpAddress(
493              amContainer.getString("nodeHttpAddress"));
494          amRequest.setNodeId(amContainer.getString("nodeId"));
495          requests.add(amRequest);
496        }
497      }
498    } catch (Exception ex) {
499      errorMessage = ex.getMessage();
500      if (request.isAppFinished()) {
501        try {
502          amContainersList = getAMContainerInfoForAHSWebService(conf, appId);
503          if (amContainersList != null && !amContainersList.isEmpty()) {
504            getAMContainerLists = true;
505            for (JSONObject amContainer : amContainersList) {
506              ContainerLogsRequest amRequest = new ContainerLogsRequest(
507                  request);
508              amRequest.setContainerId(amContainer.getString("amContainerId"));
509              requests.add(amRequest);
510            }
511          }
512        } catch (Exception e) {
513          errorMessage = e.getMessage();
514        }
515      }
516    }
517
518    if (!getAMContainerLists) {
519      System.err.println("Unable to get AM container informations "
520          + "for the application:" + appId);
521      System.err.println(errorMessage);
522      return -1;
523    }
524
525    if (amContainers.contains("ALL")) {
526      for (ContainerLogsRequest amRequest : requests) {
527        outputAMContainerLogs(amRequest, conf, logCliHelper);
528      }
529      System.out.println();      
530      System.out.println("Specified ALL for -am option. "
531          + "Printed logs for all am containers.");
532    } else {
533      for (String amContainer : amContainers) {
534        int amContainerId = Integer.parseInt(amContainer.trim());
535        if (amContainerId == -1) {
536          outputAMContainerLogs(requests.get(requests.size() - 1), conf,
537              logCliHelper);
538        } else {
539          if (amContainerId <= requests.size()) {
540            outputAMContainerLogs(requests.get(amContainerId - 1), conf,
541                logCliHelper);
542          } else {
543            System.err.println(String.format("ERROR: Specified AM containerId"
544                + " (%s) exceeds the number of AM containers (%s).",
545                amContainerId, requests.size()));
546            return -1;
547          }
548        }
549      }
550    }
551    return 0;
552  }
553
554  private void outputAMContainerLogs(ContainerLogsRequest request,
555      Configuration conf, LogCLIHelpers logCliHelper) throws Exception {
556    String nodeHttpAddress = request.getNodeHttpAddress();
557    String containerId = request.getContainerId();
558    String nodeId = request.getNodeId();
559
560    if (request.isAppFinished()) {
561      if (containerId != null && !containerId.isEmpty()) {
562        if (nodeId == null || nodeId.isEmpty()) {
563          try {
564            nodeId =
565                getContainerReport(containerId).getAssignedNode().toString();
566            request.setNodeId(nodeId);
567          } catch (Exception ex) {
568            System.err.println(ex);
569            nodeId = null;
570          }
571        }
572        if (nodeId != null && !nodeId.isEmpty()) {
573          printContainerLogsForFinishedApplication(request,
574              logCliHelper);
575        }
576      }
577    } else {
578      if (nodeHttpAddress != null && containerId != null
579          && !nodeHttpAddress.isEmpty() && !containerId.isEmpty()) {
580        printContainerLogsFromRunningApplication(conf,
581            request, logCliHelper);
582      }
583    }
584  }
585
586  private int showMetaInfo(ContainerLogsRequest request,
587      LogCLIHelpers logCliHelper) throws IOException {
588    if (!request.isAppFinished()) {
589      System.err.println("The -show_meta_info command can be only used "
590          + "with finished applications");
591      return -1;
592    } else {
593      logCliHelper.printLogMetadata(request, System.out, System.err);
594      return 0;
595    }
596  }
597
598  private int showNodeLists(ContainerLogsRequest request,
599      LogCLIHelpers logCliHelper) throws IOException {
600    if (!request.isAppFinished()) {
601      System.err.println("The -list_nodes command can be only used with "
602          + "finished applications");
603      return -1;
604    } else {
605      logCliHelper.printNodesList(request, System.out, System.err);
606      return 0;
607    }
608  }
609
610  private Options createCommandOpts() {
611    Options opts = new Options();
612    opts.addOption(HELP_CMD, false, "Displays help for all commands.");
613    Option appIdOpt =
614        new Option(APPLICATION_ID_OPTION, true, "ApplicationId (required)");
615    appIdOpt.setRequired(true);
616    opts.addOption(appIdOpt);
617    opts.addOption(CONTAINER_ID_OPTION, true, "ContainerId. "
618        + "By default, it will only print syslog if the application is runing."
619        + " Work with -logFiles to get other logs.");
620    opts.addOption(NODE_ADDRESS_OPTION, true, "NodeAddress in the format "
621        + "nodename:port");
622    opts.addOption(APP_OWNER_OPTION, true,
623        "AppOwner (assumed to be current user if not specified)");
624    Option amOption = new Option(AM_CONTAINER_OPTION, true,
625        "Prints the AM Container logs for this application. "
626        + "Specify comma-separated value to get logs for related AM "
627        + "Container. For example, If we specify -am 1,2, we will get "
628        + "the logs for the first AM Container as well as the second "
629        + "AM Container. To get logs for all AM Containers, use -am ALL. "
630        + "To get logs for the latest AM Container, use -am -1. "
631        + "By default, it will only print out syslog. Work with -logFiles "
632        + "to get other logs");
633    amOption.setValueSeparator(',');
634    amOption.setArgs(Option.UNLIMITED_VALUES);
635    amOption.setArgName("AM Containers");
636    opts.addOption(amOption);
637    Option logFileOpt = new Option(CONTAINER_LOG_FILES, true,
638        "Work with -am/-containerId and specify comma-separated value "
639        + "to get specified container log files. Use \"ALL\" to fetch all the "
640        + "log files for the container. It also supports Java Regex.");
641    logFileOpt.setValueSeparator(',');
642    logFileOpt.setArgs(Option.UNLIMITED_VALUES);
643    logFileOpt.setArgName("Log File Name");
644    opts.addOption(logFileOpt);
645    opts.addOption(SHOW_META_INFO, false, "Show the log metadata, "
646        + "including log-file names, the size of the log files. "
647        + "You can combine this with --containerId to get log metadata for "
648        + "the specific container, or with --nodeAddress to get log metadata "
649        + "for all the containers on the specific NodeManager. "
650        + "Currently, this option can only be used for finished "
651        + "applications.");
652    opts.addOption(LIST_NODES_OPTION, false,
653        "Show the list of nodes that successfully aggregated logs. "
654        + "This option can only be used with finished applications.");
655    opts.addOption(OUT_OPTION, true, "Local directory for storing individual "
656        + "container logs. The container logs will be stored based on the "
657        + "node the container ran on.");
658    opts.addOption(SIZE_OPTION, true, "Prints the log file's first 'n' bytes "
659        + "or the last 'n' bytes. Use negative values as bytes to read from "
660        + "the end and positive values as bytes to read from the beginning.");
661    opts.getOption(APPLICATION_ID_OPTION).setArgName("Application ID");
662    opts.getOption(CONTAINER_ID_OPTION).setArgName("Container ID");
663    opts.getOption(NODE_ADDRESS_OPTION).setArgName("Node Address");
664    opts.getOption(APP_OWNER_OPTION).setArgName("Application Owner");
665    opts.getOption(AM_CONTAINER_OPTION).setArgName("AM Containers");
666    opts.getOption(OUT_OPTION).setArgName("Local Directory");
667    opts.getOption(SIZE_OPTION).setArgName("size");
668    return opts;
669  }
670
671  private Options createPrintOpts(Options commandOpts) {
672    Options printOpts = new Options();
673    printOpts.addOption(commandOpts.getOption(HELP_CMD));
674    printOpts.addOption(commandOpts.getOption(CONTAINER_ID_OPTION));
675    printOpts.addOption(commandOpts.getOption(NODE_ADDRESS_OPTION));
676    printOpts.addOption(commandOpts.getOption(APP_OWNER_OPTION));
677    printOpts.addOption(commandOpts.getOption(AM_CONTAINER_OPTION));
678    printOpts.addOption(commandOpts.getOption(CONTAINER_LOG_FILES));
679    printOpts.addOption(commandOpts.getOption(SHOW_META_INFO));
680    printOpts.addOption(commandOpts.getOption(LIST_NODES_OPTION));
681    printOpts.addOption(commandOpts.getOption(OUT_OPTION));
682    printOpts.addOption(commandOpts.getOption(SIZE_OPTION));
683    return printOpts;
684  }
685
686  private List<String> parseAMContainer(CommandLine commandLine,
687      Options printOpts) throws NumberFormatException {
688    List<String> amContainersList = new ArrayList<String>();
689    String[] amContainers = commandLine.getOptionValues(AM_CONTAINER_OPTION);
690    for (String am : amContainers) {
691      boolean errorInput = false;
692      if (!am.trim().equalsIgnoreCase("ALL")) {
693        try {
694          int id = Integer.parseInt(am.trim());
695          if (id != -1 && id <= 0) {
696            errorInput = true;
697          }
698        } catch (NumberFormatException ex) {
699          errorInput = true;
700        }
701        if (errorInput) {
702          String errMessage =
703              "Invalid input for option -am. Valid inputs are 'ALL', -1 "
704              + "and any other integer which is larger than 0.";
705          printHelpMessage(printOpts);
706          throw new NumberFormatException(errMessage);
707        }
708        amContainersList.add(am.trim());
709      } else {
710        amContainersList.add("ALL");
711        break;
712      }
713    }
714    return amContainersList;
715  }
716
717  private int fetchAMContainerLogs(ContainerLogsRequest request,
718      List<String> amContainersList, LogCLIHelpers logCliHelper)
719      throws Exception {
720    List<String> logFiles = request.getLogTypes();
721    // if we do not specify the value for CONTAINER_LOG_FILES option,
722    // we will only output syslog
723    if (logFiles == null || logFiles.isEmpty()) {
724      logFiles = Arrays.asList("syslog");
725    }
726    request.setLogTypes(logFiles);
727    // If the application is running, we will call the RM WebService
728    // to get the AppAttempts which includes the nodeHttpAddress
729    // and containerId for all the AM Containers.
730    // After that, we will call NodeManager webService to get the
731    // related logs
732    if (!request.isAppFinished()) {
733      return printAMContainerLogs(getConf(), request, amContainersList,
734          logCliHelper);
735    } else {
736      // If the application is in the final state, we will call RM webservice
737      // to get all AppAttempts information first. If we get nothing,
738      // we will try to call AHS webservice to get related AppAttempts
739      // which includes nodeAddress for the AM Containers.
740      // After that, we will use nodeAddress and containerId
741      // to get logs from HDFS directly.
742      if (getConf().getBoolean(YarnConfiguration.APPLICATION_HISTORY_ENABLED,
743          YarnConfiguration.DEFAULT_APPLICATION_HISTORY_ENABLED)) {
744        return printAMContainerLogs(getConf(), request, amContainersList,
745            logCliHelper);
746      } else {
747        ApplicationId appId = request.getAppId();
748        String appOwner = request.getAppOwner();
749        System.err.println("Can not get AMContainers logs for "
750            + "the application:" + appId + " with the appOwner:" + appOwner);
751        System.err.println("This application:" + appId + " has finished."
752            + " Please enable the application-history service or explicitly"
753            + " use 'yarn logs -applicationId <appId> "
754            + "-containerId <containerId> --nodeAddress <nodeHttpAddress>' "
755            + "to get the container logs.");
756        return -1;
757      }
758    }
759  }
760
761  private int fetchContainerLogs(ContainerLogsRequest request,
762      LogCLIHelpers logCliHelper) throws IOException {
763    int resultCode = 0;
764    String appIdStr = request.getAppId().toString();
765    String containerIdStr = request.getContainerId();
766    String nodeAddress = request.getNodeId();
767    String appOwner = request.getAppOwner();
768    boolean isAppFinished = request.isAppFinished();
769    List<String> logFiles = request.getLogTypes();
770    // if we provide the node address and the application is in the final
771    // state, we could directly get logs from HDFS.
772    if (nodeAddress != null && isAppFinished) {
773      // if user specified "ALL" as the logFiles param, pass empty list
774      // to logCliHelper so that it fetches all the logs
775      return printContainerLogsForFinishedApplication(
776          request, logCliHelper);
777    }
778    String nodeHttpAddress = null;
779    String nodeId = null;
780    try {
781      // If the nodeAddress is not provided, we will try to get
782      // the ContainerReport. In the containerReport, we could get
783      // nodeAddress and nodeHttpAddress
784      ContainerReport report = getContainerReport(containerIdStr);
785      nodeHttpAddress =
786          report.getNodeHttpAddress().replaceFirst(
787            WebAppUtils.getHttpSchemePrefix(getConf()), "");
788      nodeId = report.getAssignedNode().toString();
789      request.setNodeId(nodeId);
790      request.setNodeHttpAddress(nodeHttpAddress);
791    } catch (IOException | YarnException ex) {
792      if (isAppFinished) {
793        return printContainerLogsForFinishedApplicationWithoutNodeId(
794            request, logCliHelper);
795      } else {
796        System.err.println("Unable to get logs for this container:"
797            + containerIdStr + "for the application:" + appIdStr
798            + " with the appOwner: " + appOwner);
799        System.err.println("The application: " + appIdStr
800            + " is still running, and we can not get Container report "
801            + "for the container: " + containerIdStr +". Please try later "
802            + "or after the application finishes.");
803        return -1;
804      }
805    }
806    // If the application is not in the final state,
807    // we will provide the NodeHttpAddress and get the container logs
808    // by calling NodeManager webservice.
809    if (!isAppFinished) {
810      // if we do not specify the value for CONTAINER_LOG_FILES option,
811      // we will only output syslog
812      if (logFiles == null || logFiles.isEmpty()) {
813        logFiles = Arrays.asList("syslog");
814      }
815      request.setLogTypes(logFiles);
816      printContainerLogsFromRunningApplication(getConf(), request,
817          logCliHelper);
818    } else {
819      // If the application is in the final state, we will directly
820      // get the container logs from HDFS.
821      resultCode = printContainerLogsForFinishedApplication(
822          request, logCliHelper);
823    }
824    return resultCode;
825  }
826
827  private int fetchApplicationLogs(ContainerLogsRequest options,
828      LogCLIHelpers logCliHelper) throws IOException, YarnException {
829    // If the application has finished, we would fetch the logs
830    // from HDFS.
831    // If the application is still running, we would get the full
832    // list of the containers first, then fetch the logs for each
833    // container from NM.
834    int resultCode = 0;
835    if (options.isAppFinished()) {
836      ContainerLogsRequest newOptions = getMatchedLogOptions(
837          options, logCliHelper);
838      if (newOptions == null) {
839        resultCode = -1;
840      } else {
841        resultCode =
842            logCliHelper.dumpAllContainersLogs(newOptions);
843      }
844    } else {
845      List<ContainerLogsRequest> containerLogRequests =
846          getContainersLogRequestForRunningApplication(options);
847      for (ContainerLogsRequest container : containerLogRequests) {
848        printContainerLogsFromRunningApplication(getConf(), container,
849            logCliHelper);
850      }
851    }
852    if (resultCode == -1) {
853      System.err.println("Can not find the logs for the application: "
854          + options.getAppId() + " with the appOwner: "
855          + options.getAppOwner());
856    }
857    return resultCode;
858  }
859
860  private String guessAppOwner(ApplicationReport appReport,
861      ApplicationId appId) throws IOException {
862    String appOwner = null;
863    if (appReport != null) {
864      //always use the app owner from the app report if possible
865      appOwner = appReport.getUser();
866    } else {
867      appOwner = UserGroupInformation.getCurrentUser().getShortUserName();
868      appOwner = LogCLIHelpers.getOwnerForAppIdOrNull(
869          appId, appOwner, getConf());
870    }
871    return appOwner;
872  }
873
874  private ContainerLogsRequest getMatchedLogOptions(
875      ContainerLogsRequest request, LogCLIHelpers logCliHelper)
876      throws IOException {
877    ContainerLogsRequest newOptions = new ContainerLogsRequest(request);
878    if (request.getLogTypes() != null && !request.getLogTypes().isEmpty()) {
879      List<String> matchedFiles = new ArrayList<String>();
880      if (!request.getLogTypes().contains(".*")) {
881        Set<String> files = logCliHelper.listContainerLogs(request);
882        matchedFiles = getMatchedLogFiles(
883            request, files, true);
884        if (matchedFiles.isEmpty()) {
885          return null;
886        }
887      }
888      newOptions.setLogTypes(matchedFiles);
889    }
890    return newOptions;
891  }
892
893  private List<String> getMatchedLogFiles(ContainerLogsRequest options,
894      Collection<String> candidate, boolean printError) throws IOException {
895    List<String> matchedFiles = new ArrayList<String>();
896    List<String> filePattern = options.getLogTypes();
897    for (String file : candidate) {
898      if (isFileMatching(file, filePattern)) {
899        matchedFiles.add(file);
900      }
901    }
902    if (matchedFiles.isEmpty()) {
903      if (printError) {
904        System.err.println("Can not find any log file matching the pattern: "
905            + options.getLogTypes() + " for the application: "
906            + options.getAppId());
907      }
908    }
909    return matchedFiles;
910  }
911
912  private boolean isFileMatching(String fileType,
913      List<String> logTypes) {
914    for (String logType : logTypes) {
915      Pattern filterPattern = Pattern.compile(logType);
916      boolean match = filterPattern.matcher(fileType).find();
917      if (match) {
918        return true;
919      }
920    }
921    return false;
922  }
923
924  private List<ContainerLogsRequest>
925      getContainersLogRequestForRunningApplication(
926          ContainerLogsRequest options) throws YarnException, IOException {
927    List<ContainerLogsRequest> newOptionsList =
928        new ArrayList<ContainerLogsRequest>();
929    YarnClient yarnClient = createYarnClient();
930    try {
931      List<ApplicationAttemptReport> attempts =
932          yarnClient.getApplicationAttempts(options.getAppId());
933      for (ApplicationAttemptReport attempt : attempts) {
934        List<ContainerReport> containers = yarnClient.getContainers(
935            attempt.getApplicationAttemptId());
936        for (ContainerReport container : containers) {
937          ContainerLogsRequest newOptions = new ContainerLogsRequest(options);
938          newOptions.setContainerId(container.getContainerId().toString());
939          newOptions.setNodeId(container.getAssignedNode().toString());
940          newOptions.setNodeHttpAddress(container.getNodeHttpAddress()
941              .replaceFirst(WebAppUtils.getHttpSchemePrefix(getConf()), ""));
942          // if we do not specify the value for CONTAINER_LOG_FILES option,
943          // we will only output syslog
944          List<String> logFiles = newOptions.getLogTypes();
945          if (logFiles == null || logFiles.isEmpty()) {
946            logFiles = Arrays.asList("syslog");
947            newOptions.setLogTypes(logFiles);
948          }
949          newOptionsList.add(newOptions);
950        }
951      }
952      return newOptionsList;
953    } finally {
954      yarnClient.close();
955    }
956  }
957}