All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.spoon.SpoonDeviceLogger Maven / Gradle / Ivy

package com.squareup.spoon;

import com.android.ddmlib.IDevice;
import com.android.ddmlib.MultiLineReceiver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.android.ddmlib.Log.LogLevel;

// Parts of this implementation are from AOSP's LogCatReceiver and LogCatParser.
final class SpoonDeviceLogger {
  private static final String LOGCAT = "logcat -v long";
  private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
  private static final String TEST_RUNNER = "TestRunner";
  private static final Pattern MESSAGE_START = Pattern.compile("started: ([^(]+)\\(([^)]+)\\)");
  private static final Pattern MESSAGE_END = Pattern.compile("finished: [^(]+\\([^)]+\\)");

  /**
   * This pattern is meant to parse the first line of a log message with the option
   * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the
   * following lines are the message (can be several lines).
* This first line looks something like:
* {@code "[ 00-00 00:00:00.000 :0x /]"} *
* Note: severity is one of V, D, I, W, E, A? or F. However, there doesn't seem to be * a way to actually generate an A (assert) message. Log.wtf is supposed to generate * a message with severity A, however it generates the undocumented F level. In * such a case, the parser will change the level from F to A.
* Note: the fraction of second value can have any number of digit.
* Note: the tag should be trimmed as it may have spaces at the end. */ private static final Pattern LOG_HEADER_PATTERN = Pattern.compile( "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)" + "\\s+(\\d*):\\s*(\\S+)\\s([VDIWEAF])/(.*)]$"); private final List messages = new ArrayList(); private final LogCatOutputReceiver receiver = new LogCatOutputReceiver(); private final IDevice device; private LogLevel currentLevel = LogLevel.WARN; private String currentPid = "?"; private String currentTid = "?"; private String currentTag = "?"; private String currentTime = "?:??"; public SpoonDeviceLogger(IDevice device) { this.device = device; startReceiverThread(); } private void startReceiverThread() { new Thread(new Runnable() { @Override public void run() { // Wait while the device comes online. while (device != null && !device.isOnline()) { try { Thread.sleep(DEVICE_POLL_INTERVAL_MSEC); } catch (InterruptedException e) { return; } } try { if (device != null) { device.executeShellCommand(LOGCAT, receiver, 0); } } catch (Exception e) { System.err.println("Unable to connect to logcat on device. Check connection."); e.printStackTrace(System.err); } } }).start(); } public Map> getParsedLogs() { receiver.isCancelled = true; Map> logs = new HashMap>(); DeviceTest current = null; String pid = null; synchronized (messages) { for (DeviceLogMessage message : messages) { if (current == null) { Matcher match = MESSAGE_START.matcher(message.getMessage()); if (match.matches() && TEST_RUNNER.equals(message.getTag())) { current = new DeviceTest(match.group(2), match.group(1)); pid = message.getPid(); List deviceLogMessages = new ArrayList(); deviceLogMessages.add(message); logs.put(current, deviceLogMessages); } } else { // Only log messages from the same PID. if (pid.equals(message.getPid())) { logs.get(current).add(message); } Matcher match = MESSAGE_END.matcher(message.getMessage()); if (match.matches() && TEST_RUNNER.equals(message.getTag())) { current = null; pid = null; } } } } return logs; } private class LogCatOutputReceiver extends MultiLineReceiver { private boolean isCancelled; public LogCatOutputReceiver() { setTrimLine(false); } @Override public boolean isCancelled() { return isCancelled; } @Override public void processNewLines(String[] lines) { synchronized (messages) { processLogLines(lines); } } } /** * Parse a list of strings into {@link DeviceLogMessage} objects. This method * maintains state from previous calls regarding the last seen header of * logcat messages. * * @param lines list of raw strings obtained from logcat -v long */ public void processLogLines(String[] lines) { for (String line : lines) { if (line.length() == 0) { continue; } Matcher matcher = LOG_HEADER_PATTERN.matcher(line); if (matcher.matches()) { currentTime = matcher.group(1); currentPid = matcher.group(2); currentTid = matcher.group(3); currentLevel = LogLevel.getByLetterString(matcher.group(4)); currentTag = matcher.group(5).trim(); // LogLevel doesn't support messages with severity "F". Log.wtf() is supposed // to generate "A", but generates "F". if (currentLevel == null && matcher.group(4).equals("F")) { currentLevel = LogLevel.ASSERT; } } else { messages.add( new DeviceLogMessage(currentLevel, currentPid, currentTid, currentTag, currentTime, line)); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy