(16)-logcat信息收集系统
最后更新于:2022-04-01 06:54:56
# Log收集系统
涉及三个类,关系如图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-09_56911ddc1c629.jpg)
## LogcatReceiver
log收集器的外观类,包装了后台执行线程和log内容接收器
~~~
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.device;
import com.android.tradefed.result.InputStreamSource;
/**
* Class that collects logcat in background. Continues to capture logcat even if device goes
* offline then online.
*/
public class LogcatReceiver {
private BackgroundDeviceAction mDeviceAction;
private LargeOutputReceiver mReceiver;
static final String LOGCAT_CMD = "logcat -v threadtime";
private static final String LOGCAT_DESC = "logcat";
public LogcatReceiver(ITestDevice device, long maxFileSize, int logStartDelay) {
mReceiver = new LargeOutputReceiver(LOGCAT_DESC, device.getSerialNumber(),
maxFileSize);
// FIXME: remove mLogStartDelay. Currently delay starting logcat, as starting
// immediately after a device comes online has caused adb instability
mDeviceAction = new BackgroundDeviceAction(LOGCAT_CMD, LOGCAT_DESC, device,
mReceiver, logStartDelay);
}
public void start() {
mDeviceAction.start();
}
public void stop() {
mDeviceAction.cancel();
mReceiver.cancel();
mReceiver.delete();
}
public InputStreamSource getLogcatData() {
return mReceiver.getData();
}
public InputStreamSource getLogcatData(int maxBytes) {
return mReceiver.getData(maxBytes);
}
public void clear() {
mReceiver.clear();
}
}
~~~
构造方法中初始化后台执行线程BackgoundDeviceAction和log信息接收器LargeOutputReceiver对象。
start:启动线程
stop:关闭现场,取消接收logcat信息,删除收集器中的信息
getLogcatData:得到logcat信息。
getLogcatData(int maxBytes):得到规定大小的logcat信息
clear:清空消息。
## BackgroundDeviceAction
后台线程,就是在后台线程中执行logcat命令
~~~
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache
* License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.device;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import java.io.IOException;
/**
* Runs a command on a given device repeating as necessary until the action is canceled.
* <p>
* When the class is run, the command is run on the device in a separate thread and the output is
* collected in a temporary host file.
* </p><p>
* This is done so:
* </p><ul>
* <li>if device goes permanently offline during a test, the log data is retained.</li>
* <li>to capture more data than may fit in device's circular log.</li>
* </ul>
*/
public class BackgroundDeviceAction extends Thread {
private IShellOutputReceiver mReceiver;
private ITestDevice mTestDevice;
private String mCommand;
private String mSerialNumber;
private String mDescriptor;
private boolean mIsCancelled;
private int mLogStartDelay;
/**
* Creates a {@link BackgroundDeviceAction}
*
* @param command the command to run
* @param descriptor the description of the command. For logging only.
* @param device the device to run the command on
* @param receiver the receiver for collecting the output of the command
* @param startDelay the delay to wait after the device becomes online
*/
public BackgroundDeviceAction(String command, String descriptor, ITestDevice device,
IShellOutputReceiver receiver, int startDelay) {
mCommand = command;
mDescriptor = descriptor;
mSerialNumber = device.getSerialNumber();
mTestDevice = device;
mReceiver = receiver;
mLogStartDelay = startDelay;
// don't keep VM open if this thread is still running
setDaemon(true);
}
/**
* {@inheritDoc}
* <p>
* Repeats the command until canceled.
* </p>
*/
@Override
public void run() {
while (!isCancelled()) {
if (mLogStartDelay > 0) {
CLog.d("Sleep for %d before starting %s for %s.", mLogStartDelay, mDescriptor,
mSerialNumber);
getRunUtil().sleep(mLogStartDelay);
}
CLog.d("Starting %s for %s.", mDescriptor, mSerialNumber);
try {
mTestDevice.getIDevice().executeShellCommand(mCommand, mReceiver, 0);
} catch (TimeoutException e) {
recoverDevice(e.getClass().getName());
} catch (AdbCommandRejectedException e) {
recoverDevice(e.getClass().getName());
} catch (ShellCommandUnresponsiveException e) {
recoverDevice(e.getClass().getName());
} catch (IOException e) {
recoverDevice(e.getClass().getName());
}
}
}
private void recoverDevice(String exceptionType) {
CLog.d("%s while running %s on %s. May see duplicated content in log.", exceptionType,
mDescriptor, mSerialNumber);
// FIXME: Determine when we should append a message to the receiver.
if (mReceiver instanceof LargeOutputReceiver) {
byte[] stringData = String.format(
"%s interrupted. May see duplicated content in log.", mDescriptor).getBytes();
mReceiver.addOutput(stringData, 0, stringData.length);
}
// Make sure we haven't been cancelled before we sleep for a long time
if (isCancelled()) {
return;
}
// sleep a small amount for device to settle
getRunUtil().sleep(5 * 1000);
// wait a long time for device to be online
try {
mTestDevice.waitForDeviceOnline(10 * 60 * 1000);
} catch (DeviceNotAvailableException e) {
CLog.w("Device %s not online", mSerialNumber);
}
}
/**
* Cancels the command.
*/
public synchronized void cancel() {
mIsCancelled = true;
interrupt();
}
/**
* If the command is cancelled.
*/
public synchronized boolean isCancelled() {
return mIsCancelled;
}
/**
* Get the {@link RunUtil} instance to use.
* <p/>
* Exposed for unit testing.
*/
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
}
~~~
## LargeOutputReceiver
继承adb里面的IShellOutputReceiver接口。用于接收命令执行后返回的信息
~~~
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache
* License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.device;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.SnapshotInputStreamSource;
import com.android.tradefed.util.FixedByteArrayOutputStream;
import com.android.tradefed.util.SizeLimitedOutputStream;
import com.android.tradefed.util.StreamUtil;
import java.io.IOException;
import java.io.InputStream;
/**
* A class designed to help run long running commands collect output.
* <p>
* The maximum size of the tmp file is limited to approximately {@code maxFileSize}.
* To prevent data loss when the limit has been reached, this file keeps set of tmp host
* files.
* </p>
*/
public class LargeOutputReceiver implements IShellOutputReceiver {
private String mSerialNumber;
private String mDescriptor;
private boolean mIsCancelled = false;
private SizeLimitedOutputStream mOutStream;
private long mMaxDataSize;
/**
* Creates a {@link LargeOutputReceiver}.
*
* @param descriptor the descriptor of the command to run. For logging only.
* @param serialNumber the serial number of the device. For logging only.
* @param maxDataSize the approximate max amount of data to keep.
*/
public LargeOutputReceiver(String descriptor, String serialNumber, long maxDataSize) {
mDescriptor = descriptor;
mSerialNumber = serialNumber;
mMaxDataSize = maxDataSize;
mOutStream = createOutputStream();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void addOutput(byte[] data, int offset, int length) {
if (mIsCancelled || mOutStream == null) {
return;
}
try {
mOutStream.write(data, offset, length);
} catch (IOException e) {
CLog.w("failed to write %s data for %s.", mDescriptor, mSerialNumber);
}
}
/**
* Gets the collected output as a {@link InputStreamSource}.
*
* @return The collected output from the command.
*/
public synchronized InputStreamSource getData() {
if (mOutStream != null) {
try {
return new SnapshotInputStreamSource(mOutStream.getData());
} catch (IOException e) {
CLog.e("failed to get %s data for %s.", mDescriptor, mSerialNumber);
CLog.e(e);
}
}
// return an empty InputStreamSource
return new ByteArrayInputStreamSource(new byte[0]);
}
/**
* Gets the last <var>maxBytes</var> of collected output as a {@link InputStreamSource}.
*
* @param maxBytes the maximum amount of data to return. Should be an amount that can
* comfortably fit in memory
* @return The collected output from the command, stored in memory
*/
public synchronized InputStreamSource getData(final int maxBytes) {
if (mOutStream != null) {
InputStream fullStream = null;
try {
fullStream = mOutStream.getData();
final FixedByteArrayOutputStream os = new FixedByteArrayOutputStream(maxBytes);
StreamUtil.copyStreams(fullStream, os);
return new InputStreamSource() {
@Override
public InputStream createInputStream() {
return os.getData();
}
@Override
public void cancel() {
// ignore, nothing to do
}
@Override
public long size() {
return os.size();
}
};
} catch (IOException e) {
CLog.e("failed to get %s data for %s.", mDescriptor, mSerialNumber);
CLog.e(e);
} finally {
StreamUtil.close(fullStream);
}
}
// return an empty InputStreamSource
return new ByteArrayInputStreamSource(new byte[0]);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void flush() {
if (mOutStream == null) {
return;
}
mOutStream.flush();
}
/**
* Delete currently accumulated data, and then re-create a new file.
*/
public synchronized void clear() {
delete();
mOutStream = createOutputStream();
}
private SizeLimitedOutputStream createOutputStream() {
return new SizeLimitedOutputStream(mMaxDataSize, String.format("%s_%s",
getDescriptor(), mSerialNumber), ".txt");
}
/**
* Cancels the command.
*/
public synchronized void cancel() {
mIsCancelled = true;
}
/**
* Delete all accumulated data.
*/
public void delete() {
mOutStream.delete();
mOutStream = null;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized boolean isCancelled() {
return mIsCancelled;
}
/**
* Get the descriptor.
* <p>
* Exposed for unit testing.
* </p>
*/
String getDescriptor() {
return mDescriptor;
}
}
~~~